launcher.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. /**
  4. * The launcher is responsible for parsing the capabilities from the
  5. * input configuration and launching test runners.
  6. */
  7. const fs = require("fs");
  8. const q = require("q");
  9. const configParser_1 = require("./configParser");
  10. const exitCodes_1 = require("./exitCodes");
  11. const logger_1 = require("./logger");
  12. const runner_1 = require("./runner");
  13. const taskRunner_1 = require("./taskRunner");
  14. const taskScheduler_1 = require("./taskScheduler");
  15. const helper = require("./util");
  16. let logger = new logger_1.Logger('launcher');
  17. let RUNNERS_FAILED_EXIT_CODE = 100;
  18. /**
  19. * Keeps track of a list of task results. Provides method to add a new
  20. * result, aggregate the results into a summary, count failures,
  21. * and save results into a JSON file.
  22. */
  23. class TaskResults {
  24. constructor() {
  25. // TODO: set a type for result
  26. this.results_ = [];
  27. }
  28. add(result) {
  29. this.results_.push(result);
  30. }
  31. totalSpecFailures() {
  32. return this.results_.reduce((specFailures, result) => {
  33. return specFailures + result.failedCount;
  34. }, 0);
  35. }
  36. totalProcessFailures() {
  37. return this.results_.reduce((processFailures, result) => {
  38. return !result.failedCount && result.exitCode !== 0 ? processFailures + 1 : processFailures;
  39. }, 0);
  40. }
  41. saveResults(filepath) {
  42. let jsonOutput = this.results_.reduce((jsonOutput, result) => {
  43. return jsonOutput.concat(result.specResults);
  44. }, []);
  45. let json = JSON.stringify(jsonOutput, null, ' ');
  46. fs.writeFileSync(filepath, json);
  47. }
  48. reportSummary() {
  49. let specFailures = this.totalSpecFailures();
  50. let processFailures = this.totalProcessFailures();
  51. this.results_.forEach((result) => {
  52. let capabilities = result.capabilities;
  53. let shortName = (capabilities.browserName) ? capabilities.browserName : '';
  54. shortName = (capabilities.logName) ?
  55. capabilities.logName :
  56. (capabilities.browserName) ? capabilities.browserName : '';
  57. shortName += (capabilities.version) ? capabilities.version : '';
  58. shortName += (capabilities.logName && capabilities.count < 2) ? '' : ' #' + result.taskId;
  59. if (result.failedCount) {
  60. logger.info(shortName + ' failed ' + result.failedCount + ' test(s)');
  61. }
  62. else if (result.exitCode !== 0) {
  63. logger.info(shortName + ' failed with exit code: ' + result.exitCode);
  64. }
  65. else {
  66. logger.info(shortName + ' passed');
  67. }
  68. });
  69. if (specFailures && processFailures) {
  70. logger.info('overall: ' + specFailures + ' failed spec(s) and ' + processFailures +
  71. ' process(es) failed to complete');
  72. }
  73. else if (specFailures) {
  74. logger.info('overall: ' + specFailures + ' failed spec(s)');
  75. }
  76. else if (processFailures) {
  77. logger.info('overall: ' + processFailures + ' process(es) failed to complete');
  78. }
  79. }
  80. }
  81. let taskResults_ = new TaskResults();
  82. /**
  83. * Initialize and run the tests.
  84. * Exits with 1 on test failure, and RUNNERS_FAILED_EXIT_CODE on unexpected
  85. * failures.
  86. *
  87. * @param {string=} configFile
  88. * @param {Object=} additionalConfig
  89. */
  90. let initFn = function (configFile, additionalConfig) {
  91. let configParser = new configParser_1.ConfigParser();
  92. if (configFile) {
  93. configParser.addFileConfig(configFile);
  94. }
  95. if (additionalConfig) {
  96. configParser.addConfig(additionalConfig);
  97. }
  98. let config = configParser.getConfig();
  99. logger_1.Logger.set(config);
  100. logger.debug('Running with --troubleshoot');
  101. logger.debug('Protractor version: ' + require('../package.json').version);
  102. logger.debug('Your base url for tests is ' + config.baseUrl);
  103. // Run beforeLaunch
  104. helper.runFilenameOrFn_(config.configDir, config.beforeLaunch)
  105. .then(() => {
  106. return q
  107. .Promise((resolve, reject) => {
  108. // 1) If getMultiCapabilities is set, resolve that as
  109. // `multiCapabilities`.
  110. if (config.getMultiCapabilities &&
  111. typeof config.getMultiCapabilities === 'function') {
  112. if (config.multiCapabilities.length || config.capabilities) {
  113. logger.warn('getMultiCapabilities() will override both capabilities ' +
  114. 'and multiCapabilities');
  115. }
  116. // If getMultiCapabilities is defined and a function, use this.
  117. q(config.getMultiCapabilities())
  118. .then((multiCapabilities) => {
  119. config.multiCapabilities = multiCapabilities;
  120. config.capabilities = null;
  121. })
  122. .then(() => {
  123. resolve();
  124. })
  125. .catch(err => {
  126. reject(err);
  127. });
  128. }
  129. else {
  130. resolve();
  131. }
  132. })
  133. .then(() => {
  134. // 2) Set `multicapabilities` using `capabilities`,
  135. // `multicapabilities`,
  136. // or default
  137. if (config.capabilities) {
  138. if (config.multiCapabilities.length) {
  139. logger.warn('You have specified both capabilities and ' +
  140. 'multiCapabilities. This will result in capabilities being ' +
  141. 'ignored');
  142. }
  143. else {
  144. // Use capabilities if multiCapabilities is empty.
  145. config.multiCapabilities = [config.capabilities];
  146. }
  147. }
  148. else if (!config.multiCapabilities.length) {
  149. // Default to chrome if no capabilities given
  150. config.multiCapabilities = [{ browserName: 'chrome' }];
  151. }
  152. });
  153. })
  154. .then(() => {
  155. // 3) If we're in `elementExplorer` mode, run only that.
  156. if (config.elementExplorer || config.framework === 'explorer') {
  157. if (config.multiCapabilities.length != 1) {
  158. throw new Error('Must specify only 1 browser while using elementExplorer');
  159. }
  160. else {
  161. config.capabilities = config.multiCapabilities[0];
  162. }
  163. config.framework = 'explorer';
  164. let runner = new runner_1.Runner(config);
  165. return runner.run().then((exitCode) => {
  166. process.exit(exitCode);
  167. }, (err) => {
  168. logger.error(err);
  169. process.exit(1);
  170. });
  171. }
  172. })
  173. .then(() => {
  174. // 4) Run tests.
  175. let scheduler = new taskScheduler_1.TaskScheduler(config);
  176. process.on('uncaughtException', (exc) => {
  177. let e = (exc instanceof Error) ? exc : new Error(exc);
  178. if (config.ignoreUncaughtExceptions) {
  179. // This can be a sign of a bug in the test framework, that it may
  180. // not be handling WebDriver errors properly. However, we don't
  181. // want these errors to prevent running the tests.
  182. logger.warn('Ignoring uncaught error ' + exc);
  183. return;
  184. }
  185. let errorCode = exitCodes_1.ErrorHandler.parseError(e);
  186. if (errorCode) {
  187. let protractorError = e;
  188. exitCodes_1.ProtractorError.log(logger, errorCode, protractorError.message, protractorError.stack);
  189. process.exit(errorCode);
  190. }
  191. else {
  192. logger.error(e.message);
  193. logger.error(e.stack);
  194. process.exit(exitCodes_1.ProtractorError.CODE);
  195. }
  196. });
  197. process.on('exit', (code) => {
  198. if (code) {
  199. logger.error('Process exited with error code ' + code);
  200. }
  201. else if (scheduler.numTasksOutstanding() > 0) {
  202. logger.error('BUG: launcher exited with ' + scheduler.numTasksOutstanding() +
  203. ' tasks remaining');
  204. process.exit(RUNNERS_FAILED_EXIT_CODE);
  205. }
  206. });
  207. // Run afterlaunch and exit
  208. let cleanUpAndExit = (exitCode) => {
  209. return helper.runFilenameOrFn_(config.configDir, config.afterLaunch, [exitCode])
  210. .then((returned) => {
  211. if (typeof returned === 'number') {
  212. process.exit(returned);
  213. }
  214. else {
  215. process.exit(exitCode);
  216. }
  217. }, (err) => {
  218. logger.error('Error:', err);
  219. process.exit(1);
  220. });
  221. };
  222. let totalTasks = scheduler.numTasksOutstanding();
  223. let forkProcess = false;
  224. if (totalTasks > 1) {
  225. forkProcess = true;
  226. if (config.debug) {
  227. throw new exitCodes_1.ConfigError(logger, 'Cannot run in debug mode with multiCapabilities, count > 1, or sharding');
  228. }
  229. }
  230. let deferred = q.defer(); // Resolved when all tasks are completed
  231. let createNextTaskRunner = () => {
  232. let task = scheduler.nextTask();
  233. if (task) {
  234. let taskRunner = new taskRunner_1.TaskRunner(configFile, additionalConfig, task, forkProcess);
  235. taskRunner.run()
  236. .then((result) => {
  237. if (result.exitCode && !result.failedCount) {
  238. logger.error('Runner process exited unexpectedly with error code: ' + result.exitCode);
  239. }
  240. taskResults_.add(result);
  241. task.done();
  242. createNextTaskRunner();
  243. // If all tasks are finished
  244. if (scheduler.numTasksOutstanding() === 0) {
  245. deferred.resolve();
  246. }
  247. logger.info(scheduler.countActiveTasks() + ' instance(s) of WebDriver still running');
  248. })
  249. .catch((err) => {
  250. logger.error('Error:', err.stack || err.message || err);
  251. cleanUpAndExit(RUNNERS_FAILED_EXIT_CODE);
  252. });
  253. }
  254. };
  255. // Start `scheduler.maxConcurrentTasks()` workers for handling tasks in
  256. // the beginning. As a worker finishes a task, it will pick up the next
  257. // task
  258. // from the scheduler's queue until all tasks are gone.
  259. for (let i = 0; i < scheduler.maxConcurrentTasks(); ++i) {
  260. createNextTaskRunner();
  261. }
  262. logger.info('Running ' + scheduler.countActiveTasks() + ' instances of WebDriver');
  263. // By now all runners have completed.
  264. deferred.promise
  265. .then(function () {
  266. // Save results if desired
  267. if (config.resultJsonOutputFile) {
  268. taskResults_.saveResults(config.resultJsonOutputFile);
  269. }
  270. taskResults_.reportSummary();
  271. let exitCode = 0;
  272. if (taskResults_.totalProcessFailures() > 0) {
  273. exitCode = RUNNERS_FAILED_EXIT_CODE;
  274. }
  275. else if (taskResults_.totalSpecFailures() > 0) {
  276. exitCode = 1;
  277. }
  278. return cleanUpAndExit(exitCode);
  279. })
  280. .done();
  281. })
  282. .done();
  283. };
  284. exports.init = initFn;
  285. //# sourceMappingURL=launcher.js.map