Skip to content

Commit

Permalink
feat: add test annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
wswebcreation committed Oct 25, 2020
1 parent 76ad432 commit 517b60a
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 86 deletions.
4 changes: 4 additions & 0 deletions src/browser-info.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {SauceLabsOptions} from 'saucelabs'
import {BrowserObject} from "webdriverio";

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

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

/** Saucelabs access key that has been used to launch this browser. */
accessKey: string;

/** Saucelabs driver instance to communicate with this browser. */
driver: BrowserObject
}

/** Type that describes the BrowserMap injection token. */
Expand Down
173 changes: 87 additions & 86 deletions src/launcher/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,90 +14,91 @@ export function SaucelabsLauncher(args,
captureTimeoutLauncherDecorator,
retryLauncherDecorator) {

// Apply base class mixins. This would be nice to have typed, but this is a low-priority now.
baseLauncherDecorator(this);
captureTimeoutLauncherDecorator(this);
retryLauncherDecorator(this);

// initiate driver with null to not close the tunnel too early
connectedDrivers.set(this.id, null)

const log = logger.create('SaucelabsLauncher');
const {
startConnect,
sauceConnectOptions,
seleniumCapabilities,
browserName
} = processConfig(config, args);

// Setup Browser name that will be printed out by Karma.
this.name = browserName + ' on SauceLabs';

// Listen for the start event from Karma. I know, the API is a bit different to how you
// would expect, but we need to follow this approach unless we want to spend more work
// improving type safety.
this.on('start', async (pageUrl: string) => {
if (startConnect) {
try {
// In case the "startConnect" option has been enabled, establish a tunnel and wait
// for it being ready. In case a tunnel is already active, this will just continue
// without establishing a new one.
await sauceConnect.establishTunnel(seleniumCapabilities, sauceConnectOptions);
} catch (error) {
log.error(error);

this._done('failure');
return;
}
}

try {
// See the following link for public API of the selenium server.
// https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests
const driver = await remote(seleniumCapabilities);

// Keep track of all connected drivers because it's possible that there are multiple
// driver instances (e.g. when running with concurrency)
connectedDrivers.set(this.id, driver);

const sessionId = driver.sessionId

log.info('%s session at https://saucelabs.com/tests/%s', browserName, sessionId);
log.debug('Opening "%s" on the selenium client', pageUrl);

// Store the information about the current session in the browserMap. This is necessary
// because otherwise the Saucelabs reporter is not able to report results.
browserMap.set(this.id, {
sessionId,
username: seleniumCapabilities.user,
accessKey: seleniumCapabilities.key,
region: seleniumCapabilities.region,
headless: seleniumCapabilities.headless
});

await driver.url(pageUrl);
} catch (e) {
log.error(e);

// Notify karma about the failure.
this._done('failure');
}
});

this.on('kill', async (done: () => void) => {
try {
const driver = connectedDrivers.get(this.id);
await driver.deleteSession();
} catch (e) {
// We need to ignore the exception here because we want to make sure that Karma is still
// able to retry connecting if Saucelabs itself terminated the session (and not Karma)
// For example if the "idleTimeout" is exceeded and Saucelabs errored the session. See:
// https://wiki.saucelabs.com/display/DOCS/Test+Didn%27t+See+a+New+Command+for+90+Seconds
log.error('Could not quit the Saucelabs selenium connection. Failure message:');
log.error(e);
}

connectedDrivers.delete(this.id)
return process.nextTick(done);
})
// Apply base class mixins. This would be nice to have typed, but this is a low-priority now.
baseLauncherDecorator(this);
captureTimeoutLauncherDecorator(this);
retryLauncherDecorator(this);

// initiate driver with null to not close the tunnel too early
connectedDrivers.set(this.id, null)

const log = logger.create('SaucelabsLauncher');
const {
startConnect,
sauceConnectOptions,
seleniumCapabilities,
browserName
} = processConfig(config, args);

// Setup Browser name that will be printed out by Karma.
this.name = browserName + ' on SauceLabs';

// Listen for the start event from Karma. I know, the API is a bit different to how you
// would expect, but we need to follow this approach unless we want to spend more work
// improving type safety.
this.on('start', async (pageUrl: string) => {
if (startConnect) {
try {
// In case the "startConnect" option has been enabled, establish a tunnel and wait
// for it being ready. In case a tunnel is already active, this will just continue
// without establishing a new one.
await sauceConnect.establishTunnel(seleniumCapabilities, sauceConnectOptions);
} catch (error) {
log.error(error);

this._done('failure');
return;
}
}

try {
// See the following link for public API of the selenium server.
// https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests
const driver = await remote(seleniumCapabilities);

// Keep track of all connected drivers because it's possible that there are multiple
// driver instances (e.g. when running with concurrency)
connectedDrivers.set(this.id, driver);

const sessionId = driver.sessionId

log.info('%s session at https://saucelabs.com/tests/%s', browserName, sessionId);
log.debug('Opening "%s" on the selenium client', pageUrl);

// Store the information about the current session in the browserMap. This is necessary
// because otherwise the Saucelabs reporter is not able to report results.
browserMap.set(this.id, {
sessionId,
username: seleniumCapabilities.user,
accessKey: seleniumCapabilities.key,
region: seleniumCapabilities.region,
headless: seleniumCapabilities.headless,
driver,
});

await driver.url(pageUrl);
} catch (e) {
log.error(e);

// Notify karma about the failure.
this._done('failure');
}
});

this.on('kill', async (done: () => void) => {
try {
const driver = connectedDrivers.get(this.id);
await driver.deleteSession();
} catch (e) {
// We need to ignore the exception here because we want to make sure that Karma is still
// able to retry connecting if Saucelabs itself terminated the session (and not Karma)
// For example if the "idleTimeout" is exceeded and Saucelabs errored the session. See:
// https://wiki.saucelabs.com/display/DOCS/Test+Didn%27t+See+a+New+Command+for+90+Seconds
log.error('Could not quit the Saucelabs selenium connection. Failure message:');
log.error(e);
}

connectedDrivers.delete(this.id)
return process.nextTick(done);
})
}
13 changes: 13 additions & 0 deletions src/reporter/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ export function SaucelabsReporter(logger, browserMap: BrowserMap) {
const log = logger.create('reporter.sauce');
let pendingUpdates: Promise<Job>[] = [];

// This fires when a single test is executed and will update the run in sauce labs with an annotation
// of the test including the status of the test
this.onSpecComplete = function(browser, result) {
const driver = browserMap.get(browser.id).driver
const status = result.success ? '✅' : '❌'

pendingUpdates.push(driver.execute(`sauce:context=${status}: ${result.fullName}`))

if(!result.success && result.log.length > 0){
pendingUpdates.push(driver.execute(`sauce:context=${result.log[0]}`))
}
}

// This fires whenever any browser completes. This is when we want to report results
// to the Saucelabs API, so that people can create coverage banners for their project.
this.onBrowserComplete = function (browser) {
Expand Down

0 comments on commit 517b60a

Please sign in to comment.