tools/tool-testing/clients/browserstack/index.js
import Client from '../../client.js';
import configuredClients from "./clients.js";
import { enterJob } from '../../../utils/buildmessage.js';
import { execFileSync } from '../../../utils/processes';
import { ensureDependencies } from '../../../cli/dev-bundle-helpers.js';
import {
mkdtemp,
pathJoin,
readFile,
} from '../../../fs/files';
const NPM_DEPENDENCIES = {
'selenium-webdriver': '4.1.1',
'browserstack-local': '1.4.8',
};
const USER = 'dev1141';
// A memoized key from BrowserStackClient._getBrowserStackKey.
let browserStackKey;
export default class BrowserStackClient extends Client {
constructor(options) {
super(options);
enterJob({
title: 'Installing BrowserStack WebDriver in Meteor tool',
}, () => {
ensureDependencies(NPM_DEPENDENCIES);
});
this.npmPackageExports = require('selenium-webdriver');
// Capabilities which are allowed by selenium.
this.config.seleniumOptions =
this.config.seleniumOptions || {};
// Additional capabilities which are unique to BrowserStack.
this.config.browserStackOptions =
this.config.browserStackOptions || {};
this._setName();
}
_setName() {
const name = this.config.seleniumOptions.browserName || "default";
const version = this.config.seleniumOptions.version || "";
const device =
this.config.browserStackOptions.realMobile &&
this.config.browserStackOptions.device || "";
this.name = "BrowserStack: " + name +
(version && ` Version ${version}`) +
(device && ` (Device: ${device})`);
}
connect() {
const key = BrowserStackClient._getBrowserStackKey();
if (! key) {
throw new Error(
"BrowserStack key not found. Ensure that s3cmd is setup with " +
"S3 credentials, or set BROWSERSTACK_ACCESS_KEY in your environment.");
}
const capabilities = {
// Authentication
'browserstack.user': USER,
'browserstack.key': key,
// Use the BrowserStackLocal tunnel, to allow BrowserStack to
// tunnel to the machine this server is running on.
'browserstack.local': true,
// Enabled the capturing of "Visual Logs" (i.e. Screenshots).
'browserstack.debug': true,
// On browsers that support it, capture the console
'browserstack.console': 'errors',
...this.config.seleniumOptions,
...this.config.browserStackOptions,
};
const triggerRequest = () => {
this.driver = new this.npmPackageExports.Builder()
.usingServer('https://hub-cloud.browserstack.com/wd/hub')
.withCapabilities(capabilities)
.build();
this.driver.get(this.url);
}
this._launchBrowserStackTunnel()
.then(triggerRequest)
.catch((e) => {
// In the event of an error, shut down the daemon.
this.stop();
throw e;
});
}
stop() {
this.driver && this.driver.quit();
this.driver = null;
this.tunnelProcess && this.tunnelProcess.stop(() => {});
this.tunnelProcess = null;
}
static _getBrowserStackKey() {
// Use the memoized version, first and foremost.
if (typeof browserStackKey !== "undefined") {
return browserStackKey;
}
if (process.env.BROWSERSTACK_ACCESS_KEY) {
return (browserStackKey = process.env.BROWSERSTACK_ACCESS_KEY);
}
// Try to get the credentials from S3 with the s3cmd tool.
const outputDir = pathJoin(mkdtemp(), "key");
const browserstackKey = "s3://meteor-browserstack-keys/browserstack-key";
try {
execFileSync("s3cmd", ["get",
browserstackKey,
outputDir
]);
return (browserStackKey = readFile(outputDir, "utf8").trim());
} catch (e) {
// A failure is acceptable here; it was just a try.
console.warn(`Failed to load browserstack key from
${browserstackKey}`, e);
}
return (browserStackKey = null);
}
_launchBrowserStackTunnel() {
this.tunnelProcess = new (require('browserstack-local')).Local();
const options = {
key: this.constructor._getBrowserStackKey(),
onlyAutomate: true,
verbose: true,
// The ",0" means "SSL off". It's localhost, after all.
only: `${this.host},${this.port},0`,
}
return new Promise((resolve, reject) => {
this.tunnelProcess.start(options, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
static prerequisitesMet() {
return !! this._getBrowserStackKey();
}
static pushClients(clients, appConfig) {
configuredClients.forEach((client) => {
clients.push(new BrowserStackClient(
{
...appConfig,
config: {
seleniumOptions: client.selenium,
browserStackOptions: client.browserstack,
},
},
));
});
}
}