1
1
import chalk from "chalk" ;
2
- import { getBrowserString } from "../lib/getBrowserString.js" ;
3
- import { createWorker , deleteWorker , getAvailableSessions } from "./api.js" ;
2
+ import { getBrowserString } from "./lib/getBrowserString.js" ;
3
+ import {
4
+ createWorker ,
5
+ deleteWorker ,
6
+ getAvailableSessions
7
+ } from "./browserstack/api.js" ;
8
+ import createDriver from "./selenium/createDriver.js" ;
4
9
5
10
const workers = Object . create ( null ) ;
6
11
7
12
/**
8
13
* Keys are browser strings
9
14
* Structure of a worker:
10
15
* {
11
- * debug: boolean, // Stops the worker from being cleaned up when finished
12
- * id: string,
13
- * lastTouch: number, // The last time a request was received
14
- * url: string,
15
- * browser: object, // The browser object
16
+ * browser: object // The browser object
17
+ * debug: boolean // Stops the worker from being cleaned up when finished
18
+ * lastTouch: number // The last time a request was received
19
+ * restarts: number // The number of times the worker has been restarted
16
20
* options: object // The options to create the worker
21
+ * url: string // The URL the worker is on
22
+ * quit: function // A function to stop the worker
17
23
* }
18
24
*/
19
25
@@ -31,70 +37,8 @@ const RUN_WORKER_TIMEOUT = 60 * 1000 * 2;
31
37
32
38
const WORKER_WAIT_TIME = 30000 ;
33
39
34
- export function touchBrowser ( browser ) {
35
- const fullBrowser = getBrowserString ( browser ) ;
36
- const worker = workers [ fullBrowser ] ;
37
- if ( worker ) {
38
- worker . lastTouch = Date . now ( ) ;
39
- }
40
- }
41
-
42
- async function waitForAck ( worker , { fullBrowser, verbose } ) {
43
- delete worker . lastTouch ;
44
- return new Promise ( ( resolve , reject ) => {
45
- const interval = setInterval ( ( ) => {
46
- if ( worker . lastTouch ) {
47
- if ( verbose ) {
48
- console . log ( `\n${ fullBrowser } acknowledged.` ) ;
49
- }
50
- clearTimeout ( timeout ) ;
51
- clearInterval ( interval ) ;
52
- resolve ( ) ;
53
- }
54
- } , ACKNOWLEDGE_INTERVAL ) ;
55
-
56
- const timeout = setTimeout ( ( ) => {
57
- clearInterval ( interval ) ;
58
- reject (
59
- new Error (
60
- `${ fullBrowser } not acknowledged after ${
61
- ACKNOWLEDGE_TIMEOUT / 1000 / 60
62
- } min.`
63
- )
64
- ) ;
65
- } , ACKNOWLEDGE_TIMEOUT ) ;
66
- } ) ;
67
- }
68
-
69
- async function restartWorker ( worker ) {
70
- await cleanupWorker ( worker , worker . options ) ;
71
- await createBrowserWorker (
72
- worker . url ,
73
- worker . browser ,
74
- worker . options ,
75
- worker . restarts + 1
76
- ) ;
77
- }
78
-
79
- export async function restartBrowser ( browser ) {
80
- const fullBrowser = getBrowserString ( browser ) ;
81
- const worker = workers [ fullBrowser ] ;
82
- if ( worker ) {
83
- await restartWorker ( worker ) ;
84
- }
85
- }
86
-
87
- async function ensureAcknowledged ( worker ) {
88
- const fullBrowser = getBrowserString ( worker . browser ) ;
89
- const verbose = worker . options . verbose ;
90
- try {
91
- await waitForAck ( worker , { fullBrowser, verbose } ) ;
92
- return worker ;
93
- } catch ( error ) {
94
- console . error ( error . message ) ;
95
- await restartWorker ( worker ) ;
96
- }
97
- }
40
+ // Limit concurrency to 8 by default in selenium
41
+ const MAX_SELENIUM_CONCURRENCY = 8 ;
98
42
99
43
export async function createBrowserWorker ( url , browser , options , restarts = 0 ) {
100
44
if ( restarts > MAX_WORKER_RESTARTS ) {
@@ -104,37 +48,51 @@ export async function createBrowserWorker( url, browser, options, restarts = 0 )
104
48
) } `
105
49
) ;
106
50
}
107
- const verbose = options . verbose ;
108
- while ( ( await getAvailableSessions ( ) ) <= 0 ) {
51
+ const { browserstack , debug , headless , runId , tunnelId , verbose } = options ;
52
+ while ( await maxWorkersReached ( options ) ) {
109
53
if ( verbose ) {
110
54
console . log ( "\nWaiting for available sessions..." ) ;
111
55
}
112
56
await new Promise ( ( resolve ) => setTimeout ( resolve , WORKER_WAIT_TIME ) ) ;
113
57
}
114
58
115
- const { debug, runId, tunnelId } = options ;
116
59
const fullBrowser = getBrowserString ( browser ) ;
117
60
118
- const worker = await createWorker ( {
119
- ...browser ,
120
- url : encodeURI ( url ) ,
121
- project : "jquery-migrate" ,
122
- build : `Run ${ runId } ` ,
123
-
124
- // This is the maximum timeout allowed
125
- // by BrowserStack. We do this because
126
- // we control the timeout in the runner.
127
- // See https://github.com/browserstack/api/blob/b324a6a5bc1b6052510d74e286b8e1c758c308a7/README.md#timeout300
128
- timeout : 1800 ,
129
-
130
- // Not documented in the API docs,
131
- // but required to make local testing work.
132
- // See https://www.browserstack.com/docs/automate/selenium/manage-multiple-connections#nodejs
133
- "browserstack.local" : true ,
134
- "browserstack.localIdentifier" : tunnelId
135
- } ) ;
61
+ let worker ;
62
+
63
+ if ( browserstack ) {
64
+ worker = await createWorker ( {
65
+ ...browser ,
66
+ url : encodeURI ( url ) ,
67
+ project : "jquery" ,
68
+ build : `Run ${ runId } ` ,
69
+
70
+ // This is the maximum timeout allowed
71
+ // by BrowserStack. We do this because
72
+ // we control the timeout in the runner.
73
+ // See https://github.com/browserstack/api/blob/b324a6a5bc1b6052510d74e286b8e1c758c308a7/README.md#timeout300
74
+ timeout : 1800 ,
75
+
76
+ // Not documented in the API docs,
77
+ // but required to make local testing work.
78
+ // See https://www.browserstack.com/docs/automate/selenium/manage-multiple-connections#nodejs
79
+ "browserstack.local" : true ,
80
+ "browserstack.localIdentifier" : tunnelId
81
+ } ) ;
82
+ worker . quit = ( ) => deleteWorker ( worker . id ) ;
83
+ } else {
84
+ const driver = await createDriver ( {
85
+ browserName : browser . browser ,
86
+ headless,
87
+ url,
88
+ verbose
89
+ } ) ;
90
+ worker = {
91
+ quit : ( ) => driver . quit ( )
92
+ } ;
93
+ }
136
94
137
- browser . debug = ! ! debug ;
95
+ worker . debug = ! ! debug ;
138
96
worker . url = url ;
139
97
worker . browser = browser ;
140
98
worker . restarts = restarts ;
@@ -147,6 +105,14 @@ export async function createBrowserWorker( url, browser, options, restarts = 0 )
147
105
return ensureAcknowledged ( worker ) ;
148
106
}
149
107
108
+ export function touchBrowser ( browser ) {
109
+ const fullBrowser = getBrowserString ( browser ) ;
110
+ const worker = workers [ fullBrowser ] ;
111
+ if ( worker ) {
112
+ worker . lastTouch = Date . now ( ) ;
113
+ }
114
+ }
115
+
150
116
export async function setBrowserWorkerUrl ( browser , url ) {
151
117
const fullBrowser = getBrowserString ( browser ) ;
152
118
const worker = workers [ fullBrowser ] ;
@@ -155,6 +121,14 @@ export async function setBrowserWorkerUrl( browser, url ) {
155
121
}
156
122
}
157
123
124
+ export async function restartBrowser ( browser ) {
125
+ const fullBrowser = getBrowserString ( browser ) ;
126
+ const worker = workers [ fullBrowser ] ;
127
+ if ( worker ) {
128
+ await restartWorker ( worker ) ;
129
+ }
130
+ }
131
+
158
132
/**
159
133
* Checks that all browsers have received
160
134
* a response in the given amount of time.
@@ -176,27 +150,12 @@ export async function checkLastTouches() {
176
150
}
177
151
}
178
152
179
- export async function cleanupWorker ( worker , { verbose } ) {
180
- for ( const [ fullBrowser , w ] of Object . entries ( workers ) ) {
181
- if ( w === worker ) {
182
- delete workers [ fullBrowser ] ;
183
- await deleteWorker ( worker . id ) ;
184
- if ( verbose ) {
185
- console . log ( `\nStopped ${ fullBrowser } .` ) ;
186
- }
187
- return ;
188
- }
189
- }
190
- }
191
-
192
153
export async function cleanupAllBrowsers ( { verbose } ) {
193
154
const workersRemaining = Object . values ( workers ) ;
194
155
const numRemaining = workersRemaining . length ;
195
156
if ( numRemaining ) {
196
157
try {
197
- await Promise . all (
198
- workersRemaining . map ( ( worker ) => deleteWorker ( worker . id ) )
199
- ) ;
158
+ await Promise . all ( workersRemaining . map ( ( worker ) => worker . quit ( ) ) ) ;
200
159
if ( verbose ) {
201
160
console . log (
202
161
`Stopped ${ numRemaining } browser${ numRemaining > 1 ? "s" : "" } .`
@@ -209,3 +168,75 @@ export async function cleanupAllBrowsers( { verbose } ) {
209
168
}
210
169
}
211
170
}
171
+
172
+ async function maxWorkersReached ( {
173
+ browserstack,
174
+ concurrency = MAX_SELENIUM_CONCURRENCY
175
+ } ) {
176
+ if ( browserstack ) {
177
+ return ( await getAvailableSessions ( ) ) <= 0 ;
178
+ }
179
+ return workers . length >= concurrency ;
180
+ }
181
+
182
+ async function waitForAck ( worker , { fullBrowser, verbose } ) {
183
+ delete worker . lastTouch ;
184
+ return new Promise ( ( resolve , reject ) => {
185
+ const interval = setInterval ( ( ) => {
186
+ if ( worker . lastTouch ) {
187
+ if ( verbose ) {
188
+ console . log ( `\n${ fullBrowser } acknowledged.` ) ;
189
+ }
190
+ clearTimeout ( timeout ) ;
191
+ clearInterval ( interval ) ;
192
+ resolve ( ) ;
193
+ }
194
+ } , ACKNOWLEDGE_INTERVAL ) ;
195
+
196
+ const timeout = setTimeout ( ( ) => {
197
+ clearInterval ( interval ) ;
198
+ reject (
199
+ new Error (
200
+ `${ fullBrowser } not acknowledged after ${
201
+ ACKNOWLEDGE_TIMEOUT / 1000 / 60
202
+ } min.`
203
+ )
204
+ ) ;
205
+ } , ACKNOWLEDGE_TIMEOUT ) ;
206
+ } ) ;
207
+ }
208
+
209
+ async function ensureAcknowledged ( worker ) {
210
+ const fullBrowser = getBrowserString ( worker . browser ) ;
211
+ const verbose = worker . options . verbose ;
212
+ try {
213
+ await waitForAck ( worker , { fullBrowser, verbose } ) ;
214
+ return worker ;
215
+ } catch ( error ) {
216
+ console . error ( error . message ) ;
217
+ await restartWorker ( worker ) ;
218
+ }
219
+ }
220
+
221
+ async function cleanupWorker ( worker , { verbose } ) {
222
+ for ( const [ fullBrowser , w ] of Object . entries ( workers ) ) {
223
+ if ( w === worker ) {
224
+ delete workers [ fullBrowser ] ;
225
+ await worker . quit ( ) ;
226
+ if ( verbose ) {
227
+ console . log ( `\nStopped ${ fullBrowser } .` ) ;
228
+ }
229
+ return ;
230
+ }
231
+ }
232
+ }
233
+
234
+ async function restartWorker ( worker ) {
235
+ await cleanupWorker ( worker , worker . options ) ;
236
+ await createBrowserWorker (
237
+ worker . url ,
238
+ worker . browser ,
239
+ worker . options ,
240
+ worker . restarts + 1
241
+ ) ;
242
+ }
0 commit comments