diff options
Diffstat (limited to 'node_modules/selenium-webdriver')
34 files changed, 710 insertions, 305 deletions
diff --git a/node_modules/selenium-webdriver/CHANGES.md b/node_modules/selenium-webdriver/CHANGES.md index b9ac5fd22..1cdc96814 100644 --- a/node_modules/selenium-webdriver/CHANGES.md +++ b/node_modules/selenium-webdriver/CHANGES.md @@ -1,3 +1,42 @@ +## v3.3.0 + +* Added warning log messages when the user creates new managed promises, or + schedules unchained tasks. Users may opt in to printing these log messages + with + + ```js + const {logging} = require('selenium-webdriver'); + logging.installConsoleHandler(); + logging.getLogger('promise.ControlFlow').setLevel(logging.Level.WARNING); + ``` +* If the `JAVA_HOME` environment variable is set, use it to locate java.exe. + + +## v3.2.0 + +* Release skipped to stay in sync with the main Selenium project. + + +## v3.1.0 + +* The `lib` package is once again platform agnostic (excluding `lib/devmode`). +* Deprecated `promise.when(value, callback, errback)`. + Use `promise.fulfilled(value).then(callback, errback)` +* Changed `promise.fulfilled(value)`, `promise.rejected(reason)` and + `promise.defer()` to all use native promises when the promise manager is + disabled. +* Properly handle W3C error responses to new session commands. +* Updated `selenium-webdriver/testing` to export `describe.only` along with + `describe.skip`. +* Fixed `selenium-webdriver/lib/until.ableToSwitchToFrame`. It was previously + dropping arguments and would never work. +* Added the ability to use Firefox Nightly +* If Firefox cannot be found in the default location, look for it on the PATH +* Allow SafariDriver to use Safari Technology Preview. +* Use the proper wire command for WebElement.getLocation() and + WebElement.getSize() for W3C compliant drivers. + + ## v3.0.1 * More API adjustments to align with native Promises diff --git a/node_modules/selenium-webdriver/README.md b/node_modules/selenium-webdriver/README.md index bc281d504..31708b186 100644 --- a/node_modules/selenium-webdriver/README.md +++ b/node_modules/selenium-webdriver/README.md @@ -136,16 +136,16 @@ will also have "best effort" support. Releases older than the latest LTS, _semver-major_ releases, and all unstable release branches (e.g. "v.Next") are considered strictly unsupported. -For example, suppose the current LTS and stable releases are v4.2.4 and v5.4.1, +For example, suppose the current LTS and stable releases are v6.9.5 and v7.5.0, respectively. Then a Selenium release would have the following support levels: | Version | Support | | ------- | ------------- | -| <= 4.1 | _unsupported_ | -| 4.2 | supported | -| 5.0-3 | best effort | -| 5.4 | supported | -| >= 5.5 | best effort | +| <= 6.8 | _unsupported_ | +| 6.9 | supported | +| 7.0-4 | best effort | +| 7.5 | supported | +| >= 7.5 | best effort | | v.Next | _unsupported_ | ### Support Level Definitions @@ -168,11 +168,11 @@ months, the support window for selenium-webdriver will be roughly: | Date | LTS | Stable | | --------- | ---: | -----: | -| (current) | 4.2 | 5.0 | -| 2016-04 | 4.2 | 6.0 | -| 2016-10 | 6.0 | 7.0 | +| (current) | 6.9 | 7.5 | | 2017-04 | 6.0 | 8.0 | | 2017-10 | 8.0 | 9.0 | +| 2018-04 | 8.0 | 10.0 | +| 2018-10 | 10.0 | 11.0 | ## Issues diff --git a/node_modules/selenium-webdriver/chrome.js b/node_modules/selenium-webdriver/chrome.js index eb33df9ed..2dbc93351 100644 --- a/node_modules/selenium-webdriver/chrome.js +++ b/node_modules/selenium-webdriver/chrome.js @@ -59,7 +59,7 @@ * let options = new chrome.Options(); * // configure browser options ... * - * let driver = new chrome.Driver(options, service); + * let driver = chrome.Driver.createSession(options, service); * * Users should only instantiate the {@link Driver} class directly when they * need a custom driver service configuration (as shown above). For normal @@ -595,7 +595,7 @@ class Options { * let options = new chrome.Options().setMobileEmulation( * {deviceName: 'Google Nexus 5'}); * - * let driver = new chrome.Driver(options); + * let driver = chrome.Driver.createSession(options); * * __Example 2: Using Custom Screen Configuration__ * @@ -605,7 +605,7 @@ class Options { * pixelRatio: 3.0 * }); * - * let driver = new chrome.Driver(options); + * let driver = chrome.Driver.createSession(options); * * * [em]: https://sites.google.com/a/chromium.org/chromedriver/mobile-emulation diff --git a/node_modules/selenium-webdriver/edge.js b/node_modules/selenium-webdriver/edge.js index ee9d43383..9280cabd9 100644 --- a/node_modules/selenium-webdriver/edge.js +++ b/node_modules/selenium-webdriver/edge.js @@ -58,7 +58,7 @@ * var options = new edge.Options(); * // configure browser options ... * - * var driver = new edge.Driver(options, service); + * var driver = edge.Driver.createSession(options, service); * * Users should only instantiate the {@link Driver} class directly when they * need a custom driver service configuration (as shown above). For normal diff --git a/node_modules/selenium-webdriver/firefox/binary.js b/node_modules/selenium-webdriver/firefox/binary.js index b997b480d..a1360bab7 100644 --- a/node_modules/selenium-webdriver/firefox/binary.js +++ b/node_modules/selenium-webdriver/firefox/binary.js @@ -47,27 +47,17 @@ const NO_FOCUS_LIB_AMD64 = isDevMode ? const X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so'; -let foundBinary = null; -let foundDevBinary = null; - - /** - * Checks the default Windows Firefox locations in Program Files. - * - * @param {boolean=} opt_dev Whether to find the Developer Edition. + * @param {string} file Path to the file to find, relative to the program files + * root. * @return {!Promise<?string>} A promise for the located executable. * The promise will resolve to {@code null} if Firefox was not found. */ -function defaultWindowsLocation(opt_dev) { - var files = [ +function findInProgramFiles(file) { + let files = [ process.env['PROGRAMFILES'] || 'C:\\Program Files', process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)' - ].map(function(prefix) { - if (opt_dev) { - return path.join(prefix, 'Firefox Developer Edition\\firefox.exe'); - } - return path.join(prefix, 'Mozilla Firefox\\firefox.exe'); - }); + ].map(prefix => path.join(prefix, file)); return io.exists(files[0]).then(function(exists) { return exists ? files[0] : io.exists(files[1]).then(function(exists) { return exists ? files[1] : null; @@ -77,49 +67,104 @@ function defaultWindowsLocation(opt_dev) { /** - * Locates the Firefox binary for the current system. + * Provides methods for locating the executable for a Firefox release channel + * on Windows and MacOS. For other systems (i.e. Linux), Firefox will always + * be located on the system PATH. * - * @param {boolean=} opt_dev Whether to find the Developer Edition. This only - * used on Windows and OSX. - * @return {!Promise<string>} A promise for the located binary. The promise will - * be rejected if Firefox cannot be located. + * @final */ -function findFirefox(opt_dev) { - if (opt_dev && foundDevBinary) { - return foundDevBinary; +class Channel { + /** + * @param {string} darwin The path to check when running on MacOS. + * @param {string} win32 The path to check when running on Windows. + */ + constructor(darwin, win32) { + /** @private @const */ this.darwin_ = darwin; + /** @private @const */ this.win32_ = win32; + /** @private {Promise<string>} */ + this.found_ = null; } - if (!opt_dev && foundBinary) { - return foundBinary; + /** + * Attempts to locate the Firefox executable for this release channel. This + * will first check the default installation location for the channel before + * checking the user's PATH. The returned promise will be rejected if Firefox + * can not be found. + * + * @return {!Promise<string>} A promise for the location of the located + * Firefox executable. + */ + locate() { + if (this.found_) { + return this.found_; + } + + let found; + switch (process.platform) { + case 'darwin': + found = io.exists(this.darwin_) + .then(exists => exists ? this.darwin_ : io.findInPath('firefox')); + break; + + case 'win32': + found = findInProgramFiles(this.win32_) + .then(found => found || io.findInPath('firefox.exe')); + break; + + default: + found = Promise.resolve(io.findInPath('firefox')); + break; + } + + this.found_ = found.then(found => { + if (found) { + // TODO: verify version info. + return found; + } + throw Error('Could not locate Firefox on the current system'); + }); + return this.found_; } +} - let found; - if (process.platform === 'darwin') { - let exe = opt_dev - ? '/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin' - : '/Applications/Firefox.app/Contents/MacOS/firefox-bin'; - found = io.exists(exe).then(exists => exists ? exe : null); - } else if (process.platform === 'win32') { - found = defaultWindowsLocation(opt_dev); +/** + * Firefox's developer channel. + * @const + * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#aurora> + */ +Channel.AURORA = new Channel( + '/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin', + 'Firefox Developer Edition\\firefox.exe'); - } else { - found = Promise.resolve(io.findInPath('firefox')); - } +/** + * Firefox's beta channel. Note this is provided mainly for convenience as + * the beta channel has the same installation location as the main release + * channel. + * @const + * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#beta> + */ +Channel.BETA = new Channel( + '/Applications/Firefox.app/Contents/MacOS/firefox-bin', + 'Mozilla Firefox\\firefox.exe'); - found = found.then(found => { - if (found) { - return found; - } - throw Error('Could not locate Firefox on the current system'); - }); +/** + * Firefox's release channel. + * @const + * @see <https://www.mozilla.org/en-US/firefox/desktop/> + */ +Channel.RELEASE = new Channel( + '/Applications/Firefox.app/Contents/MacOS/firefox-bin', + 'Mozilla Firefox\\firefox.exe'); - if (opt_dev) { - return foundDevBinary = found; - } else { - return foundBinary = found; - } -} +/** + * Firefox's nightly release channel. + * @const + * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly> + */ +Channel.NIGHTLY = new Channel( + '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-bin', + 'Nightly\\firefox.exe'); /** @@ -151,20 +196,23 @@ function installNoFocusLibs(profileDir) { * use with WebDriver. * * If created _without_ a path for the Firefox binary to use, this class will - * attempt to find Firefox when {@link #launch()} is called. For OSX and + * attempt to find Firefox when {@link #launch()} is called. For MacOS and * Windows, this class will look for Firefox in the current platform's default - * installation location (e.g. /Applications/Firefox.app on OSX). For all other - * platforms, the Firefox executable must be available on your system `PATH`. + * installation location (e.g. /Applications/Firefox.app on MacOS). For all + * other platforms, the Firefox executable must be available on your system + * `PATH`. * * @final */ class Binary { /** - * @param {string=} opt_exe Path to the Firefox binary to use. + * @param {?(string|Channel)=} opt_exeOrChannel Either the path to a specific + * Firefox binary to use, or a {@link Channel} instance that describes + * how to locate the desired Firefox version. */ - constructor(opt_exe) { - /** @private {(string|undefined)} */ - this.exe_ = opt_exe; + constructor(opt_exeOrChannel) { + /** @private {?(string|Channel)} */ + this.exe_ = opt_exeOrChannel || null; /** @private {!Array.<string>} */ this.args_ = []; @@ -187,7 +235,7 @@ class Binary { * on the current system. */ getExe() { - return this.exe_; + return typeof this.exe_ === 'string' ? this.exe_ : undefined; } /** @@ -223,6 +271,8 @@ class Binary { * * @param {boolean=} opt_use Whether to use the developer edition. Defaults to * true. + * @deprecated Use the {@link Channel} class to indicate the desired Firefox + * version when creating a new binary: `new Binary(Channel.AURORA)`. */ useDevEdition(opt_use) { this.devEdition_ = opt_use === undefined || !!opt_use; @@ -238,7 +288,13 @@ class Binary { * used by this instance. */ locate() { - return Promise.resolve(this.exe_ || findFirefox(this.devEdition_)); + if (typeof this.exe_ === 'string') { + return Promise.resolve(this.exe_); + } else if (this.exe_ instanceof Channel) { + return this.exe_.locate(); + } + let channel = this.devEdition_ ? Channel.AURORA : Channel.RELEASE; + return channel.locate(); } /** @@ -284,4 +340,5 @@ class Binary { exports.Binary = Binary; +exports.Channel = Channel; diff --git a/node_modules/selenium-webdriver/firefox/extension.js b/node_modules/selenium-webdriver/firefox/extension.js index 60abb06b8..3bda759a7 100644 --- a/node_modules/selenium-webdriver/firefox/extension.js +++ b/node_modules/selenium-webdriver/firefox/extension.js @@ -161,7 +161,7 @@ function readManifest(addonPath) { manifest = io.stat(addonPath).then(function(stats) { if (!stats.isDirectory()) { throw Error( - 'Add-on path is niether a xpi nor a directory: ' + addonPath); + 'Add-on path is neither a xpi nor a directory: ' + addonPath); } return io.read(path.join(addonPath, 'install.rdf')); }); diff --git a/node_modules/selenium-webdriver/firefox/index.js b/node_modules/selenium-webdriver/firefox/index.js index 4ea1702a9..d5c88274a 100644 --- a/node_modules/selenium-webdriver/firefox/index.js +++ b/node_modules/selenium-webdriver/firefox/index.js @@ -26,28 +26,31 @@ * * __Customizing the Firefox Profile__ * - * The {@link Profile} class may be used to configure the browser profile used - * with WebDriver, with functions to install additional + * The {@linkplain Profile} class may be used to configure the browser profile + * used with WebDriver, with functions to install additional * {@linkplain Profile#addExtension extensions}, configure browser * {@linkplain Profile#setPreference preferences}, and more. For example, you * may wish to include Firebug: * - * var firefox = require('selenium-webdriver/firefox'); + * const {Builder} = require('selenium-webdriver'); + * const firefox = require('selenium-webdriver/firefox'); * - * var profile = new firefox.Profile(); + * let profile = new firefox.Profile(); * profile.addExtension('/path/to/firebug.xpi'); * profile.setPreference('extensions.firebug.showChromeErrors', true); * - * var options = new firefox.Options().setProfile(profile); - * var driver = new firefox.Driver(options); + * let options = new firefox.Options().setProfile(profile); + * let driver = new Builder() + * .forBrowser('firefox') + * .setFirefoxOptions(options) + * .build(); * - * The {@link Profile} class may also be used to configure WebDriver based on a - * pre-existing browser profile: + * The {@linkplain Profile} class may also be used to configure WebDriver based + * on a pre-existing browser profile: * - * var profile = new firefox.Profile( + * let profile = new firefox.Profile( * '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing'); - * var options = new firefox.Options().setProfile(profile); - * var driver = new firefox.Driver(options); + * let options = new firefox.Options().setProfile(profile); * * The FirefoxDriver will _never_ modify a pre-existing profile; instead it will * create a copy for it to modify. By extension, there are certain browser @@ -56,21 +59,35 @@ * * __Using a Custom Firefox Binary__ * - * On Windows and OSX, the FirefoxDriver will search for Firefox in its + * On Windows and MacOS, the FirefoxDriver will search for Firefox in its * default installation location: * - * * Windows: C:\Program Files and C:\Program Files (x86). - * * Mac OS X: /Applications/Firefox.app + * - Windows: C:\Program Files and C:\Program Files (x86). + * - MacOS: /Applications/Firefox.app + * + * For Linux, Firefox will always be located on the PATH: `$(where firefox)`. * - * For Linux, Firefox will be located on the PATH: `$(where firefox)`. + * Several methods are provided for starting Firefox with a custom executable. + * First, on Windows and MacOS, you may configure WebDriver to check the default + * install location for a non-release channel. If the requested channel cannot + * be found in its default location, WebDriver will fallback to searching your + * PATH. _Note:_ on Linux, Firefox is _always_ located on your path, regardless + * of the requested channel. + * + * const {Builder} = require('selenium-webdriver'); + * const firefox = require('selenium-webdriver/firefox'); + * + * let options = new firefox.Options().setBinary(firefox.Channel.NIGHTLY); + * let driver = new Builder() + * .forBrowser('firefox') + * .setFirefoxOptions(options) + * .build(); * - * You can configure WebDriver to start use a custom Firefox installation with - * the {@link Binary} class: + * On all platforms, you may configrue WebDriver to use a Firefox specific + * executable: * - * var firefox = require('selenium-webdriver/firefox'); - * var binary = new firefox.Binary('/my/firefox/install/dir/firefox-bin'); - * var options = new firefox.Options().setBinary(binary); - * var driver = new firefox.Driver(options); + * let options = new firefox.Options() + * .setBinary('/my/firefox/install/dir/firefox-bin'); * * __Remote Testing__ * @@ -84,11 +101,14 @@ * binaries are never copied to remote machines and must be referenced by * installation path. * - * var options = new firefox.Options() + * const {Builder} = require('selenium-webdriver'); + * const firefox = require('selenium-webdriver/firefox'); + * + * let options = new firefox.Options() * .setProfile('/profile/path/on/remote/host') * .setBinary('/install/dir/on/remote/host/firefox-bin'); * - * var driver = new (require('selenium-webdriver')).Builder() + * let driver = new Builder() * .forBrowser('firefox') * .usingServer('http://127.0.0.1:4444/wd/hub') * .setFirefoxOptions(options) @@ -99,8 +119,7 @@ * To test versions of Firefox prior to Firefox 47, you must disable the use of * the geckodriver using the {@link Options} class. * - * var options = new firefox.Options().useGeckoDriver(false); - * var driver = new firefox.Driver(options); + * let options = new firefox.Options().useGeckoDriver(false); * * Alternatively, you may disable the geckodriver at runtime by setting the * environment variable `SELENIUM_MARIONETTE=false`. @@ -113,7 +132,7 @@ const url = require('url'); -const Binary = require('./binary').Binary, +const {Binary, Channel} = require('./binary'), Profile = require('./profile').Profile, decodeProfile = require('./profile').decode, http = require('../http'), @@ -196,16 +215,24 @@ class Options { } /** - * Sets the binary to use. The binary may be specified as the path to a Firefox - * executable, or as a {@link Binary} object. + * Sets the binary to use. The binary may be specified as the path to a + * Firefox executable, a specific {@link Channel}, or as a {@link Binary} + * object. * - * @param {(string|!Binary)} binary The binary to use. + * @param {(string|!Binary|!Channel)} binary The binary to use. * @return {!Options} A self reference. + * @throws {TypeError} If `binary` is an invalid type. */ setBinary(binary) { - if (typeof binary === 'string') { + if (typeof binary === 'string' || binary instanceof Channel) { binary = new Binary(binary); } + + if (!(binary instanceof Binary)) { + throw TypeError( + 'binary must be a string path, Channel, or Binary object'); + } + this.binary_ = binary; return this; } @@ -687,6 +714,7 @@ class Driver extends webdriver.WebDriver { exports.Binary = Binary; +exports.Channel = Channel; exports.Context = Context; exports.Driver = Driver; exports.Options = Options; diff --git a/node_modules/selenium-webdriver/index.js b/node_modules/selenium-webdriver/index.js index 3cde7f396..a1e208b46 100644 --- a/node_modules/selenium-webdriver/index.js +++ b/node_modules/selenium-webdriver/index.js @@ -58,7 +58,7 @@ var seleniumServer; * Starts an instance of the Selenium server if not yet running. * @param {string} jar Path to the server jar to use. * @return {!Promise<string>} A promise for the server's - * addrss once started. + * address once started. */ function startSeleniumServer(jar) { if (!seleniumServer) { @@ -160,7 +160,7 @@ function createDriver(ctor, ...args) { /** @override */ this.catch = pd.then.bind(pd); } - } + }; promise.CancellableThenable.addImplementation(thenableWebDriverProxy); THENABLE_DRIVERS.set(ctor, thenableWebDriverProxy); } diff --git a/node_modules/selenium-webdriver/lib/README b/node_modules/selenium-webdriver/lib/README index 583293864..c39abbece 100644 --- a/node_modules/selenium-webdriver/lib/README +++ b/node_modules/selenium-webdriver/lib/README @@ -1,6 +1,5 @@ This directory contains modules internal to selenium-webdriver that are not intended for general consumption. They may change at any time. -With the exception of the test/ directory, all files under this directory -may only depend on built-in JavaScript features and other modules in the -directory. +All files in this directory and the atoms/ subdirectory may only depend on +built-in JavaScript features and other modules in this directory. diff --git a/node_modules/selenium-webdriver/lib/actions.js b/node_modules/selenium-webdriver/lib/actions.js index 1b059bbbf..0e34f3783 100644 --- a/node_modules/selenium-webdriver/lib/actions.js +++ b/node_modules/selenium-webdriver/lib/actions.js @@ -128,7 +128,7 @@ class ActionSequence { } /** - * Moves the mouse. The location to move to may be specified in terms of the + * Moves the mouse. The location to move to may be specified in terms of the * mouse's current location, an offset relative to the top-left corner of an * element, or an element (in which case the middle of the element is used). * @@ -333,7 +333,7 @@ class ActionSequence { /** * Performs a modifier key press. The modifier key is <em>not released</em> * until {@link #keyUp} or {@link #sendKeys} is called. The key press will be - * targetted at the currently focused element. + * targeted at the currently focused element. * * @param {!input.Key} key The modifier key to push. Must be one of * {ALT, CONTROL, SHIFT, COMMAND, META}. @@ -347,7 +347,7 @@ class ActionSequence { } /** - * Performs a modifier key release. The release is targetted at the currently + * Performs a modifier key release. The release is targeted at the currently * focused element. * @param {!input.Key} key The modifier key to release. Must be one of * {ALT, CONTROL, SHIFT, COMMAND, META}. @@ -363,7 +363,7 @@ class ActionSequence { /** * Simulates typing multiple keys. Each modifier key encountered in the * sequence will not be released until it is encountered again. All key events - * will be targetted at the currently focused element. + * will be targeted at the currently focused element. * * @param {...(string|!input.Key|!Array<(string|!input.Key)>)} var_args * The keys to type. diff --git a/node_modules/selenium-webdriver/lib/by.js b/node_modules/selenium-webdriver/lib/by.js index ac448e683..8c718be64 100644 --- a/node_modules/selenium-webdriver/lib/by.js +++ b/node_modules/selenium-webdriver/lib/by.js @@ -153,7 +153,7 @@ class By { } /** - * Locates eleemnts by the ID attribute. This locator uses the CSS selector + * Locates elements by the ID attribute. This locator uses the CSS selector * `*[id="$ID"]`, _not_ `document.getElementById`. * * @param {string} id The ID to search for. diff --git a/node_modules/selenium-webdriver/lib/capabilities.js b/node_modules/selenium-webdriver/lib/capabilities.js index 61396d54d..65b290f5f 100644 --- a/node_modules/selenium-webdriver/lib/capabilities.js +++ b/node_modules/selenium-webdriver/lib/capabilities.js @@ -103,7 +103,7 @@ const Capability = { */ PROXY: 'proxy', - /** Whether the driver supports changing the brower's orientation. */ + /** Whether the driver supports changing the browser's orientation. */ ROTATABLE: 'rotatable', /** @@ -130,7 +130,7 @@ const Capability = { /** * Defines how the driver should handle unexpected alerts. The value should - * be one of "accept", "dismiss", or "ignore. + * be one of "accept", "dismiss", or "ignore". */ UNEXPECTED_ALERT_BEHAVIOR: 'unexpectedAlertBehavior', @@ -367,7 +367,7 @@ class Capabilities extends Map { /** * Sets the logging preferences. Preferences may be specified as a - * {@link ./logging.Preferences} instance, or a as a map of log-type to + * {@link ./logging.Preferences} instance, or as a map of log-type to * log-level. * @param {!(./logging.Preferences|Object<string>)} prefs The logging * preferences. @@ -408,7 +408,7 @@ class Capabilities extends Map { /** * Sets the default action to take with an unexpected alert before returning * an error. - * @param {string} behavior The desired behavior; should be "accept", + * @param {string} behavior The desired behavior should be "accept", * "dismiss", or "ignore". Defaults to "dismiss". * @return {!Capabilities} A self reference. */ diff --git a/node_modules/selenium-webdriver/lib/error.js b/node_modules/selenium-webdriver/lib/error.js index 555e5cbc5..82f81b07c 100644 --- a/node_modules/selenium-webdriver/lib/error.js +++ b/node_modules/selenium-webdriver/lib/error.js @@ -311,7 +311,7 @@ class UnknownMethodError extends WebDriverError { /** - * Reports an unsupport operation. + * Reports an unsupported operation. */ class UnsupportedOperationError extends WebDriverError { /** @param {string=} opt_error the error message, if any. */ @@ -462,17 +462,29 @@ function checkResponse(data) { return data; } +/** + * Tests if the given value is a valid error response object according to the + * W3C WebDriver spec. + * + * @param {?} data The value to test. + * @return {boolean} Whether the given value data object is a valid error + * response. + * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol + */ +function isErrorResponse(data) { + return data && typeof data === 'object' && typeof data.error === 'string'; +} /** * Throws an error coded from the W3C protocol. A generic error will be thrown - * if the privded `data` is not a valid encoded error. + * if the provided `data` is not a valid encoded error. * * @param {{error: string, message: string}} data The error data to decode. * @throws {WebDriverError} the decoded error. * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol */ function throwDecodedError(data) { - if (data && typeof data === 'object' && typeof data.error === 'string') { + if (isErrorResponse(data)) { let ctor = ERROR_CODE_TO_TYPE.get(data.error) || WebDriverError; throw new ctor(data.message); } @@ -551,5 +563,6 @@ module.exports = { checkResponse: checkResponse, checkLegacyResponse: checkLegacyResponse, encodeError: encodeError, + isErrorResponse: isErrorResponse, throwDecodedError: throwDecodedError, }; diff --git a/node_modules/selenium-webdriver/lib/events.js b/node_modules/selenium-webdriver/lib/events.js index 65e63de8c..82b34803e 100644 --- a/node_modules/selenium-webdriver/lib/events.js +++ b/node_modules/selenium-webdriver/lib/events.js @@ -22,7 +22,7 @@ */ class Listener { /** - * @param {!Function} fn The acutal listener function. + * @param {!Function} fn The actual listener function. * @param {(Object|undefined)} scope The object in whose scope to invoke the * listener. * @param {boolean} oneshot Whether this listener should only be used once. diff --git a/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so b/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so Binary files differdeleted file mode 100644 index 248c32db5..000000000 --- a/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so +++ /dev/null diff --git a/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so b/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so Binary files differdeleted file mode 100644 index 004062c7b..000000000 --- a/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so +++ /dev/null diff --git a/node_modules/selenium-webdriver/lib/firefox/webdriver.json b/node_modules/selenium-webdriver/lib/firefox/webdriver.json index 38601a28c..0dbe56bbd 100644 --- a/node_modules/selenium-webdriver/lib/firefox/webdriver.json +++ b/node_modules/selenium-webdriver/lib/firefox/webdriver.json @@ -37,7 +37,6 @@ "network.http.phishy-userpass-length": 255, "offline-apps.allow_by_default": true, "prompts.tab_modal.enabled": false, - "security.csp.enable": false, "security.fileuri.origin_policy": 3, "security.fileuri.strict_origin_policy": false, "signon.rememberSignons": false, @@ -61,6 +60,8 @@ "dom.max_script_run_time": 30, "dom.report_all_js_exceptions": true, "javascript.options.showInConsole": true, + "network.captive-portal-service.enabled": false, + "security.csp.enable": false, "startup.homepage_welcome_url": "about:blank", "startup.homepage_welcome_url.additional": "about:blank", "webdriver_accept_untrusted_certs": true, diff --git a/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi b/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi Binary files differindex f9a51cf4f..a7b0fa3a7 100644 --- a/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi +++ b/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi diff --git a/node_modules/selenium-webdriver/lib/http.js b/node_modules/selenium-webdriver/lib/http.js index 68bc43213..136a48e63 100644 --- a/node_modules/selenium-webdriver/lib/http.js +++ b/node_modules/selenium-webdriver/lib/http.js @@ -25,17 +25,26 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); - const cmd = require('./command'); -const devmode = require('./devmode'); const error = require('./error'); const logging = require('./logging'); const promise = require('./promise'); const Session = require('./session').Session; const WebElement = require('./webdriver').WebElement; +const {getAttribute, isDisplayed} = (function() { + try { + return { + getAttribute: require('./atoms/getAttribute.js'), + isDisplayed: require('./atoms/is-displayed.js') + }; + } catch (ex) { + throw Error( + 'Failed to import atoms modules. If running in devmode, you need to run' + + ' `./go node:atoms` from the project root: ' + ex); + } +})(); + /** * Converts a headers map to a HTTP header block string. @@ -116,43 +125,15 @@ class Response { const DEV_ROOT = '../../../../buck-out/gen/javascript/'; -/** @enum {string} */ +/** @enum {!Function} */ const Atom = { - GET_ATTRIBUTE: devmode - ? path.join(__dirname, DEV_ROOT, 'webdriver/atoms/getAttribute.js') - : path.join(__dirname, 'atoms/getAttribute.js'), - IS_DISPLAYED: devmode - ? path.join(__dirname, DEV_ROOT, 'atoms/fragments/is-displayed.js') - : path.join(__dirname, 'atoms/isDisplayed.js'), + GET_ATTRIBUTE: getAttribute, + IS_DISPLAYED: isDisplayed }; -const ATOMS = /** !Map<string, !Promise<string>> */new Map(); const LOG = logging.getLogger('webdriver.http'); -/** - * @param {Atom} file The atom file to load. - * @return {!Promise<string>} A promise that will resolve to the contents of the - * file. - */ -function loadAtom(file) { - if (ATOMS.has(file)) { - return ATOMS.get(file); - } - let contents = /** !Promise<string> */new Promise((resolve, reject) => { - LOG.finest(() => `Loading atom ${file}`); - fs.readFile(file, 'utf8', function(err, data) { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); - ATOMS.set(file, contents); - return contents; -} - function post(path) { return resource('POST', path); } function del(path) { return resource('DELETE', path); } @@ -168,17 +149,26 @@ var CommandSpec; var CommandTransformer; +class InternalTypeError extends TypeError {} + + /** * @param {!cmd.Command} command The initial command. * @param {Atom} atom The name of the atom to execute. * @return {!Promise<!cmd.Command>} The transformed command to execute. */ function toExecuteAtomCommand(command, atom, ...params) { - return loadAtom(atom).then(atom => { - return new cmd.Command(cmd.Name.EXECUTE_SCRIPT) + return new Promise((resolve, reject) => { + if (typeof atom !== 'function') { + reject(new InternalTypeError('atom is not a function: ' + typeof atom)); + return; + } + + let newCmd = new cmd.Command(cmd.Name.EXECUTE_SCRIPT) .setParameter('sessionId', command.getParameter('sessionId')) .setParameter('script', `return (${atom}).apply(null, arguments)`) .setParameter('args', params.map(param => command.getParameter(param))); + resolve(newCmd); }); } @@ -269,6 +259,8 @@ const W3C_COMMAND_MAP = new Map([ [cmd.Name.GET_ELEMENT_ATTRIBUTE, (cmd) => { return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name'); }], + [cmd.Name.GET_ELEMENT_LOCATION, get('/session/:sessionId/element/:id/rect')], + [cmd.Name.GET_ELEMENT_SIZE, get('/session/:sessionId/element/:id/rect')], [cmd.Name.IS_ELEMENT_DISPLAYED, (cmd) => { return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id'); }], @@ -437,7 +429,8 @@ class Executor { this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`); let parsed = - parseHttpResponse(/** @type {!Response} */ (response), this.w3c); + parseHttpResponse( + command, /** @type {!Response} */ (response), this.w3c); if (command.getName() === cmd.Name.NEW_SESSION || command.getName() === cmd.Name.DESCRIBE_SESSION) { @@ -447,7 +440,7 @@ class Executor { } // The remote end is a W3C compliant server if there is no `status` - // field in the response. This is not appliable for the DESCRIBE_SESSION + // field in the response. This is not applicable for the DESCRIBE_SESSION // command, which is not defined in the W3C spec. if (command.getName() === cmd.Name.NEW_SESSION) { this.w3c = this.w3c || !('status' in parsed); @@ -485,29 +478,42 @@ function tryParse(str) { /** * Callback used to parse {@link Response} objects from a * {@link HttpClient}. + * + * @param {!cmd.Command} command The command the response is for. * @param {!Response} httpResponse The HTTP response to parse. * @param {boolean} w3c Whether the response should be processed using the * W3C wire protocol. * @return {?} The parsed response. * @throws {WebDriverError} If the HTTP response is an error. */ -function parseHttpResponse(httpResponse, w3c) { +function parseHttpResponse(command, httpResponse, w3c) { let parsed = tryParse(httpResponse.body); if (parsed !== undefined) { + if (httpResponse.status < 200) { + // This should never happen, but throw the raw response so + // users report it. + throw new error.WebDriverError( + `Unexpected HTTP response:\n${httpResponse}`); + } + if (w3c) { if (httpResponse.status > 399) { error.throwDecodedError(parsed); } + return parsed; + } - if (httpResponse.status < 200) { - // This should never happen, but throw the raw response so - // users report it. - throw new error.WebDriverError( - `Unexpected HTTP response:\n${httpResponse}`); - } - } else { - error.checkLegacyResponse(parsed); + // If this is a new session command, we need to check for a W3C compliant + // error object. This is necessary since a successful new session command + // is what puts the executor into W3C mode. + if (httpResponse.status > 399 + && (command.getName() == cmd.Name.NEW_SESSION + || command.getName() === cmd.Name.DESCRIBE_SESSION) + && error.isErrorResponse(parsed)) { + error.throwDecodedError(parsed); } + + error.checkLegacyResponse(parsed); return parsed; } diff --git a/node_modules/selenium-webdriver/lib/input.js b/node_modules/selenium-webdriver/lib/input.js index 058530ebc..4ea938d45 100644 --- a/node_modules/selenium-webdriver/lib/input.js +++ b/node_modules/selenium-webdriver/lib/input.js @@ -144,7 +144,7 @@ class FileDetector { /** * Handles the file specified by the given path, preparing it for use with * the current browser. If the path does not refer to a valid file, it will - * be returned unchanged, otherwisee a path suitable for use with the current + * be returned unchanged, otherwise a path suitable for use with the current * browser will be returned. * * This default implementation is a no-op. Subtypes may override this function diff --git a/node_modules/selenium-webdriver/lib/promise.js b/node_modules/selenium-webdriver/lib/promise.js index 32d0c98e6..b26cd23ff 100644 --- a/node_modules/selenium-webdriver/lib/promise.js +++ b/node_modules/selenium-webdriver/lib/promise.js @@ -48,7 +48,7 @@ * > e => console.error('FAILURE: ' + e)); * > ``` * > - * > The motiviation behind this change and full deprecation plan are documented + * > The motivation behind this change and full deprecation plan are documented * > in [issue 2969](https://github.com/SeleniumHQ/selenium/issues/2969). * > * > @@ -87,8 +87,7 @@ * The control flow is based on the concept of tasks and task queues. Tasks are * functions that define the basic unit of work for the control flow to execute. * Each task is scheduled via {@link ControlFlow#execute()}, which will return - * a {@link ManagedPromise ManagedPromise} that will be resolved with the task's - * result. + * a {@link ManagedPromise} that will be resolved with the task's result. * * A task queue contains all of the tasks scheduled within a single turn of the * [JavaScript event loop][JSEL]. The control flow will create a new task queue @@ -103,13 +102,13 @@ * * Whenever the control flow creates a new task queue, it will automatically * begin executing tasks in the next available turn of the event loop. This - * execution is scheduled using a "micro-task" timer, such as a (native) - * `ManagedPromise.then()` callback. + * execution is [scheduled as a microtask][MicrotasksArticle] like e.g. a + * (native) `Promise.then()` callback. * * setTimeout(() => console.log('a')); - * ManagedPromise.resolve().then(() => console.log('b')); // A native promise. + * Promise.resolve().then(() => console.log('b')); // A native promise. * flow.execute(() => console.log('c')); - * ManagedPromise.resolve().then(() => console.log('d')); + * Promise.resolve().then(() => console.log('d')); * setTimeout(() => console.log('fin')); * // b * // c @@ -118,13 +117,13 @@ * // fin * * In the example above, b/c/d is logged before a/fin because native promises - * and this module use "micro-task" timers, which have a higher priority than - * "macro-tasks" like `setTimeout`. + * and this module use "microtask" timers, which have a higher priority than + * "macrotasks" like `setTimeout`. * * ## Task Execution * - * Upon creating a task queue, and whenever an exisiting queue completes a task, - * the control flow will schedule a micro-task timer to process any scheduled + * Upon creating a task queue, and whenever an existing queue completes a task, + * the control flow will schedule a microtask timer to process any scheduled * tasks. This ensures no task is ever started within the same turn of the * JavaScript event loop in which it was scheduled, nor is a task ever started * within the same turn that another finishes. @@ -140,13 +139,13 @@ * discarded and the task's promised result (previously returned by * {@link ControlFlow#execute()}) is immediately rejected with the thrown * error. - * 3. The task function returns sucessfully. + * 3. The task function returns successfully. * * If a task function created a new task queue, the control flow will wait for * that queue to complete before processing the task result. If the queue * completes without error, the flow will settle the task's promise with the - * value originaly returned by the task function. On the other hand, if the task - * queue termintes with an error, the task's promise will be rejected with that + * value originally returned by the task function. On the other hand, if the task + * queue terminates with an error, the task's promise will be rejected with that * error. * * flow.execute(function() { @@ -161,7 +160,7 @@ * ## ManagedPromise Integration * * In addition to the {@link ControlFlow} class, the promise module also exports - * a [ManagedPromise/A+] {@linkplain ManagedPromise implementation} that is deeply + * a [Promises/A+] {@linkplain ManagedPromise implementation} that is deeply * integrated with the ControlFlow. First and foremost, each promise * {@linkplain ManagedPromise#then() callback} is scheduled with the * control flow as a task. As a result, each callback is invoked in its own turn @@ -328,7 +327,7 @@ * Even though a subtask's promised result will never resolve while the task * function is on the stack, it will be treated as a promise resolved within the * task. In all other scenarios, a task's promise behaves just like a normal - * promise. In the sample below, `C/D` is loggged before `B` because the + * promise. In the sample below, `C/D` is logged before `B` because the * resolution of `subtask1` interrupts the flow of the enclosing task. Within * the final subtask, `E/F` is logged in order because `subtask1` is a resolved * promise when that task runs. @@ -467,17 +466,17 @@ * * ES6 promises do not require users to handle a promise rejections. This can * result in subtle bugs as the rejections are silently "swallowed" by the - * ManagedPromise class. + * Promise class. * - * ManagedPromise.reject(Error('boom')); + * Promise.reject(Error('boom')); * // ... *crickets* ... * * Selenium's promise module, on the other hand, requires that every rejection * be explicitly handled. When a {@linkplain ManagedPromise ManagedPromise} is * rejected and no callbacks are defined on that promise, it is considered an - * _unhandled rejection_ and reproted to the active task queue. If the rejection + * _unhandled rejection_ and reported to the active task queue. If the rejection * remains unhandled after a single turn of the [event loop][JSEL] (scheduled - * with a micro-task), it will propagate up the stack. + * with a microtask), it will propagate up the stack. * * ## Error Propagation * @@ -534,7 +533,7 @@ * * When a subtask is discarded due to an unreported rejection in its parent * frame, the existing callbacks on that task will never settle and the - * callbacks will not be invoked. If a new callback is attached ot the subtask + * callbacks will not be invoked. If a new callback is attached to the subtask * _after_ it has been discarded, it is handled the same as adding a callback * to a cancelled promise: the error-callback path is invoked. This behavior is * intended to handle cases where the user saves a reference to a task promise, @@ -582,9 +581,9 @@ * * Bottom line: you __*must*__ handle rejected promises. * - * # ManagedPromise/A+ Compatibility + * # Promises/A+ Compatibility * - * This `promise` module is compliant with the [ManagedPromise/A+][] specification + * This `promise` module is compliant with the [Promises/A+] specification * except for sections `2.2.6.1` and `2.2.6.2`: * * > @@ -595,10 +594,10 @@ * > must execute in the order of their originating calls to `then`. * > * - * Specifically, the conformance tests contains the following scenario (for + * Specifically, the conformance tests contain the following scenario (for * brevity, only the fulfillment version is shown): * - * var p1 = ManagedPromise.resolve(); + * var p1 = Promise.resolve(); * p1.then(function() { * console.log('A'); * p1.then(() => console.log('B')); @@ -609,7 +608,7 @@ * // B * * Since the [ControlFlow](#scheduling_callbacks) executes promise callbacks as - * tasks, with this module, the result would be + * tasks, with this module, the result would be: * * var p2 = promise.fulfilled(); * p2.then(function() { @@ -623,7 +622,8 @@ * * [JSEL]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * [GF]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* - * [ManagedPromise/A+]: https://promisesaplus.com/ + * [Promises/A+]: https://promisesaplus.com/ + * [MicrotasksArticle]: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ */ 'use strict'; @@ -667,7 +667,7 @@ function getUid(obj) { /** - * Runs the given function after a micro-task yield. + * Runs the given function after a microtask yield. * @param {function()} fn The function to run. */ function asyncRun(fn) { @@ -942,7 +942,7 @@ class Thenable { /** * Marker interface for objects that allow consumers to request the cancellation - * of a promies-based operation. A cancelled promise will be rejected with a + * of a promise-based operation. A cancelled promise will be rejected with a * {@link CancellationError}. * * This interface is considered package-private and should not be used outside @@ -1003,6 +1003,9 @@ const PromiseState = { */ const ON_CANCEL_HANDLER = new WeakMap; +const SKIP_LOG = Symbol('skip-log'); +const FLOW_LOG = logging.getLogger('promise.ControlFlow'); + /** * Represents the eventual value of a completed operation. Each promise may be @@ -1025,14 +1028,29 @@ class ManagedPromise { * functions, one for fulfilling the promise and another for rejecting it. * @param {ControlFlow=} opt_flow The control flow * this instance was created under. Defaults to the currently active flow. + * @param {?=} opt_skipLog An internal parameter used to skip logging the + * creation of this promise. This parameter has no effect unless it is + * strictly equal to an internal symbol. In other words, this parameter + * is always ignored for external code. */ - constructor(resolver, opt_flow) { + constructor(resolver, opt_flow, opt_skipLog) { if (!usePromiseManager()) { throw TypeError( 'Unable to create a managed promise instance: the promise manager has' + ' been disabled by the SELENIUM_PROMISE_MANAGER environment' + ' variable: ' + process.env['SELENIUM_PROMISE_MANAGER']); + } else if (opt_skipLog !== SKIP_LOG) { + FLOW_LOG.warning(() => { + let e = + captureStackTrace( + 'ManagedPromiseError', + 'Creating a new managed Promise. This call will fail when the' + + ' promise manager is disabled', + ManagedPromise) + return e.stack; + }); } + getUid(this); /** @private {!ControlFlow} */ @@ -1308,7 +1326,7 @@ class ManagedPromise { * @param {!Function} fn The function to use as the top of the stack when * recording the callback's creation point. * @return {!ManagedPromise<R>} A new promise which will be resolved with the - * esult of the invoked callback. + * result of the invoked callback. * @template R * @private */ @@ -1384,6 +1402,37 @@ function isPending(promise) { /** + * Structural interface for a deferred promise resolver. + * @record + * @template T + */ +function Resolver() {} + + +/** + * The promised value for this resolver. + * @type {!Thenable<T>} + */ +Resolver.prototype.promise; + + +/** + * Resolves the promised value with the given `value`. + * @param {T|Thenable<T>} value + * @return {void} + */ +Resolver.prototype.resolve; + + +/** + * Rejects the promised value with the given `reason`. + * @param {*} reason + * @return {void} + */ +Resolver.prototype.reject; + + +/** * Represents a value that will be resolved at some point in the future. This * class represents the protected "producer" half of a ManagedPromise - each Deferred * has a {@code promise} property that may be returned to consumers for @@ -1395,20 +1444,25 @@ function isPending(promise) { * {@link ControlFlow} as an unhandled failure. * * @template T + * @implements {Resolver<T>} */ class Deferred { /** * @param {ControlFlow=} opt_flow The control flow this instance was * created under. This should only be provided during unit tests. + * @param {?=} opt_skipLog An internal parameter used to skip logging the + * creation of this promise. This parameter has no effect unless it is + * strictly equal to an internal symbol. In other words, this parameter + * is always ignored for external code. */ - constructor(opt_flow) { + constructor(opt_flow, opt_skipLog) { var fulfill, reject; /** @type {!ManagedPromise<T>} */ this.promise = new ManagedPromise(function(f, r) { fulfill = f; reject = r; - }, opt_flow); + }, opt_flow, opt_skipLog); var self = this; var checkNotSelf = function(value) { @@ -1421,16 +1475,24 @@ class Deferred { * Resolves this deferred with the given value. It is safe to call this as a * normal function (with no bound "this"). * @param {(T|IThenable<T>|Thenable)=} opt_value The fulfilled value. + * @const */ - this.fulfill = function(opt_value) { + this.resolve = function(opt_value) { checkNotSelf(opt_value); fulfill(opt_value); }; /** + * An alias for {@link #resolve}. + * @const + */ + this.fulfill = this.resolve; + + /** * Rejects this promise with the given reason. It is safe to call this as a * normal function (with no bound "this"). * @param {*=} opt_reason The rejection reason. + * @const */ this.reject = function(opt_reason) { checkNotSelf(opt_reason); @@ -1487,36 +1549,76 @@ function delayed(ms) { /** - * Creates a new deferred object. - * @return {!Deferred<T>} The new deferred object. + * Creates a new deferred resolver. + * + * If the promise manager is currently enabled, this function will return a + * {@link Deferred} instance. Otherwise, it will return a resolver for a + * {@linkplain NativePromise native promise}. + * + * @return {!Resolver<T>} A new deferred resolver. * @template T */ function defer() { - return new Deferred(); + if (usePromiseManager()) { + return new Deferred(); + } + let resolve, reject; + let promise = new NativePromise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + return {promise, resolve, reject}; } /** * Creates a promise that has been resolved with the given value. + * + * If the promise manager is currently enabled, this function will return a + * {@linkplain ManagedPromise managed promise}. Otherwise, it will return a + * {@linkplain NativePromise native promise}. + * * @param {T=} opt_value The resolved value. - * @return {!ManagedPromise<T>} The resolved promise. - * @deprecated Use {@link ManagedPromise#resolve Promise.resolve(value)}. + * @return {!Thenable<T>} The resolved promise. * @template T */ function fulfilled(opt_value) { - return ManagedPromise.resolve(opt_value); + let ctor = usePromiseManager() ? ManagedPromise : NativePromise; + if (opt_value instanceof ctor) { + return /** @type {!Thenable} */(opt_value); + } + + if (usePromiseManager()) { + // We can skip logging warnings about creating a managed promise because + // this function will automatically switch to use a native promise when + // the promise manager is disabled. + return new ManagedPromise( + resolve => resolve(opt_value), undefined, SKIP_LOG); + } + return NativePromise.resolve(opt_value); } /** * Creates a promise that has been rejected with the given reason. + * + * If the promise manager is currently enabled, this function will return a + * {@linkplain ManagedPromise managed promise}. Otherwise, it will return a + * {@linkplain NativePromise native promise}. + * * @param {*=} opt_reason The rejection reason; may be any value, but is * usually an Error or a string. - * @return {!ManagedPromise<?>} The rejected promise. - * @deprecated Use {@link ManagedPromise#reject Promise.reject(reason)}. + * @return {!Thenable<?>} The rejected promise. */ function rejected(opt_reason) { - return ManagedPromise.reject(opt_reason); + if (usePromiseManager()) { + // We can skip logging warnings about creating a managed promise because + // this function will automatically switch to use a native promise when + // the promise manager is disabled. + return new ManagedPromise( + (_, reject) => reject(opt_reason), undefined, SKIP_LOG); + } + return NativePromise.reject(opt_reason); } @@ -1610,21 +1712,17 @@ function thenFinally(promise, callback) { * @param {Function=} opt_errback The function to call when the value is * rejected. * @return {!Thenable} A new promise. + * @deprecated Use `promise.fulfilled(value).then(opt_callback, opt_errback)` */ function when(value, opt_callback, opt_errback) { - if (Thenable.isImplementation(value)) { - return value.then(opt_callback, opt_errback); - } - - return createPromise(resolve => resolve(value)) - .then(opt_callback, opt_errback); + return fulfilled(value).then(opt_callback, opt_errback); } /** * Invokes the appropriate callback function as soon as a promised `value` is - * resolved. This function is similar to `when()`, except it does not return - * a new promise. + * resolved. + * * @param {*} value The value to observe. * @param {Function} callback The function to call when the value is * resolved successfully. @@ -1826,7 +1924,7 @@ function filter(arr, fn, opt_self) { */ function fullyResolved(value) { if (isPromise(value)) { - return when(value, fullyResolveValue); + return fulfilled(value).then(fullyResolveValue); } return fullyResolveValue(value); } @@ -1973,7 +2071,7 @@ class Scheduler { /** * Schedules a task to wait for a condition to hold. * - * If the condition is defined as a function, it may return any value. Promies + * If the condition is defined as a function, it may return any value. Promise * will be resolved before testing if the condition holds (resolution time * counts towards the timeout). Once resolved, values are always evaluated as * booleans. @@ -1997,7 +2095,7 @@ class Scheduler { * @param {string=} opt_message An optional error message to include if the * wait times out; defaults to the empty string. * @return {!Thenable<T>} A promise that will be fulfilled - * when the condition has been satisified. The promise shall be rejected + * when the condition has been satisfied. The promise shall be rejected * if the wait times out waiting for the condition. * @throws {TypeError} If condition is not a function or promise or if timeout * is not a number >= 0. @@ -2018,6 +2116,10 @@ function usePromiseManager() { /** + * Creates a new promise with the given `resolver` function. If the promise + * manager is currently enabled, the returned promise will be a + * {@linkplain ManagedPromise} instance. Otherwise, it will be a native promise. + * * @param {function( * function((T|IThenable<T>|Thenable|null)=), * function(*=))} resolver @@ -2040,7 +2142,7 @@ function createPromise(resolver) { * @param {string=} opt_message An optional error message to include if the * wait times out; defaults to the empty string. * @return {!Thenable<T>} A promise that will be fulfilled - * when the condition has been satisified. The promise shall be rejected + * when the condition has been satisfied. The promise shall be rejected * if the wait times out waiting for the condition. * @throws {TypeError} If condition is not a function or promise or if timeout * is not a number >= 0. @@ -2164,7 +2266,7 @@ const SIMPLE_SCHEDULER = new SimpleScheduler; /** * Handles the execution of scheduled tasks, each of which may be an * asynchronous operation. The control flow will ensure tasks are executed in - * the ordered scheduled, starting each task only once those before it have + * the order scheduled, starting each task only once those before it have * completed. * * Each task scheduled within this flow may return a {@link ManagedPromise} to @@ -2172,21 +2274,21 @@ const SIMPLE_SCHEDULER = new SimpleScheduler; * promises to be resolved before marking the task as completed. * * Tasks and each callback registered on a {@link ManagedPromise} will be run - * in their own ControlFlow frame. Any tasks scheduled within a frame will take + * in their own ControlFlow frame. Any tasks scheduled within a frame will take * priority over previously scheduled tasks. Furthermore, if any of the tasks in * the frame fail, the remainder of the tasks in that frame will be discarded * and the failure will be propagated to the user through the callback/task's * promised result. * * Each time a ControlFlow empties its task queue, it will fire an - * {@link ControlFlow.EventType.IDLE IDLE} event. Conversely, - * whenever the flow terminates due to an unhandled error, it will remove all + * {@link ControlFlow.EventType.IDLE IDLE} event. Conversely, whenever + * the flow terminates due to an unhandled error, it will remove all * remaining tasks in its queue and fire an * {@link ControlFlow.EventType.UNCAUGHT_EXCEPTION UNCAUGHT_EXCEPTION} event. * If there are no listeners registered with the flow, the error will be * rethrown to the global error handler. * - * Refer to the {@link ./promise} module documentation for a detailed + * Refer to the {@link ./promise} module documentation for a detailed * explanation of how the ControlFlow coordinates task execution. * * @implements {Scheduler} @@ -2212,7 +2314,7 @@ class ControlFlow extends events.EventEmitter { this.taskQueues_ = null; /** - * Micro task that controls shutting down the control flow. Upon shut down, + * Microtask that controls shutting down the control flow. Upon shut down, * the flow will emit an * {@link ControlFlow.EventType.IDLE} event. Idle events * always follow a brief timeout in order to catch latent errors from the @@ -2221,8 +2323,8 @@ class ControlFlow extends events.EventEmitter { * by the promise system until the next turn of the event loop: * * // Schedule 1 task that fails. - * var result = promise.controlFlow().schedule('example', - * function() { return promise.rejected('failed'); }); + * var result = promise.controlFlow().execute( + * () => promise.rejected('failed'), 'example'); * // Set a callback on the result. This delays reporting the unhandled * // failure for 1 turn of the event loop. * result.then(function() {}); @@ -2246,7 +2348,7 @@ class ControlFlow extends events.EventEmitter { /** * Returns a string representation of this control flow, which is its current * {@linkplain #getSchedule() schedule}, sans task stack traces. - * @return {string} The string representation of this contorl flow. + * @return {string} The string representation of this control flow. * @override */ toString() { @@ -2258,8 +2360,7 @@ class ControlFlow extends events.EventEmitter { * control flow stack and cause rejections within parent tasks. If error * propagation is disabled, tasks will not be aborted when an unhandled * promise rejection is detected, but the rejection _will_ trigger an - * {@link ControlFlow.EventType.UNCAUGHT_EXCEPTION} - * event. + * {@link ControlFlow.EventType.UNCAUGHT_EXCEPTION} event. * * The default behavior is to propagate all unhandled rejections. _The use * of this option is highly discouraged._ @@ -2293,7 +2394,7 @@ class ControlFlow extends events.EventEmitter { * {@code opt_includeStackTraces === true}, the string will include the * stack trace from when each task was scheduled. * @param {string=} opt_includeStackTraces Whether to include the stack traces - * from when each task was scheduled. Defaults to false. + * from when each task was scheduled. Defaults to false. * @return {string} String representation of this flow's internal state. */ getSchedule(opt_includeStackTraces) { @@ -2345,7 +2446,7 @@ class ControlFlow extends events.EventEmitter { } /** - * Returns the currently actively task queue for this flow. If there is no + * Returns the currently active task queue for this flow. If there is no * active queue, one will be created. * @return {!TaskQueue} the currently active task queue for this flow. * @private @@ -2377,15 +2478,31 @@ class ControlFlow extends events.EventEmitter { } if (!this.hold_) { - var holdIntervalMs = 2147483647; // 2^31-1; max timer length for Node.js + let holdIntervalMs = 2147483647; // 2^31-1; max timer length for Node.js this.hold_ = setInterval(function() {}, holdIntervalMs); } - var task = new Task( + let task = new Task( this, fn, opt_description || '<anonymous>', - {name: 'Task', top: ControlFlow.prototype.execute}); + {name: 'Task', top: ControlFlow.prototype.execute}, + true); + + let q = this.getActiveQueue_(); + + for (let i = q.tasks_.length; i > 0; i--) { + let previousTask = q.tasks_[i - 1]; + if (previousTask.userTask_) { + FLOW_LOG.warning(() => { + return `Detected scheduling of an unchained task. +When the promise manager is disabled, unchained tasks will not wait for +previously scheduled tasks to finish before starting to execute. +New task: ${task.promise.stack_.stack} +Previous task: ${previousTask.promise.stack_.stack}`.split(/\n/).join('\n '); + }); + break; + } + } - var q = this.getActiveQueue_(); q.enqueue(task); this.emit(ControlFlow.EventType.SCHEDULE_TASK, task.description); return task.promise; @@ -2393,7 +2510,7 @@ class ControlFlow extends events.EventEmitter { /** @override */ promise(resolver) { - return new ManagedPromise(resolver, this); + return new ManagedPromise(resolver, this, SKIP_LOG); } /** @override */ @@ -2622,7 +2739,7 @@ class MicroTask { } /** - * Runs the given function after a micro-task yield. + * Runs the given function after a microtask yield. * @param {function()} fn The function to run. */ static run(fn) { @@ -2662,9 +2779,11 @@ class Task extends Deferred { * @param {string} description A description of the task for debugging. * @param {{name: string, top: !Function}=} opt_stackOptions Options to use * when capturing the stacktrace for when this task was created. + * @param {boolean=} opt_isUserTask Whether this task was explicitly scheduled + * by the use of the promise manager. */ - constructor(flow, fn, description, opt_stackOptions) { - super(flow); + constructor(flow, fn, description, opt_stackOptions, opt_isUserTask) { + super(flow, SKIP_LOG); getUid(this); /** @type {function(): (T|!ManagedPromise<T>)} */ @@ -2676,6 +2795,9 @@ class Task extends Deferred { /** @type {TaskQueue} */ this.queue = null; + /** @private @const {boolean} */ + this.userTask_ = !!opt_isUserTask; + /** * Whether this task is considered block. A blocked task may be registered * in a task queue, but will be dropped if it is still blocked when it @@ -2889,7 +3011,7 @@ class TaskQueue extends events.EventEmitter { } // Now that all of the remaining tasks have been silently cancelled (e.g. no - // exisitng callbacks on those tasks will fire), clear the silence bit on + // existing callbacks on those tasks will fire), clear the silence bit on // the cancellation error. This ensures additional callbacks registered in // the future will actually execute. cancellation.silent_ = false; @@ -2935,7 +3057,7 @@ class TaskQueue extends events.EventEmitter { this.subQ_.once('end', () => { // On task completion. this.subQ_ = null; - this.pending_ && this.pending_.task.fulfill(result); + this.pending_ && this.pending_.task.resolve(result); }); this.subQ_.once('error', e => { // On task failure. @@ -3068,7 +3190,7 @@ class TaskQueue extends events.EventEmitter { } return task; } -}; +} @@ -3214,7 +3336,7 @@ function consume(generatorFn, opt_self, ...var_args) { function pump(fn, opt_arg) { if (ret instanceof ManagedPromise && !isPending(ret)) { - return; // Defererd was cancelled; silently abort. + return; // Deferred was cancelled; silently abort. } try { @@ -3246,6 +3368,7 @@ module.exports = { MultipleUnhandledRejectionError: MultipleUnhandledRejectionError, Thenable: Thenable, Promise: ManagedPromise, + Resolver: Resolver, Scheduler: Scheduler, all: all, asap: asap, @@ -3254,6 +3377,7 @@ module.exports = { consume: consume, controlFlow: controlFlow, createFlow: createFlow, + createPromise: createPromise, defer: defer, delayed: delayed, filter: filter, @@ -3275,7 +3399,7 @@ module.exports = { * The promise manager is currently enabled by default, but may be disabled * by setting the environment variable `SELENIUM_PROMISE_MANAGER=0` or by * setting this property to false. Setting this property will always take - * precedence ove the use of the environment variable. + * precedence over the use of the environment variable. * * @return {boolean} Whether the promise manager is enabled. * @see <https://github.com/SeleniumHQ/selenium/issues/2969> diff --git a/node_modules/selenium-webdriver/lib/symbols.js b/node_modules/selenium-webdriver/lib/symbols.js index d5c62504e..6c7cd1d9d 100644 --- a/node_modules/selenium-webdriver/lib/symbols.js +++ b/node_modules/selenium-webdriver/lib/symbols.js @@ -30,7 +30,7 @@ module.exports = { * available, the serialize method will return a promise that will be resolved * with the serialized form. * - * Note that the described method is analgous to objects that define a + * Note that the described method is analogous to objects that define a * `toJSON()` method, except the serialized result may be a promise, or * another object with a promised property. */ diff --git a/node_modules/selenium-webdriver/lib/test/index.js b/node_modules/selenium-webdriver/lib/test/index.js index ba34ddab4..b3275ccbb 100644 --- a/node_modules/selenium-webdriver/lib/test/index.js +++ b/node_modules/selenium-webdriver/lib/test/index.js @@ -24,6 +24,7 @@ var build = require('./build'), webdriver = require('../../'), flow = webdriver.promise.controlFlow(), firefox = require('../../firefox'), + logging = require('../../lib/logging'), safari = require('../../safari'), remote = require('../../remote'), testing = require('../../testing'), @@ -56,6 +57,11 @@ var noMarionette = /^0|false$/i.test(process.env['SELENIUM_GECKODRIVER']); var startServer = !!serverJar && !remoteUrl; var nativeRun = !serverJar && !remoteUrl; +if (/^1|true$/i.test(process.env['SELENIUM_VERBOSE'])) { + logging.installConsoleHandler(); + logging.getLogger('webdriver.http').setLevel(logging.Level.ALL); +} + var browsersToTest = (function() { var permitRemoteBrowsers = !!remoteUrl || !!serverJar; var permitUnknownBrowsers = !nativeRun; diff --git a/node_modules/selenium-webdriver/lib/until.js b/node_modules/selenium-webdriver/lib/until.js index 0cc6f5ea3..b0e68b88b 100644 --- a/node_modules/selenium-webdriver/lib/until.js +++ b/node_modules/selenium-webdriver/lib/until.js @@ -72,7 +72,7 @@ const webdriver = require('./webdriver'), exports.ableToSwitchToFrame = function ableToSwitchToFrame(frame) { var condition; if (typeof frame === 'number' || frame instanceof webdriver.WebElement) { - condition = attemptToSwitchFrames; + condition = driver => attemptToSwitchFrames(driver, frame); } else { condition = function(driver) { let locator = /** @type {!(By|Function)} */(frame); diff --git a/node_modules/selenium-webdriver/lib/webdriver.js b/node_modules/selenium-webdriver/lib/webdriver.js index 081d77bda..c8d04e82a 100644 --- a/node_modules/selenium-webdriver/lib/webdriver.js +++ b/node_modules/selenium-webdriver/lib/webdriver.js @@ -457,12 +457,12 @@ class IWebDriver { * while evaluating the condition, they will be allowed to propagate. In the * event a condition returns a {@link promise.Promise promise}, the polling * loop will wait for it to be resolved and use the resolved value for whether - * the condition has been satisified. Note the resolution time for a promise + * the condition has been satisfied. Note the resolution time for a promise * is factored into whether a wait has timed out. * * Note, if the provided condition is a {@link WebElementCondition}, then * the wait will return a {@link WebElementPromise} that will resolve to the - * element that satisified the condition. + * element that satisfied the condition. * * _Example:_ waiting up to 10 seconds for an element to be present on the * page. @@ -779,7 +779,7 @@ class WebDriver { 'WebDriver.createSession()'); if (typeof opt_onQuit === 'function') { session = session.catch(err => { - return Promise.resolve(opt_onQuit.call(void 0)).then(_ => {throw err}); + return Promise.resolve(opt_onQuit.call(void 0)).then(_ => {throw err;}); }); } const ctor = opt_ctor || WebDriver; @@ -850,7 +850,7 @@ class WebDriver { 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. + // to throw an error when attempting to use a driver post-quit. return /** @type {!promise.Thenable} */(promise.finally(result, () => { this.session_ = this.flow_.promise((_, reject) => { reject(new error.NoSuchSessionError( @@ -1181,7 +1181,7 @@ class Navigation { /** * Provides methods for managing browser and driver state. * - * This class should never be instantiated directly. Insead, obtain an instance + * This class should never be instantiated directly. Instead, obtain an instance * with {@linkplain WebDriver#manage() webdriver.manage()}. */ class Options { @@ -1427,7 +1427,7 @@ Options.Cookie.prototype.expiry; /** * An interface for managing timeout behavior for WebDriver instances. * - * This class should never be instantiated directly. Insead, obtain an instance + * This class should never be instantiated directly. Instead, obtain an instance * with * * webdriver.manage().timeouts() @@ -2009,7 +2009,7 @@ class WebElement { * this instance. * * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is - * processed in the keysequence, that key state is toggled until one of the + * processed in the key sequence, that key state is toggled until one of the * following occurs: * * - The modifier key is encountered again in the sequence. At this point the @@ -2028,13 +2028,13 @@ class WebElement { * Key.chord(Key.CONTROL, "a"), * "now text is"); * - * - The end of the keysequence is encountered. When there are no more keys + * - The end of the key sequence is encountered. When there are no more keys * to type, all depressed modifier keys are released (with accompanying * keyup events). * * If this element is a file input ({@code <input type="file">}), the * specified key sequence should specify the path to the file to attach to - * the element. This is analgous to the user clicking "Browse..." and entering + * the element. This is analogous to the user clicking "Browse..." and entering * the path into the file select dialog. * * var form = driver.findElement(By.css('form')); @@ -2050,7 +2050,7 @@ class WebElement { * * __Note:__ On browsers where native keyboard events are not supported * (e.g. Firefox on OS X), key events will be synthesized. Special - * punctionation keys will be synthesized according to a standard QWERTY en-us + * punctuation keys will be synthesized according to a standard QWERTY en-us * keyboard layout. * * @param {...(number|string|!IThenable<(number|string)>)} var_args The @@ -2214,7 +2214,7 @@ class WebElement { /** * Schedules a command to query whether the DOM element represented by this - * instance is enabled, as dicted by the {@code disabled} attribute. + * instance is enabled, as dictated by the {@code disabled} attribute. * @return {!promise.Thenable<boolean>} A promise that will be * resolved with whether this element is currently enabled. */ @@ -2327,7 +2327,7 @@ class WebElementPromise extends WebElement { if (promise.CancellableThenable.isImplementation(el)) { /** @type {!promise.CancellableThenable} */(el).cancel(opt_reason); } - } + }; /** @override */ this.then = el.then.bind(el); diff --git a/node_modules/selenium-webdriver/opera.js b/node_modules/selenium-webdriver/opera.js index cc84f2af5..5107bae4e 100644 --- a/node_modules/selenium-webdriver/opera.js +++ b/node_modules/selenium-webdriver/opera.js @@ -62,7 +62,7 @@ * var options = new opera.Options(); * // configure browser options ... * - * var driver = new opera.Driver(options, service); + * var driver = opera.Driver.createSession(options, service); * * Users should only instantiate the {@link Driver} class directly when they * need a custom driver service configuration (as shown above). For normal diff --git a/node_modules/selenium-webdriver/package.json b/node_modules/selenium-webdriver/package.json index 5c3935833..dfb557bf4 100644 --- a/node_modules/selenium-webdriver/package.json +++ b/node_modules/selenium-webdriver/package.json @@ -1,6 +1,6 @@ { "name": "selenium-webdriver", - "version": "3.0.1", + "version": "3.3.0", "description": "The official WebDriver JavaScript bindings from the Selenium project", "license": "Apache-2.0", "keywords": [ diff --git a/node_modules/selenium-webdriver/remote/index.js b/node_modules/selenium-webdriver/remote/index.js index 87dee7e8d..cc0b66462 100644 --- a/node_modules/selenium-webdriver/remote/index.js +++ b/node_modules/selenium-webdriver/remote/index.js @@ -492,7 +492,12 @@ class SeleniumServer extends DriverService { return jvmArgs.concat('-jar', jar, '-port', port).concat(args); }); - super('java', { + let java = 'java'; + if (process.env['JAVA_HOME']) { + java = path.join(process.env['JAVA_HOME'], 'bin/java'); + } + + super(java, { loopback: options.loopback, port: port, args: args, diff --git a/node_modules/selenium-webdriver/safari.js b/node_modules/selenium-webdriver/safari.js index 97d512bc7..5a57387c1 100644 --- a/node_modules/selenium-webdriver/safari.js +++ b/node_modules/selenium-webdriver/safari.js @@ -68,7 +68,7 @@ class ServiceBuilder extends remote.DriverService.Builder { const OPTIONS_CAPABILITY_KEY = 'safari.options'; - +const TECHNOLOGY_PREVIEW_OPTIONS_KEY = 'technologyPreview'; /** * Configuration options specific to the {@link Driver SafariDriver}. @@ -89,16 +89,17 @@ class Options { * Extracts the SafariDriver specific options from the given capabilities * object. * @param {!Capabilities} capabilities The capabilities object. - * @return {!Options} The ChromeDriver options. + * @return {!Options} The SafariDriver options. */ static fromCapabilities(capabilities) { var options = new Options(); - var o = capabilities.get(OPTIONS_CAPABILITY_KEY); + if (o instanceof Options) { options = o; } else if (o) { options.setCleanSession(o.cleanSession); + options.setTechnologyPreview(o[TECHNOLOGY_PREVIEW_OPTIONS_KEY]); } if (capabilities.has(Capability.PROXY)) { @@ -149,6 +150,22 @@ class Options { } /** + * Instruct the SafariDriver to use the Safari Technology Preview if true. + * Otherwise, use the release version of Safari. Defaults to using the release version of Safari. + * + * @param {boolean} useTechnologyPreview + * @return {!Options} A self reference. + */ + setTechnologyPreview(useTechnologyPreview) { + if (!this.options_) { + this.options_ = {}; + } + + this.options_[TECHNOLOGY_PREVIEW_OPTIONS_KEY] = !!useTechnologyPreview; + return this; + } + + /** * Converts this options instance to a {@link Capabilities} object. * @param {Capabilities=} opt_capabilities The capabilities to * merge these options into, if any. @@ -179,6 +196,23 @@ class Options { } } +/** + * @param {(Options|Object<string, *>)=} o The options object + * @return {boolean} + */ +function useTechnologyPreview(o) { + if (o instanceof Options) { + return !!(o.options_ && o.options_[TECHNOLOGY_PREVIEW_OPTIONS_KEY]); + } + + if (o && typeof o === 'object') { + return !!o[TECHNOLOGY_PREVIEW_OPTIONS_KEY]; + } + + return false; +} + +const SAFARIDRIVER_TECHNOLOGY_PREVIEW_EXE = '/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver'; /** * A WebDriver client for Safari. This class should never be instantiated @@ -200,14 +234,19 @@ class Driver extends webdriver.WebDriver { * @return {!Driver} A new driver instance. */ static createSession(opt_config, opt_flow) { - let caps; + let caps, exe; + if (opt_config instanceof Options) { caps = opt_config.toCapabilities(); } else { - caps = opt_config || Capabilities.safari() + caps = opt_config || Capabilities.safari(); + } + + if (useTechnologyPreview(caps.get(OPTIONS_CAPABILITY_KEY))) { + exe = SAFARIDRIVER_TECHNOLOGY_PREVIEW_EXE; } - let service = new ServiceBuilder().build(); + let service = new ServiceBuilder(exe).build(); let executor = new http.Executor( service.start().then(url => new http.HttpClient(url))); diff --git a/node_modules/selenium-webdriver/test/firefox/firefox_test.js b/node_modules/selenium-webdriver/test/firefox/firefox_test.js index 485964f91..dbf9910fa 100644 --- a/node_modules/selenium-webdriver/test/firefox/firefox_test.js +++ b/node_modules/selenium-webdriver/test/firefox/firefox_test.js @@ -26,6 +26,8 @@ var firefox = require('../../firefox'), Context = require('../../firefox').Context, error = require('../..').error; +var {consume} = require('../../lib/promise'); + var JETPACK_EXTENSION = path.join(__dirname, '../../lib/test/data/firefox/jetpack-sample.xpi'); @@ -53,9 +55,7 @@ test.suite(function(env) { * skipped if dev cannot be found on the current system. */ function runWithFirefoxDev(options, testFn) { - let binary = new firefox.Binary(); - binary.useDevEdition(); - return binary.locate().then(exe => { + return firefox.Channel.AURORA.locate().then(exe => { options.setBinary(exe); driver = env.builder() .setFirefoxOptions(options) @@ -67,21 +67,33 @@ test.suite(function(env) { }); } - test.it('can start Firefox with custom preferences', function*() { - var profile = new firefox.Profile(); - profile.setPreference('general.useragent.override', 'foo;bar'); + describe('can start Firefox with custom preferences', function() { + function runTest(opt_dir) { + return consume(function*() { + let profile = new firefox.Profile(opt_dir); + profile.setPreference('general.useragent.override', 'foo;bar'); + + let options = new firefox.Options().setProfile(profile); + + driver = env.builder(). + setFirefoxOptions(options). + build(); - var options = new firefox.Options().setProfile(profile); + yield driver.get('data:text/html,<html><div>content</div></html>'); - driver = env.builder(). - setFirefoxOptions(options). - build(); + var userAgent = yield driver.executeScript( + 'return window.navigator.userAgent'); + assert(userAgent).equalTo('foo;bar'); + }); + } - yield driver.get('data:text/html,<html><div>content</div></html>'); + test.it('profile created from scratch', function() { + return runTest(); + }); - var userAgent = yield driver.executeScript( - 'return window.navigator.userAgent'); - assert(userAgent).equalTo('foo;bar'); + test.it('profile created from template', function() { + return io.tmpDir().then(runTest); + }); }); test.it('can start Firefox with a jetpack extension', function() { diff --git a/node_modules/selenium-webdriver/test/lib/http_test.js b/node_modules/selenium-webdriver/test/lib/http_test.js index 1c6c073ad..2dd27869d 100644 --- a/node_modules/selenium-webdriver/test/lib/http_test.js +++ b/node_modules/selenium-webdriver/test/lib/http_test.js @@ -325,6 +325,37 @@ describe('http', function() { assert.ok(executor.w3c, 'should never downgrade'); }); }); + + it('handles legacy new session failures', function() { + let rawResponse = { + status: error.ErrorCode.NO_SUCH_ELEMENT, + value: {message: 'hi'} + }; + + send.returns(Promise.resolve( + new http.Response(500, {}, JSON.stringify(rawResponse)))); + + return executor.execute(command) + .then(() => assert.fail('should have failed'), + e => { + assert.ok(e instanceof error.NoSuchElementError); + assert.equal(e.message, 'hi'); + }); + }); + + it('handles w3c new session failures', function() { + let rawResponse = {error: 'no such element', message: 'oops'}; + + send.returns(Promise.resolve( + new http.Response(500, {}, JSON.stringify(rawResponse)))); + + return executor.execute(command) + .then(() => assert.fail('should have failed'), + e => { + assert.ok(e instanceof error.NoSuchElementError); + assert.equal(e.message, 'oops'); + }); + }); }); describe('extracts Session from DESCRIBE_SESSION response', function() { diff --git a/node_modules/selenium-webdriver/test/lib/promise_test.js b/node_modules/selenium-webdriver/test/lib/promise_test.js index 96d2ccc22..8da1cd89e 100644 --- a/node_modules/selenium-webdriver/test/lib/promise_test.js +++ b/node_modules/selenium-webdriver/test/lib/promise_test.js @@ -301,6 +301,25 @@ describe('promise', function() { }); promiseManagerSuite(() => { + describe('fulfilled', function() { + it('returns input value if it is already a valid promise', function() { + let p = promise.createPromise(function() {}); + let r = promise.fulfilled(p); + assert.strictEqual(p, r); + }); + + it('creates a new promise fulfilled with input', function() { + return promise.fulfilled(1234).then(v => assert.equal(1234, v)); + }); + + it('can convert thenables to valid promise', function() { + let thenable = {then: function(cb) {cb(1234)}}; + let p = promise.fulfilled(thenable); + assert.notStrictEqual(thenable, p); + return p.then(v => assert.equal(1234, v)); + }); + }); + describe('when', function() { it('ReturnsAResolvedPromiseIfGivenANonPromiseValue', function() { var ret = promise.when('abc'); diff --git a/node_modules/selenium-webdriver/test/lib/until_test.js b/node_modules/selenium-webdriver/test/lib/until_test.js index 31b2b32ad..3226a467a 100644 --- a/node_modules/selenium-webdriver/test/lib/until_test.js +++ b/node_modules/selenium-webdriver/test/lib/until_test.js @@ -70,39 +70,54 @@ describe('until', function() { .then(fail, (e2) => assert.strictEqual(e2, e)); }); + const ELEMENT_ID = 'some-element-id'; + const ELEMENT_INDEX = 1234; + + function onSwitchFrame(expectedId) { + if (typeof expectedId === 'string') { + expectedId = WebElement.buildId(expectedId); + } else { + assert.equal(typeof expectedId, 'number', 'must be string or number'); + } + return cmd => { + assert.deepEqual( + cmd.getParameter('id'), expectedId, 'frame ID not specified'); + return true; + }; + } + it('byIndex', function() { - executor.on(CommandName.SWITCH_TO_FRAME, () => true); - return driver.wait(until.ableToSwitchToFrame(0), 100); + executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_INDEX)); + return driver.wait(until.ableToSwitchToFrame(ELEMENT_INDEX), 100); }); it('byWebElement', function() { - executor.on(CommandName.SWITCH_TO_FRAME, () => true); - var el = new webdriver.WebElement(driver, {ELEMENT: 1234}); + executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)); + + var el = new webdriver.WebElement(driver, ELEMENT_ID); return driver.wait(until.ableToSwitchToFrame(el), 100); }); it('byWebElementPromise', function() { - executor.on(CommandName.SWITCH_TO_FRAME, () => true); + executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)); var el = new webdriver.WebElementPromise(driver, - Promise.resolve(new webdriver.WebElement(driver, {ELEMENT: 1234}))); + Promise.resolve(new webdriver.WebElement(driver, ELEMENT_ID))); return driver.wait(until.ableToSwitchToFrame(el), 100); }); it('byLocator', function() { - executor.on(CommandName.FIND_ELEMENTS, () => [WebElement.buildId(1234)]); - executor.on(CommandName.SWITCH_TO_FRAME, () => true); + executor.on(CommandName.FIND_ELEMENTS, () => [WebElement.buildId(ELEMENT_ID)]); + executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)); return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 100); }); it('byLocator_elementNotInitiallyFound', function() { - var foundResponses = [[], [], [WebElement.buildId(1234)]]; + let foundResponses = [[], [], [WebElement.buildId(ELEMENT_ID)]]; executor.on(CommandName.FIND_ELEMENTS, () => foundResponses.shift()); - executor.on(CommandName.SWITCH_TO_FRAME, () => true); + executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID)); return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 2000) - .then(function() { - assert.equal(foundResponses.length, 0); - }); + .then(() => assert.deepEqual(foundResponses, [])); }); it('timesOutIfNeverAbletoSwitchFrames', function() { diff --git a/node_modules/selenium-webdriver/testing/index.js b/node_modules/selenium-webdriver/testing/index.js index 5bb82d15f..88763c7d5 100644 --- a/node_modules/selenium-webdriver/testing/index.js +++ b/node_modules/selenium-webdriver/testing/index.js @@ -41,16 +41,14 @@ * The provided wrappers leverage the {@link webdriver.promise.ControlFlow} * to simplify writing asynchronous tests: * - * var By = require('selenium-webdriver').By, - * until = require('selenium-webdriver').until, - * firefox = require('selenium-webdriver/firefox'), - * test = require('selenium-webdriver/testing'); + * var {Builder, By, until} = require('selenium-webdriver'); + * var test = require('selenium-webdriver/testing'); * * test.describe('Google Search', function() { * var driver; * * test.before(function() { - * driver = new firefox.Driver(); + * driver = new Builder().forBrowser('firefox').build(); * }); * * test.after(function() { @@ -293,6 +291,19 @@ exports.describe = function(name, opt_fn) { /** + * An alias for {@link #describe()} that marks the suite as exclusive, + * suppressing all other test suites. + * @param {string} name The suite name. + * @param {function()=} opt_fn The suite function, or `undefined` to define + * a pending test suite. + */ +exports.describe.only = function(name, opt_fn) { + let desc = getMochaGlobal('describe'); + return opt_fn ? desc.only(name, opt_fn) : desc.only(name); +}; + + +/** * Defines a suppressed test suite. * @param {string} name The suite name. * @param {function()=} opt_fn The suite function, or `undefined` to define |