UNPKG

@fpjs-incubator/broyster

Version:
161 lines (160 loc) 7.31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BrowserStackLauncher = void 0; const tslib_1 = require("tslib"); const custom_servers_1 = require("./custom_servers"); const capture_timeout_handler_1 = require("./capture_timeout_handler"); function BrowserStackLauncher(args, browserMap, logger, config, baseLauncherDecorator, retryLauncherDecorator, browserStackSessionFactory, browserStackLocalManager, browserStackSessionsManager, browserStackBrowsers) { var _a, _b; baseLauncherDecorator(this); retryLauncherDecorator(this); const log = logger.create('Browserstack ' + this.id); const bsLocalManagerPromise = browserStackLocalManager.run(log); const suitableDevicesPromise = getSuitableDevices(browserStackBrowsers, args, log); const captureTimeout = new capture_timeout_handler_1.CaptureTimeout(this, config, log); let startAttempt = 0; this.name = makeLauncherName(args); let browser; let pendingHeartBeat; const timeout = Date.now() + ((_b = (_a = config.browserStack) === null || _a === void 0 ? void 0 : _a.queueTimeout) !== null && _b !== void 0 ? _b : 60000); const heartbeat = () => { var _a, _b; pendingHeartBeat = setTimeout(() => tslib_1.__awaiter(this, void 0, void 0, function* () { if (!browser) { return; } if (Date.now() > timeout) { clearTimeout(pendingHeartBeat); return; } try { yield browser.getTitle(); heartbeat(); } catch (e) { clearTimeout(pendingHeartBeat); } return; }), ((_b = (_a = config.browserStack) === null || _a === void 0 ? void 0 : _a.idleTimeout) !== null && _b !== void 0 ? _b : 10000) * 0.9); }; this.on('start', (pageUrl) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const [{ name: deviceName, osVersion }] = yield Promise.all([chooseDevice(), bsLocalManagerPromise]); // The queue should be checked right before creating a BrowserStack session to reduce the probability of a race // condition where another Karma session also checks the queue in these events. yield browserStackSessionsManager.ensureQueue(this, log); log.debug(`creating browser with attributes: ${JSON.stringify(args)}`); log.debug(`attempt: ${startAttempt}`); log.debug(`device name: ${deviceName}`); log.debug(`OS version override: ${osVersion}`); startAttempt += 1; browser = yield browserStackSessionFactory.createBrowser(Object.assign(Object.assign({}, args), { osVersion }), deviceName, this.id, log); captureTimeout.onStart(); const sessionId = (yield browser.getSession()).getId(); log.debug(`WebDriver SessionId: ${sessionId}`); browserMap.set(this.id, { browser, sessionId }); pageUrl = makeUrl(pageUrl, args.useHttps); yield browser.get(pageUrl); heartbeat(); } catch (err) { log.error(`Failed to start ${this.name}:`); log.error(err); this._done('failure'); // this may end up hanging the process // however it is very unlikely as it requires all attempts to fail and never create a driver } })); this.on('done', () => { captureTimeout.onDone(); }); this.on('kill', (done) => tslib_1.__awaiter(this, void 0, void 0, function* () { var _c, _d; if (pendingHeartBeat) { clearTimeout(pendingHeartBeat); } try { log.debug('killing browser ' + this.id); const browser = (_c = browserMap.get(this.id)) === null || _c === void 0 ? void 0 : _c.browser; if (browser) { yield browser.quit(); log.debug('killed'); } else { log.debug('browser not found, cannot kill'); } } catch (err) { log.error(`Could not quit the BrowserStack connection for ${this.name}. Failure message:`); log.error((_d = err) !== null && _d !== void 0 ? _d : String(err)); } browserMap.delete(this.id); this._done(); return process.nextTick(done); })); this.on('exit', (done) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield browserStackLocalManager.kill(log); done(); })); const chooseDevice = () => tslib_1.__awaiter(this, void 0, void 0, function* () { const devices = yield suitableDevicesPromise; if (devices.length === 0) { throw new Error('No device available for the given configuration'); } const device = devices[startAttempt % devices.length]; if (device.name) { log.info(`Using ${device.name} for the browser ${this.name}`); } return device; }); } exports.BrowserStackLauncher = BrowserStackLauncher; /** * Returns the list of devices suitable for the given launcher configuration. */ function getSuitableDevices(browserStackBrowsers, args, log) { var _a, _b, _c, _d; return tslib_1.__awaiter(this, void 0, void 0, function* () { let rawDevices = null; switch (args.platform) { case 'iOS': rawDevices = yield browserStackBrowsers.getIOSDevices((_a = args.osVersion) !== null && _a !== void 0 ? _a : null, args.deviceType === 'iPad' ? 'ipad' : 'iphone', ((_b = args.browserName) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'chrome' ? 'chrome' : 'safari', true, log); break; case 'Android': rawDevices = yield browserStackBrowsers.getAndroidDevices((_c = args.osVersion) !== null && _c !== void 0 ? _c : null, ((_d = args.browserName) === null || _d === void 0 ? void 0 : _d.toLowerCase()) === 'samsung' ? 'samsung' : 'chrome', true, log); break; } const devices = rawDevices ? rawDevices.map((device) => { var _a; return ({ name: (_a = device.device) !== null && _a !== void 0 ? _a : undefined, osVersion: device.os_version }); }) : [{ name: undefined, osVersion: args.osVersion }]; log.debug(`devices suitable for attributes ${JSON.stringify(args)}: ${JSON.stringify(devices)}`); return devices; }); } function makeLauncherName(args) { const components = [args.browserName]; if (args.browserVersion) { components.push(args.browserVersion); } if (args.deviceType) { components.push(`on ${args.deviceType}`); } if (args.platform) { components.push(`on ${args.platform}`); if (args.osVersion) { components.push(args.osVersion); } } components.push('on BrowserStack'); return components.join(' '); } function makeUrl(karmaUrl, isHttps) { if (isHttps === undefined) { return karmaUrl; } const url = new URL(karmaUrl); url.protocol = isHttps ? 'https' : 'http'; if (isHttps) { url.port = (0, custom_servers_1.calculateHttpsPort)(parseInt(url.port)).toString(); } return url.href; }