UNPKG

18 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const events_1 = require("events");
4const q = require("q");
5const selenium_webdriver_1 = require("selenium-webdriver");
6const util = require("util");
7const browser_1 = require("./browser");
8const driverProviders_1 = require("./driverProviders");
9const logger_1 = require("./logger");
10const plugins_1 = require("./plugins");
11const ptor_1 = require("./ptor");
12const helper = require("./util");
13let logger = new logger_1.Logger('runner');
14/*
15 * Runner is responsible for starting the execution of a test run and triggering
16 * setup, teardown, managing config, etc through its various dependencies.
17 *
18 * The Protractor Runner is a node EventEmitter with the following events:
19 * - testPass
20 * - testFail
21 * - testsDone
22 *
23 * @param {Object} config
24 * @constructor
25 */
26class Runner extends events_1.EventEmitter {
27 constructor(config) {
28 super();
29 /**
30 * Responsible for cleaning up test run and exiting the process.
31 * @private
32 * @param {int} Standard unix exit code
33 */
34 this.exit_ = function (exitCode) {
35 return helper.runFilenameOrFn_(this.config_.configDir, this.config_.onCleanUp, [exitCode])
36 .then((returned) => {
37 if (typeof returned === 'number') {
38 return returned;
39 }
40 else {
41 return exitCode;
42 }
43 });
44 };
45 this.config_ = config;
46 if (config.v8Debug) {
47 // Call this private function instead of sending SIGUSR1 because Windows.
48 process['_debugProcess'](process.pid);
49 }
50 if (config.nodeDebug) {
51 process['_debugProcess'](process.pid);
52 let flow = selenium_webdriver_1.promise.controlFlow();
53 this.ready_ = flow.execute(() => {
54 let nodedebug = require('child_process').fork('debug', ['localhost:5858']);
55 process.on('exit', function () {
56 nodedebug.kill('SIGTERM');
57 });
58 nodedebug.on('exit', function () {
59 process.exit(1);
60 });
61 }, 'start the node debugger').then(() => {
62 return flow.timeout(1000, 'waiting for debugger to attach');
63 });
64 }
65 if (config.capabilities && config.capabilities.seleniumAddress) {
66 config.seleniumAddress = config.capabilities.seleniumAddress;
67 }
68 this.loadDriverProvider_(config);
69 this.setTestPreparer(config.onPrepare);
70 }
71 /**
72 * Registrar for testPreparers - executed right before tests run.
73 * @public
74 * @param {string/Fn} filenameOrFn
75 */
76 setTestPreparer(filenameOrFn) {
77 this.preparer_ = filenameOrFn;
78 }
79 /**
80 * Executor of testPreparer
81 * @public
82 * @param {string[]=} An optional list of command line arguments the framework will accept.
83 * @return {q.Promise} A promise that will resolve when the test preparers
84 * are finished.
85 */
86 runTestPreparer(extraFlags) {
87 let unknownFlags = this.config_.unknownFlags_ || [];
88 if (extraFlags) {
89 unknownFlags = unknownFlags.filter((f) => extraFlags.indexOf(f) === -1);
90 }
91 if (unknownFlags.length > 0 && !this.config_.disableChecks) {
92 // TODO: Make this throw a ConfigError in Protractor 6.
93 logger.warn('Ignoring unknown extra flags: ' + unknownFlags.join(', ') + '. This will be' +
94 ' an error in future versions, please use --disableChecks flag to disable the ' +
95 ' Protractor CLI flag checks. ');
96 }
97 return this.plugins_.onPrepare().then(() => {
98 return helper.runFilenameOrFn_(this.config_.configDir, this.preparer_);
99 });
100 }
101 /**
102 * Called after each test finishes.
103 *
104 * Responsible for `restartBrowserBetweenTests`
105 *
106 * @public
107 * @return {q.Promise} A promise that will resolve when the work here is done
108 */
109 afterEach() {
110 let ret;
111 this.frameworkUsesAfterEach = true;
112 if (this.config_.restartBrowserBetweenTests) {
113 this.restartPromise = this.restartPromise || q(ptor_1.protractor.browser.restart());
114 ret = this.restartPromise;
115 this.restartPromise = undefined;
116 }
117 return ret || q();
118 }
119 /**
120 * Grab driver provider based on type
121 * @private
122 *
123 * Priority
124 * 1) if directConnect is true, use that
125 * 2) if seleniumAddress is given, use that
126 * 3) if a Sauce Labs account is given, use that
127 * 4) if a seleniumServerJar is specified, use that
128 * 5) try to find the seleniumServerJar in protractor/selenium
129 */
130 loadDriverProvider_(config) {
131 this.config_ = config;
132 this.driverprovider_ = driverProviders_1.buildDriverProvider(this.config_);
133 }
134 /**
135 * Getter for the Runner config object
136 * @public
137 * @return {Object} config
138 */
139 getConfig() {
140 return this.config_;
141 }
142 /**
143 * Get the control flow used by this runner.
144 * @return {Object} WebDriver control flow.
145 */
146 controlFlow() {
147 return selenium_webdriver_1.promise.controlFlow();
148 }
149 /**
150 * Sets up convenience globals for test specs
151 * @private
152 */
153 setupGlobals_(browser_) {
154 // Keep $, $$, element, and by/By under the global protractor namespace
155 ptor_1.protractor.browser = browser_;
156 ptor_1.protractor.$ = browser_.$;
157 ptor_1.protractor.$$ = browser_.$$;
158 ptor_1.protractor.element = browser_.element;
159 ptor_1.protractor.by = ptor_1.protractor.By = browser_1.ProtractorBrowser.By;
160 ptor_1.protractor.ExpectedConditions = browser_.ExpectedConditions;
161 if (!this.config_.noGlobals) {
162 // Export protractor to the global namespace to be used in tests.
163 global.browser = browser_;
164 global.$ = browser_.$;
165 global.$$ = browser_.$$;
166 global.element = browser_.element;
167 global.by = global.By = ptor_1.protractor.By;
168 global.ExpectedConditions = ptor_1.protractor.ExpectedConditions;
169 }
170 global.protractor = ptor_1.protractor;
171 if (!this.config_.skipSourceMapSupport) {
172 // Enable sourcemap support for stack traces.
173 require('source-map-support').install();
174 }
175 // Required by dart2js machinery.
176 // https://code.google.com/p/dart/source/browse/branches/bleeding_edge/dart/sdk/lib/js/dart2js/js_dart2js.dart?spec=svn32943&r=32943#487
177 global.DartObject = function (o) {
178 this.o = o;
179 };
180 }
181 /**
182 * Create a new driver from a driverProvider. Then set up a
183 * new protractor instance using this driver.
184 * This is used to set up the initial protractor instances and any
185 * future ones.
186 *
187 * @param {Plugin} plugins The plugin functions
188 * @param {ProtractorBrowser=} parentBrowser The browser which spawned this one
189 *
190 * @return {Protractor} a protractor instance.
191 * @public
192 */
193 createBrowser(plugins, parentBrowser) {
194 let config = this.config_;
195 let driver = this.driverprovider_.getNewDriver();
196 let blockingProxyUrl;
197 if (config.useBlockingProxy) {
198 blockingProxyUrl = this.driverprovider_.getBPUrl();
199 }
200 let initProperties = {
201 baseUrl: config.baseUrl,
202 rootElement: config.rootElement,
203 untrackOutstandingTimeouts: config.untrackOutstandingTimeouts,
204 params: config.params,
205 getPageTimeout: config.getPageTimeout,
206 allScriptsTimeout: config.allScriptsTimeout,
207 debuggerServerPort: config.debuggerServerPort,
208 ng12Hybrid: config.ng12Hybrid,
209 waitForAngularEnabled: true
210 };
211 if (parentBrowser) {
212 initProperties.baseUrl = parentBrowser.baseUrl;
213 initProperties.rootElement = parentBrowser.angularAppRoot();
214 initProperties.untrackOutstandingTimeouts = !parentBrowser.trackOutstandingTimeouts_;
215 initProperties.params = parentBrowser.params;
216 initProperties.getPageTimeout = parentBrowser.getPageTimeout;
217 initProperties.allScriptsTimeout = parentBrowser.allScriptsTimeout;
218 initProperties.debuggerServerPort = parentBrowser.debuggerServerPort;
219 initProperties.ng12Hybrid = parentBrowser.ng12Hybrid;
220 initProperties.waitForAngularEnabled = parentBrowser.waitForAngularEnabled();
221 }
222 let browser_ = new browser_1.ProtractorBrowser(driver, initProperties.baseUrl, initProperties.rootElement, initProperties.untrackOutstandingTimeouts, blockingProxyUrl);
223 browser_.params = initProperties.params;
224 browser_.plugins_ = plugins || new plugins_1.Plugins({});
225 if (initProperties.getPageTimeout) {
226 browser_.getPageTimeout = initProperties.getPageTimeout;
227 }
228 if (initProperties.allScriptsTimeout) {
229 browser_.allScriptsTimeout = initProperties.allScriptsTimeout;
230 }
231 if (initProperties.debuggerServerPort) {
232 browser_.debuggerServerPort = initProperties.debuggerServerPort;
233 }
234 if (initProperties.ng12Hybrid) {
235 browser_.ng12Hybrid = initProperties.ng12Hybrid;
236 }
237 browser_.ready =
238 browser_.ready
239 .then(() => {
240 return browser_.waitForAngularEnabled(initProperties.waitForAngularEnabled);
241 })
242 .then(() => {
243 return driver.manage().timeouts().setScriptTimeout(initProperties.allScriptsTimeout || 0);
244 })
245 .then(() => {
246 return browser_;
247 });
248 browser_.getProcessedConfig = () => {
249 return selenium_webdriver_1.promise.when(config);
250 };
251 browser_.forkNewDriverInstance =
252 (useSameUrl, copyMockModules, copyConfigUpdates = true) => {
253 let newBrowser = this.createBrowser(plugins);
254 if (copyMockModules) {
255 newBrowser.mockModules_ = browser_.mockModules_;
256 }
257 if (useSameUrl) {
258 newBrowser.ready = newBrowser.ready
259 .then(() => {
260 return browser_.driver.getCurrentUrl();
261 })
262 .then((url) => {
263 return newBrowser.get(url);
264 })
265 .then(() => {
266 return newBrowser;
267 });
268 }
269 return newBrowser;
270 };
271 let replaceBrowser = () => {
272 let newBrowser = browser_.forkNewDriverInstance(false, true);
273 if (browser_ === ptor_1.protractor.browser) {
274 this.setupGlobals_(newBrowser);
275 }
276 return newBrowser;
277 };
278 browser_.restart = () => {
279 // Note: because tests are not paused at this point, any async
280 // calls here are not guaranteed to complete before the tests resume.
281 // Seperate solutions depending on if the control flow is enabled (see lib/browser.ts)
282 if (browser_.controlFlowIsEnabled()) {
283 return browser_.restartSync().ready;
284 }
285 else {
286 return this.driverprovider_.quitDriver(browser_.driver)
287 .then(replaceBrowser)
288 .then(newBrowser => newBrowser.ready);
289 }
290 };
291 browser_.restartSync = () => {
292 if (!browser_.controlFlowIsEnabled()) {
293 throw TypeError('Unable to use `browser.restartSync()` when the control flow is disabled');
294 }
295 this.driverprovider_.quitDriver(browser_.driver);
296 return replaceBrowser();
297 };
298 return browser_;
299 }
300 /**
301 * Final cleanup on exiting the runner.
302 *
303 * @return {q.Promise} A promise which resolves on finish.
304 * @private
305 */
306 shutdown_() {
307 return driverProviders_1.DriverProvider.quitDrivers(this.driverprovider_, this.driverprovider_.getExistingDrivers());
308 }
309 /**
310 * The primary workhorse interface. Kicks off the test running process.
311 *
312 * @return {q.Promise} A promise which resolves to the exit code of the tests.
313 * @public
314 */
315 run() {
316 let testPassed;
317 let plugins = this.plugins_ = new plugins_1.Plugins(this.config_);
318 let pluginPostTestPromises;
319 let browser_;
320 let results;
321 if (this.config_.framework !== 'explorer' && !this.config_.specs.length) {
322 throw new Error('Spec patterns did not match any files.');
323 }
324 if (this.config_.SELENIUM_PROMISE_MANAGER != null) {
325 selenium_webdriver_1.promise.USE_PROMISE_MANAGER = this.config_.SELENIUM_PROMISE_MANAGER;
326 }
327 if (this.config_.webDriverLogDir || this.config_.highlightDelay) {
328 this.config_.useBlockingProxy = true;
329 }
330 // 0) Wait for debugger
331 return q(this.ready_)
332 .then(() => {
333 // 1) Setup environment
334 // noinspection JSValidateTypes
335 return this.driverprovider_.setupEnv();
336 })
337 .then(() => {
338 // 2) Create a browser and setup globals
339 browser_ = this.createBrowser(plugins);
340 this.setupGlobals_(browser_);
341 return browser_.ready.then(browser_.getSession)
342 .then((session) => {
343 logger.debug('WebDriver session successfully started with capabilities ' +
344 util.inspect(session.getCapabilities()));
345 }, (err) => {
346 logger.error('Unable to start a WebDriver session.');
347 throw err;
348 });
349 // 3) Setup plugins
350 })
351 .then(() => {
352 return plugins.setup();
353 // 4) Execute test cases
354 })
355 .then(() => {
356 // Do the framework setup here so that jasmine and mocha globals are
357 // available to the onPrepare function.
358 let frameworkPath = '';
359 if (this.config_.framework === 'jasmine' || this.config_.framework === 'jasmine2') {
360 frameworkPath = './frameworks/jasmine.js';
361 }
362 else if (this.config_.framework === 'mocha') {
363 frameworkPath = './frameworks/mocha.js';
364 }
365 else if (this.config_.framework === 'debugprint') {
366 // Private framework. Do not use.
367 frameworkPath = './frameworks/debugprint.js';
368 }
369 else if (this.config_.framework === 'explorer') {
370 // Private framework. Do not use.
371 frameworkPath = './frameworks/explorer.js';
372 }
373 else if (this.config_.framework === 'custom') {
374 if (!this.config_.frameworkPath) {
375 throw new Error('When config.framework is custom, ' +
376 'config.frameworkPath is required.');
377 }
378 frameworkPath = this.config_.frameworkPath;
379 }
380 else {
381 throw new Error('config.framework (' + this.config_.framework + ') is not a valid framework.');
382 }
383 if (this.config_.restartBrowserBetweenTests) {
384 // TODO(sjelin): replace with warnings once `afterEach` support is required
385 let restartDriver = () => {
386 if (!this.frameworkUsesAfterEach) {
387 this.restartPromise = q(browser_.restart());
388 }
389 };
390 this.on('testPass', restartDriver);
391 this.on('testFail', restartDriver);
392 }
393 // We need to save these promises to make sure they're run, but we
394 // don't
395 // want to delay starting the next test (because we can't, it's just
396 // an event emitter).
397 pluginPostTestPromises = [];
398 this.on('testPass', (testInfo) => {
399 pluginPostTestPromises.push(plugins.postTest(true, testInfo));
400 });
401 this.on('testFail', (testInfo) => {
402 pluginPostTestPromises.push(plugins.postTest(false, testInfo));
403 });
404 logger.debug('Running with spec files ' + this.config_.specs);
405 return require(frameworkPath).run(this, this.config_.specs);
406 // 5) Wait for postTest plugins to finish
407 })
408 .then((testResults) => {
409 results = testResults;
410 return q.all(pluginPostTestPromises);
411 // 6) Teardown plugins
412 })
413 .then(() => {
414 return plugins.teardown();
415 // 7) Teardown
416 })
417 .then(() => {
418 results = helper.joinTestLogs(results, plugins.getResults());
419 this.emit('testsDone', results);
420 testPassed = results.failedCount === 0;
421 if (this.driverprovider_.updateJob) {
422 return this.driverprovider_.updateJob({ 'passed': testPassed }).then(() => {
423 return this.driverprovider_.teardownEnv();
424 });
425 }
426 else {
427 return this.driverprovider_.teardownEnv();
428 }
429 // 8) Let plugins do final cleanup
430 })
431 .then(() => {
432 return plugins.postResults();
433 // 9) Exit process
434 })
435 .then(() => {
436 let exitCode = testPassed ? 0 : 1;
437 return this.exit_(exitCode);
438 })
439 .fin(() => {
440 return this.shutdown_();
441 });
442 }
443}
444exports.Runner = Runner;
445//# sourceMappingURL=runner.js.map
\No newline at end of file