Skip to content

Commit 83dfb2c

Browse files
committed
[js] For Firefox and Chrome, the builder will now always return a firefox.Driver
or chrome.Driver instance, respectively, even when targeting a remote server. This ensures users will always have access to browser-specific commands like firefox.Driver.prototype.setContext or chrome.Driver.prototype.launchApp. Whether the targeted remote end actually exposes these commands depends entirely on what the user has setup and is outside our control. Fixes #2464
1 parent dcaf7b2 commit 83dfb2c

4 files changed

Lines changed: 86 additions & 14 deletions

File tree

javascript/node/selenium-webdriver/CHANGES.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
* Properly send HTTP requests when using a WebDriver server proxy
1313
* Properly configure proxies when using the geckodriver
1414
* `http.Executor` now accepts a promised client. The `builder.Builder` class
15-
will now use this over a `command.DeferredExecutor` when creating WebDriver
16-
instances.
15+
will now use this instead of a `command.DeferredExecutor` when creating
16+
WebDriver instances.
17+
* For Chrome and Firefox, the `builder.Builder` class will always return an
18+
instanceof `chrome.Driver` and `firefox.Driver`, respectively, even when
19+
configured to use a remote server (from `builder.Builder#usingServer(url)`,
20+
`SELENIUM_REMOTE_URL`, etc).
1721

1822
### API Changes
1923

javascript/node/selenium-webdriver/builder.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,15 @@ class Builder {
486486
let client = Promise.resolve(url)
487487
.then(url => new _http.HttpClient(url, this.agent_, this.proxy_));
488488
let executor = new _http.Executor(client);
489+
490+
if (browser === Browser.CHROME) {
491+
return new chrome.Driver(capabilities, null, this.flow_, executor);
492+
}
493+
494+
if (browser === Browser.FIREFOX) {
495+
return new firefox.Driver(capabilities, this.flow_, executor);
496+
}
497+
489498
return WebDriver.createSession(executor, capabilities, this.flow_);
490499
}
491500

javascript/node/selenium-webdriver/chrome.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,20 @@ const Command = {
156156
function createExecutor(url) {
157157
let client = url.then(url => new http.HttpClient(url));
158158
let executor = new http.Executor(client);
159+
configureExecutor(executor);
160+
return executor;
161+
}
162+
163+
164+
/**
165+
* Configures the given executor with Chrome-specific commands.
166+
* @param {!http.Executor} executor the executor to configure.
167+
*/
168+
function configureExecutor(executor) {
159169
executor.defineCommand(
160170
Command.LAUNCH_APP,
161-
'POST', '/session/:sessionId/chromium/launch_app');
162-
return executor;
171+
'POST',
172+
'/session/:sessionId/chromium/launch_app');
163173
}
164174

165175

@@ -763,10 +773,29 @@ class Driver extends webdriver.WebDriver {
763773
* the {@linkplain #getDefaultService default service} by default.
764774
* @param {promise.ControlFlow=} opt_flow The control flow to use,
765775
* or {@code null} to use the currently active flow.
776+
* @param {http.Executor=} opt_executor A pre-configured command executor that
777+
* should be used to send commands to the remote end. The provided
778+
* executor should not be reused with other clients as its internal
779+
* command mappings will be updated to support Chrome-specific commands.
780+
*
781+
* You may provide either a custom executor or a driver service, but not both.
782+
*
783+
* @throws {Error} if both `opt_service` and `opt_executor` are provided.
766784
*/
767-
constructor(opt_config, opt_service, opt_flow) {
768-
let service = opt_service || getDefaultService();
769-
let executor = createExecutor(service.start());
785+
constructor(opt_config, opt_service, opt_flow, opt_executor) {
786+
if (opt_service && opt_executor) {
787+
throw Error(
788+
'Either a DriverService or Executor may be provided, but not both');
789+
}
790+
791+
let executor;
792+
if (opt_executor) {
793+
executor = opt_executor;
794+
configureExecutor(executor);
795+
} else {
796+
let service = opt_service || getDefaultService();
797+
executor = createExecutor(service.start());
798+
}
770799

771800
let caps =
772801
opt_config instanceof Options ? opt_config.toCapabilities() :

javascript/node/selenium-webdriver/firefox/index.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -398,19 +398,28 @@ const ExtensionCommand = {
398398
function createExecutor(serverUrl) {
399399
let client = serverUrl.then(url => new http.HttpClient(url));
400400
let executor = new http.Executor(client);
401+
configureExecutor(executor);
402+
return executor;
403+
}
401404

405+
406+
/**
407+
* Configures the given executor with Firefox-specific commands.
408+
* @param {!http.Executor} executor the executor to configure.
409+
*/
410+
function configureExecutor(executor) {
402411
executor.defineCommand(
403412
ExtensionCommand.GET_CONTEXT,
404413
'GET',
405414
'/session/:sessionId/moz/context');
415+
406416
executor.defineCommand(
407417
ExtensionCommand.SET_CONTEXT,
408418
'POST',
409419
'/session/:sessionId/moz/context');
410-
411-
return executor;
412420
}
413421

422+
414423
/**
415424
* A WebDriver client for Firefox.
416425
*/
@@ -422,8 +431,18 @@ class Driver extends webdriver.WebDriver {
422431
* object.
423432
* @param {promise.ControlFlow=} opt_flow The flow to
424433
* schedule commands through. Defaults to the active flow object.
434+
* @param {http.Executor=} opt_executor A pre-configured command executor to
435+
* use for communicating with an externally managed remoted end (which is
436+
* assumed to already be running). The provided executor should not be
437+
* reused with other clients as its internal command mappings will be
438+
* updated to support Firefox-specific commands.
439+
*
440+
* _This parameter may only be used with Mozilla's GeckoDriver._
441+
*
442+
* @throws {Error} If a custom command executor is provided and the driver is
443+
* configured to use the legacy FirefoxDriver from the Selenium project.
425444
*/
426-
constructor(opt_config, opt_flow) {
445+
constructor(opt_config, opt_flow, opt_executor) {
427446
let caps;
428447
if (opt_config instanceof Options) {
429448
caps = opt_config.toCapabilities();
@@ -453,9 +472,15 @@ class Driver extends webdriver.WebDriver {
453472
let useMarionette = !noMarionette;
454473

455474
if (useMarionette) {
456-
let service = createGeckoDriverService(binary);
457-
serverUrl = service.start();
458-
onQuit = () => service.kill();
475+
if (opt_executor) {
476+
configureExecutor(opt_executor);
477+
serverUrl = Promise.reject(Error('unexpected variable use'));
478+
onQuit = function noop() {};
479+
} else {
480+
let service = createGeckoDriverService(binary);
481+
serverUrl = service.start();
482+
onQuit = () => service.kill();
483+
}
459484

460485
if (profile) {
461486
caps.set(Capability.PROFILE, profile.encode());
@@ -474,6 +499,11 @@ class Driver extends webdriver.WebDriver {
474499
caps = {required, desired: caps};
475500
}
476501
} else {
502+
if (opt_executor) {
503+
throw Error('You may not use a custom command executor with the legacy'
504+
+ ' FirefoxDriver');
505+
}
506+
477507
profile = profile || new Profile;
478508

479509
let freePort = portprober.findFreePort();
@@ -503,7 +533,7 @@ class Driver extends webdriver.WebDriver {
503533
};
504534
}
505535

506-
let executor = createExecutor(serverUrl);
536+
let executor = opt_executor || createExecutor(serverUrl);
507537
let driver = webdriver.WebDriver.createSession(executor, caps, opt_flow);
508538
super(driver.getSession(), executor, driver.controlFlow());
509539

0 commit comments

Comments
 (0)