webdriver-manager
Version:
A selenium server and browser driver manager for your end to end tests.
458 lines • 19.4 kB
JavaScript
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
;