From b390490dc93fc77284c484dc06d6e5fcc28d0468 Mon Sep 17 00:00:00 2001 From: Jason Leyba Date: Mon, 31 Oct 2016 11:25:03 -0700 Subject: [PATCH] [js] Builder.build() now returns a thenable WebDriver object. Users can issue commands directly, or through a standard promise callback. This is the same pattern used for WebElements with WebDriver.findElement(). Removed Builder.buildAsync() as it was redundant with the change above. This change is part of #2969, to support awaiting the build result: async function demo(url); { let driver = await new Builder().forBrowser('firefox').build(); return driver.get(url); } --- javascript/node/selenium-webdriver/CHANGES.md | 11 + javascript/node/selenium-webdriver/chrome.js | 45 +- javascript/node/selenium-webdriver/edge.js | 15 +- .../node/selenium-webdriver/firefox/index.js | 71 +- javascript/node/selenium-webdriver/ie.js | 15 +- javascript/node/selenium-webdriver/index.js | 131 ++- .../node/selenium-webdriver/lib/session.js | 2 +- .../node/selenium-webdriver/lib/webdriver.js | 793 ++++++++++-------- javascript/node/selenium-webdriver/opera.js | 9 +- .../node/selenium-webdriver/phantomjs.js | 18 +- javascript/node/selenium-webdriver/safari.js | 20 +- .../selenium-webdriver/test/actions_test.js | 2 +- .../test/chrome/options_test.js | 2 +- .../selenium-webdriver/test/cookie_test.js | 4 +- .../test/element_finding_test.js | 2 +- .../test/execute_script_test.js | 2 +- .../test/firefox/firefox_test.js | 6 +- .../test/lib/webdriver_test.js | 18 - .../selenium-webdriver/test/logging_test.js | 10 +- .../test/page_loading_test.js | 4 +- .../test/phantomjs/execute_phantomjs_test.js | 2 +- .../selenium-webdriver/test/proxy_test.js | 5 +- .../selenium-webdriver/test/session_test.js | 2 +- .../test/stale_element_test.js | 2 +- .../selenium-webdriver/test/tag_name_test.js | 2 +- .../selenium-webdriver/test/upload_test.js | 2 +- .../selenium-webdriver/test/window_test.js | 2 +- 27 files changed, 649 insertions(+), 548 deletions(-) diff --git a/javascript/node/selenium-webdriver/CHANGES.md b/javascript/node/selenium-webdriver/CHANGES.md index 09cf6d0a76f66..a5dc6f5057fd8 100644 --- a/javascript/node/selenium-webdriver/CHANGES.md +++ b/javascript/node/selenium-webdriver/CHANGES.md @@ -29,6 +29,17 @@ - Removed `#isPending()` - Removed `#cancel()` - Removed `#finally()` + * Changed all subclasses of `webdriver.WebDriver` to overload the static + function `WebDriver.createSession()` instead of doing work in the + constructor. All constructors now inherit the base class' function signature. + Users are still encouraged to use the `Builder` class instead of creating + drivers directly. + * `Builder#build()` now returns a "thenable" WebDriver instance, allowing users + to immediately schedule commands (as before), or issue them through standard + promise callbacks. This is the same pattern already employed for WebElements. + * Removed `Builder#buildAsync()` as it was redundant with the new semantics of + `build()`. + ## v3.0.0-beta-3 diff --git a/javascript/node/selenium-webdriver/chrome.js b/javascript/node/selenium-webdriver/chrome.js index 37058093870c5..eb33df9ed3819 100644 --- a/javascript/node/selenium-webdriver/chrome.js +++ b/javascript/node/selenium-webdriver/chrome.js @@ -119,8 +119,7 @@ const fs = require('fs'), const http = require('./http'), io = require('./io'), - Capabilities = require('./lib/capabilities').Capabilities, - Capability = require('./lib/capabilities').Capability, + {Capabilities, Capability} = require('./lib/capabilities'), command = require('./lib/command'), logging = require('./lib/logging'), promise = require('./lib/promise'), @@ -678,34 +677,27 @@ class Options { * Creates a new WebDriver client for Chrome. */ class Driver extends webdriver.WebDriver { + /** - * @param {(Capabilities|Options)=} opt_config The configuration - * options. - * @param {remote.DriverService=} opt_service The session to use; will use - * the {@linkplain #getDefaultService default service} by default. - * @param {promise.ControlFlow=} opt_flow The control flow to use, - * or {@code null} to use the currently active flow. - * @param {http.Executor=} opt_executor A pre-configured command executor that - * should be used to send commands to the remote end. The provided - * executor should not be reused with other clients as its internal - * command mappings will be updated to support Chrome-specific commands. - * - * You may provide either a custom executor or a driver service, but not both. + * Creates a new session with the ChromeDriver. * - * @throws {Error} if both `opt_service` and `opt_executor` are provided. + * @param {(Capabilities|Options)=} opt_config The configuration options. + * @param {(remote.DriverService|http.Executor)=} opt_serviceExecutor Either + * a DriverService to use for the remote end, or a preconfigured executor + * for an externally managed endpoint. If neither is provided, the + * {@linkplain ##getDefaultService default service} will be used by + * default. + * @param {promise.ControlFlow=} opt_flow The control flow to use, or `null` + * to use the currently active flow. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_service, opt_flow, opt_executor) { - if (opt_service && opt_executor) { - throw Error( - 'Either a DriverService or Executor may be provided, but not both'); - } - + static createSession(opt_config, opt_serviceExecutor, opt_flow) { let executor; - if (opt_executor) { - executor = opt_executor; + if (opt_serviceExecutor instanceof http.Executor) { + executor = opt_serviceExecutor; configureExecutor(executor); } else { - let service = opt_service || getDefaultService(); + let service = opt_serviceExecutor || getDefaultService(); executor = createExecutor(service.start()); } @@ -713,9 +705,8 @@ class Driver extends webdriver.WebDriver { opt_config instanceof Options ? opt_config.toCapabilities() : (opt_config || Capabilities.chrome()); - let driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - - super(driver.getSession(), executor, driver.controlFlow()); + return /** @type {!Driver} */( + webdriver.WebDriver.createSession(executor, caps, opt_flow, this)); } /** diff --git a/javascript/node/selenium-webdriver/edge.js b/javascript/node/selenium-webdriver/edge.js index d4b08a105ba87..ee9d43383c38c 100644 --- a/javascript/node/selenium-webdriver/edge.js +++ b/javascript/node/selenium-webdriver/edge.js @@ -259,14 +259,17 @@ function getDefaultService() { */ class Driver extends webdriver.WebDriver { /** + * Creates a new browser session for Microsoft's Edge browser. + * * @param {(capabilities.Capabilities|Options)=} opt_config The configuration * options. * @param {remote.DriverService=} opt_service The session to use; will use * the {@linkplain #getDefaultService default service} by default. * @param {promise.ControlFlow=} opt_flow The control flow to use, or * {@code null} to use the currently active flow. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_service, opt_flow) { + static createSession(opt_config, opt_service, opt_flow) { var service = opt_service || getDefaultService(); var client = service.start().then(url => new http.HttpClient(url)); var executor = new http.Executor(client); @@ -275,14 +278,8 @@ class Driver extends webdriver.WebDriver { opt_config instanceof Options ? opt_config.toCapabilities() : (opt_config || capabilities.Capabilities.edge()); - var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - super(driver.getSession(), executor, driver.controlFlow()); - - /** @override */ - this.quit = () => { - return /** @type {!promise.Thenable} */( - promise.finally(super.quit(), service.kill.bind(service))); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } /** diff --git a/javascript/node/selenium-webdriver/firefox/index.js b/javascript/node/selenium-webdriver/firefox/index.js index 14505a2abd0c2..4ea1702a9ded4 100644 --- a/javascript/node/selenium-webdriver/firefox/index.js +++ b/javascript/node/selenium-webdriver/firefox/index.js @@ -440,7 +440,11 @@ class ServiceBuilder extends remote.DriverService.Builder { /** - * @typedef {{driver: !webdriver.WebDriver, onQuit: function()}} + * @typedef {{executor: !command.Executor, + * capabilities: (!capabilities.Capabilities| + * {desired: (capabilities.Capabilities|undefined), + * required: (capabilities.Capabilities|undefined)}), + * onQuit: function(this: void): ?}} */ var DriverSpec; @@ -450,11 +454,9 @@ var DriverSpec; * @param {!capabilities.Capabilities} caps * @param {Profile} profile * @param {Binary} binary - * @param {(promise.ControlFlow|undefined)} flow * @return {DriverSpec} */ -function createGeckoDriver( - executor, caps, profile, binary, flow) { +function createGeckoDriver(executor, caps, profile, binary) { let firefoxOptions = {}; caps.set('moz:firefoxOptions', firefoxOptions); @@ -499,7 +501,7 @@ function createGeckoDriver( sessionCaps = {required, desired: caps}; } - /** @type {(command.Executor|undefined)} */ + /** @type {!command.Executor} */ let cmdExecutor; let onQuit = function() {}; @@ -519,12 +521,11 @@ function createGeckoDriver( onQuit = () => service.kill(); } - let driver = - webdriver.WebDriver.createSession( - /** @type {!http.Executor} */(cmdExecutor), - sessionCaps, - flow); - return {driver, onQuit}; + return { + executor: cmdExecutor, + capabilities: sessionCaps, + onQuit + }; } @@ -532,7 +533,6 @@ function createGeckoDriver( * @param {!capabilities.Capabilities} caps * @param {Profile} profile * @param {!Binary} binary - * @param {(promise.ControlFlow|undefined)} flow * @return {DriverSpec} */ function createLegacyDriver(caps, profile, binary, flow) { @@ -555,18 +555,18 @@ function createLegacyDriver(caps, profile, binary, flow) { return ready.then(() => serverUrl); }); - let onQuit = function() { - return command.then(command => { - command.kill(); - return preparedProfile.then(io.rmDir) - .then(() => command.result(), - () => command.result()); - }); + return { + executor: createExecutor(serverUrl), + capabilities: caps, + onQuit: function() { + return command.then(command => { + command.kill(); + return preparedProfile.then(io.rmDir) + .then(() => command.result(), + () => command.result()); + }); + } }; - - let executor = createExecutor(serverUrl); - let driver = webdriver.WebDriver.createSession(executor, caps, flow); - return {driver, onQuit}; } @@ -575,6 +575,8 @@ function createLegacyDriver(caps, profile, binary, flow) { */ class Driver extends webdriver.WebDriver { /** + * Creates a new Firefox session. + * * @param {(Options|capabilities.Capabilities|Object)=} opt_config The * configuration options for this driver, specified as either an * {@link Options} or {@link capabilities.Capabilities}, or as a raw hash @@ -595,8 +597,9 @@ class Driver extends webdriver.WebDriver { * schedule commands through. Defaults to the active flow object. * @throws {Error} If a custom command executor is provided and the driver is * configured to use the legacy FirefoxDriver from the Selenium project. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_executor, opt_flow) { + static createSession(opt_config, opt_executor, opt_flow) { let caps; if (opt_config instanceof Options) { caps = opt_config.toCapabilities(); @@ -616,8 +619,6 @@ class Driver extends webdriver.WebDriver { caps.delete(Capability.PROFILE); } - let serverUrl, onQuit; - // Users must now explicitly disable marionette to use the legacy // FirefoxDriver. let noMarionette = @@ -627,12 +628,7 @@ class Driver extends webdriver.WebDriver { let spec; if (useMarionette) { - spec = createGeckoDriver( - opt_executor, - caps, - profile, - binary, - opt_flow); + spec = createGeckoDriver(opt_executor, caps, profile, binary); } else { if (opt_executor) { throw Error('You may not use a custom command executor with the legacy' @@ -641,15 +637,8 @@ class Driver extends webdriver.WebDriver { spec = createLegacyDriver(caps, profile, binary, opt_flow); } - super(spec.driver.getSession(), - spec.driver.getExecutor(), - spec.driver.controlFlow()); - - /** @override */ - this.quit = () => { - return /** @type {!promise.Thenable} */( - promise.finally(super.quit(), spec.onQuit)); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + spec.executor, spec.capabilities, opt_flow, this, spec.onQuit)); } /** diff --git a/javascript/node/selenium-webdriver/ie.js b/javascript/node/selenium-webdriver/ie.js index ffd68e4c2cc92..5b86fa58e2fac 100644 --- a/javascript/node/selenium-webdriver/ie.js +++ b/javascript/node/selenium-webdriver/ie.js @@ -403,12 +403,15 @@ function createServiceFromCapabilities(capabilities) { */ class Driver extends webdriver.WebDriver { /** + * Creates a new session for Microsoft's Internet Explorer. + * * @param {(capabilities.Capabilities|Options)=} opt_config The configuration * options. * @param {promise.ControlFlow=} opt_flow The control flow to use, * or {@code null} to use the currently active flow. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_flow) { + static createSession(opt_config, opt_flow) { var caps = opt_config instanceof Options ? opt_config.toCapabilities() : (opt_config || capabilities.Capabilities.ie()); @@ -416,15 +419,9 @@ class Driver extends webdriver.WebDriver { var service = createServiceFromCapabilities(caps); var client = service.start().then(url => new http.HttpClient(url)); var executor = new http.Executor(client); - var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - - super(driver.getSession(), executor, driver.controlFlow()); - /** @override */ - this.quit = () => { - return /** @type {!promise.Thenable} */( - promise.finally(super.quit(), service.kill.bind(service))); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } /** diff --git a/javascript/node/selenium-webdriver/index.js b/javascript/node/selenium-webdriver/index.js index d619dea84e680..3cde7f396e466 100644 --- a/javascript/node/selenium-webdriver/index.js +++ b/javascript/node/selenium-webdriver/index.js @@ -47,6 +47,7 @@ const safari = require('./safari'); const Browser = capabilities.Browser; const Capabilities = capabilities.Capabilities; const Capability = capabilities.Capability; +const Session = session.Session; const WebDriver = webdriver.WebDriver; @@ -93,6 +94,80 @@ function ensureFileDetectorsAreEnabled(ctor) { } +/** + * A thenable wrapper around a {@linkplain webdriver.IWebDriver IWebDriver} + * instance that allows commands to be issued directly instead of having to + * repeatedly call `then`: + * + * let driver = new Builder().build(); + * driver.then(d => d.get(url)); // You can do this... + * driver.get(url); // ...or this + * + * If the driver instance fails to resolve (e.g. the session cannot be created), + * every issued command will fail. + * + * @extends {webdriver.IWebDriver} + * @extends {promise.CancellableThenable} + * @interface + */ +class ThenableWebDriver { + /** @param {...?} args */ + static createSession(...args) {} +} + + +/** + * @const {!Map, ...?), + * function(new: ThenableWebDriver, !IThenable, ...?)>} + */ +const THENABLE_DRIVERS = new Map; + + +/** + * @param {function(new: WebDriver, !IThenable, ...?)} ctor + * @param {...?} args + * @return {!ThenableWebDriver} + */ +function createDriver(ctor, ...args) { + let thenableWebDriverProxy = THENABLE_DRIVERS.get(ctor); + if (!thenableWebDriverProxy) { + /** @implements {ThenableWebDriver} */ + thenableWebDriverProxy = class extends ctor { + /** + * @param {!IThenable} session + * @param {...?} rest + */ + constructor(session, ...rest) { + super(session, ...rest); + + const pd = this.getSession().then(session => { + return new ctor(session, ...rest); + }); + + /** + * @param {(string|Error)=} opt_reason + * @override + */ + this.cancel = function(opt_reason) { + if (promise.CancellableThenable.isImplementation(pd)) { + /** @type {!promise.CancellableThenable} */(pd).cancel(opt_reason); + } + }; + + /** @override */ + this.then = pd.then.bind(pd); + + /** @override */ + this.catch = pd.then.bind(pd); + } + } + promise.CancellableThenable.addImplementation(thenableWebDriverProxy); + THENABLE_DRIVERS.set(ctor, thenableWebDriverProxy); + } + return thenableWebDriverProxy.createSession(...args); +} + + /** * Creates new {@link webdriver.WebDriver WebDriver} instances. The environment * variables listed below may be used to override a builder's configuration, @@ -468,15 +543,14 @@ class Builder { * Creates a new WebDriver client based on this builder's current * configuration. * - * While this method will immediately return a new WebDriver instance, any - * commands issued against it will be deferred until the associated browser - * has been fully initialized. Users may call {@link #buildAsync()} to obtain - * a promise that will not be fulfilled until the browser has been created - * (the difference is purely in style). + * This method will return a {@linkplain ThenableWebDriver} instance, allowing + * users to issue commands directly without calling `then()`. The returned + * thenable wraps a promise that will resolve to a concrete + * {@linkplain webdriver.WebDriver WebDriver} instance. The promise will be + * rejected if the remote end fails to create a new session. * - * @return {!webdriver.WebDriver} A new WebDriver instance. + * @return {!ThenableWebDriver} A new WebDriver instance. * @throws {Error} If the current configuration is invalid. - * @see #buildAsync() */ build() { // Create a copy for any changes we may need to make based on the current @@ -546,63 +620,47 @@ class Builder { if (browser === Browser.CHROME) { const driver = ensureFileDetectorsAreEnabled(chrome.Driver); - return new driver(capabilities, null, this.flow_, executor); + return createDriver( + driver, capabilities, executor, this.flow_); } if (browser === Browser.FIREFOX) { const driver = ensureFileDetectorsAreEnabled(firefox.Driver); - return new driver(capabilities, executor, this.flow_); + return createDriver( + driver, capabilities, executor, this.flow_); } - - return WebDriver.createSession(executor, capabilities, this.flow_); + return createDriver( + WebDriver, executor, capabilities, this.flow_); } // Check for a native browser. switch (browser) { case Browser.CHROME: - return new chrome.Driver(capabilities, null, this.flow_); + return createDriver(chrome.Driver, capabilities, null, this.flow_); case Browser.FIREFOX: - return new firefox.Driver(capabilities, null, this.flow_); + return createDriver(firefox.Driver, capabilities, null, this.flow_); case Browser.INTERNET_EXPLORER: - return new ie.Driver(capabilities, this.flow_); + return createDriver(ie.Driver, capabilities, this.flow_); case Browser.EDGE: - return new edge.Driver(capabilities, null, this.flow_); + return createDriver(edge.Driver, capabilities, null, this.flow_); case Browser.OPERA: - return new opera.Driver(capabilities, null, this.flow_); + return createDriver(opera.Driver, capabilities, null, this.flow_); case Browser.PHANTOM_JS: - return new phantomjs.Driver(capabilities, this.flow_); + return createDriver(phantomjs.Driver, capabilities, this.flow_); case Browser.SAFARI: - return new safari.Driver(capabilities, this.flow_); + return createDriver(safari.Driver, capabilities, this.flow_); default: throw new Error('Do not know how to build driver: ' + browser + '; did you forget to call usingServer(url)?'); } } - - /** - * Creates a new WebDriver client based on this builder's current - * configuration. This method returns a promise that will not be fulfilled - * until the new browser session has been fully initialized. - * - * __Note:__ this method is purely a convenience wrapper around - * {@link #build()}. - * - * @return {!promise.Thenable} A promise that will be - * fulfilled with the newly created WebDriver instance once the browser - * has been fully initialized. - * @see #build() - */ - buildAsync() { - let driver = this.build(); - return driver.getSession().then(() => driver); - } } @@ -621,6 +679,7 @@ exports.EventEmitter = events.EventEmitter; exports.FileDetector = input.FileDetector; exports.Key = input.Key; exports.Session = session.Session; +exports.ThenableWebDriver = ThenableWebDriver; exports.TouchSequence = actions.TouchSequence; exports.WebDriver = webdriver.WebDriver; exports.WebElement = webdriver.WebElement; diff --git a/javascript/node/selenium-webdriver/lib/session.js b/javascript/node/selenium-webdriver/lib/session.js index 296291d4cd018..64ff6be7676ca 100644 --- a/javascript/node/selenium-webdriver/lib/session.js +++ b/javascript/node/selenium-webdriver/lib/session.js @@ -17,7 +17,7 @@ 'use strict'; -const Capabilities = require('./capabilities').Capabilities; +const {Capabilities} = require('./capabilities'); /** diff --git a/javascript/node/selenium-webdriver/lib/webdriver.js b/javascript/node/selenium-webdriver/lib/webdriver.js index 243f94e1e48ae..34fbc7ca88627 100644 --- a/javascript/node/selenium-webdriver/lib/webdriver.js +++ b/javascript/node/selenium-webdriver/lib/webdriver.js @@ -28,7 +28,7 @@ const command = require('./command'); const error = require('./error'); const input = require('./input'); const logging = require('./logging'); -const Session = require('./session').Session; +const {Session} = require('./session'); const Symbols = require('./symbols'); const promise = require('./promise'); @@ -237,145 +237,14 @@ function fromWireValue(driver, value) { /** - * Creates a new WebDriver client, which provides control over a browser. - * - * Every command.Command returns a {@link promise.Promise} that - * represents the result of that command. Callbacks may be registered on this - * object to manipulate the command result or catch an expected error. Any - * commands scheduled with a callback are considered sub-commands and will - * execute before the next command in the current frame. For example: - * - * var message = []; - * driver.call(message.push, message, 'a').then(function() { - * driver.call(message.push, message, 'b'); - * }); - * driver.call(message.push, message, 'c'); - * driver.call(function() { - * alert('message is abc? ' + (message.join('') == 'abc')); - * }); + * Structural interface for a WebDriver client. * + * @record */ -class WebDriver { - /** - * @param {!(Session|IThenable)} session Either a - * known session or a promise that will be resolved to a session. - * @param {!command.Executor} executor The executor to use when sending - * commands to the browser. - * @param {promise.ControlFlow=} opt_flow The flow to - * schedule commands through. Defaults to the active flow object. - */ - constructor(session, executor, opt_flow) { - /** @private {!promise.ControlFlow} */ - this.flow_ = opt_flow || promise.controlFlow(); - - /** @private {!promise.Thenable} */ - this.session_ = this.flow_.promise(resolve => resolve(session)); - - /** @private {!command.Executor} */ - this.executor_ = executor; - - /** @private {input.FileDetector} */ - this.fileDetector_ = null; - } - - /** - * Creates a new WebDriver client for an existing session. - * @param {!command.Executor} executor Command executor to use when querying - * for session details. - * @param {string} sessionId ID of the session to attach to. - * @param {promise.ControlFlow=} opt_flow The control flow all - * driver commands should execute under. Defaults to the - * {@link promise.controlFlow() currently active} control flow. - * @return {!WebDriver} A new client for the specified session. - */ - static attachToSession(executor, sessionId, opt_flow) { - let flow = opt_flow || promise.controlFlow(); - let cmd = new command.Command(command.Name.DESCRIBE_SESSION) - .setParameter('sessionId', sessionId); - let session = flow.execute( - () => executeCommand(executor, cmd).catch(err => { - // The DESCRIBE_SESSION command is not supported by the W3C spec, so - // if we get back an unknown command, just return a session with - // unknown capabilities. - if (err instanceof error.UnknownCommandError) { - return new Session(sessionId, new Capabilities); - } - throw err; - }), - 'WebDriver.attachToSession()'); - return new WebDriver(session, executor, flow); - } - - /** - * Creates a new WebDriver session. - * - * By default, the requested session `capabilities` are merely "desired" and - * the remote end will still create a new session even if it cannot satisfy - * all of the requested capabilities. You can query which capabilities a - * session actually has using the - * {@linkplain #getCapabilities() getCapabilities()} method on the returned - * WebDriver instance. - * - * To define _required capabilities_, provide the `capabilities` as an object - * literal with `required` and `desired` keys. The `desired` key may be - * omitted if all capabilities are required, and vice versa. If the server - * cannot create a session with all of the required capabilities, it will - * return an {@linkplain error.SessionNotCreatedError}. - * - * let required = new Capabilities().set('browserName', 'firefox'); - * let desired = new Capabilities().set('version', '45'); - * let driver = WebDriver.createSession(executor, {required, desired}); - * - * This function will always return a WebDriver instance. If there is an error - * creating the session, such as the aforementioned SessionNotCreatedError, - * the driver will have a rejected {@linkplain #getSession session} promise. - * It is recommended that this promise is left _unhandled_ so it will - * propagate through the {@linkplain promise.ControlFlow control flow} and - * cause subsequent commands to fail. - * - * let required = Capabilities.firefox(); - * let driver = WebDriver.createSession(executor, {required}); - * - * // If the createSession operation failed, then this command will also - * // also fail, propagating the creation failure. - * driver.get('http://www.google.com').catch(e => console.log(e)); - * - * @param {!command.Executor} executor The executor to create the new session - * with. - * @param {(!Capabilities| - * {desired: (Capabilities|undefined), - * required: (Capabilities|undefined)})} capabilities The desired - * capabilities for the new session. - * @param {promise.ControlFlow=} opt_flow The control flow all driver - * commands should execute under, including the initial session creation. - * Defaults to the {@link promise.controlFlow() currently active} - * control flow. - * @return {!WebDriver} The driver for the newly created session. - */ - static createSession(executor, capabilities, opt_flow) { - let flow = opt_flow || promise.controlFlow(); - let cmd = new command.Command(command.Name.NEW_SESSION); - - if (capabilities && (capabilities.desired || capabilities.required)) { - cmd.setParameter('desiredCapabilities', capabilities.desired); - cmd.setParameter('requiredCapabilities', capabilities.required); - } else { - cmd.setParameter('desiredCapabilities', capabilities); - } - - let session = flow.execute( - () => executeCommand(executor, cmd), - 'WebDriver.createSession()'); - return new WebDriver(session, executor, flow); - } +class IWebDriver { - /** - * @return {!promise.ControlFlow} The control flow used by this - * instance. - */ - controlFlow() { - return this.flow_; - } + /** @return {!promise.ControlFlow} The control flow used by this instance. */ + controlFlow() {} /** * Schedules a {@link command.Command} to be executed by this driver's @@ -387,104 +256,40 @@ class WebDriver { * with the command result. * @template T */ - schedule(command, description) { - var self = this; - - checkHasNotQuit(); - command.setParameter('sessionId', this.session_); - - // If any of the command parameters are rejected promises, those - // rejections may be reported as unhandled before the control flow - // attempts to execute the command. To ensure parameters errors - // propagate through the command itself, we resolve all of the - // command parameters now, but suppress any errors until the ControlFlow - // actually executes the command. This addresses scenarios like catching - // an element not found error in: - // - // driver.findElement(By.id('foo')).click().catch(function(e) { - // if (e instanceof NoSuchElementError) { - // // Do something. - // } - // }); - var prepCommand = toWireValue(command.getParameters()); - prepCommand.catch(function() {}); - - var flow = this.flow_; - var executor = this.executor_; - return flow.execute(function() { - // A call to WebDriver.quit() may have been scheduled in the same event - // loop as this |command|, which would prevent us from detecting that the - // driver has quit above. Therefore, we need to make another quick check. - // We still check above so we can fail as early as possible. - checkHasNotQuit(); - - // Retrieve resolved command parameters; any previously suppressed errors - // will now propagate up through the control flow as part of the command - // execution. - return prepCommand.then(function(parameters) { - command.setParameters(parameters); - return executor.execute(command); - }).then(value => fromWireValue(self, value)); - }, description); - - function checkHasNotQuit() { - if (!self.session_) { - throw new error.NoSuchSessionError( - 'This driver instance does not have a valid session ID ' + - '(did you call WebDriver.quit()?) and may no longer be ' + - 'used.'); - } - } - } + schedule(command, description) {} /** * Sets the {@linkplain input.FileDetector file detector} that should be * used with this instance. * @param {input.FileDetector} detector The detector to use or {@code null}. */ - setFileDetector(detector) { - this.fileDetector_ = detector; - } + setFileDetector(detector) {} /** * @return {!command.Executor} The command executor used by this instance. */ - getExecutor() { - return this.executor_; - } + getExecutor() {} /** - * @return {!promise.Thenable} A promise for this client's - * session. + * @return {!promise.Thenable} A promise for this client's session. */ - getSession() { - return this.session_; - } + getSession() {} /** - * @return {!promise.Thenable} A promise - * that will resolve with the this instance's capabilities. + * @return {!promise.Thenable} A promise that will resolve with + * the this instance's capabilities. */ - getCapabilities() { - return this.session_.then(session => session.getCapabilities()); - } + getCapabilities() {} /** - * Schedules a command to quit the current session. After calling quit, this - * instance will be invalidated and may no longer be used to issue commands - * against the browser. - * @return {!promise.Thenable} A promise that will be resolved - * when the command has completed. + * Terminates the browser session. After calling quit, this instance will be + * invalidated and may no longer be used to issue commands against the + * browser. + * + * @return {!promise.Thenable} A promise that will be resolved when the + * command has completed. */ - quit() { - var result = this.schedule( - new command.Command(command.Name.QUIT), - 'WebDriver.quit()'); - // Delete our session ID when the quit command finishes; this will allow us - // to throw an error when attemnpting to use a driver post-quit. - return /** @type {!promise.Thenable} */( - promise.finally(result, () => delete this.session_)); - } + quit() {} /** * Creates a new action sequence using this driver. The sequence will not be @@ -499,9 +304,7 @@ class WebDriver { * * @return {!actions.ActionSequence} A new action sequence for this instance. */ - actions() { - return new actions.ActionSequence(this); - } + actions() {} /** * Creates a new touch sequence using this driver. The sequence will not be @@ -515,9 +318,7 @@ class WebDriver { * * @return {!actions.TouchSequence} A new touch sequence for this instance. */ - touchActions() { - return new actions.TouchSequence(this); - } + touchActions() {} /** * Schedules a command to execute JavaScript in the context of the currently @@ -555,18 +356,7 @@ class WebDriver { * scripts return value. * @template T */ - executeScript(script, var_args) { - if (typeof script === 'function') { - script = 'return (' + script + ').apply(null, arguments);'; - } - let args = - arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : []; - return this.schedule( - new command.Command(command.Name.EXECUTE_SCRIPT). - setParameter('script', script). - setParameter('args', args), - 'WebDriver.executeScript()'); - } + executeScript(script, var_args) {} /** * Schedules a command to execute asynchronous JavaScript in the context of the @@ -644,17 +434,7 @@ class WebDriver { * scripts return value. * @template T */ - executeAsyncScript(script, var_args) { - if (typeof script === 'function') { - script = 'return (' + script + ').apply(null, arguments);'; - } - let args = Array.prototype.slice.call(arguments, 1); - return this.schedule( - new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT). - setParameter('script', script). - setParameter('args', args), - 'WebDriver.executeScript()'); - } + executeAsyncScript(script, var_args) {} /** * Schedules a command to execute a custom function. @@ -665,18 +445,7 @@ class WebDriver { * with the function's result. * @template T */ - call(fn, opt_scope, var_args) { - let args = Array.prototype.slice.call(arguments, 2); - return this.flow_.execute(function() { - return promise.fullyResolved(args).then(function(args) { - if (promise.isGenerator(fn)) { - args.unshift(fn, opt_scope); - return promise.consume.apply(null, args); - } - return fn.apply(opt_scope, args); - }); - }, 'WebDriver.call(' + (fn.name || 'function') + ')'); - } + call(fn, opt_scope, var_args) {} /** * Schedules a command to wait for a condition to hold. The condition may be @@ -730,40 +499,7 @@ class WebDriver { * the returned value will be a {@link WebElementPromise}. * @template T */ - wait(condition, opt_timeout, opt_message) { - if (promise.isPromise(condition)) { - return this.flow_.wait( - /** @type {!IThenable} */(condition), - opt_timeout, opt_message); - } - - var message = opt_message; - var fn = /** @type {!Function} */(condition); - if (condition instanceof Condition) { - message = message || condition.description(); - fn = condition.fn; - } - - var driver = this; - var result = this.flow_.wait(function() { - if (promise.isGenerator(fn)) { - return promise.consume(fn, null, [driver]); - } - return fn(driver); - }, opt_timeout, message); - - if (condition instanceof WebElementCondition) { - result = new WebElementPromise(this, result.then(function(value) { - if (!(value instanceof WebElement)) { - throw TypeError( - 'WebElementCondition did not resolve to a WebElement: ' - + Object.prototype.toString.call(value)); - } - return value; - })); - } - return result; - } + wait(condition, opt_timeout, opt_message) {} /** * Schedules a command to make the driver sleep for the given amount of time. @@ -771,31 +507,21 @@ class WebDriver { * @return {!promise.Thenable} A promise that will be resolved * when the sleep has finished. */ - sleep(ms) { - return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')'); - } + sleep(ms) {} /** * Schedules a command to retrieve the current window handle. * @return {!promise.Thenable} A promise that will be * resolved with the current window handle. */ - getWindowHandle() { - return this.schedule( - new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE), - 'WebDriver.getWindowHandle()'); - } + getWindowHandle() {} /** * Schedules a command to retrieve the current list of available window handles. * @return {!promise.Thenable>} A promise that will * be resolved with an array of window handles. */ - getAllWindowHandles() { - return this.schedule( - new command.Command(command.Name.GET_WINDOW_HANDLES), - 'WebDriver.getAllWindowHandles()'); - } + getAllWindowHandles() {} /** * Schedules a command to retrieve the current page's source. The page source @@ -805,21 +531,14 @@ class WebDriver { * @return {!promise.Thenable} A promise that will be * resolved with the current page source. */ - getPageSource() { - return this.schedule( - new command.Command(command.Name.GET_PAGE_SOURCE), - 'WebDriver.getPageSource()'); - } + getPageSource() {} /** * Schedules a command to close the current window. * @return {!promise.Thenable} A promise that will be resolved * when this command has completed. */ - close() { - return this.schedule(new command.Command(command.Name.CLOSE), - 'WebDriver.close()'); - } + close() {} /** * Schedules a command to navigate to the given URL. @@ -827,30 +546,21 @@ class WebDriver { * @return {!promise.Thenable} A promise that will be resolved * when the document has finished loading. */ - get(url) { - return this.navigate().to(url); - } + get(url) {} /** * Schedules a command to retrieve the URL of the current page. * @return {!promise.Thenable} A promise that will be * resolved with the current URL. */ - getCurrentUrl() { - return this.schedule( - new command.Command(command.Name.GET_CURRENT_URL), - 'WebDriver.getCurrentUrl()'); - } + getCurrentUrl() {} /** * Schedules a command to retrieve the current page's title. * @return {!promise.Thenable} A promise that will be * resolved with the current page's title. */ - getTitle() { - return this.schedule(new command.Command(command.Name.GET_TITLE), - 'WebDriver.getTitle()'); - } + getTitle() {} /** * Schedule a command to find an element on the page. If the element cannot be @@ -891,6 +601,404 @@ class WebDriver { * commands against the located element. If the element is not found, the * element will be invalidated and all scheduled commands aborted. */ + findElement(locator) {} + + /** + * Schedule a command to search for multiple elements on the page. + * + * @param {!(by.By|Function)} locator The locator to use. + * @return {!promise.Thenable>} A + * promise that will resolve to an array of WebElements. + */ + findElements(locator) {} + + /** + * Schedule a command to take a screenshot. The driver makes a best effort to + * return a screenshot of the following, in order of preference: + * + * 1. Entire page + * 2. Current window + * 3. Visible portion of the current frame + * 4. The entire display containing the browser + * + * @return {!promise.Thenable} A promise that will be + * resolved to the screenshot as a base-64 encoded PNG. + */ + takeScreenshot() {} + + /** + * @return {!Options} The options interface for this instance. + */ + manage() {} + + /** + * @return {!Navigation} The navigation interface for this instance. + */ + navigate() {} + + /** + * @return {!TargetLocator} The target locator interface for this + * instance. + */ + switchTo() {} +} + + +/** + * Each WebDriver instance provides automated control over a browser session. + * + * @implements {IWebDriver} + */ +class WebDriver { + /** + * @param {!(Session|IThenable)} session Either a known session or a + * promise that will be resolved to a session. + * @param {!command.Executor} executor The executor to use when sending + * commands to the browser. + * @param {promise.ControlFlow=} opt_flow The flow to + * schedule commands through. Defaults to the active flow object. + * @param {(function(this: void): ?)=} opt_onQuit A function to call, if any, + * when the session is terminated. + */ + constructor(session, executor, opt_flow, opt_onQuit) { + /** @private {!promise.ControlFlow} */ + this.flow_ = opt_flow || promise.controlFlow(); + + /** @private {!promise.Thenable} */ + this.session_ = this.flow_.promise(resolve => resolve(session)); + + /** @private {!command.Executor} */ + this.executor_ = executor; + + /** @private {input.FileDetector} */ + this.fileDetector_ = null; + + /** @private @const {(function(this: void): ?|undefined)} */ + this.onQuit_ = opt_onQuit; + } + + /** + * Creates a new WebDriver client for an existing session. + * @param {!command.Executor} executor Command executor to use when querying + * for session details. + * @param {string} sessionId ID of the session to attach to. + * @param {promise.ControlFlow=} opt_flow The control flow all + * driver commands should execute under. Defaults to the + * {@link promise.controlFlow() currently active} control flow. + * @return {!WebDriver} A new client for the specified session. + */ + static attachToSession(executor, sessionId, opt_flow) { + let flow = opt_flow || promise.controlFlow(); + let cmd = new command.Command(command.Name.DESCRIBE_SESSION) + .setParameter('sessionId', sessionId); + let session = flow.execute( + () => executeCommand(executor, cmd).catch(err => { + // The DESCRIBE_SESSION command is not supported by the W3C spec, so + // if we get back an unknown command, just return a session with + // unknown capabilities. + if (err instanceof error.UnknownCommandError) { + return new Session(sessionId, new Capabilities); + } + throw err; + }), + 'WebDriver.attachToSession()'); + return new WebDriver(session, executor, flow); + } + + /** + * Creates a new WebDriver session. + * + * By default, the requested session `capabilities` are merely "desired" and + * the remote end will still create a new session even if it cannot satisfy + * all of the requested capabilities. You can query which capabilities a + * session actually has using the + * {@linkplain #getCapabilities() getCapabilities()} method on the returned + * WebDriver instance. + * + * To define _required capabilities_, provide the `capabilities` as an object + * literal with `required` and `desired` keys. The `desired` key may be + * omitted if all capabilities are required, and vice versa. If the server + * cannot create a session with all of the required capabilities, it will + * return an {@linkplain error.SessionNotCreatedError}. + * + * let required = new Capabilities().set('browserName', 'firefox'); + * let desired = new Capabilities().set('version', '45'); + * let driver = WebDriver.createSession(executor, {required, desired}); + * + * This function will always return a WebDriver instance. If there is an error + * creating the session, such as the aforementioned SessionNotCreatedError, + * the driver will have a rejected {@linkplain #getSession session} promise. + * It is recommended that this promise is left _unhandled_ so it will + * propagate through the {@linkplain promise.ControlFlow control flow} and + * cause subsequent commands to fail. + * + * let required = Capabilities.firefox(); + * let driver = WebDriver.createSession(executor, {required}); + * + * // If the createSession operation failed, then this command will also + * // also fail, propagating the creation failure. + * driver.get('http://www.google.com').catch(e => console.log(e)); + * + * @param {!command.Executor} executor The executor to create the new session + * with. + * @param {(!Capabilities| + * {desired: (Capabilities|undefined), + * required: (Capabilities|undefined)})} capabilities The desired + * capabilities for the new session. + * @param {promise.ControlFlow=} opt_flow The control flow all driver + * commands should execute under, including the initial session creation. + * Defaults to the {@link promise.controlFlow() currently active} + * control flow. + * @param {(function(new: WebDriver, + * !IThenable, + * !command.Executor, + * promise.ControlFlow=))=} opt_ctor + * A reference to the constructor of the specific type of WebDriver client + * to instantiate. Will create a vanilla {@linkplain WebDriver} instance + * if a constructor is not provided. + * @param {(function(this: void): ?)=} opt_onQuit A callback to invoke when + * the newly created session is terminated. This should be used to clean + * up any resources associated with the session. + * @return {!WebDriver} The driver for the newly created session. + */ + static createSession( + executor, capabilities, opt_flow, opt_ctor, opt_onQuit) { + let flow = opt_flow || promise.controlFlow(); + let cmd = new command.Command(command.Name.NEW_SESSION); + + if (capabilities && (capabilities.desired || capabilities.required)) { + cmd.setParameter('desiredCapabilities', capabilities.desired); + cmd.setParameter('requiredCapabilities', capabilities.required); + } else { + cmd.setParameter('desiredCapabilities', capabilities); + } + + let session = flow.execute( + () => executeCommand(executor, cmd), + 'WebDriver.createSession()'); + const ctor = opt_ctor || WebDriver; + return new ctor(session, executor, flow, opt_onQuit); + } + + /** @override */ + controlFlow() { + return this.flow_; + } + + /** @override */ + schedule(command, description) { + command.setParameter('sessionId', this.session_); + + // If any of the command parameters are rejected promises, those + // rejections may be reported as unhandled before the control flow + // attempts to execute the command. To ensure parameters errors + // propagate through the command itself, we resolve all of the + // command parameters now, but suppress any errors until the ControlFlow + // actually executes the command. This addresses scenarios like catching + // an element not found error in: + // + // driver.findElement(By.id('foo')).click().catch(function(e) { + // if (e instanceof NoSuchElementError) { + // // Do something. + // } + // }); + var prepCommand = toWireValue(command.getParameters()); + prepCommand.catch(function() {}); + + var flow = this.flow_; + var executor = this.executor_; + return flow.execute(() => { + // Retrieve resolved command parameters; any previously suppressed errors + // will now propagate up through the control flow as part of the command + // execution. + return prepCommand.then(function(parameters) { + command.setParameters(parameters); + return executor.execute(command); + }).then(value => fromWireValue(this, value)); + }, description); + } + + /** @override */ + setFileDetector(detector) { + this.fileDetector_ = detector; + } + + /** @override */ + getExecutor() { + return this.executor_; + } + + /** @override */ + getSession() { + return this.session_; + } + + /** @override */ + getCapabilities() { + return this.session_.then(s => s.getCapabilities()); + } + + /** @override */ + quit() { + var result = this.schedule( + new command.Command(command.Name.QUIT), + 'WebDriver.quit()'); + // Delete our session ID when the quit command finishes; this will allow us + // to throw an error when attemnpting to use a driver post-quit. + return /** @type {!promise.Thenable} */(promise.finally(result, () => { + this.session_ = this.flow_.promise((_, reject) => { + reject(new error.NoSuchSessionError( + 'This driver instance does not have a valid session ID ' + + '(did you call WebDriver.quit()?) and may no longer be used.')); + }); + + // Only want the session rejection to bubble if accessed. + this.session_.catch(function() {}); + + if (this.onQuit_) { + return this.onQuit_.call(void 0); + } + })); + } + + /** @override */ + actions() { + return new actions.ActionSequence(this); + } + + /** @override */ + touchActions() { + return new actions.TouchSequence(this); + } + + /** @override */ + executeScript(script, var_args) { + if (typeof script === 'function') { + script = 'return (' + script + ').apply(null, arguments);'; + } + let args = + arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : []; + return this.schedule( + new command.Command(command.Name.EXECUTE_SCRIPT). + setParameter('script', script). + setParameter('args', args), + 'WebDriver.executeScript()'); + } + + /** @override */ + executeAsyncScript(script, var_args) { + if (typeof script === 'function') { + script = 'return (' + script + ').apply(null, arguments);'; + } + let args = Array.prototype.slice.call(arguments, 1); + return this.schedule( + new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT). + setParameter('script', script). + setParameter('args', args), + 'WebDriver.executeScript()'); + } + + /** @override */ + call(fn, opt_scope, var_args) { + let args = Array.prototype.slice.call(arguments, 2); + return this.flow_.execute(function() { + return promise.fullyResolved(args).then(function(args) { + if (promise.isGenerator(fn)) { + args.unshift(fn, opt_scope); + return promise.consume.apply(null, args); + } + return fn.apply(opt_scope, args); + }); + }, 'WebDriver.call(' + (fn.name || 'function') + ')'); + } + + /** @override */ + wait(condition, opt_timeout, opt_message) { + if (promise.isPromise(condition)) { + return this.flow_.wait( + /** @type {!IThenable} */(condition), + opt_timeout, opt_message); + } + + var message = opt_message; + var fn = /** @type {!Function} */(condition); + if (condition instanceof Condition) { + message = message || condition.description(); + fn = condition.fn; + } + + var driver = this; + var result = this.flow_.wait(function() { + if (promise.isGenerator(fn)) { + return promise.consume(fn, null, [driver]); + } + return fn(driver); + }, opt_timeout, message); + + if (condition instanceof WebElementCondition) { + result = new WebElementPromise(this, result.then(function(value) { + if (!(value instanceof WebElement)) { + throw TypeError( + 'WebElementCondition did not resolve to a WebElement: ' + + Object.prototype.toString.call(value)); + } + return value; + })); + } + return result; + } + + /** @override */ + sleep(ms) { + return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')'); + } + + /** @override */ + getWindowHandle() { + return this.schedule( + new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE), + 'WebDriver.getWindowHandle()'); + } + + /** @override */ + getAllWindowHandles() { + return this.schedule( + new command.Command(command.Name.GET_WINDOW_HANDLES), + 'WebDriver.getAllWindowHandles()'); + } + + /** @override */ + getPageSource() { + return this.schedule( + new command.Command(command.Name.GET_PAGE_SOURCE), + 'WebDriver.getPageSource()'); + } + + /** @override */ + close() { + return this.schedule(new command.Command(command.Name.CLOSE), + 'WebDriver.close()'); + } + + /** @override */ + get(url) { + return this.navigate().to(url); + } + + /** @override */ + getCurrentUrl() { + return this.schedule( + new command.Command(command.Name.GET_CURRENT_URL), + 'WebDriver.getCurrentUrl()'); + } + + /** @override */ + getTitle() { + return this.schedule(new command.Command(command.Name.GET_TITLE), + 'WebDriver.getTitle()'); + } + + /** @override */ findElement(locator) { let id; locator = by.checkedLocator(locator); @@ -925,13 +1033,7 @@ class WebDriver { }); } - /** - * Schedule a command to search for multiple elements on the page. - * - * @param {!(by.By|Function)} locator The locator to use. - * @return {!promise.Thenable>} A - * promise that will resolve to an array of WebElements. - */ + /** @override */ findElements(locator) { locator = by.checkedLocator(locator); if (typeof locator === 'function') { @@ -973,41 +1075,23 @@ class WebDriver { }); } - /** - * Schedule a command to take a screenshot. The driver makes a best effort to - * return a screenshot of the following, in order of preference: - * - * 1. Entire page - * 2. Current window - * 3. Visible portion of the current frame - * 4. The entire display containing the browser - * - * @return {!promise.Thenable} A promise that will be - * resolved to the screenshot as a base-64 encoded PNG. - */ + /** @override */ takeScreenshot() { return this.schedule(new command.Command(command.Name.SCREENSHOT), 'WebDriver.takeScreenshot()'); } - /** - * @return {!Options} The options interface for this instance. - */ + /** @override */ manage() { return new Options(this); } - /** - * @return {!Navigation} The navigation interface for this instance. - */ + /** @override */ navigate() { return new Navigation(this); } - /** - * @return {!TargetLocator} The target locator interface for this - * instance. - */ + /** @override */ switchTo() { return new TargetLocator(this); } @@ -2459,6 +2543,7 @@ module.exports = { Options: Options, TargetLocator: TargetLocator, Timeouts: Timeouts, + IWebDriver: IWebDriver, WebDriver: WebDriver, WebElement: WebElement, WebElementCondition: WebElementCondition, diff --git a/javascript/node/selenium-webdriver/opera.js b/javascript/node/selenium-webdriver/opera.js index 7106511f78d44..cc84f2af56b4e 100644 --- a/javascript/node/selenium-webdriver/opera.js +++ b/javascript/node/selenium-webdriver/opera.js @@ -348,14 +348,17 @@ class Options { */ class Driver extends webdriver.WebDriver { /** + * Creates a new session for Opera. + * * @param {(capabilities.Capabilities|Options)=} opt_config The configuration * options. * @param {remote.DriverService=} opt_service The session to use; will use * the {@link getDefaultService default service} by default. * @param {promise.ControlFlow=} opt_flow The control flow to use, * or {@code null} to use the currently active flow. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_service, opt_flow) { + static createSession(opt_config, opt_service, opt_flow) { var service = opt_service || getDefaultService(); var client = service.start().then(url => new http.HttpClient(url)); var executor = new http.Executor(client); @@ -379,8 +382,8 @@ class Driver extends webdriver.WebDriver { caps = options.toCapabilities(caps); } - var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - super(driver.getSession(), executor, driver.controlFlow()); + return /** @type {!Driver} */( + webdriver.WebDriver.createSession(executor, caps, opt_flow, this)); } /** diff --git a/javascript/node/selenium-webdriver/phantomjs.js b/javascript/node/selenium-webdriver/phantomjs.js index acd538f4ba2c8..baa7cb378361e 100644 --- a/javascript/node/selenium-webdriver/phantomjs.js +++ b/javascript/node/selenium-webdriver/phantomjs.js @@ -148,6 +148,8 @@ function createExecutor(url) { */ class Driver extends webdriver.WebDriver { /** + * Creates a new PhantomJS session. + * * @param {capabilities.Capabilities=} opt_capabilities The desired * capabilities. * @param {promise.ControlFlow=} opt_flow The control flow to use, @@ -155,8 +157,9 @@ class Driver extends webdriver.WebDriver { * @param {string=} opt_logFile Path to the log file for the phantomjs * executable's output. For convenience, this may be set at runtime with * the `SELENIUM_PHANTOMJS_LOG` environment variable. + * @return {!Driver} A new driver reference. */ - constructor(opt_capabilities, opt_flow, opt_logFile) { + static createSession(opt_capabilities, opt_flow, opt_logFile) { // TODO: add an Options class for consistency with the other driver types. var caps = opt_capabilities || capabilities.Capabilities.phantomjs(); @@ -214,17 +217,8 @@ class Driver extends webdriver.WebDriver { }); var executor = createExecutor(service.start()); - var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - - super(driver.getSession(), executor, driver.controlFlow()); - - var boundQuit = this.quit.bind(this); - - /** @override */ - this.quit = function() { - let killService = () => service.kill(); - return boundQuit().then(killService, killService); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } /** diff --git a/javascript/node/selenium-webdriver/safari.js b/javascript/node/selenium-webdriver/safari.js index 2b9af51e71dd1..97d512bc76b7e 100644 --- a/javascript/node/selenium-webdriver/safari.js +++ b/javascript/node/selenium-webdriver/safari.js @@ -23,13 +23,11 @@ const http = require('./http'); const io = require('./io'); -const Capabilities = require('./lib/capabilities').Capabilities; -const Capability = require('./lib/capabilities').Capability; +const {Capabilities, Capability} = require('./lib/capabilities'); const command = require('./lib/command'); const error = require('./lib/error'); const logging = require('./lib/logging'); const promise = require('./lib/promise'); -const Session = require('./lib/session').Session; const Symbols = require('./lib/symbols'); const webdriver = require('./lib/webdriver'); const portprober = require('./net/portprober'); @@ -193,12 +191,15 @@ class Options { */ class Driver extends webdriver.WebDriver { /** + * Creates a new Safari session. + * * @param {(Options|Capabilities)=} opt_config The configuration * options for the new session. * @param {promise.ControlFlow=} opt_flow The control flow to create * the driver under. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_flow) { + static createSession(opt_config, opt_flow) { let caps; if (opt_config instanceof Options) { caps = opt_config.toCapabilities(); @@ -209,16 +210,9 @@ class Driver extends webdriver.WebDriver { let service = new ServiceBuilder().build(); let executor = new http.Executor( service.start().then(url => new http.HttpClient(url))); - let onQuit = () => service.kill(); - - let driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - super(driver.getSession(), executor, driver.controlFlow()); - /** @override */ - this.quit = () => { - return /** @type {!promise.Thenable} */( - promise.finally(super.quit(), onQuit)); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } } diff --git a/javascript/node/selenium-webdriver/test/actions_test.js b/javascript/node/selenium-webdriver/test/actions_test.js index 3ac0849d82c99..ef218f7d7cfdb 100644 --- a/javascript/node/selenium-webdriver/test/actions_test.js +++ b/javascript/node/selenium-webdriver/test/actions_test.js @@ -26,7 +26,7 @@ var Browser = require('..').Browser, test.suite(function(env) { var driver; - test.beforeEach(function*() { driver = yield env.builder().buildAsync(); }); + test.beforeEach(function*() { driver = yield env.builder().build(); }); test.afterEach(function() { return driver.quit(); }); test.ignore( diff --git a/javascript/node/selenium-webdriver/test/chrome/options_test.js b/javascript/node/selenium-webdriver/test/chrome/options_test.js index 5a89fd309a4c6..d36a044e4b913 100644 --- a/javascript/node/selenium-webdriver/test/chrome/options_test.js +++ b/javascript/node/selenium-webdriver/test/chrome/options_test.js @@ -215,7 +215,7 @@ test.suite(function(env) { driver = yield env.builder() .setChromeOptions(options) - .buildAsync(); + .build(); yield driver.get(test.Pages.ajaxyPage); diff --git a/javascript/node/selenium-webdriver/test/cookie_test.js b/javascript/node/selenium-webdriver/test/cookie_test.js index a9b13eb1b0996..40f7d9b57d6bb 100644 --- a/javascript/node/selenium-webdriver/test/cookie_test.js +++ b/javascript/node/selenium-webdriver/test/cookie_test.js @@ -30,7 +30,7 @@ test.suite(function(env) { var driver; test.before(function*() { - driver = yield env.builder().buildAsync(); + driver = yield env.builder().build(); }); test.after(function() { @@ -38,7 +38,7 @@ test.suite(function(env) { }); // Cookie handling is broken. - test.ignore(env.browsers(Browser.PHANTOMJS, Browser.SAFARI)). + test.ignore(env.browsers(Browser.PHANTOM_JS, Browser.SAFARI)). describe('Cookie Management;', function() { test.beforeEach(function*() { diff --git a/javascript/node/selenium-webdriver/test/element_finding_test.js b/javascript/node/selenium-webdriver/test/element_finding_test.js index 6bf3b0659cec5..9f4568b8baf8a 100644 --- a/javascript/node/selenium-webdriver/test/element_finding_test.js +++ b/javascript/node/selenium-webdriver/test/element_finding_test.js @@ -35,7 +35,7 @@ test.suite(function(env) { var driver; test.before(function*() { - driver = yield env.builder().buildAsync(); + driver = yield env.builder().build(); }); after(function() { diff --git a/javascript/node/selenium-webdriver/test/execute_script_test.js b/javascript/node/selenium-webdriver/test/execute_script_test.js index 73fabc5b4c5c2..97eaa1ed10cf1 100644 --- a/javascript/node/selenium-webdriver/test/execute_script_test.js +++ b/javascript/node/selenium-webdriver/test/execute_script_test.js @@ -30,7 +30,7 @@ test.suite(function(env) { var driver; test.before(function*() { - driver = yield env.builder().buildAsync(); + driver = yield env.builder().build(); }); test.after(function() { diff --git a/javascript/node/selenium-webdriver/test/firefox/firefox_test.js b/javascript/node/selenium-webdriver/test/firefox/firefox_test.js index ebd9e27f0b251..485964f91c9af 100644 --- a/javascript/node/selenium-webdriver/test/firefox/firefox_test.js +++ b/javascript/node/selenium-webdriver/test/firefox/firefox_test.js @@ -157,8 +157,8 @@ test.suite(function(env) { it('can start multiple sessions with single binary instance', function*() { var options = new firefox.Options().setBinary(new firefox.Binary); env.builder().setFirefoxOptions(options); - driver1 = yield env.builder().buildAsync(); - driver2 = yield env.builder().buildAsync(); + driver1 = yield env.builder().build(); + driver2 = yield env.builder().build(); // Ok if this doesn't fail. }); @@ -177,7 +177,7 @@ test.suite(function(env) { var driver; test.beforeEach(function*() { - driver = yield env.builder().buildAsync(); + driver = yield env.builder().build(); }); test.afterEach(function() { diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js index ca78a1b0fe828..705e763c54a84 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js @@ -516,24 +516,6 @@ describe('WebDriver', function() { .then(assert.fail, assertIsStubError); }); - it('testCannotScheduleCommandsIfTheSessionIdHasBeenDeleted', function() { - var driver = new FakeExecutor().createDriver(); - delete driver.session_; - assert.throws(() => driver.get('http://www.google.com')); - }); - - it('testDeletesSessionIdAfterQuitting', function() { - var driver; - let executor = new FakeExecutor(). - expect(CName.QUIT). - end(); - - driver = executor.createDriver(); - return driver.quit().then(function() { - assert.equal(void 0, driver.session_); - }); - }); - it('testReportsErrorWhenExecutingCommandsAfterExecutingAQuit', function() { let executor = new FakeExecutor(). expect(CName.QUIT). diff --git a/javascript/node/selenium-webdriver/test/logging_test.js b/javascript/node/selenium-webdriver/test/logging_test.js index b7ec0610cb5d0..546879715f14d 100644 --- a/javascript/node/selenium-webdriver/test/logging_test.js +++ b/javascript/node/selenium-webdriver/test/logging_test.js @@ -52,7 +52,7 @@ test.suite(function(env) { driver = yield env.builder() .setLoggingPrefs(prefs) - .buildAsync(); + .build(); yield driver.get(dataUrl( '