@fpjs-incubator/broyster
Version:
161 lines (160 loc) • 7.31 kB
JavaScript
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;
}
;