From 58fa94b2776e15117f78ed5ed3c2171a5fefae11 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Mon, 29 Jul 2024 15:45:10 -0700 Subject: [PATCH] feat: Support simulator/emulator test (#9) * feat: Support simulator/emulator test * rename file * extract sauce opts from caps * let testcafe handle exception --- .gitignore | 1 + src/device.ts | 19 +++++++++++++++++++ src/driver.ts | 52 +++++++++++++++++++++++++++++++++++++++++---------- src/errors.ts | 7 +++++++ src/index.ts | 22 ++++++++++++++++++++++ 5 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 src/device.ts diff --git a/.gitignore b/.gitignore index 3063f07..3955c21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ lib node_modules +.vscode diff --git a/src/device.ts b/src/device.ts new file mode 100644 index 0000000..ec54662 --- /dev/null +++ b/src/device.ts @@ -0,0 +1,19 @@ +/** + * Checks if the provided name indicates a device (simulator or emulator). + * + * @param name - The name to check. + * @returns `true` if the name includes 'Simulator' or 'Emulator', otherwise `false`. + */ +export function isDevice(name: string): boolean { + return name.includes('Simulator') || name.includes('Emulator'); +} + +/** + * Checks if the provided name indicates a simulator. + * + * @param name - The name to check. + * @returns `true` if the name includes 'Simulator', otherwise `false`. + */ +export function isSimulator(name: string): boolean { + return name.includes('Simulator'); +} diff --git a/src/driver.ts b/src/driver.ts index aa418c0..6bd123d 100644 --- a/src/driver.ts +++ b/src/driver.ts @@ -1,4 +1,6 @@ import wd, { Client } from 'webdriver'; +import { isDevice, isSimulator } from './device'; +import { CreateSessionError } from './errors'; export class SauceDriver { private readonly username: string; @@ -12,6 +14,39 @@ export class SauceDriver { this.tunnelName = tunnelName; } + createCapabilities( + browserName: string, + browserVersion: string, + platformName: string, + ): WebDriver.Capabilities { + const sauceOpts = { + name: 'testcafe sauce provider job', // TODO make this configurable + build: 'TCPRVDR', // TODO make this configurable + tunnelIdentifier: this.tunnelName, + idleTimeout: 3600, // 1 hour + enableTestReport: true, + }; + + if (!isDevice(browserName)) { + return { + browserName, + browserVersion, + platformName, + 'sauce:options': sauceOpts, + }; + } + + const isSim = isSimulator(browserName); + return { + browserName: isSim ? 'Safari' : 'Chrome', + platformName: isSim ? 'iOS' : 'Android', + 'appium:deviceName': browserName, + 'appium:platformVersion': browserVersion, + 'appium:automationName': isSim ? 'XCUITest' : 'UiAutomator2', + 'sauce:options': sauceOpts, + }; + } + async openBrowser( browserId: string, url: string, @@ -21,27 +56,24 @@ export class SauceDriver { ) { const webDriver = await wd.newSession({ protocol: 'https', - hostname: `ondemand.saucelabs.com`, // TODO multi region support + hostname: `ondemand.us-west-1.saucelabs.com`, // TODO multi region support port: 443, user: this.username, key: this.accessKey, - capabilities: { + capabilities: this.createCapabilities( browserName, browserVersion, platformName, - 'sauce:options': { - name: 'testcafe sauce provider job', // TODO make this configurable - build: 'TCPRVDR', // TODO make this configurable - tunnelIdentifier: this.tunnelName, - idleTimeout: 3600, // 1 hour - enableTestReport: true, - } as WebDriver.DesiredCapabilities, - }, + ), logLevel: 'error', connectionRetryTimeout: 9 * 60 * 1000, // 9 minutes connectionRetryCount: 3, path: '/wd/hub', }); + if (!webDriver.sessionId) { + throw new CreateSessionError(); + } + this.sessions.set(browserId, webDriver); // TODO do we need a keep-alive? diff --git a/src/errors.ts b/src/errors.ts index b39edd3..3f62164 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -17,3 +17,10 @@ export class TunnelNameError extends Error { this.name = 'TunnelNameError'; } } + +export class CreateSessionError extends Error { + constructor() { + super('Failed to run test on Sauce Labs: no session id returned'); + this.name = 'CreateSessionError'; + } +} diff --git a/src/index.ts b/src/index.ts index 59c5bb2..41c151c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { SauceDriver } from './driver.js'; import { AuthError, TunnelNameError } from './errors'; import { getPlatforms } from './api'; import { rcompareOses, rcompareVersions } from './sort'; +import { isDevice } from './device.js'; type Browser = string; type Version = string; @@ -89,6 +90,27 @@ module.exports = { }); }); }); + + const devices: string[] = []; + browserMap.forEach((versionMap, name) => { + if (!isDevice(name)) { + return; + } + [...versionMap.keys()] + .sort(rcompareVersions) + .slice(0, 2) + .forEach((v) => { + const oses = versionMap.get(v); + if (!oses) { + return; + } + oses.forEach((os) => { + devices.push(`${name}@${v}:${os}`); + }); + }); + }); + devices.sort().reverse(); + platforms.push(...devices); }, /**