Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
feat(runner): allow protractor to restart browser between tests
Browse files Browse the repository at this point in the history
Enables adding `restartBrowserBetweenTests: true` to your configuration file.
Note that this will slow down test suites considerably.
Closes #1435
  • Loading branch information
hankduan committed Dec 16, 2014
1 parent b28355d commit 1670384
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 56 deletions.
4 changes: 4 additions & 0 deletions docs/referenceConf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 --------------------------------------------------
// ---------------------------------------------------------------------------
Expand Down
10 changes: 10 additions & 0 deletions lib/driverProviders/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ Each file exports a function which takes in the configuration as a parameter and
*/
DriverProvider.prototype.setupEnv

/**
* @return {Array.<webdriver.WebDriver>} 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.
Expand Down
66 changes: 47 additions & 19 deletions lib/driverProviders/driverProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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;
1 change: 0 additions & 1 deletion lib/driverProviders/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ var webdriver = require('selenium-webdriver'),
* @constructor
*/
var MockExecutor = function() {
this.drivers_ = [];
};

/**
Expand Down
4 changes: 2 additions & 2 deletions lib/frameworks/jasmine.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 comment has been minimized.

Copy link
@sjelin

sjelin May 1, 2015

Contributor

Do you remember why you made this change? I'm finding that neither testPass nor testFail are being emitted in jasmine 1.3

This comment has been minimized.

Copy link
@hankduan

hankduan May 1, 2015

Author Contributor

Because if the test is skipped, I think it'll emit testFail since it didn't pass.

This comment has been minimized.

Copy link
@hankduan

hankduan May 1, 2015

Author Contributor

This comment has been minimized.

Copy link
@sjelin

sjelin May 1, 2015

Contributor

Yeah

This comment has been minimized.

Copy link
@sjelin

sjelin May 1, 2015

Contributor

But only in jasmine 1.3

This comment has been minimized.

Copy link
@hankduan

hankduan May 1, 2015

Author Contributor

I guess check for values of passCount and failCount for jasmine 1.3. Otherwise you can go back to using 'passed()', but make sure you account for the case where the test is skipped/pending.

this.emitter.emit('testPass');
} else {
} else if (spec.results().failedCount) {
this.emitter.emit('testFail');
}

Expand Down
68 changes: 35 additions & 33 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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;

Expand All @@ -193,45 +191,34 @@ 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.
*
* @return {q.Promise} A promise which resolves on finish.
* @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.
*
Expand All @@ -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.');
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
23 changes: 23 additions & 0 deletions spec/restartBrowserBetweenTests/setCookies_spec.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
22 changes: 22 additions & 0 deletions spec/restartBrowserBetweenTestsConf.js
Original file line number Diff line number Diff line change
@@ -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,
};

0 comments on commit 1670384

Please sign in to comment.