Skip to content

Commit

Permalink
Make it easier to create WebDriver instances in custom flows for para…
Browse files Browse the repository at this point in the history
…llel

execution.

Fixes issue 7470.
  • Loading branch information
jleyba committed Jul 31, 2014
1 parent 9e30b6d commit 098bedd
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 16 deletions.
2 changes: 2 additions & 0 deletions javascript/node/selenium-webdriver/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
`getAlert` will be removed in `2.45.0`.
* FIXED: 7563: Mocha integration no longer disables timeouts. Default Mocha
timeouts apply (2000 ms) and may be changed using `this.timeout(ms)`.
* FIXED: 7470: Make it easier to create WebDriver instances in custom flows for
parallel execution.

## v2.42.1

Expand Down
29 changes: 24 additions & 5 deletions javascript/node/selenium-webdriver/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,24 @@ var goog = base.require('goog'),

/**
* @param {!webdriver.Capabilities} capabilities The desired capabilities.
* @param {webdriver.promise.ControlFlow} flow The control flow to use, or
* {@code null} to use the currently active flow.
* @return {webdriver.WebDriver} A new WebDriver instance or {@code null}
* if the requested browser is not natively supported in Node.
*/
function createNativeDriver(capabilities) {
function createNativeDriver(capabilities, flow) {
switch (capabilities.get(Capability.BROWSER_NAME)) {
case Browser.CHROME:
// Requiring 'chrome' above would create a cycle:
// index -> builder -> chrome -> index
var chrome = require('./chrome');
return chrome.createDriver(capabilities);
return chrome.createDriver(capabilities, null, flow);

case Browser.PHANTOM_JS:
// Requiring 'phantomjs' would create a cycle:
// index -> builder -> phantomjs -> index
var phantomjs = require('./phantomjs');
return phantomjs.createDriver(capabilities);
return phantomjs.createDriver(capabilities, flow);

default:
return null;
Expand All @@ -56,6 +58,9 @@ function createNativeDriver(capabilities) {
*/
var Builder = function() {
goog.base(this);

/** @private {webdriver.promise.ControlFlow} */
this.flow_ = null;
};
goog.inherits(Builder, AbstractBuilder);

Expand Down Expand Up @@ -84,6 +89,20 @@ Builder.prototype.setChromeOptions = function(options) {
};


/**
* Sets the control flow that created drivers should execute actions in. If
* the flow is never set, or is set to {@code null}, it will use the active
* flow at the time {@link #build()} is called.
* @param {webdriver.promise.ControlFlow} flow The control flow to use, or
* {@code null} to
* @return {!Builder} A self reference.
*/
Builder.prototype.setControlFlow = function(flow) {
this.flow_ = flow;
return this;
};


/**
* @override
*/
Expand All @@ -93,7 +112,7 @@ Builder.prototype.build = function() {
// If a remote server wasn't specified, check for browsers we support
// natively in node before falling back to using the java Selenium server.
if (!url) {
var driver = createNativeDriver(this.getCapabilities());
var driver = createNativeDriver(this.getCapabilities(), this.flow_);
if (driver) {
return driver;
}
Expand All @@ -103,7 +122,7 @@ Builder.prototype.build = function() {
}

var executor = executors.createExecutor(url);
return WebDriver.createSession(executor, this.getCapabilities());
return WebDriver.createSession(executor, this.getCapabilities(), this.flow_);
};


Expand Down
6 changes: 4 additions & 2 deletions javascript/node/selenium-webdriver/chrome.js
Original file line number Diff line number Diff line change
Expand Up @@ -444,9 +444,11 @@ Options.prototype.toJSON = function() {
* @param {(webdriver.Capabilities|Options)=} opt_options The session options.
* @param {remote.DriverService=} opt_service The session to use; will use
* the {@link getDefaultService default service} by default.
* @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or
* {@code null} to use the currently active flow.
* @return {!webdriver.WebDriver} A new WebDriver instance.
*/
function createDriver(opt_options, opt_service) {
function createDriver(opt_options, opt_service, opt_flow) {
var service = opt_service || getDefaultService();
var executor = executors.createExecutor(service.start());

Expand All @@ -458,7 +460,7 @@ function createDriver(opt_options, opt_service) {
}

return webdriver.WebDriver.createSession(
executor, options.toCapabilities());
executor, options.toCapabilities(), opt_flow);
}


Expand Down
51 changes: 51 additions & 0 deletions javascript/node/selenium-webdriver/example/parallel_flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2014 Selenium committers
// Copyright 2014 Software Freedom Conservancy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview An example of starting multiple WebDriver clients that run
* in parallel in separate control flows.
*/

var webdriver = require('..');

for (var i = 0; i < 3; i++) {
(function(n) {
var flow = new webdriver.promise.ControlFlow()
.on('uncaughtException', function(e) {
console.log('uncaughtException in flow %d: %s', n, e);
});

var driver = new webdriver.Builder().
withCapabilities(webdriver.Capabilities.chrome()).
setControlFlow(flow). // Comment out this line to see the difference.
build();

// Position and resize window so it's easy to see them running together.
driver.manage().window().setSize(600, 400);
driver.manage().window().setPosition(300 * i, 400 * i);

driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.wait(function() {
return driver.getTitle().then(function(title) {
return 'webdriver - Google Search' === title;
});
}, 1000);

driver.quit();
})(i);
}

7 changes: 5 additions & 2 deletions javascript/node/selenium-webdriver/phantomjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,11 @@ var WEBDRIVER_TO_PHANTOMJS_LEVEL = (function() {
/**
* Creates a new PhantomJS WebDriver client.
* @param {webdriver.Capabilities=} opt_capabilities The desired capabilities.
* @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or
* {@code null} to use the currently active flow.
* @return {!webdriver.WebDriver} A new WebDriver instance.
*/
function createDriver(opt_capabilities) {
function createDriver(opt_capabilities, opt_flow) {
var capabilities = opt_capabilities || webdriver.Capabilities.phantomjs();
var exe = findExecutable(capabilities.get(BINARY_PATH_CAPABILITY));
var args = ['--webdriver-logfile=' + DEFAULT_LOG_FILE];
Expand Down Expand Up @@ -149,7 +151,8 @@ function createDriver(opt_capabilities) {
});

var executor = executors.createExecutor(service.start());
var driver = webdriver.WebDriver.createSession(executor, capabilities);
var driver = webdriver.WebDriver.createSession(
executor, capabilities, opt_flow);
var boundQuit = driver.quit.bind(driver);
driver.quit = function() {
return boundQuit().thenFinally(service.kill.bind(service));
Expand Down
59 changes: 59 additions & 0 deletions javascript/webdriver/test/webdriver_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,36 @@ function testAttachToSession_failsToGetSessionInfo() {
}


function testAttachToSession_usesActiveFlowByDefault() {
var testHelper = TestHelper.expectingSuccess().
expect(CName.DESCRIBE_SESSION).
withParameters({'sessionId': SESSION_ID}).
andReturnSuccess({}).
replayAll();

var driver = webdriver.WebDriver.attachToSession(testHelper.executor,
SESSION_ID);
assertEquals(driver.controlFlow(), webdriver.promise.controlFlow());
testHelper.execute();
}


function testAttachToSession_canAttachInCustomFlow() {
var testHelper = TestHelper.expectingSuccess().
expect(CName.DESCRIBE_SESSION).
withParameters({'sessionId': SESSION_ID}).
andReturnSuccess({}).
replayAll();

var otherFlow = new webdriver.promise.ControlFlow(goog.global);
var driver = webdriver.WebDriver.attachToSession(testHelper.executor,
SESSION_ID, otherFlow);
assertEquals(otherFlow, driver.controlFlow());
assertNotEquals(otherFlow, webdriver.promise.controlFlow());
testHelper.execute();
}


function testCreateSession_happyPathWithCapabilitiesHashObject() {
var testHelper = TestHelper.expectingSuccess().
expect(CName.NEW_SESSION).
Expand Down Expand Up @@ -343,6 +373,35 @@ function testCreateSession_failsToCreateSession() {
}


function testCreateSession_usesActiveFlowByDefault() {
var testHelper = TestHelper.expectingSuccess().
expect(CName.NEW_SESSION).
withParameters({'desiredCapabilities': {}}).
andReturnSuccess({}).
replayAll();

var driver = webdriver.WebDriver.createSession(testHelper.executor, {});
assertEquals(webdriver.promise.controlFlow(), driver.controlFlow());
testHelper.execute();
}


function testCreateSession_canCreateInCustomFlow() {
var testHelper = TestHelper.expectingSuccess().
expect(CName.NEW_SESSION).
withParameters({'desiredCapabilities': {}}).
andReturnSuccess({}).
replayAll();

var otherFlow = new webdriver.promise.ControlFlow(goog.global);
var driver = webdriver.WebDriver.createSession(
testHelper.executor, {}, otherFlow);
assertEquals(otherFlow, driver.controlFlow());
assertNotEquals(otherFlow, webdriver.promise.controlFlow());
testHelper.execute();
}


function testToWireValue_function() {
var fn = function() { return 'foo'; };
var callback;
Expand Down
29 changes: 22 additions & 7 deletions javascript/webdriver/webdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,17 @@ webdriver.WebDriver = function(session, executor, opt_flow) {
* @param {!webdriver.CommandExecutor} executor Command executor to use when
* querying for session details.
* @param {string} sessionId ID of the session to attach to.
* @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
* commands should execute under. Defaults to the
* {@link webdriver.promise.controlFlow() currently active} control flow.
* @return {!webdriver.WebDriver} A new client for the specified session.
*/
webdriver.WebDriver.attachToSession = function(executor, sessionId) {
webdriver.WebDriver.attachToSession = function(executor, sessionId, opt_flow) {
return webdriver.WebDriver.acquireSession_(executor,
new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).
setParameter('sessionId', sessionId),
'WebDriver.attachToSession()');
'WebDriver.attachToSession()',
opt_flow);
};


Expand All @@ -107,13 +111,19 @@ webdriver.WebDriver.attachToSession = function(executor, sessionId) {
* session with.
* @param {!webdriver.Capabilities} desiredCapabilities The desired
* capabilities for the new session.
* @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
* commands should execute under, including the initial session creation.
* Defaults to the {@link webdriver.promise.controlFlow() currently active}
* control flow.
* @return {!webdriver.WebDriver} The driver for the newly created session.
*/
webdriver.WebDriver.createSession = function(executor, desiredCapabilities) {
webdriver.WebDriver.createSession = function(
executor, desiredCapabilities, opt_flow) {
return webdriver.WebDriver.acquireSession_(executor,
new webdriver.Command(webdriver.CommandName.NEW_SESSION).
setParameter('desiredCapabilities', desiredCapabilities),
'WebDriver.createSession()');
'WebDriver.createSession()',
opt_flow);
};


Expand All @@ -126,19 +136,24 @@ webdriver.WebDriver.createSession = function(executor, desiredCapabilities) {
* @param {!webdriver.Command} command The command to send to fetch the session
* details.
* @param {string} description A descriptive debug label for this action.
* @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
* commands should execute under. Defaults to the
* {@link webdriver.promise.controlFlow() currently active} control flow.
* @return {!webdriver.WebDriver} A new WebDriver client for the session.
* @private
*/
webdriver.WebDriver.acquireSession_ = function(executor, command, description) {
var session = webdriver.promise.controlFlow().execute(function() {
webdriver.WebDriver.acquireSession_ = function(
executor, command, description, opt_flow) {
var flow = opt_flow || webdriver.promise.controlFlow();
var session = flow.execute(function() {
return webdriver.WebDriver.executeCommand_(executor, command).
then(function(response) {
bot.response.checkResponse(response);
return new webdriver.Session(response['sessionId'],
response['value']);
});
}, description);
return new webdriver.WebDriver(session, executor);
return new webdriver.WebDriver(session, executor, flow);
};


Expand Down

0 comments on commit 098bedd

Please sign in to comment.