Skip to content

Commit 34d7301

Browse files
gregmagolanthePunderWoman
authored andcommitted
build: start only the minimum number of Saucelabs browsers required (angular#50393)
This should save on Saucelabs resources so that if only one saucelabs test is run then only one set of browsers will be started. PR Close angular#50393
1 parent 3c093d7 commit 34d7301

File tree

3 files changed

+59
-48
lines changed

3 files changed

+59
-48
lines changed

scripts/test/run-saucelabs-tests.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
set -eu -o pipefail
77

88
NUMBER_OF_PARALLEL_BROWSERS="${1:-2}"
9+
shift
910

1011
if [[ -z "${SAUCE_USERNAME:-}" ]]; then
1112
echo "ERROR: SAUCE_USERNAME environment variable must be set; see tools/saucelabs-daemon/README.md for more info."
@@ -50,6 +51,6 @@ trap kill_background_service INT TERM
5051
sleep 2
5152

5253
# Run all of the saucelabs test targets
53-
yarn bazel test --config=saucelabs --jobs="$NUMBER_OF_PARALLEL_BROWSERS" ${TESTS}
54+
yarn bazel test --config=saucelabs --jobs="$NUMBER_OF_PARALLEL_BROWSERS" ${TESTS} "$@"
5455

5556
kill_background_service

tools/saucelabs-daemon/background-service/cli.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,13 @@ if (!parallelExecutions) {
4343
throw Error(`Please specify a non-zero number of parallel browsers to start.`);
4444
}
4545

46-
const browserInstances: Browser[] = [];
47-
for (let i = 0; i < parallelExecutions; i++) {
48-
browserInstances.push(...Object.values(customLaunchers) as any);
49-
}
50-
5146
// Start the daemon and launch the given browser
5247
const daemon = new SaucelabsDaemon(
5348
username,
5449
accessKey,
5550
process.env.CIRCLE_BUILD_NUM!,
56-
browserInstances,
51+
Object.values(customLaunchers) as Browser[],
52+
parallelExecutions,
5753
sauceConnect,
5854
{tunnelIdentifier},
5955
);

tools/saucelabs-daemon/background-service/saucelabs-daemon.ts

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class SaucelabsDaemon {
5252
private _pendingTests = new Map<RemoteBrowser, BrowserTest>();
5353

5454
/** List of active browsers that are managed by the daemon. */
55-
private _activeBrowsers = new Set<RemoteBrowser>();
55+
private _activeBrowsers: RemoteBrowser[] = [];
5656

5757
/** Map that contains test ids with their claimed browser. */
5858
private _runningTests = new Map<number, RemoteBrowser>();
@@ -69,11 +69,15 @@ export class SaucelabsDaemon {
6969
/* Promise indicating whether we the tunnel is active, or if we are still connecting. */
7070
private _connection: Promise<void>|undefined = undefined;
7171

72+
/* Number of parallel executions started */
73+
private _parallelExecutions: number = 0;
74+
7275
constructor(
7376
private _username: string,
7477
private _accessKey: string,
7578
private _buildName: string,
7679
private _browsers: Browser[],
80+
private _maxParallelExecutions: number,
7781
private _sauceConnect: string,
7882
private _userCapabilities: object = {},
7983
) {
@@ -104,7 +108,7 @@ export class SaucelabsDaemon {
104108
}
105109
});
106110
await Promise.all(quitBrowsers);
107-
this._activeBrowsers.clear();
111+
this._activeBrowsers = [];
108112
this._runningTests.clear();
109113
this._pendingTests.clear();
110114
}
@@ -154,36 +158,26 @@ export class SaucelabsDaemon {
154158
async startTest(test: BrowserTest): Promise<boolean> {
155159
await this.connectTunnel();
156160

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();
160166
}
161167

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+
}
180173

174+
if (browser.state == 'launching') {
175+
this._pendingTests.set(browser, test);
176+
} else {
181177
this._startBrowserTest(browser, test);
182-
183-
return true;
184178
}
185179

186-
return false;
180+
return true;
187181
}
188182

189183
/**
@@ -195,16 +189,18 @@ export class SaucelabsDaemon {
195189
private async _connect() {
196190
await openSauceConnectTunnel(
197191
(this._userCapabilities as any).tunnelIdentifier, this._sauceConnect);
198-
await this._launchBrowsers();
199192
}
200193

201194
/**
202195
* @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.
205199
**/
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}...`);
208204

209205
// Once the tunnel is established we can launch browsers
210206
await Promise.all(
@@ -231,7 +227,7 @@ export class SaucelabsDaemon {
231227

232228
// Keep track of the launched browser. We do this before it even completed the
233229
// launch as we can then handle scheduled tests when the browser is still launching.
234-
this._activeBrowsers.add(launched);
230+
this._activeBrowsers.push(launched);
235231

236232
// See the following link for public API of the selenium server.
237233
// https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests
@@ -261,7 +257,9 @@ export class SaucelabsDaemon {
261257
// If a test has been scheduled before the browser completed launching, run
262258
// it now given that the browser is ready now.
263259
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);
265263
}
266264
}),
267265
);
@@ -294,16 +292,32 @@ export class SaucelabsDaemon {
294292

295293
/**
296294
* @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.
298298
**/
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;
304304
}
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;
307321
}
308322

309323
/**

0 commit comments

Comments
 (0)