start.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const fs = require("fs");
  4. const http = require("http");
  5. const minimist = require("minimist");
  6. const path = require("path");
  7. const binaries_1 = require("../binaries");
  8. const cli_1 = require("../cli");
  9. const config_1 = require("../config");
  10. const files_1 = require("../files");
  11. const utils_1 = require("../utils");
  12. const Opt = require("./");
  13. const opts_1 = require("./opts");
  14. const commandName = 'start';
  15. config_1.Config.runCommand = commandName;
  16. let logger = new cli_1.Logger('start');
  17. let prog = new cli_1.Program()
  18. .command(commandName, 'start up the selenium server')
  19. .action(start)
  20. .addOption(opts_1.Opts[Opt.OUT_DIR])
  21. .addOption(opts_1.Opts[Opt.SELENIUM_PORT])
  22. .addOption(opts_1.Opts[Opt.APPIUM_PORT])
  23. .addOption(opts_1.Opts[Opt.AVD_PORT])
  24. .addOption(opts_1.Opts[Opt.VERSIONS_STANDALONE])
  25. .addOption(opts_1.Opts[Opt.VERSIONS_CHROME])
  26. .addOption(opts_1.Opts[Opt.VERSIONS_GECKO])
  27. .addOption(opts_1.Opts[Opt.VERSIONS_ANDROID])
  28. .addOption(opts_1.Opts[Opt.VERSIONS_APPIUM])
  29. .addOption(opts_1.Opts[Opt.CHROME_LOGS])
  30. .addOption(opts_1.Opts[Opt.LOGGING])
  31. .addOption(opts_1.Opts[Opt.ANDROID])
  32. .addOption(opts_1.Opts[Opt.AVDS])
  33. .addOption(opts_1.Opts[Opt.AVD_USE_SNAPSHOTS])
  34. .addOption(opts_1.Opts[Opt.STARTED_SIGNIFIER])
  35. .addOption(opts_1.Opts[Opt.SIGNAL_VIA_IPC])
  36. .addOption(opts_1.Opts[Opt.QUIET])
  37. .addOption(opts_1.Opts[Opt.DETACH]);
  38. if (config_1.Config.osType() === 'Darwin') {
  39. prog.addOption(opts_1.Opts[Opt.IOS]);
  40. }
  41. if (config_1.Config.osType() === 'Windows_NT') {
  42. prog.addOption(opts_1.Opts[Opt.VERSIONS_IE]).addOption(opts_1.Opts[Opt.IE64]).addOption(opts_1.Opts[Opt.EDGE]);
  43. }
  44. exports.program = prog;
  45. // stand alone runner
  46. let argv = minimist(process.argv.slice(2), prog.getMinimistOptions());
  47. if (argv._[0] === 'start-run') {
  48. prog.run(JSON.parse(JSON.stringify(argv)));
  49. }
  50. else if (argv._[0] === 'start-help') {
  51. prog.printHelp();
  52. }
  53. // Manage processes used in android emulation
  54. let androidProcesses = [];
  55. let androidActiveAVDs = [];
  56. /**
  57. * Parses the options and starts the selenium standalone server.
  58. * @param options
  59. */
  60. function start(options) {
  61. if (options[Opt.DETACH].getBoolean()) {
  62. return detachedRun(options);
  63. }
  64. let osType = config_1.Config.osType();
  65. let stdio = options[Opt.QUIET].getBoolean() ? 'pipe' : 'inherit';
  66. let binaries = files_1.FileManager.setupBinaries();
  67. let seleniumPort = options[Opt.SELENIUM_PORT].getString();
  68. let appiumPort = options[Opt.APPIUM_PORT].getString();
  69. let avdPort = options[Opt.AVD_PORT].getNumber();
  70. let android = options[Opt.ANDROID].getBoolean();
  71. let outputDir = config_1.Config.getSeleniumDir();
  72. if (options[Opt.OUT_DIR].getString()) {
  73. if (path.isAbsolute(options[Opt.OUT_DIR].getString())) {
  74. outputDir = options[Opt.OUT_DIR].getString();
  75. }
  76. else {
  77. outputDir = path.resolve(config_1.Config.getBaseDir(), options[Opt.OUT_DIR].getString());
  78. }
  79. }
  80. try {
  81. // check if folder exists
  82. fs.statSync(outputDir).isDirectory();
  83. }
  84. catch (e) {
  85. // if the folder does not exist, quit early.
  86. logger.warn('the out_dir path ' + outputDir + ' does not exist, run webdriver-manager update');
  87. return;
  88. }
  89. let chromeLogs = null;
  90. let loggingFile = null;
  91. if (options[Opt.CHROME_LOGS].getString()) {
  92. if (path.isAbsolute(options[Opt.CHROME_LOGS].getString())) {
  93. chromeLogs = options[Opt.CHROME_LOGS].getString();
  94. }
  95. else {
  96. chromeLogs = path.resolve(config_1.Config.getBaseDir(), options[Opt.CHROME_LOGS].getString());
  97. }
  98. }
  99. binaries[binaries_1.Standalone.id].versionCustom = options[Opt.VERSIONS_STANDALONE].getString();
  100. binaries[binaries_1.ChromeDriver.id].versionCustom = options[Opt.VERSIONS_CHROME].getString();
  101. binaries[binaries_1.GeckoDriver.id].versionCustom = options[Opt.VERSIONS_GECKO].getString();
  102. if (options[Opt.VERSIONS_IE]) {
  103. binaries[binaries_1.IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString();
  104. }
  105. binaries[binaries_1.AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString();
  106. binaries[binaries_1.Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString();
  107. let downloadedBinaries = files_1.FileManager.downloadedBinaries(outputDir);
  108. if (downloadedBinaries[binaries_1.Standalone.id] == null) {
  109. logger.error('Selenium Standalone is not present. Install with ' +
  110. 'webdriver-manager update --standalone');
  111. process.exit(1);
  112. }
  113. let promises = [];
  114. let args = [];
  115. if (osType === 'Linux') {
  116. // selenium server may take a long time to start because /dev/random is BLOCKING if there is not
  117. // enough entropy the solution is to use /dev/urandom, which is NON-BLOCKING (use /dev/./urandom
  118. // because of a java bug)
  119. // https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/1301
  120. // https://bugs.openjdk.java.net/browse/JDK-6202721
  121. promises.push(Promise.resolve(args.push('-Djava.security.egd=file:///dev/./urandom')));
  122. }
  123. if (options[Opt.LOGGING].getString()) {
  124. if (path.isAbsolute(options[Opt.LOGGING].getString())) {
  125. loggingFile = options[Opt.LOGGING].getString();
  126. }
  127. else {
  128. loggingFile = path.resolve(config_1.Config.getBaseDir(), options[Opt.LOGGING].getString());
  129. }
  130. promises.push(Promise.resolve(args.push('-Djava.util.logging.config.file=' + loggingFile)));
  131. }
  132. if (downloadedBinaries[binaries_1.ChromeDriver.id] != null) {
  133. let chrome = binaries[binaries_1.ChromeDriver.id];
  134. promises.push(chrome.getUrl(chrome.versionCustom)
  135. .then(() => {
  136. args.push('-Dwebdriver.chrome.driver=' +
  137. path.resolve(outputDir, binaries[binaries_1.ChromeDriver.id].executableFilename()));
  138. if (chromeLogs != null) {
  139. args.push('-Dwebdriver.chrome.logfile=' + chromeLogs);
  140. }
  141. })
  142. .catch(err => {
  143. console.log(err);
  144. }));
  145. }
  146. if (downloadedBinaries[binaries_1.GeckoDriver.id] != null) {
  147. let gecko = binaries[binaries_1.GeckoDriver.id];
  148. promises.push(gecko.getUrl(gecko.versionCustom)
  149. .then(() => {
  150. args.push('-Dwebdriver.gecko.driver=' +
  151. path.resolve(outputDir, binaries[binaries_1.GeckoDriver.id].executableFilename()));
  152. })
  153. .catch(err => {
  154. console.log(err);
  155. }));
  156. }
  157. if (downloadedBinaries[binaries_1.IEDriver.id] != null) {
  158. let ie = binaries[binaries_1.IEDriver.id];
  159. promises.push(ie.getUrl(ie.versionCustom)
  160. .then(() => {
  161. binaries[binaries_1.IEDriver.id].osarch = 'Win32'; // use Win 32 by default
  162. if (options[Opt.IE64].getBoolean()) {
  163. binaries[binaries_1.IEDriver.id].osarch =
  164. config_1.Config.osArch(); // use the system architecture
  165. }
  166. args.push('-Dwebdriver.ie.driver=' +
  167. path.resolve(outputDir, binaries[binaries_1.IEDriver.id].executableFilename()));
  168. })
  169. .catch(err => {
  170. console.log(err);
  171. }));
  172. }
  173. if (options[Opt.EDGE] && options[Opt.EDGE].getString()) {
  174. // validate that the file exists prior to adding it to args
  175. try {
  176. let edgeFile = options[Opt.EDGE].getString();
  177. if (fs.statSync(edgeFile).isFile()) {
  178. promises.push(Promise.resolve(args.push('-Dwebdriver.edge.driver=' + options[Opt.EDGE].getString())));
  179. }
  180. }
  181. catch (err) {
  182. // Either the default file or user specified location of the edge
  183. // driver does not exist.
  184. }
  185. }
  186. Promise.all(promises).then(() => {
  187. let standalone = binaries[binaries_1.Standalone.id];
  188. standalone.getUrl(standalone.versionCustom)
  189. .then(() => {
  190. // starting android
  191. if (android) {
  192. if (downloadedBinaries[binaries_1.AndroidSDK.id] != null) {
  193. let avds = options[Opt.AVDS].getString();
  194. startAndroid(outputDir, binaries[binaries_1.AndroidSDK.id], avds.split(','), options[Opt.AVD_USE_SNAPSHOTS].getBoolean(), avdPort, stdio);
  195. }
  196. else {
  197. logger.warn('Not starting android because it is not installed');
  198. }
  199. }
  200. if (downloadedBinaries[binaries_1.Appium.id] != null) {
  201. startAppium(outputDir, binaries[binaries_1.Appium.id], binaries[binaries_1.AndroidSDK.id], appiumPort, stdio);
  202. }
  203. args.push('-jar');
  204. args.push(path.resolve(outputDir, binaries[binaries_1.Standalone.id].filename()));
  205. })
  206. .catch(err => {
  207. console.log(err);
  208. })
  209. .then(() => {
  210. // Add the port parameter, has to declared after the jar file
  211. if (seleniumPort) {
  212. args.push('-port', seleniumPort);
  213. }
  214. let argsToString = '';
  215. for (let arg in args) {
  216. argsToString += ' ' + args[arg];
  217. }
  218. logger.info('java' + argsToString);
  219. let seleniumProcess = utils_1.spawn('java', args, stdio);
  220. if (options[Opt.STARTED_SIGNIFIER].getString()) {
  221. 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);
  222. }
  223. logger.info('seleniumProcess.pid: ' + seleniumProcess.pid);
  224. seleniumProcess.on('exit', (code) => {
  225. logger.info('Selenium Standalone has exited with code ' + code);
  226. shutdownEverything();
  227. process.exit(process.exitCode || code);
  228. });
  229. seleniumProcess.on('error', (error) => {
  230. logger.warn('Selenium Standalone server encountered an error: ' + error);
  231. });
  232. process.stdin.resume();
  233. process.stdin.on('data', (chunk) => {
  234. logger.info('Attempting to shut down selenium nicely');
  235. shutdownEverything(seleniumPort);
  236. });
  237. process.on('SIGINT', () => {
  238. logger.info('Staying alive until the Selenium Standalone process exits');
  239. shutdownEverything(seleniumPort);
  240. });
  241. });
  242. });
  243. }
  244. function startAndroid(outputDir, sdk, avds, useSnapshots, port, stdio) {
  245. let sdkPath = path.resolve(outputDir, sdk.executableFilename());
  246. if (avds[0] == 'all') {
  247. avds = require(path.resolve(sdkPath, 'available_avds.json'));
  248. }
  249. else if (avds[0] == 'none') {
  250. avds.length = 0;
  251. }
  252. const minAVDPort = 5554;
  253. const maxAVDPort = 5586 - 2 * avds.length;
  254. if (avds.length && ((port < minAVDPort) || (port > maxAVDPort))) {
  255. throw new RangeError('AVD Port must be between ' + minAVDPort + ' and ' + maxAVDPort + ' to emulate ' +
  256. avds.length + ' android devices');
  257. }
  258. avds.forEach((avd, i) => {
  259. // Credit to appium-ci, which this code was adapted from
  260. let emuBin = 'emulator'; // TODO(sjelin): get the 64bit linux version working
  261. let emuArgs = [
  262. '-avd',
  263. avd + '-v' + sdk.versionCustom + '-wd-manager',
  264. '-netfast',
  265. ];
  266. let portArg = null;
  267. if (!useSnapshots) {
  268. emuArgs = emuArgs.concat(['-no-snapshot-load', '-no-snapshot-save']);
  269. }
  270. if (port) {
  271. portArg = port + i * 2;
  272. emuArgs = emuArgs.concat(['-port', '' + portArg]);
  273. }
  274. if (emuBin !== 'emulator') {
  275. emuArgs = emuArgs.concat(['-qemu', '-enable-kvm']);
  276. }
  277. logger.info('Starting ' + avd + ' on ' + (portArg == null ? 'default port' : 'port ' + portArg));
  278. let child = utils_1.spawn(path.resolve(sdkPath, 'tools', emuBin), emuArgs, stdio);
  279. child.on('error', (error) => {
  280. logger.warn(avd + ' encountered an error: ' + error);
  281. });
  282. androidProcesses.push(child);
  283. androidActiveAVDs.push(avd);
  284. });
  285. }
  286. function killAndroid() {
  287. for (var i = 0; i < androidProcesses.length; i++) {
  288. logger.info('Shutting down ' + androidActiveAVDs[i]);
  289. androidProcesses[i].kill();
  290. }
  291. androidProcesses.length = androidActiveAVDs.length = 0;
  292. }
  293. // Manage appium process
  294. let appiumProcess;
  295. function startAppium(outputDir, binary, androidSDK, port, stdio) {
  296. logger.info('Starting appium server');
  297. if (androidSDK) {
  298. process.env.ANDROID_HOME = path.resolve(outputDir, androidSDK.executableFilename());
  299. }
  300. appiumProcess = utils_1.spawn('npm', ['run', 'appium'].concat(port ? ['--', '--port', port] : []), stdio, { cwd: path.resolve(outputDir, binary.filename()) });
  301. }
  302. function killAppium() {
  303. if (appiumProcess != null) {
  304. appiumProcess.kill();
  305. appiumProcess = null;
  306. }
  307. }
  308. function signalWhenReady(signal, viaIPC, outputDir, seleniumPort, appiumPort, androidSDK, avdPort, avdNames) {
  309. const maxWait = 10 * 60 * 1000; // Ten minutes
  310. function waitFor(getStatus, testStatus, desc) {
  311. const checkInterval = 100;
  312. return new Promise((resolve, reject) => {
  313. let waited = 0;
  314. (function recursiveCheck() {
  315. setTimeout(() => {
  316. getStatus()
  317. .then((status) => {
  318. if (!testStatus(status)) {
  319. return Promise.reject('Invalid status' + (desc ? ' for ' + desc : '') + ': ' + status);
  320. }
  321. })
  322. .then(() => {
  323. resolve();
  324. }, (error) => {
  325. waited += checkInterval;
  326. if (waited < maxWait) {
  327. recursiveCheck();
  328. }
  329. else {
  330. reject('Timed out' + (desc ? ' wating for' + desc : '') +
  331. '. Final rejection reason: ' + JSON.stringify(error));
  332. }
  333. });
  334. }, checkInterval);
  335. })();
  336. });
  337. }
  338. ;
  339. function waitForAndroid(avdPort, avdName, appiumPort) {
  340. let sdkPath = path.resolve(outputDir, androidSDK.executableFilename());
  341. logger.info('Waiting for ' + avdName + '\'s emulator to start');
  342. return utils_1.adb(sdkPath, avdPort, 'wait-for-device', maxWait)
  343. .then(() => {
  344. logger.info('Waiting for ' + avdName + '\'s OS to boot up');
  345. return waitFor(() => {
  346. return utils_1.adb(sdkPath, avdPort, 'shell', maxWait, ['getprop', 'sys.boot_completed']);
  347. }, (status) => {
  348. return status.trim() == '1';
  349. }, avdName + '\'s OS');
  350. }, (error) => {
  351. return Promise.reject('Failed to wait for ' + avdName + '\'s emulator to start (' + error.code + ': ' +
  352. error.message + ')');
  353. })
  354. .then(() => {
  355. logger.info('Waiting for ' + avdName + ' to be ready to launch chrome');
  356. let version = binaries_1.AndroidSDK.VERSIONS[parseInt(avdName.slice('android-'.length))];
  357. return utils_1.request('POST', appiumPort, '/wd/hub/session', maxWait, {
  358. desiredCapabilities: {
  359. browserName: 'chrome',
  360. platformName: 'Android',
  361. platformVersion: version,
  362. deviceName: 'Android Emulator'
  363. }
  364. })
  365. .then((data) => {
  366. return JSON.parse(data)['sessionId'];
  367. }, (error) => {
  368. return Promise.reject('Could not start chrome on ' + avdName + ' (' + error.code + ': ' +
  369. error.message + ')');
  370. });
  371. })
  372. .then((sessionId) => {
  373. logger.info('Shutting down dummy chrome instance for ' + avdName);
  374. return utils_1.request('DELETE', appiumPort, '/wd/hub/session/' + sessionId)
  375. .then(() => { }, (error) => {
  376. return Promise.reject('Could not close chrome on ' + avdName + ' (' + error.code + ': ' +
  377. error.message + ')');
  378. });
  379. });
  380. }
  381. let pending = [waitFor(() => {
  382. return utils_1.request('GET', seleniumPort, '/wd/hub/status', maxWait);
  383. }, (status) => {
  384. return JSON.parse(status).status == 0;
  385. }, 'selenium server')];
  386. if (appiumPort) {
  387. pending.push(waitFor(() => {
  388. return utils_1.request('GET', appiumPort, '/wd/hub/status', maxWait);
  389. }, (status) => {
  390. return JSON.parse(status).status == 0;
  391. }, 'appium server'));
  392. }
  393. if (androidSDK && avdPort) {
  394. for (let i = 0; i < avdNames.length; i++) {
  395. pending.push(waitForAndroid(avdPort + 2 * i, avdNames[i], appiumPort));
  396. }
  397. }
  398. Promise.all(pending).then(() => {
  399. logger.info('Everything started');
  400. sendStartedSignal(signal, viaIPC);
  401. }, (error) => {
  402. logger.error(error);
  403. shutdownEverything(seleniumPort);
  404. process.exitCode = 1;
  405. });
  406. }
  407. function sendStartedSignal(signal, viaIPC) {
  408. if (viaIPC) {
  409. if (process.send) {
  410. return process.send(signal);
  411. }
  412. else {
  413. logger.warn('No IPC channel, sending signal via stdout');
  414. }
  415. }
  416. console.log(signal);
  417. }
  418. function shutdownEverything(seleniumPort) {
  419. if (seleniumPort) {
  420. http.get('http://localhost:' + seleniumPort + '/selenium-server/driver/?cmd=shutDownSeleniumServer');
  421. }
  422. killAndroid();
  423. killAppium();
  424. }
  425. function detachedRun(options) {
  426. var file = path.resolve(__dirname, '..', 'webdriver.js');
  427. var oldSignal = options[Opt.STARTED_SIGNIFIER].getString();
  428. var oldViaIPC = options[Opt.SIGNAL_VIA_IPC].getBoolean();
  429. options[Opt.DETACH].value = false;
  430. options[Opt.STARTED_SIGNIFIER].value = 'server started';
  431. options[Opt.SIGNAL_VIA_IPC].value = true;
  432. let args = [file, commandName].concat(cli_1.unparseOptions(options));
  433. var unreffed = false;
  434. let child = utils_1.spawn(process.execPath, args, ['ignore', 1, 2, 'ipc']);
  435. child.on('message', (message) => {
  436. if (message == options[Opt.STARTED_SIGNIFIER].getString()) {
  437. if (oldSignal) {
  438. sendStartedSignal(oldSignal, oldViaIPC);
  439. }
  440. logger.info('Detached pid: ' + child.pid);
  441. child.disconnect();
  442. child.unref();
  443. unreffed = true;
  444. }
  445. });
  446. child.on('exit', (code) => {
  447. if (!unreffed) {
  448. if (code == 0) {
  449. logger.warn('Server never seemed to start, and has now exited');
  450. }
  451. else {
  452. logger.error('Server never seemed to start, and has probably crashed');
  453. }
  454. process.exit(code);
  455. }
  456. });
  457. }
  458. //# sourceMappingURL=start.js.map