Skip to content

Commit 517b60a

Browse files
committed
feat: add test annotation
1 parent 76ad432 commit 517b60a

File tree

3 files changed

+104
-86
lines changed

3 files changed

+104
-86
lines changed

src/browser-info.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {SauceLabsOptions} from 'saucelabs'
2+
import {BrowserObject} from "webdriverio";
23

34
type SauceBaseOption = Pick<SauceLabsOptions, 'headless' | 'region'>
45

@@ -15,6 +16,9 @@ export interface SaucelabsBrowser extends SauceBaseOption {
1516

1617
/** Saucelabs access key that has been used to launch this browser. */
1718
accessKey: string;
19+
20+
/** Saucelabs driver instance to communicate with this browser. */
21+
driver: BrowserObject
1822
}
1923

2024
/** Type that describes the BrowserMap injection token. */

src/launcher/launcher.ts

Lines changed: 87 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -14,90 +14,91 @@ export function SaucelabsLauncher(args,
1414
captureTimeoutLauncherDecorator,
1515
retryLauncherDecorator) {
1616

17-
// Apply base class mixins. This would be nice to have typed, but this is a low-priority now.
18-
baseLauncherDecorator(this);
19-
captureTimeoutLauncherDecorator(this);
20-
retryLauncherDecorator(this);
21-
22-
// initiate driver with null to not close the tunnel too early
23-
connectedDrivers.set(this.id, null)
24-
25-
const log = logger.create('SaucelabsLauncher');
26-
const {
27-
startConnect,
28-
sauceConnectOptions,
29-
seleniumCapabilities,
30-
browserName
31-
} = processConfig(config, args);
32-
33-
// Setup Browser name that will be printed out by Karma.
34-
this.name = browserName + ' on SauceLabs';
35-
36-
// Listen for the start event from Karma. I know, the API is a bit different to how you
37-
// would expect, but we need to follow this approach unless we want to spend more work
38-
// improving type safety.
39-
this.on('start', async (pageUrl: string) => {
40-
if (startConnect) {
41-
try {
42-
// In case the "startConnect" option has been enabled, establish a tunnel and wait
43-
// for it being ready. In case a tunnel is already active, this will just continue
44-
// without establishing a new one.
45-
await sauceConnect.establishTunnel(seleniumCapabilities, sauceConnectOptions);
46-
} catch (error) {
47-
log.error(error);
48-
49-
this._done('failure');
50-
return;
51-
}
52-
}
53-
54-
try {
55-
// See the following link for public API of the selenium server.
56-
// https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests
57-
const driver = await remote(seleniumCapabilities);
58-
59-
// Keep track of all connected drivers because it's possible that there are multiple
60-
// driver instances (e.g. when running with concurrency)
61-
connectedDrivers.set(this.id, driver);
62-
63-
const sessionId = driver.sessionId
64-
65-
log.info('%s session at https://saucelabs.com/tests/%s', browserName, sessionId);
66-
log.debug('Opening "%s" on the selenium client', pageUrl);
67-
68-
// Store the information about the current session in the browserMap. This is necessary
69-
// because otherwise the Saucelabs reporter is not able to report results.
70-
browserMap.set(this.id, {
71-
sessionId,
72-
username: seleniumCapabilities.user,
73-
accessKey: seleniumCapabilities.key,
74-
region: seleniumCapabilities.region,
75-
headless: seleniumCapabilities.headless
76-
});
77-
78-
await driver.url(pageUrl);
79-
} catch (e) {
80-
log.error(e);
81-
82-
// Notify karma about the failure.
83-
this._done('failure');
84-
}
85-
});
86-
87-
this.on('kill', async (done: () => void) => {
88-
try {
89-
const driver = connectedDrivers.get(this.id);
90-
await driver.deleteSession();
91-
} catch (e) {
92-
// We need to ignore the exception here because we want to make sure that Karma is still
93-
// able to retry connecting if Saucelabs itself terminated the session (and not Karma)
94-
// For example if the "idleTimeout" is exceeded and Saucelabs errored the session. See:
95-
// https://wiki.saucelabs.com/display/DOCS/Test+Didn%27t+See+a+New+Command+for+90+Seconds
96-
log.error('Could not quit the Saucelabs selenium connection. Failure message:');
97-
log.error(e);
98-
}
99-
100-
connectedDrivers.delete(this.id)
101-
return process.nextTick(done);
102-
})
17+
// Apply base class mixins. This would be nice to have typed, but this is a low-priority now.
18+
baseLauncherDecorator(this);
19+
captureTimeoutLauncherDecorator(this);
20+
retryLauncherDecorator(this);
21+
22+
// initiate driver with null to not close the tunnel too early
23+
connectedDrivers.set(this.id, null)
24+
25+
const log = logger.create('SaucelabsLauncher');
26+
const {
27+
startConnect,
28+
sauceConnectOptions,
29+
seleniumCapabilities,
30+
browserName
31+
} = processConfig(config, args);
32+
33+
// Setup Browser name that will be printed out by Karma.
34+
this.name = browserName + ' on SauceLabs';
35+
36+
// Listen for the start event from Karma. I know, the API is a bit different to how you
37+
// would expect, but we need to follow this approach unless we want to spend more work
38+
// improving type safety.
39+
this.on('start', async (pageUrl: string) => {
40+
if (startConnect) {
41+
try {
42+
// In case the "startConnect" option has been enabled, establish a tunnel and wait
43+
// for it being ready. In case a tunnel is already active, this will just continue
44+
// without establishing a new one.
45+
await sauceConnect.establishTunnel(seleniumCapabilities, sauceConnectOptions);
46+
} catch (error) {
47+
log.error(error);
48+
49+
this._done('failure');
50+
return;
51+
}
52+
}
53+
54+
try {
55+
// See the following link for public API of the selenium server.
56+
// https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests
57+
const driver = await remote(seleniumCapabilities);
58+
59+
// Keep track of all connected drivers because it's possible that there are multiple
60+
// driver instances (e.g. when running with concurrency)
61+
connectedDrivers.set(this.id, driver);
62+
63+
const sessionId = driver.sessionId
64+
65+
log.info('%s session at https://saucelabs.com/tests/%s', browserName, sessionId);
66+
log.debug('Opening "%s" on the selenium client', pageUrl);
67+
68+
// Store the information about the current session in the browserMap. This is necessary
69+
// because otherwise the Saucelabs reporter is not able to report results.
70+
browserMap.set(this.id, {
71+
sessionId,
72+
username: seleniumCapabilities.user,
73+
accessKey: seleniumCapabilities.key,
74+
region: seleniumCapabilities.region,
75+
headless: seleniumCapabilities.headless,
76+
driver,
77+
});
78+
79+
await driver.url(pageUrl);
80+
} catch (e) {
81+
log.error(e);
82+
83+
// Notify karma about the failure.
84+
this._done('failure');
85+
}
86+
});
87+
88+
this.on('kill', async (done: () => void) => {
89+
try {
90+
const driver = connectedDrivers.get(this.id);
91+
await driver.deleteSession();
92+
} catch (e) {
93+
// We need to ignore the exception here because we want to make sure that Karma is still
94+
// able to retry connecting if Saucelabs itself terminated the session (and not Karma)
95+
// For example if the "idleTimeout" is exceeded and Saucelabs errored the session. See:
96+
// https://wiki.saucelabs.com/display/DOCS/Test+Didn%27t+See+a+New+Command+for+90+Seconds
97+
log.error('Could not quit the Saucelabs selenium connection. Failure message:');
98+
log.error(e);
99+
}
100+
101+
connectedDrivers.delete(this.id)
102+
return process.nextTick(done);
103+
})
103104
}

src/reporter/reporter.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ export function SaucelabsReporter(logger, browserMap: BrowserMap) {
99
const log = logger.create('reporter.sauce');
1010
let pendingUpdates: Promise<Job>[] = [];
1111

12+
// This fires when a single test is executed and will update the run in sauce labs with an annotation
13+
// of the test including the status of the test
14+
this.onSpecComplete = function(browser, result) {
15+
const driver = browserMap.get(browser.id).driver
16+
const status = result.success ? '✅' : '❌'
17+
18+
pendingUpdates.push(driver.execute(`sauce:context=${status}: ${result.fullName}`))
19+
20+
if(!result.success && result.log.length > 0){
21+
pendingUpdates.push(driver.execute(`sauce:context=${result.log[0]}`))
22+
}
23+
}
24+
1225
// This fires whenever any browser completes. This is when we want to report results
1326
// to the Saucelabs API, so that people can create coverage banners for their project.
1427
this.onBrowserComplete = function (browser) {

0 commit comments

Comments
 (0)