UNPKG

webdriver-manager

Version:

A selenium server and browser driver manager for your end to end tests.

458 lines 19.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const http = require("http"); const minimist = require("minimist"); const path = require("path"); const binaries_1 = require("../binaries"); const cli_1 = require("../cli"); const config_1 = require("../config"); const files_1 = require("../files"); const utils_1 = require("../utils"); const Opt = require("./"); const opts_1 = require("./opts"); const commandName = 'start'; config_1.Config.runCommand = commandName; let logger = new cli_1.Logger('start'); let prog = new cli_1.Program() .command(commandName, 'start up the selenium server') .action(start) .addOption(opts_1.Opts[Opt.OUT_DIR]) .addOption(opts_1.Opts[Opt.SELENIUM_PORT]) .addOption(opts_1.Opts[Opt.APPIUM_PORT]) .addOption(opts_1.Opts[Opt.AVD_PORT]) .addOption(opts_1.Opts[Opt.VERSIONS_STANDALONE]) .addOption(opts_1.Opts[Opt.VERSIONS_CHROME]) .addOption(opts_1.Opts[Opt.VERSIONS_GECKO]) .addOption(opts_1.Opts[Opt.VERSIONS_ANDROID]) .addOption(opts_1.Opts[Opt.VERSIONS_APPIUM]) .addOption(opts_1.Opts[Opt.CHROME_LOGS]) .addOption(opts_1.Opts[Opt.LOGGING]) .addOption(opts_1.Opts[Opt.ANDROID]) .addOption(opts_1.Opts[Opt.AVDS]) .addOption(opts_1.Opts[Opt.AVD_USE_SNAPSHOTS]) .addOption(opts_1.Opts[Opt.STARTED_SIGNIFIER]) .addOption(opts_1.Opts[Opt.SIGNAL_VIA_IPC]) .addOption(opts_1.Opts[Opt.QUIET]) .addOption(opts_1.Opts[Opt.DETACH]); if (config_1.Config.osType() === 'Darwin') { prog.addOption(opts_1.Opts[Opt.IOS]); } if (config_1.Config.osType() === 'Windows_NT') { prog.addOption(opts_1.Opts[Opt.VERSIONS_IE]).addOption(opts_1.Opts[Opt.IE64]).addOption(opts_1.Opts[Opt.EDGE]); } exports.program = prog; // stand alone runner let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); if (argv._[0] === 'start-run') { prog.run(JSON.parse(JSON.stringify(argv))); } else if (argv._[0] === 'start-help') { prog.printHelp(); } // Manage processes used in android emulation let androidProcesses = []; let androidActiveAVDs = []; /** * Parses the options and starts the selenium standalone server. * @param options */ function start(options) { if (options[Opt.DETACH].getBoolean()) { return detachedRun(options); } let osType = config_1.Config.osType(); let stdio = options[Opt.QUIET].getBoolean() ? 'pipe' : 'inherit'; let binaries = files_1.FileManager.setupBinaries(); let seleniumPort = options[Opt.SELENIUM_PORT].getString(); let appiumPort = options[Opt.APPIUM_PORT].getString(); let avdPort = options[Opt.AVD_PORT].getNumber(); let android = options[Opt.ANDROID].getBoolean(); let outputDir = config_1.Config.getSeleniumDir(); if (options[Opt.OUT_DIR].getString()) { if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { outputDir = options[Opt.OUT_DIR].getString(); } else { outputDir = path.resolve(config_1.Config.getBaseDir(), options[Opt.OUT_DIR].getString()); } } try { // check if folder exists fs.statSync(outputDir).isDirectory(); } catch (e) { // if the folder does not exist, quit early. logger.warn('the out_dir path ' + outputDir + ' does not exist, run webdriver-manager update'); return; } let chromeLogs = null; let loggingFile = null; if (options[Opt.CHROME_LOGS].getString()) { if (path.isAbsolute(options[Opt.CHROME_LOGS].getString())) { chromeLogs = options[Opt.CHROME_LOGS].getString(); } else { chromeLogs = path.resolve(config_1.Config.getBaseDir(), options[Opt.CHROME_LOGS].getString()); } } binaries[binaries_1.Standalone.id].versionCustom = options[Opt.VERSIONS_STANDALONE].getString(); binaries[binaries_1.ChromeDriver.id].versionCustom = options[Opt.VERSIONS_CHROME].getString(); binaries[binaries_1.GeckoDriver.id].versionCustom = options[Opt.VERSIONS_GECKO].getString(); if (options[Opt.VERSIONS_IE]) { binaries[binaries_1.IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString(); } binaries[binaries_1.AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString(); binaries[binaries_1.Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString(); let downloadedBinaries = files_1.FileManager.downloadedBinaries(outputDir); if (downloadedBinaries[binaries_1.Standalone.id] == null) { logger.error('Selenium Standalone is not present. Install with ' + 'webdriver-manager update --standalone'); process.exit(1); } let promises = []; let args = []; if (osType === 'Linux') { // selenium server may take a long time to start because /dev/random is BLOCKING if there is not // enough entropy the solution is to use /dev/urandom, which is NON-BLOCKING (use /dev/./urandom // because of a java bug) // https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/1301 // https://bugs.openjdk.java.net/browse/JDK-6202721 promises.push(Promise.resolve(args.push('-Djava.security.egd=file:///dev/./urandom'))); } if (options[Opt.LOGGING].getString()) { if (path.isAbsolute(options[Opt.LOGGING].getString())) { loggingFile = options[Opt.LOGGING].getString(); } else { loggingFile = path.resolve(config_1.Config.getBaseDir(), options[Opt.LOGGING].getString()); } promises.push(Promise.resolve(args.push('-Djava.util.logging.config.file=' + loggingFile))); } if (downloadedBinaries[binaries_1.ChromeDriver.id] != null) { let chrome = binaries[binaries_1.ChromeDriver.id]; promises.push(chrome.getUrl(chrome.versionCustom) .then(() => { args.push('-Dwebdriver.chrome.driver=' + path.resolve(outputDir, binaries[binaries_1.ChromeDriver.id].executableFilename())); if (chromeLogs != null) { args.push('-Dwebdriver.chrome.logfile=' + chromeLogs); } }) .catch(err => { console.log(err); })); } if (downloadedBinaries[binaries_1.GeckoDriver.id] != null) { let gecko = binaries[binaries_1.GeckoDriver.id]; promises.push(gecko.getUrl(gecko.versionCustom) .then(() => { args.push('-Dwebdriver.gecko.driver=' + path.resolve(outputDir, binaries[binaries_1.GeckoDriver.id].executableFilename())); }) .catch(err => { console.log(err); })); } if (downloadedBinaries[binaries_1.IEDriver.id] != null) { let ie = binaries[binaries_1.IEDriver.id]; promises.push(ie.getUrl(ie.versionCustom) .then(() => { binaries[binaries_1.IEDriver.id].osarch = 'Win32'; // use Win 32 by default if (options[Opt.IE64].getBoolean()) { binaries[binaries_1.IEDriver.id].osarch = config_1.Config.osArch(); // use the system architecture } args.push('-Dwebdriver.ie.driver=' + path.resolve(outputDir, binaries[binaries_1.IEDriver.id].executableFilename())); }) .catch(err => { console.log(err); })); } if (options[Opt.EDGE] && options[Opt.EDGE].getString()) { // validate that the file exists prior to adding it to args try { let edgeFile = options[Opt.EDGE].getString(); if (fs.statSync(edgeFile).isFile()) { promises.push(Promise.resolve(args.push('-Dwebdriver.edge.driver=' + options[Opt.EDGE].getString()))); } } catch (err) { // Either the default file or user specified location of the edge // driver does not exist. } } Promise.all(promises).then(() => { let standalone = binaries[binaries_1.Standalone.id]; standalone.getUrl(standalone.versionCustom) .then(() => { // starting android if (android) { if (downloadedBinaries[binaries_1.AndroidSDK.id] != null) { let avds = options[Opt.AVDS].getString(); startAndroid(outputDir, binaries[binaries_1.AndroidSDK.id], avds.split(','), options[Opt.AVD_USE_SNAPSHOTS].getBoolean(), avdPort, stdio); } else { logger.warn('Not starting android because it is not installed'); } } if (downloadedBinaries[binaries_1.Appium.id] != null) { startAppium(outputDir, binaries[binaries_1.Appium.id], binaries[binaries_1.AndroidSDK.id], appiumPort, stdio); } args.push('-jar'); args.push(path.resolve(outputDir, binaries[binaries_1.Standalone.id].filename())); }) .catch(err => { console.log(err); }) .then(() => { // Add the port parameter, has to declared after the jar file if (seleniumPort) { args.push('-port', seleniumPort); } let argsToString = ''; for (let arg in args) { argsToString += ' ' + args[arg]; } logger.info('java' + argsToString); let seleniumProcess = utils_1.spawn('java', args, stdio); if (options[Opt.STARTED_SIGNIFIER].getString()) { signalWhenReady(options[Opt.STARTED_SIGNIFIER].getString(), options[Opt.SIGNAL_VIA_IPC].getBoolean(), outputDir, seleniumPort, downloadedBinaries[binaries_1.Appium.id] ? appiumPort : '', binaries[binaries_1.AndroidSDK.id], avdPort, androidActiveAVDs); } logger.info('seleniumProcess.pid: ' + seleniumProcess.pid); seleniumProcess.on('exit', (code) => { logger.info('Selenium Standalone has exited with code ' + code); shutdownEverything(); process.exit(process.exitCode || code); }); seleniumProcess.on('error', (error) => { logger.warn('Selenium Standalone server encountered an error: ' + error); }); process.stdin.resume(); process.stdin.on('data', (chunk) => { logger.info('Attempting to shut down selenium nicely'); shutdownEverything(seleniumPort); }); process.on('SIGINT', () => { logger.info('Staying alive until the Selenium Standalone process exits'); shutdownEverything(seleniumPort); }); }); }); } function startAndroid(outputDir, sdk, avds, useSnapshots, port, stdio) { let sdkPath = path.resolve(outputDir, sdk.executableFilename()); if (avds[0] == 'all') { avds = require(path.resolve(sdkPath, 'available_avds.json')); } else if (avds[0] == 'none') { avds.length = 0; } const minAVDPort = 5554; const maxAVDPort = 5586 - 2 * avds.length; if (avds.length && ((port < minAVDPort) || (port > maxAVDPort))) { throw new RangeError('AVD Port must be between ' + minAVDPort + ' and ' + maxAVDPort + ' to emulate ' + avds.length + ' android devices'); } avds.forEach((avd, i) => { // Credit to appium-ci, which this code was adapted from let emuBin = 'emulator'; // TODO(sjelin): get the 64bit linux version working let emuArgs = [ '-avd', avd + '-v' + sdk.versionCustom + '-wd-manager', '-netfast', ]; let portArg = null; if (!useSnapshots) { emuArgs = emuArgs.concat(['-no-snapshot-load', '-no-snapshot-save']); } if (port) { portArg = port + i * 2; emuArgs = emuArgs.concat(['-port', '' + portArg]); } if (emuBin !== 'emulator') { emuArgs = emuArgs.concat(['-qemu', '-enable-kvm']); } logger.info('Starting ' + avd + ' on ' + (portArg == null ? 'default port' : 'port ' + portArg)); let child = utils_1.spawn(path.resolve(sdkPath, 'tools', emuBin), emuArgs, stdio); child.on('error', (error) => { logger.warn(avd + ' encountered an error: ' + error); }); androidProcesses.push(child); androidActiveAVDs.push(avd); }); } function killAndroid() { for (var i = 0; i < androidProcesses.length; i++) { logger.info('Shutting down ' + androidActiveAVDs[i]); androidProcesses[i].kill(); } androidProcesses.length = androidActiveAVDs.length = 0; } // Manage appium process let appiumProcess; function startAppium(outputDir, binary, androidSDK, port, stdio) { logger.info('Starting appium server'); if (androidSDK) { process.env.ANDROID_HOME = path.resolve(outputDir, androidSDK.executableFilename()); } appiumProcess = utils_1.spawn('npm', ['run', 'appium'].concat(port ? ['--', '--port', port] : []), stdio, { cwd: path.resolve(outputDir, binary.filename()) }); } function killAppium() { if (appiumProcess != null) { appiumProcess.kill(); appiumProcess = null; } } function signalWhenReady(signal, viaIPC, outputDir, seleniumPort, appiumPort, androidSDK, avdPort, avdNames) { const maxWait = 10 * 60 * 1000; // Ten minutes function waitFor(getStatus, testStatus, desc) { const checkInterval = 100; return new Promise((resolve, reject) => { let waited = 0; (function recursiveCheck() { setTimeout(() => { getStatus() .then((status) => { if (!testStatus(status)) { return Promise.reject('Invalid status' + (desc ? ' for ' + desc : '') + ': ' + status); } }) .then(() => { resolve(); }, (error) => { waited += checkInterval; if (waited < maxWait) { recursiveCheck(); } else { reject('Timed out' + (desc ? ' wating for' + desc : '') + '. Final rejection reason: ' + JSON.stringify(error)); } }); }, checkInterval); })(); }); } ; function waitForAndroid(avdPort, avdName, appiumPort) { let sdkPath = path.resolve(outputDir, androidSDK.executableFilename()); logger.info('Waiting for ' + avdName + '\'s emulator to start'); return utils_1.adb(sdkPath, avdPort, 'wait-for-device', maxWait) .then(() => { logger.info('Waiting for ' + avdName + '\'s OS to boot up'); return waitFor(() => { return utils_1.adb(sdkPath, avdPort, 'shell', maxWait, ['getprop', 'sys.boot_completed']); }, (status) => { return status.trim() == '1'; }, avdName + '\'s OS'); }, (error) => { return Promise.reject('Failed to wait for ' + avdName + '\'s emulator to start (' + error.code + ': ' + error.message + ')'); }) .then(() => { logger.info('Waiting for ' + avdName + ' to be ready to launch chrome'); let version = binaries_1.AndroidSDK.VERSIONS[parseInt(avdName.slice('android-'.length))]; return utils_1.request('POST', appiumPort, '/wd/hub/session', maxWait, { desiredCapabilities: { browserName: 'chrome', platformName: 'Android', platformVersion: version, deviceName: 'Android Emulator' } }) .then((data) => { return JSON.parse(data)['sessionId']; }, (error) => { return Promise.reject('Could not start chrome on ' + avdName + ' (' + error.code + ': ' + error.message + ')'); }); }) .then((sessionId) => { logger.info('Shutting down dummy chrome instance for ' + avdName); return utils_1.request('DELETE', appiumPort, '/wd/hub/session/' + sessionId) .then(() => { }, (error) => { return Promise.reject('Could not close chrome on ' + avdName + ' (' + error.code + ': ' + error.message + ')'); }); }); } let pending = [waitFor(() => { return utils_1.request('GET', seleniumPort, '/wd/hub/status', maxWait); }, (status) => { return JSON.parse(status).status == 0; }, 'selenium server')]; if (appiumPort) { pending.push(waitFor(() => { return utils_1.request('GET', appiumPort, '/wd/hub/status', maxWait); }, (status) => { return JSON.parse(status).status == 0; }, 'appium server')); } if (androidSDK && avdPort) { for (let i = 0; i < avdNames.length; i++) { pending.push(waitForAndroid(avdPort + 2 * i, avdNames[i], appiumPort)); } } Promise.all(pending).then(() => { logger.info('Everything started'); sendStartedSignal(signal, viaIPC); }, (error) => { logger.error(error); shutdownEverything(seleniumPort); process.exitCode = 1; }); } function sendStartedSignal(signal, viaIPC) { if (viaIPC) { if (process.send) { return process.send(signal); } else { logger.warn('No IPC channel, sending signal via stdout'); } } console.log(signal); } function shutdownEverything(seleniumPort) { if (seleniumPort) { http.get('http://localhost:' + seleniumPort + '/selenium-server/driver/?cmd=shutDownSeleniumServer'); } killAndroid(); killAppium(); } function detachedRun(options) { var file = path.resolve(__dirname, '..', 'webdriver.js'); var oldSignal = options[Opt.STARTED_SIGNIFIER].getString(); var oldViaIPC = options[Opt.SIGNAL_VIA_IPC].getBoolean(); options[Opt.DETACH].value = false; options[Opt.STARTED_SIGNIFIER].value = 'server started'; options[Opt.SIGNAL_VIA_IPC].value = true; let args = [file, commandName].concat(cli_1.unparseOptions(options)); var unreffed = false; let child = utils_1.spawn(process.execPath, args, ['ignore', 1, 2, 'ipc']); child.on('message', (message) => { if (message == options[Opt.STARTED_SIGNIFIER].getString()) { if (oldSignal) { sendStartedSignal(oldSignal, oldViaIPC); } logger.info('Detached pid: ' + child.pid); child.disconnect(); child.unref(); unreffed = true; } }); child.on('exit', (code) => { if (!unreffed) { if (code == 0) { logger.warn('Server never seemed to start, and has now exited'); } else { logger.error('Server never seemed to start, and has probably crashed'); } process.exit(code); } }); } //# sourceMappingURL=start.js.map