@@ -52,7 +52,7 @@ export class SaucelabsDaemon {
52
52
private _pendingTests = new Map < RemoteBrowser , BrowserTest > ( ) ;
53
53
54
54
/** List of active browsers that are managed by the daemon. */
55
- private _activeBrowsers = new Set < RemoteBrowser > ( ) ;
55
+ private _activeBrowsers : RemoteBrowser [ ] = [ ] ;
56
56
57
57
/** Map that contains test ids with their claimed browser. */
58
58
private _runningTests = new Map < number , RemoteBrowser > ( ) ;
@@ -69,11 +69,15 @@ export class SaucelabsDaemon {
69
69
/* Promise indicating whether we the tunnel is active, or if we are still connecting. */
70
70
private _connection : Promise < void > | undefined = undefined ;
71
71
72
+ /* Number of parallel executions started */
73
+ private _parallelExecutions : number = 0 ;
74
+
72
75
constructor (
73
76
private _username : string ,
74
77
private _accessKey : string ,
75
78
private _buildName : string ,
76
79
private _browsers : Browser [ ] ,
80
+ private _maxParallelExecutions : number ,
77
81
private _sauceConnect : string ,
78
82
private _userCapabilities : object = { } ,
79
83
) {
@@ -104,7 +108,7 @@ export class SaucelabsDaemon {
104
108
}
105
109
} ) ;
106
110
await Promise . all ( quitBrowsers ) ;
107
- this . _activeBrowsers . clear ( ) ;
111
+ this . _activeBrowsers = [ ] ;
108
112
this . _runningTests . clear ( ) ;
109
113
this . _pendingTests . clear ( ) ;
110
114
}
@@ -154,36 +158,26 @@ export class SaucelabsDaemon {
154
158
async startTest ( test : BrowserTest ) : Promise < boolean > {
155
159
await this . connectTunnel ( ) ;
156
160
157
- const browsers = this . _findMatchingBrowsers ( test . requestedBrowserId ) ;
158
- if ( ! browsers . length ) {
159
- return false ;
161
+ if ( this . _parallelExecutions < this . _maxParallelExecutions ) {
162
+ // Start additional browsers on each test start until the max parallel executions are
163
+ // reached to avoid the race condition of starting a browser and then having another test
164
+ // start steal it before is claimed by this test.
165
+ await this . launchBrowserSet ( ) ;
160
166
}
161
167
162
- // Find the first available browser and start the test.
163
- for ( const browser of browsers ) {
164
- // If the browser is claimed, continue searching.
165
- if ( browser . state === 'claimed' ) {
166
- continue ;
167
- }
168
-
169
- // If the browser is launching, check if it can be pre-claimed so that
170
- // the test starts once the browser is ready. If it's already claimed,
171
- // continue searching.
172
- if ( browser . state === 'launching' ) {
173
- if ( this . _pendingTests . has ( browser ) ) {
174
- continue ;
175
- } else {
176
- this . _pendingTests . set ( browser , test ) ;
177
- return true ;
178
- }
179
- }
168
+ let browser = this . _findAvailableBrowser ( test . requestedBrowserId ) ;
169
+ if ( ! browser ) {
170
+ console . error ( `No available browser ${ test . requestedBrowserId } for test ${ test . testId } !` ) ;
171
+ return false ;
172
+ }
180
173
174
+ if ( browser . state == 'launching' ) {
175
+ this . _pendingTests . set ( browser , test ) ;
176
+ } else {
181
177
this . _startBrowserTest ( browser , test ) ;
182
-
183
- return true ;
184
178
}
185
179
186
- return false ;
180
+ return true ;
187
181
}
188
182
189
183
/**
@@ -195,16 +189,18 @@ export class SaucelabsDaemon {
195
189
private async _connect ( ) {
196
190
await openSauceConnectTunnel (
197
191
( this . _userCapabilities as any ) . tunnelIdentifier , this . _sauceConnect ) ;
198
- await this . _launchBrowsers ( ) ;
199
192
}
200
193
201
194
/**
202
195
* @internal
203
- * Launches all browsers. If there are pending tests waiting for a particular browser to launch
204
- * before they can start, those tests are started once the browser is launched.
196
+ * Launches a set of browsers and increments the count of parallel browser started. If there are
197
+ * pending tests waiting for a particular browser to launch before they can start, those tests are
198
+ * started once the browser is launched.
205
199
**/
206
- private async _launchBrowsers ( ) {
207
- console . debug ( 'Launching browsers...' ) ;
200
+ private async launchBrowserSet ( ) {
201
+ this . _parallelExecutions ++ ;
202
+ console . debug (
203
+ `Launching browsers set ${ this . _parallelExecutions } of ${ this . _maxParallelExecutions } ...` ) ;
208
204
209
205
// Once the tunnel is established we can launch browsers
210
206
await Promise . all (
@@ -231,7 +227,7 @@ export class SaucelabsDaemon {
231
227
232
228
// Keep track of the launched browser. We do this before it even completed the
233
229
// launch as we can then handle scheduled tests when the browser is still launching.
234
- this . _activeBrowsers . add ( launched ) ;
230
+ this . _activeBrowsers . push ( launched ) ;
235
231
236
232
// See the following link for public API of the selenium server.
237
233
// https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests
@@ -261,7 +257,9 @@ export class SaucelabsDaemon {
261
257
// If a test has been scheduled before the browser completed launching, run
262
258
// it now given that the browser is ready now.
263
259
if ( this . _pendingTests . has ( launched ) ) {
264
- this . _startBrowserTest ( launched , this . _pendingTests . get ( launched ) ! ) ;
260
+ const test = this . _pendingTests . get ( launched ) ! ;
261
+ this . _pendingTests . delete ( launched ) ;
262
+ this . _startBrowserTest ( launched , test ) ;
265
263
}
266
264
} ) ,
267
265
) ;
@@ -294,16 +292,32 @@ export class SaucelabsDaemon {
294
292
295
293
/**
296
294
* @internal
297
- * Given a browserId, returns a list of matching browsers from the list of active browsers.
295
+ * Given a browserId, returns a browser that matches the browserId and is free
296
+ * or launching with no pending test. If no such browser if found, returns
297
+ * null.
298
298
**/
299
- private _findMatchingBrowsers ( browserId : string ) : RemoteBrowser [ ] {
300
- const browsers : RemoteBrowser [ ] = [ ] ;
301
- this . _activeBrowsers . forEach ( b => {
302
- if ( b . id = == browserId ) {
303
- browsers . push ( b ) ;
299
+ private _findAvailableBrowser ( browserId : string ) : RemoteBrowser | null {
300
+ for ( const browser of this . _activeBrowsers ) {
301
+ // If the browser ID doesn't match, continue searching.
302
+ if ( browser . id ! == browserId ) {
303
+ continue ;
304
304
}
305
- } ) ;
306
- return browsers ;
305
+
306
+ // If the browser is claimed, continue searching.
307
+ if ( browser . state === 'claimed' ) {
308
+ continue ;
309
+ }
310
+
311
+ // If the browser is launching, check if it can be pre-claimed so that
312
+ // the test starts once the browser is ready. If it's already claimed,
313
+ // continue searching.
314
+ if ( browser . state === 'launching' && this . _pendingTests . has ( browser ) ) {
315
+ continue ;
316
+ }
317
+
318
+ return browser ;
319
+ }
320
+ return null ;
307
321
}
308
322
309
323
/**
0 commit comments