From 56ac17f5a980f1dfc99352f5755b5ce6f24e28de Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Mon, 17 Nov 2014 19:53:46 -0500 Subject: [PATCH] First attempt at multi-instance patch --- lib/driverProviders/direct.js | 25 ++++++--- lib/protractor.js | 103 +++++++++++++++++++++++++++++++++- lib/runner.js | 1 + 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/lib/driverProviders/direct.js b/lib/driverProviders/direct.js index 41c15defb..d3798ccc3 100644 --- a/lib/driverProviders/direct.js +++ b/lib/driverProviders/direct.js @@ -54,14 +54,12 @@ DirectDriverProvider.prototype.teardownEnv = function() { }; /** - * Retrieve the webdriver for the runner. + * Retrieve a new instance of this webdriver. * @public * @return webdriver instance */ -DirectDriverProvider.prototype.getDriver = function() { - if (this.driver_) { - return this.driver_; - } +DirectDriverProvider.prototype.newDriverInstance = function() { + var driver = null; switch (this.config_.capabilities.browserName) { case 'chrome': var chromeDriverFile = this.config_.chromeDriver || @@ -78,19 +76,32 @@ DirectDriverProvider.prototype.getDriver = function() { } var service = new chrome.ServiceBuilder(chromeDriverFile).build(); - this.driver_ = chrome.createDriver( + driver = chrome.createDriver( new webdriver.Capabilities(this.config_.capabilities), service); break; case 'firefox': if (this.config_.firefoxPath) { this.config_.capabilities.firefox_binary = this.config_.firefoxPath; } - this.driver_ = new firefox.Driver(this.config_.capabilities); + driver = new firefox.Driver(this.config_.capabilities); break; default: throw new Error('browserName ' + this.config_.capabilities.browserName + 'is not supported with directConnect.'); } + return driver; +}; + +/** + * Retrieve the webdriver for the runner. + * @public + * @return webdriver instance + */ +DirectDriverProvider.prototype.getDriver = function() { + if (this.driver_) { + return this.driver_; + } + this.driver_ = this.newDriverInstance(); return this.driver_; }; diff --git a/lib/protractor.js b/lib/protractor.js index 99f588d9b..ad1bbffc5 100644 --- a/lib/protractor.js +++ b/lib/protractor.js @@ -991,12 +991,13 @@ var buildElementHelper = function(ptor) { * @alias browser * @constructor * @extends {webdriver.WebDriver} + * @param {Runner} runner * @param {webdriver.WebDriver} webdriver * @param {string=} opt_baseUrl A base URL to run get requests against. * @param {string=} opt_rootElement Selector element that has an ng-app in * scope. */ -var Protractor = function(webdriverInstance, opt_baseUrl, opt_rootElement) { +var Protractor = function(runner, webdriverInstance, opt_baseUrl, opt_rootElement) { // These functions should delegate to the webdriver instance, but should // wait for Angular to sync up before performing the action. This does not // include functions which are overridden by protractor below. @@ -1022,6 +1023,14 @@ var Protractor = function(webdriverInstance, opt_baseUrl, opt_rootElement) { */ this.driver = webdriverInstance; + /** + * The runner that created this Protractor. + * Use this to spawn new instances of the underlying driver. + * + * @type {Runner} + */ + this.runner = runner; + /** * Helper function for finding elements. * @@ -1104,9 +1113,96 @@ var Protractor = function(webdriverInstance, opt_baseUrl, opt_rootElement) { */ this.mockModules_ = []; + /** + * Spawned child instances of this instance. + * + * @type {Array<{Protractor}>} + */ + this.instances_ = []; + + /** + * Reference to the Protractor instance that spawned us. + * + * @type {Protractor} + */ + this.parentInstance = null; + this.addBaseMockModules_(); }; +/** + * Returns a browser object for a new instance of the underlying driver, + * allowing scripts to test cross-window operations. + * Throws an error if the underlying driver does not support multiple + * instances. + * + * @public + * @param {string} url The url to navigate the new browser to. Defaults to the + * current url of the browser instance newInstance() is + * called on if url is falsy. If url === '', the default + * Protractor browser URL will be used. + * @return {Protractor} + */ +Protractor.prototype.newInstance = function(url) { + if (!('newDriverInstance' in this.runner.driverprovider_)) { + throw 'Protractor cannot spawn another browser instance; ' + + 'not supported by underlying driver'; + } + + var driver = this.runner.driverprovider_.newDriverInstance(); + // lib/runner.js, Runner.prototype.run setup, step 2 + driver.manage().timeouts().setScriptTimeout(this.runner.config_.allScriptsTimeout); + var browser = new Protractor(this.runner, driver, this.baseUrl, this.rootEl); + browser.parentInstance = this; + + if (url !== '') { + url = url || this.executeScript_('return window.location.href;'); + browser.get(url); + } + + this.instances_.push(browser); + return browser; +}; + +/** + * Closes this browser instance, its driver, and any child instances. + * Note that it is *your* responsibility to quit all child instances, as this + * will not be done by Protractor automatically. + * Throws an exception if trying to quite the root browser instance. + * + * @public + * @return {!webdriver.promise.Promise.} A promise that will resolve when + * all related browser instances have + * exited. + */ +Protractor.prototype.quit = function() { + if (this === global.browser) { + throw 'Tried to quit root browser instance.'; + } + + var qpromises = []; + var self = this; + + // Notify our parent that we've exited + qpromises.push(this.driver.getSession().then(function(session_) { + if (session_) { + return self.driver.quit(); + } + })); + var i = this.parentInstance.instances_.indexOf(this); + if (i >= 0) { + this.parentInstance.instances_.splice(i, 1); + } + + // Make our children exit + this.instances_.forEach(function(i) { + qpromises.push(i.quit()); + }); + this.instances_ = []; + + return webdriver.promise.all(qpromises); +}; + /** * The same as {@code webdriver.WebDriver.prototype.executeScript}, * but with a customized description for debugging. @@ -1543,12 +1639,13 @@ Protractor.prototype.pause = function(opt_debugPort) { /** * Create a new instance of Protractor by wrapping a webdriver instance. * + * @param {Runner} runner The Runner that created this Protractor. * @param {webdriver.WebDriver} webdriver The configured webdriver instance. * @param {string=} opt_baseUrl A URL to prepend to relative gets. * @return {Protractor} */ -exports.wrapDriver = function(webdriver, opt_baseUrl, opt_rootElement) { - return new Protractor(webdriver, opt_baseUrl, opt_rootElement); +exports.wrapDriver = function(runner, webdriver, opt_baseUrl, opt_rootElement) { + return new Protractor(runner, webdriver, opt_baseUrl, opt_rootElement); }; /** diff --git a/lib/runner.js b/lib/runner.js index 1628c38c4..083b0fd28 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -146,6 +146,7 @@ Runner.prototype.controlFlow = function() { */ Runner.prototype.setupGlobals_ = function(driver) { var browser = protractor.wrapDriver( + this, driver, this.config_.baseUrl, this.config_.rootElement);