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, }, }, )); }); } }