From 167038499aacfd5def03472f9f548529b273e1e0 Mon Sep 17 00:00:00 2001 From: Hank Duan Date: Fri, 12 Dec 2014 15:00:43 -0800 Subject: [PATCH] feat(runner): allow protractor to restart browser between tests Enables adding `restartBrowserBetweenTests: true` to your configuration file. Note that this will slow down test suites considerably. Closes #1435 --- docs/referenceConf.js | 4 ++ lib/driverProviders/README.md | 10 +++ lib/driverProviders/driverProvider.js | 66 ++++++++++++------ lib/driverProviders/mock.js | 1 - lib/frameworks/jasmine.js | 4 +- lib/runner.js | 68 ++++++++++--------- scripts/test.js | 3 +- .../setCookies_spec.js | 23 +++++++ spec/restartBrowserBetweenTestsConf.js | 22 ++++++ 9 files changed, 145 insertions(+), 56 deletions(-) create mode 100644 spec/restartBrowserBetweenTests/setCookies_spec.js create mode 100644 spec/restartBrowserBetweenTestsConf.js diff --git a/docs/referenceConf.js b/docs/referenceConf.js index 3af371275..fa02675e5 100644 --- a/docs/referenceConf.js +++ b/docs/referenceConf.js @@ -216,6 +216,10 @@ exports.config = { // The path is relative to the location of this config. resultJsonOutputFile: null, + // If true, protractor will restart the browser between each test. + // CAUTION: This will cause your tests to slow down drastically. + restartBrowserBetweenTests: false, + // --------------------------------------------------------------------------- // ----- The test framework -------------------------------------------------- // --------------------------------------------------------------------------- diff --git a/lib/driverProviders/README.md b/lib/driverProviders/README.md index c77b369d5..aa098b702 100644 --- a/lib/driverProviders/README.md +++ b/lib/driverProviders/README.md @@ -13,11 +13,21 @@ Each file exports a function which takes in the configuration as a parameter and */ DriverProvider.prototype.setupEnv +/** + * @return {Array.} Array of existing webdriver instances. + */ +DriverProvider.prototype.getExistingDrivers + /** * @return {webdriver.WebDriver} A new setup driver instance. */ DriverProvider.prototype.getNewDriver +/** + * @param {webdriver.WebDriver} The driver instance to quit. + */ +DriverProvider.prototype.quitDriver + /** * @return {q.promise} A promise which will resolve when the environment * is down. diff --git a/lib/driverProviders/driverProvider.js b/lib/driverProviders/driverProvider.js index 94dbd805a..d873b20ef 100644 --- a/lib/driverProviders/driverProvider.js +++ b/lib/driverProviders/driverProvider.js @@ -7,36 +7,24 @@ var webdriver = require('selenium-webdriver'), q = require('q'); + var DriverProvider = function(config) { this.config_ = config; this.drivers_ = []; }; + /** - * Teardown and destroy the environment and do any associated cleanup. - * Shuts down the drivers. + * Get all existing drivers. * * @public - * @return {q.promise} A promise which will resolve when the environment - * is down. + * @return array of webdriver instances */ -DriverProvider.prototype.teardownEnv = function() { - var deferredArray = this.drivers_.map(function(driver) { - var deferred = q.defer(); - driver.getSession().then(function(session_) { - if (session_) { - driver.quit().then(function() { - deferred.resolve(); - }); - } else { - deferred.resolve(); - } - }); - return deferred.promise; - }); - return q.all(deferredArray); +DriverProvider.prototype.getExistingDrivers = function() { + return this.drivers_.slice(); // Create a shallow copy }; + /** * Create a new driver. * @@ -52,4 +40,44 @@ DriverProvider.prototype.getNewDriver = function() { return newDriver; }; + +/** + * Quit a driver. + * + * @public + * @param webdriver instance + */ +DriverProvider.prototype.quitDriver = function(driver) { + var driverIndex = this.drivers_.indexOf(driver); + if (driverIndex >= 0) { + this.drivers_.splice(driverIndex, 1); + } + + var deferred = q.defer(); + driver.getSession().then(function(session_) { + if (session_) { + driver.quit().then(function() { + deferred.resolve(); + }); + } else { + deferred.resolve(); + } + }); + return deferred.promise; +}; + + +/** + * Teardown and destroy the environment and do any associated cleanup. + * Shuts down the drivers. + * + * @public + * @return {q.promise} A promise which will resolve when the environment + * is down. + */ +DriverProvider.prototype.teardownEnv = function() { + return q.all(this.drivers_.map(this.quitDriver.bind(this))); +}; + + module.exports = DriverProvider; diff --git a/lib/driverProviders/mock.js b/lib/driverProviders/mock.js index 06054d872..f9b140908 100644 --- a/lib/driverProviders/mock.js +++ b/lib/driverProviders/mock.js @@ -11,7 +11,6 @@ var webdriver = require('selenium-webdriver'), * @constructor */ var MockExecutor = function() { - this.drivers_ = []; }; /** diff --git a/lib/frameworks/jasmine.js b/lib/frameworks/jasmine.js index f680a3456..ea0bd45be 100644 --- a/lib/frameworks/jasmine.js +++ b/lib/frameworks/jasmine.js @@ -30,9 +30,9 @@ exports.run = function(runner, specs) { this.startTime = new Date(); }; RunnerReporter.prototype.reportSpecResults = function(spec) { - if (spec.results().passed()) { + if (spec.results().passedCount) { this.emitter.emit('testPass'); - } else { + } else if (spec.results().failedCount) { this.emitter.emit('testFail'); } diff --git a/lib/runner.js b/lib/runner.js index 4f075ea38..11152c094 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -24,7 +24,6 @@ var Runner = function(config) { this.preparer_ = null; this.driverprovider_ = null; this.config_ = config; - this.drivers_ = []; if (config.v8Debug) { // Call this private function instead of sending SIGUSR1 because Windows. @@ -147,13 +146,13 @@ Runner.prototype.controlFlow = function() { * Sets up convenience globals for test specs * @private */ -Runner.prototype.setupGlobals_ = function(browser) { +Runner.prototype.setupGlobals_ = function(browser_) { // Export protractor to the global namespace to be used in tests. global.protractor = protractor; - global.browser = browser; - global.$ = browser.$; - global.$$ = browser.$$; - global.element = browser.element; + global.browser = browser_; + global.$ = browser_.$; + global.$$ = browser_.$$; + global.element = browser_.element; global.by = global.By = protractor.By; // Enable sourcemap support for stack traces. @@ -176,13 +175,12 @@ Runner.prototype.setupGlobals_ = function(browser) { Runner.prototype.createBrowser = function() { var config = this.config_; var driver = this.driverprovider_.getNewDriver(); - this.drivers_.push(driver); driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout); - var browser = protractor.wrapDriver(driver, + var browser_ = protractor.wrapDriver(driver, config.baseUrl, config.rootElement); - browser.params = config.params; + browser_.params = config.params; if (config.getPageTimeout) { - browser.getPageTimeout = config.getPageTimeout; + browser_.getPageTimeout = config.getPageTimeout; } var self = this; @@ -193,21 +191,22 @@ Runner.prototype.createBrowser = function() { * @param {boolean} opt_copyMockModules Whether to apply same mock modules on creation * @return {Protractor} a protractor instance. */ - browser.forkNewDriverInstance = function(opt_useSameUrl, opt_copyMockModules) { + browser_.forkNewDriverInstance = function(opt_useSameUrl, opt_copyMockModules) { var newBrowser = self.createBrowser(); if (opt_copyMockModules) { - newBrowser.mockModules_ = browser.mockModules_; + newBrowser.mockModules_ = browser_.mockModules_; } if (opt_useSameUrl) { - browser.driver.getCurrentUrl().then(function(url) { + browser_.driver.getCurrentUrl().then(function(url) { newBrowser.get(url); }); } return newBrowser; }; - return browser; + return browser_; }; + /** * Final cleanup on exiting the runner. * @@ -215,23 +214,11 @@ Runner.prototype.createBrowser = function() { * @private */ Runner.prototype.shutdown_ = function() { - var deferredArr = this.drivers_.map(function(driver) { - var deferred = q.defer(); - driver.getSession().then(function(session_) { - if (session_) { - driver.quit().then(function() { - deferred.resolve(); - }); - } else { - deferred.resolve(); - } - }); - return deferred.promise; - }); - return q.all(deferredArr); + return q.all( + this.driverprovider_.getExistingDrivers(). + map(this.driverprovider_.quitDriver.bind(this.driverprovider_))); }; - /** * The primary workhorse interface. Kicks off the test running process. * @@ -241,7 +228,8 @@ Runner.prototype.shutdown_ = function() { Runner.prototype.run = function() { var self = this, testPassed, - plugins; + plugins, + browser_; if (!this.config_.specs.length) { throw new Error('Spec patterns did not match any files.'); @@ -256,9 +244,9 @@ Runner.prototype.run = function() { }); // 2) Create a browser and setup globals }).then(function() { - var browser = self.createBrowser(); - self.setupGlobals_(browser); - return browser.getSession().then(function(session) { + browser_ = self.createBrowser(); + self.setupGlobals_(browser_); + return browser_.getSession().then(function(session) { log.debug('WebDriver session successfully started with capabilities ' + util.inspect(session.getCapabilities())); }, function(err) { @@ -287,6 +275,20 @@ Runner.prototype.run = function() { ') is not a valid framework.'); } + if (self.config_.restartBrowserBetweenTests) { + var restartDriver = function() { + // Note: because tests are not paused at this point, any async + // calls here are not guaranteed to complete before the tests resume. + self.driverprovider_.quitDriver(browser_.driver); + // Copy mock modules, but do not navigate to previous URL. + browser_ = browser_.forkNewDriverInstance(false, true); + self.setupGlobals_(browser_); + }; + + self.on('testPass', restartDriver); + self.on('testFail', restartDriver); + } + return require(frameworkPath).run(self, self.config_.specs). then(function(testResults) { return helper.joinTestLogs(pluginSetupResults, testResults); diff --git a/scripts/test.js b/scripts/test.js index c52e6f5c7..f10a5cc99 100755 --- a/scripts/test.js +++ b/scripts/test.js @@ -24,7 +24,8 @@ var passingTests = [ 'node lib/cli.js spec/pluginsFullConf.js', 'node lib/cli.js spec/ngHintSuccessConfig.js', 'node lib/cli.js spec/interactionConf.js', - 'node lib/cli.js spec/directConnectConf.js' + 'node lib/cli.js spec/directConnectConf.js', + 'node lib/cli.js spec/restartBrowserBetweenTestsConf.js' ]; passingTests.push( diff --git a/spec/restartBrowserBetweenTests/setCookies_spec.js b/spec/restartBrowserBetweenTests/setCookies_spec.js new file mode 100644 index 000000000..2712f0712 --- /dev/null +++ b/spec/restartBrowserBetweenTests/setCookies_spec.js @@ -0,0 +1,23 @@ +var env = require('../environment.js'); + +describe('pages with login', function() { + it('should set a cookie', function() { + browser.get(env.baseUrl+'/index.html'); + + browser.manage().addCookie('testcookie', 'Jane-1234'); + + // Make sure the cookie is set. + browser.manage().getCookie('testcookie').then(function(cookie) { + expect(cookie.value).toEqual('Jane-1234'); + }); + }); + + it('should check the cookie is gone', function() { + browser.get(env.baseUrl+'/index.html'); + + // Make sure the cookie is gone. + browser.manage().getCookie('testcookie').then(function(cookie) { + expect(cookie).toEqual(null); + }); + }); +}); diff --git a/spec/restartBrowserBetweenTestsConf.js b/spec/restartBrowserBetweenTestsConf.js new file mode 100644 index 000000000..4d0bc1454 --- /dev/null +++ b/spec/restartBrowserBetweenTestsConf.js @@ -0,0 +1,22 @@ +var env = require('./environment.js'); + +// The main suite of Protractor tests. +exports.config = { + seleniumAddress: env.seleniumAddress, + + // Spec patterns are relative to this directory. + specs: [ + 'restartBrowserBetweenTests/*_spec.js' + ], + + capabilities: env.capabilities, + + baseUrl: env.baseUrl, + + jasmineNodeOpts: { + isVerbose: true, + realtimeFailure: true + }, + + restartBrowserBetweenTests: true, +};