"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const q = require("q"); const selenium_webdriver_1 = require("selenium-webdriver"); const util = require("util"); const browser_1 = require("./browser"); const driverProviders_1 = require("./driverProviders"); const logger_1 = require("./logger"); const plugins_1 = require("./plugins"); const ptor_1 = require("./ptor"); const helper = require("./util"); let logger = new logger_1.Logger('runner'); /* * Runner is responsible for starting the execution of a test run and triggering * setup, teardown, managing config, etc through its various dependencies. * * The Protractor Runner is a node EventEmitter with the following events: * - testPass * - testFail * - testsDone * * @param {Object} config * @constructor */ class Runner extends events_1.EventEmitter { constructor(config) { super(); /** * Responsible for cleaning up test run and exiting the process. * @private * @param {int} Standard unix exit code */ this.exit_ = function (exitCode) { return helper.runFilenameOrFn_(this.config_.configDir, this.config_.onCleanUp, [exitCode]) .then((returned) => { if (typeof returned === 'number') { return returned; } else { return exitCode; } }); }; this.config_ = config; if (config.v8Debug) { // Call this private function instead of sending SIGUSR1 because Windows. process['_debugProcess'](process.pid); } if (config.nodeDebug) { process['_debugProcess'](process.pid); let flow = selenium_webdriver_1.promise.controlFlow(); this.ready_ = flow.execute(() => { let nodedebug = require('child_process').fork('debug', ['localhost:5858']); process.on('exit', function () { nodedebug.kill('SIGTERM'); }); nodedebug.on('exit', function () { process.exit(1); }); }, 'start the node debugger').then(() => { return flow.timeout(1000, 'waiting for debugger to attach'); }); } if (config.capabilities && config.capabilities.seleniumAddress) { config.seleniumAddress = config.capabilities.seleniumAddress; } this.loadDriverProvider_(config); this.setTestPreparer(config.onPrepare); } /** * Registrar for testPreparers - executed right before tests run. * @public * @param {string/Fn} filenameOrFn */ setTestPreparer(filenameOrFn) { this.preparer_ = filenameOrFn; } /** * Executor of testPreparer * @public * @param {string[]=} An optional list of command line arguments the framework will accept. * @return {q.Promise} A promise that will resolve when the test preparers * are finished. */ runTestPreparer(extraFlags) { let unknownFlags = this.config_.unknownFlags_ || []; if (extraFlags) { unknownFlags = unknownFlags.filter((f) => extraFlags.indexOf(f) === -1); } if (unknownFlags.length > 0 && !this.config_.disableChecks) { // TODO: Make this throw a ConfigError in Protractor 6. logger.warn('Ignoring unknown extra flags: ' + unknownFlags.join(', ') + '. This will be' + ' an error in future versions, please use --disableChecks flag to disable the ' + ' Protractor CLI flag checks. '); } return this.plugins_.onPrepare().then(() => { return helper.runFilenameOrFn_(this.config_.configDir, this.preparer_); }); } /** * Called after each test finishes. * * Responsible for `restartBrowserBetweenTests` * * @public * @return {q.Promise} A promise that will resolve when the work here is done */ afterEach() { let ret; this.frameworkUsesAfterEach = true; if (this.config_.restartBrowserBetweenTests) { this.restartPromise = this.restartPromise || q(ptor_1.protractor.browser.restart()); ret = this.restartPromise; this.restartPromise = undefined; } return ret || q(); } /** * Grab driver provider based on type * @private * * Priority * 1) if directConnect is true, use that * 2) if seleniumAddress is given, use that * 3) if a Sauce Labs account is given, use that * 4) if a seleniumServerJar is specified, use that * 5) try to find the seleniumServerJar in protractor/selenium */ loadDriverProvider_(config) { this.config_ = config; this.driverprovider_ = driverProviders_1.buildDriverProvider(this.config_); } /** * Getter for the Runner config object * @public * @return {Object} config */ getConfig() { return this.config_; } /** * Get the control flow used by this runner. * @return {Object} WebDriver control flow. */ controlFlow() { return selenium_webdriver_1.promise.controlFlow(); } /** * Sets up convenience globals for test specs * @private */ setupGlobals_(browser_) { // Keep $, $$, element, and by/By under the global protractor namespace ptor_1.protractor.browser = browser_; ptor_1.protractor.$ = browser_.$; ptor_1.protractor.$$ = browser_.$$; ptor_1.protractor.element = browser_.element; ptor_1.protractor.by = ptor_1.protractor.By = browser_1.ProtractorBrowser.By; ptor_1.protractor.ExpectedConditions = browser_.ExpectedConditions; if (!this.config_.noGlobals) { // Export protractor to the global namespace to be used in tests. global.browser = browser_; global.$ = browser_.$; global.$$ = browser_.$$; global.element = browser_.element; global.by = global.By = ptor_1.protractor.By; global.ExpectedConditions = ptor_1.protractor.ExpectedConditions; } global.protractor = ptor_1.protractor; if (!this.config_.skipSourceMapSupport) { // Enable sourcemap support for stack traces. require('source-map-support').install(); } // Required by dart2js machinery. // https://code.google.com/p/dart/source/browse/branches/bleeding_edge/dart/sdk/lib/js/dart2js/js_dart2js.dart?spec=svn32943&r=32943#487 global.DartObject = function (o) { this.o = o; }; } /** * Create a new driver from a driverProvider. Then set up a * new protractor instance using this driver. * This is used to set up the initial protractor instances and any * future ones. * * @param {Plugin} plugins The plugin functions * @param {ProtractorBrowser=} parentBrowser The browser which spawned this one * * @return {Protractor} a protractor instance. * @public */ createBrowser(plugins, parentBrowser) { let config = this.config_; let driver = this.driverprovider_.getNewDriver(); let blockingProxyUrl; if (config.useBlockingProxy) { blockingProxyUrl = this.driverprovider_.getBPUrl(); } let initProperties = { baseUrl: config.baseUrl, rootElement: config.rootElement, untrackOutstandingTimeouts: config.untrackOutstandingTimeouts, params: config.params, getPageTimeout: config.getPageTimeout, allScriptsTimeout: config.allScriptsTimeout, debuggerServerPort: config.debuggerServerPort, ng12Hybrid: config.ng12Hybrid, waitForAngularEnabled: true }; if (parentBrowser) { initProperties.baseUrl = parentBrowser.baseUrl; initProperties.rootElement = parentBrowser.angularAppRoot(); initProperties.untrackOutstandingTimeouts = !parentBrowser.trackOutstandingTimeouts_; initProperties.params = parentBrowser.params; initProperties.getPageTimeout = parentBrowser.getPageTimeout; initProperties.allScriptsTimeout = parentBrowser.allScriptsTimeout; initProperties.debuggerServerPort = parentBrowser.debuggerServerPort; initProperties.ng12Hybrid = parentBrowser.ng12Hybrid; initProperties.waitForAngularEnabled = parentBrowser.waitForAngularEnabled(); } let browser_ = new browser_1.ProtractorBrowser(driver, initProperties.baseUrl, initProperties.rootElement, initProperties.untrackOutstandingTimeouts, blockingProxyUrl); browser_.params = initProperties.params; browser_.plugins_ = plugins || new plugins_1.Plugins({}); if (initProperties.getPageTimeout) { browser_.getPageTimeout = initProperties.getPageTimeout; } if (initProperties.allScriptsTimeout) { browser_.allScriptsTimeout = initProperties.allScriptsTimeout; } if (initProperties.debuggerServerPort) { browser_.debuggerServerPort = initProperties.debuggerServerPort; } if (initProperties.ng12Hybrid) { browser_.ng12Hybrid = initProperties.ng12Hybrid; } browser_.ready = browser_.ready .then(() => { return browser_.waitForAngularEnabled(initProperties.waitForAngularEnabled); }) .then(() => { return driver.manage().timeouts().setScriptTimeout(initProperties.allScriptsTimeout || 0); }) .then(() => { return browser_; }); browser_.getProcessedConfig = () => { return selenium_webdriver_1.promise.when(config); }; browser_.forkNewDriverInstance = (useSameUrl, copyMockModules, copyConfigUpdates = true) => { let newBrowser = this.createBrowser(plugins); if (copyMockModules) { newBrowser.mockModules_ = browser_.mockModules_; } if (useSameUrl) { newBrowser.ready = newBrowser.ready .then(() => { return browser_.driver.getCurrentUrl(); }) .then((url) => { return newBrowser.get(url); }) .then(() => { return newBrowser; }); } return newBrowser; }; let replaceBrowser = () => { let newBrowser = browser_.forkNewDriverInstance(false, true); if (browser_ === ptor_1.protractor.browser) { this.setupGlobals_(newBrowser); } return newBrowser; }; browser_.restart = () => { // Note: because tests are not paused at this point, any async // calls here are not guaranteed to complete before the tests resume. // Seperate solutions depending on if the control flow is enabled (see lib/browser.ts) if (browser_.controlFlowIsEnabled()) { return browser_.restartSync().ready; } else { return this.driverprovider_.quitDriver(browser_.driver) .then(replaceBrowser) .then(newBrowser => newBrowser.ready); } }; browser_.restartSync = () => { if (!browser_.controlFlowIsEnabled()) { throw TypeError('Unable to use `browser.restartSync()` when the control flow is disabled'); } this.driverprovider_.quitDriver(browser_.driver); return replaceBrowser(); }; return browser_; } /** * Final cleanup on exiting the runner. * * @return {q.Promise} A promise which resolves on finish. * @private */ shutdown_() { return driverProviders_1.DriverProvider.quitDrivers(this.driverprovider_, this.driverprovider_.getExistingDrivers()); } /** * The primary workhorse interface. Kicks off the test running process. * * @return {q.Promise} A promise which resolves to the exit code of the tests. * @public */ run() { let testPassed; let plugins = this.plugins_ = new plugins_1.Plugins(this.config_); let pluginPostTestPromises; let browser_; let results; if (this.config_.framework !== 'explorer' && !this.config_.specs.length) { throw new Error('Spec patterns did not match any files.'); } if (this.config_.SELENIUM_PROMISE_MANAGER != null) { selenium_webdriver_1.promise.USE_PROMISE_MANAGER = this.config_.SELENIUM_PROMISE_MANAGER; } if (this.config_.webDriverLogDir || this.config_.highlightDelay) { this.config_.useBlockingProxy = true; } // 0) Wait for debugger return q(this.ready_) .then(() => { // 1) Setup environment // noinspection JSValidateTypes return this.driverprovider_.setupEnv(); }) .then(() => { // 2) Create a browser and setup globals browser_ = this.createBrowser(plugins); this.setupGlobals_(browser_); return browser_.ready.then(browser_.getSession) .then((session) => { logger.debug('WebDriver session successfully started with capabilities ' + util.inspect(session.getCapabilities())); }, (err) => { logger.error('Unable to start a WebDriver session.'); throw err; }); // 3) Setup plugins }) .then(() => { return plugins.setup(); // 4) Execute test cases }) .then(() => { // Do the framework setup here so that jasmine and mocha globals are // available to the onPrepare function. let frameworkPath = ''; if (this.config_.framework === 'jasmine' || this.config_.framework === 'jasmine2') { frameworkPath = './frameworks/jasmine.js'; } else if (this.config_.framework === 'mocha') { frameworkPath = './frameworks/mocha.js'; } else if (this.config_.framework === 'debugprint') { // Private framework. Do not use. frameworkPath = './frameworks/debugprint.js'; } else if (this.config_.framework === 'explorer') { // Private framework. Do not use. frameworkPath = './frameworks/explorer.js'; } else if (this.config_.framework === 'custom') { if (!this.config_.frameworkPath) { throw new Error('When config.framework is custom, ' + 'config.frameworkPath is required.'); } frameworkPath = this.config_.frameworkPath; } else { throw new Error('config.framework (' + this.config_.framework + ') is not a valid framework.'); } if (this.config_.restartBrowserBetweenTests) { // TODO(sjelin): replace with warnings once `afterEach` support is required let restartDriver = () => { if (!this.frameworkUsesAfterEach) { this.restartPromise = q(browser_.restart()); } }; this.on('testPass', restartDriver); this.on('testFail', restartDriver); } // We need to save these promises to make sure they're run, but we // don't // want to delay starting the next test (because we can't, it's just // an event emitter). pluginPostTestPromises = []; this.on('testPass', (testInfo) => { pluginPostTestPromises.push(plugins.postTest(true, testInfo)); }); this.on('testFail', (testInfo) => { pluginPostTestPromises.push(plugins.postTest(false, testInfo)); }); logger.debug('Running with spec files ' + this.config_.specs); return require(frameworkPath).run(this, this.config_.specs); // 5) Wait for postTest plugins to finish }) .then((testResults) => { results = testResults; return q.all(pluginPostTestPromises); // 6) Teardown plugins }) .then(() => { return plugins.teardown(); // 7) Teardown }) .then(() => { results = helper.joinTestLogs(results, plugins.getResults()); this.emit('testsDone', results); testPassed = results.failedCount === 0; if (this.driverprovider_.updateJob) { return this.driverprovider_.updateJob({ 'passed': testPassed }).then(() => { return this.driverprovider_.teardownEnv(); }); } else { return this.driverprovider_.teardownEnv(); } // 8) Let plugins do final cleanup }) .then(() => { return plugins.postResults(); // 9) Exit process }) .then(() => { let exitCode = testPassed ? 0 : 1; return this.exit_(exitCode); }) .fin(() => { return this.shutdown_(); }); } } exports.Runner = Runner; //# sourceMappingURL=runner.js.map