Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add configuration options #13

Merged
merged 13 commits into from
Aug 19, 2024
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ This is the official Sauce Labs browser provider plugin for [TestCafe](http://de
npm install testcafe-browser-provider-sauce
```

## Setup

Before using this plugin, you need to set the `SAUCE_USERNAME` and
`SAUCE_ACCESS_KEY` environment variables. Your Sauce Labs Username and Access
Key are available from your [dashboard](https://app.saucelabs.com/user-settings).

Furthermore, a [Sauce Connect](https://docs.saucelabs.com/secure-connections/sauce-connect-5/)
tunnel is required to run tests on Sauce Labs. After launching a tunnel, specify
the tunnel name using the `SAUCE_TUNNEL_NAME` environment variable.

## Usage

You can determine the available browser aliases by running
Expand All @@ -19,7 +29,7 @@ testcafe -b sauce
When you run tests from the command line, use the alias when specifying browsers:

```
testcafe sauce:chrome 'path/to/test/file.js'
testcafe "sauce:chrome@latest:Windows 11" 'path/to/test/file.js'
```

When you use API, pass the alias to the `browsers()` method:
Expand All @@ -28,10 +38,28 @@ When you use API, pass the alias to the `browsers()` method:
testCafe
.createRunner()
.src('path/to/test/file.js')
.browsers('sauce:chrome')
.browsers('sauce:chrome@latest:Windows 11')
.run();
```

## Configuration

Full overview of the available configuration options.

Mandatory environment variables:

- `SAUCE_USERNAME` - Your Sauce Labs username.
- `SAUCE_ACCESS_KEY` - Your Sauce Labs access key.
- `SAUCE_TUNNEL_NAME` - The Sauce Connect tunnel name.

Optional environment variables:

- `SAUCE_JOB_NAME` - Specify the job name for all jobs. Defaults to `TestCafe via ${browserName}@${browserVersion} on ${platformName}`.
- `SAUCE_BUILD` - All jobs will be associated with this build. The default value is randomly generated.
- `SAUCE_TAGS` - A comma separated list of tags to apply to all jobs.
- `SAUCE_REGION` - The Sauce Labs region. Valid values are `us-west-1` (default) or `eu-central-1`.
- `SAUCE_SCREEN_RESOLUTION` - The desktop browser screen resolution (not applicable to mobile). The format is `1920x1080`.

## Development

To use the local version of the plugin, you can link the package:
Expand Down
43 changes: 36 additions & 7 deletions src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,56 @@ export type Size = {
height: number;
};

export type Region = 'us-west-1' | 'eu-central-1' | 'staging';

const hostByRegion = new Map<Region, string>([
['us-west-1', 'ondemand.us-west-1.saucelabs.com'],
['eu-central-1', 'ondemand.eu-central-1.saucelabs.com'],
['staging', 'ondemand.staging.saucelabs.net'],
]);

export class SauceDriver {
private readonly username: string;
private readonly accessKey: string;
private readonly tunnelName: string;
private sessions = new Map<string, Client>();

constructor(username: string, accessKey: string, tunnelName: string) {
private readonly build: string;
private readonly tags?: string[];
private readonly region: Region;
private readonly jobName?: string;

constructor(
username: string,
accessKey: string,
region: Region,
tunnelName: string,
jobName?: string,
build?: string,
tags?: string[],
) {
this.username = username;
this.accessKey = accessKey;
this.region = region;
this.tunnelName = tunnelName;
this.jobName = jobName;
this.build = build ?? Math.random().toString(36).substring(2, 10);
this.tags = tags;
}

createCapabilities(
browserName: string,
browserVersion: string,
platformName: string,
screenResolution?: string,
): WebDriver.Capabilities {
const sauceOpts = {
name: 'testcafe sauce provider job', // TODO make this configurable
build: 'TCPRVDR', // TODO make this configurable
name:
this.jobName ??
`TestCafe via ${browserName}@${browserVersion} on ${platformName}`,
build: this.build,
tags: this.tags,
tunnelIdentifier: this.tunnelName,
screenResolution: screenResolution,
idleTimeout: 3600, // 1 hour
enableTestReport: true,
};
Expand Down Expand Up @@ -59,17 +88,19 @@ export class SauceDriver {
browserName: string,
browserVersion: string,
platformName: string,
screenResolution?: string,
) {
const webDriver = await wd.newSession({
protocol: 'https',
hostname: `ondemand.us-west-1.saucelabs.com`, // TODO multi region support
hostname: hostByRegion.get(this.region),
port: 443,
user: this.username,
key: this.accessKey,
capabilities: this.createCapabilities(
browserName,
browserVersion,
platformName,
screenResolution,
),
logLevel: 'error',
connectionRetryTimeout: 9 * 60 * 1000, // 9 minutes
Expand All @@ -82,8 +113,6 @@ export class SauceDriver {

this.sessions.set(browserId, webDriver);

// TODO do we need a keep-alive?

await webDriver.navigateTo(url);

return {
Expand Down
9 changes: 9 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,12 @@ export class WindowSizeRangeError extends Error {
this.name = 'WindowSizeRangeError';
}
}

export class InvalidRegionError extends Error {
constructor() {
super(
'Invalid region. The region must be one of the following: us-west-1, eu-central-1.',
);
this.name = 'InvalidRegionError';
}
}
29 changes: 27 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { SauceDriver } from './driver';
import { AuthError, TunnelNameError, WindowSizeRangeError } from './errors';
import {
AuthError,
InvalidRegionError,
TunnelNameError,
WindowSizeRangeError,
} from './errors';
import { getPlatforms } from './api';
import { rcompareOses, rcompareVersions } from './sort';
import { isDevice } from './device';
Expand Down Expand Up @@ -40,15 +45,34 @@ module.exports = {
const username = process.env.SAUCE_USERNAME;
const accessKey = process.env.SAUCE_ACCESS_KEY;
const tunnelName = process.env.SAUCE_TUNNEL_NAME;
const build = process.env.SAUCE_BUILD;
const tags = (process.env.SAUCE_TAGS || '').split(',');
const region = process.env.SAUCE_REGION || 'us-west-1';
const jobName = process.env.SAUCE_JOB_NAME;

if (!username || !accessKey) {
throw new AuthError();
}
if (!tunnelName) {
throw new TunnelNameError();
}
if (
region !== 'us-west-1' &&
region !== 'eu-central-1' &&
region !== 'staging'
) {
throw new InvalidRegionError();
}

sauceDriver = new SauceDriver(username, accessKey, tunnelName);
sauceDriver = new SauceDriver(
username,
accessKey,
region,
tunnelName,
jobName,
build,
tags,
);

const resp = await getPlatforms({ username, accessKey });
const browserMap = new Map<Browser, Map<Version, Set<Os>>>();
Expand Down Expand Up @@ -145,6 +169,7 @@ module.exports = {
bName,
bVersion,
os,
process.env.SAUCE_SCREEN_RESOLUTION,
);
console.log('Browser started.');

Expand Down