UNPKG

11.6 kBJavaScriptView Raw
1"use strict";
2Object.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 */
7const fs = require("fs");
8const q = require("q");
9const configParser_1 = require("./configParser");
10const exitCodes_1 = require("./exitCodes");
11const logger_1 = require("./logger");
12const runner_1 = require("./runner");
13const taskRunner_1 = require("./taskRunner");
14const taskScheduler_1 = require("./taskScheduler");
15const helper = require("./util");
16let logger = new logger_1.Logger('launcher');
17let 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 */
23class 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}
81let 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 */
90let 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};
284exports.init = initFn;
285//# sourceMappingURL=launcher.js.map
\No newline at end of file