From bd65bb67e25a79b019d745b7262b2008ce2adb15 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 16 Nov 2016 01:59:39 +0100 Subject: incrementally verify denoms The denominations are not stored in a separate object store. --- node_modules/selenium-webdriver/CHANGES.md | 60 + node_modules/selenium-webdriver/LICENSE | 2 +- node_modules/selenium-webdriver/README.md | 13 +- node_modules/selenium-webdriver/chrome.js | 47 +- node_modules/selenium-webdriver/edge.js | 16 +- .../selenium-webdriver/example/chrome_android.js | 31 +- .../example/chrome_mobile_emulation.js | 34 +- .../selenium-webdriver/example/google_search.js | 22 +- .../example/google_search_generator.js | 41 +- .../example/google_search_test.js | 48 +- node_modules/selenium-webdriver/example/logging.js | 26 +- .../selenium-webdriver/example/parallel_flows.js | 3 + node_modules/selenium-webdriver/firefox/binary.js | 17 + node_modules/selenium-webdriver/firefox/index.js | 103 +- node_modules/selenium-webdriver/firefox/profile.js | 27 +- node_modules/selenium-webdriver/http/util.js | 154 +- node_modules/selenium-webdriver/ie.js | 16 +- node_modules/selenium-webdriver/index.js | 141 +- node_modules/selenium-webdriver/lib/actions.js | 28 +- .../lib/firefox/amd64/libnoblur64.so | Bin 41262 -> 41623 bytes .../lib/firefox/i386/libnoblur.so | Bin 30887 -> 36934 bytes .../selenium-webdriver/lib/firefox/webdriver.xpi | Bin 706947 -> 707042 bytes node_modules/selenium-webdriver/lib/http.js | 201 +- node_modules/selenium-webdriver/lib/logging.js | 8 +- node_modules/selenium-webdriver/lib/promise.js | 927 +++-- .../selenium-webdriver/lib/safari/client.js | 56 - node_modules/selenium-webdriver/lib/session.js | 2 +- node_modules/selenium-webdriver/lib/test/build.js | 54 +- .../selenium-webdriver/lib/test/data/formPage.html | 1 + .../selenium-webdriver/lib/test/fileserver.js | 22 +- .../selenium-webdriver/lib/test/httpserver.js | 10 +- node_modules/selenium-webdriver/lib/test/index.js | 43 +- node_modules/selenium-webdriver/lib/webdriver.js | 1059 +++--- node_modules/selenium-webdriver/opera.js | 9 +- node_modules/selenium-webdriver/package.json | 27 +- node_modules/selenium-webdriver/phantomjs.js | 21 +- node_modules/selenium-webdriver/remote/index.js | 28 +- node_modules/selenium-webdriver/safari.js | 499 +-- .../selenium-webdriver/test/actions_test.js | 20 +- .../selenium-webdriver/test/chrome/options_test.js | 16 +- .../selenium-webdriver/test/cookie_test.js | 114 +- .../test/element_finding_test.js | 386 ++- .../selenium-webdriver/test/execute_script_test.js | 214 +- .../selenium-webdriver/test/fingerprint_test.js | 27 +- .../test/firefox/firefox_test.js | 124 +- .../test/firefox/profile_test.js | 1 - .../selenium-webdriver/test/http/util_test.js | 48 +- node_modules/selenium-webdriver/test/io_test.js | 2 +- .../selenium-webdriver/test/lib/http_test.js | 14 +- .../test/lib/promise_aplus_test.js | 92 +- .../test/lib/promise_error_test.js | 1344 ++++---- .../test/lib/promise_flow_test.js | 3616 ++++++++++---------- .../test/lib/promise_generator_test.js | 452 +-- .../selenium-webdriver/test/lib/promise_test.js | 1705 ++++----- .../selenium-webdriver/test/lib/until_test.js | 2 +- .../selenium-webdriver/test/lib/webdriver_test.js | 890 ++--- .../selenium-webdriver/test/logging_test.js | 88 +- .../selenium-webdriver/test/net/portprober_test.js | 47 +- .../selenium-webdriver/test/page_loading_test.js | 162 +- .../test/phantomjs/execute_phantomjs_test.js | 36 +- node_modules/selenium-webdriver/test/proxy_test.js | 46 +- .../selenium-webdriver/test/remote_test.js | 44 +- .../selenium-webdriver/test/safari_test.js | 12 +- .../selenium-webdriver/test/session_test.js | 21 +- .../selenium-webdriver/test/stale_element_test.js | 34 +- .../selenium-webdriver/test/tag_name_test.js | 12 +- .../selenium-webdriver/test/testing/assert_test.js | 5 +- .../selenium-webdriver/test/testing/index_test.js | 280 +- .../selenium-webdriver/test/upload_test.js | 26 +- .../selenium-webdriver/test/window_test.js | 154 +- node_modules/selenium-webdriver/testing/index.js | 213 +- 71 files changed, 7233 insertions(+), 6810 deletions(-) delete mode 100644 node_modules/selenium-webdriver/lib/safari/client.js (limited to 'node_modules/selenium-webdriver') diff --git a/node_modules/selenium-webdriver/CHANGES.md b/node_modules/selenium-webdriver/CHANGES.md index 614f905b4..b9ac5fd22 100644 --- a/node_modules/selenium-webdriver/CHANGES.md +++ b/node_modules/selenium-webdriver/CHANGES.md @@ -1,3 +1,63 @@ +## v3.0.1 + +* More API adjustments to align with native Promises + - Deprecated `promise.fulfilled(value)`, use `promise.Promise#resolve(value)` + - Deprecated `promise.rejected(reason)`, use `promise.Promise#reject(reason)` +* When a `wait()` condition times out, the returned promise will now be + rejected with an `error.TimeoutError` instead of a generic `Error` object. +* `WebDriver#wait()` will now throw a TypeError if an invalid wait condition is + provided. +* Properly catch unhandled promise rejections with an action sequence (only + impacts when the promise manager is disabled). + + +## v3.0.0 + +* (__NOTICE__) The minimum supported version of Node is now 6.9.0 LTS +* Removed support for the SafariDriver browser extension. This has been + replaced by Apple's safaridriver, which is included wtih Safari 10 + (available on OS X El Capitan and macOS Sierra). + + To use Safari 9 or older, users will have to use an older version of Selenium. + +* geckodriver v0.11.0 or newer is now required for Firefox. +* Fixed potential reference errors in `selenium-webdriver/testing` when users + create a cycle with mocha by running with mocha's `--hook` flag. +* Fixed `WebDriver.switchTo().activeElement()` to use the correct HTTP method + for compatibility with the W3C spec. +* Update the `selenium-webdriver/firefox` module to use geckodriver's + "moz:firefoxOptions" dictionary for Firefox-specific configuration values. +* Extending the `selenium-webdriver/testing` module to support tests defined + using generator functions. +* The promise manager can be disabled by setting an enviornment variable: + `SELENIUM_PROMISE_MANAGER=0`. This is part of a larger plan to remove the + promise manager, as documented at + +* When communicating with a W3C-compliant remote end, use the atoms library for + the `WebElement.getAttribute()` and `WebElement.isDisplayed()` commands. This + behavior is consistent with the java, .net, python, and ruby clients. + + +### API Changes + + * Removed `safari.Options#useLegacyDriver()` + * Reduced the API on `promise.Thenable` for compatibility with native promises: + - 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 * Fixed a bug where the promise manager would silently drop callbacks after diff --git a/node_modules/selenium-webdriver/LICENSE b/node_modules/selenium-webdriver/LICENSE index d64569567..d43f2c0a4 100644 --- a/node_modules/selenium-webdriver/LICENSE +++ b/node_modules/selenium-webdriver/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2016 Software Freedom Conservancy (SFC) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/node_modules/selenium-webdriver/README.md b/node_modules/selenium-webdriver/README.md index 0b70aeddc..bc281d504 100644 --- a/node_modules/selenium-webdriver/README.md +++ b/node_modules/selenium-webdriver/README.md @@ -11,22 +11,15 @@ Selenium may be installed via npm with npm install selenium-webdriver You will need to download additional components to work with each of the major -browsers. The drivers for Chrome, Firefox, Safari, PhantomJS, Opera, and +browsers. The drivers for Chrome, Firefox, PhantomJS, Opera, and Microsoft's IE and Edge web browsers are all standalone executables that should be placed on your system [PATH]. Apple's safaridriver is shipped with -Safari 10 in macOS Sierra. You will need to enable Remote Automation in -the Develop menu of Safari 10 before testing. +Safari 10 for OS X El Capitan and macOS Sierra. You will need to enable Remote +Automation in the Develop menu of Safari 10 before testing. > **NOTE:** Mozilla's [geckodriver] is only required for Firefox 47+. > Everything you need for Firefox 38-46 is included with this package. -> **NOTE:** Apple's [safaridriver] is preferred for testing Safari 10+. -> To test versions of Safari prior to Safari 10, The -> [SafariDriver.safariextz][release] browser extension should be -> installed in your browser before using Selenium. We recommend -> disabling the extension when using the browser without Selenium -> or installing the extension in a profile only used for testing. - | Browser | Component | | ----------------- | ---------------------------------- | diff --git a/node_modules/selenium-webdriver/chrome.js b/node_modules/selenium-webdriver/chrome.js index 13d7b7bbf..eb33df9ed 100644 --- a/node_modules/selenium-webdriver/chrome.js +++ b/node_modules/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)); } /** @@ -728,7 +719,7 @@ class Driver extends webdriver.WebDriver { /** * Schedules a command to launch Chrome App with given ID. * @param {string} id ID of the App to launch. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when app is launched. */ launchApp(id) { diff --git a/node_modules/selenium-webdriver/edge.js b/node_modules/selenium-webdriver/edge.js index 9685a2c21..ee9d43383 100644 --- a/node_modules/selenium-webdriver/edge.js +++ b/node_modules/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,15 +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()); - - var boundQuit = this.quit.bind(this); - - /** @override */ - this.quit = function() { - return boundQuit().finally(service.kill.bind(service)); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } /** diff --git a/node_modules/selenium-webdriver/example/chrome_android.js b/node_modules/selenium-webdriver/example/chrome_android.js index 990a4c445..bc0701cf9 100644 --- a/node_modules/selenium-webdriver/example/chrome_android.js +++ b/node_modules/selenium-webdriver/example/chrome_android.js @@ -21,18 +21,23 @@ * AVD). */ -var webdriver = require('..'), - By = webdriver.By, - until = webdriver.until, - chrome = require('../chrome'); +'use strict'; -var driver = new webdriver.Builder() - .forBrowser('chrome') - .setChromeOptions(new chrome.Options().androidChrome()) - .build(); +const {Builder, By, promise, until} = require('..'); +const {Options} = require('../chrome'); -driver.get('http://www.google.com/ncr'); -driver.findElement(By.name('q')).sendKeys('webdriver'); -driver.findElement(By.name('btnG')).click(); -driver.wait(until.titleIs('webdriver - Google Search'), 1000); -driver.quit(); +promise.consume(function* () { + let driver; + try { + driver = yield new Builder() + .forBrowser('chrome') + .setChromeOptions(new Options().androidChrome()) + .build(); + yield driver.get('http://www.google.com/ncr'); + yield driver.findElement(By.name('q')).sendKeys('webdriver'); + yield driver.findElement(By.name('btnG')).click(); + yield driver.wait(until.titleIs('webdriver - Google Search'), 1000); + } finally { + yield driver && driver.quit(); + } +}).then(_ => console.log('SUCCESS'), err => console.error('ERROR: ' + err)); diff --git a/node_modules/selenium-webdriver/example/chrome_mobile_emulation.js b/node_modules/selenium-webdriver/example/chrome_mobile_emulation.js index d3081127d..790be2bcf 100644 --- a/node_modules/selenium-webdriver/example/chrome_mobile_emulation.js +++ b/node_modules/selenium-webdriver/example/chrome_mobile_emulation.js @@ -20,20 +20,24 @@ * ChromeDriver. */ -var webdriver = require('..'), - By = webdriver.By, - until = webdriver.until, - chrome = require('../chrome'); +'use strict'; +const {Builder, By, promise, until} = require('..'); +const {Options} = require('../chrome'); -var driver = new webdriver.Builder() - .forBrowser('chrome') - .setChromeOptions(new chrome.Options() - .setMobileEmulation({deviceName: 'Google Nexus 5'})) - .build(); - -driver.get('http://www.google.com/ncr'); -driver.findElement(By.name('q')).sendKeys('webdriver'); -driver.findElement(By.name('btnG')).click(); -driver.wait(until.titleIs('webdriver - Google Search'), 1000); -driver.quit(); +promise.consume(function* () { + let driver; + try { + driver = yield new Builder() + .forBrowser('chrome') + .setChromeOptions( + new Options().setMobileEmulation({deviceName: 'Google Nexus 5'})) + .build(); + yield driver.get('http://www.google.com/ncr'); + yield driver.findElement(By.name('q')).sendKeys('webdriver'); + yield driver.findElement(By.name('btnG')).click(); + yield driver.wait(until.titleIs('webdriver - Google Search'), 1000); + } finally { + yield driver && driver.quit(); + } +}).then(_ => console.log('SUCCESS'), err => console.error('ERROR: ' + err)); diff --git a/node_modules/selenium-webdriver/example/google_search.js b/node_modules/selenium-webdriver/example/google_search.js index 22d0d21ce..b9b821328 100644 --- a/node_modules/selenium-webdriver/example/google_search.js +++ b/node_modules/selenium-webdriver/example/google_search.js @@ -16,8 +16,10 @@ // under the License. /** - * @fileoverview An example WebDriver script. This requires the chromedriver - * to be present on the system PATH. + * @fileoverview An example WebDriver script. + * + * Before running this script, ensure that Mozilla's geckodriver is present on + * your system PATH: * * Usage: * // Default behavior @@ -35,16 +37,14 @@ * node selenium-webdriver/example/google_search.js */ -var webdriver = require('..'), - By = webdriver.By, - until = webdriver.until; +const {Builder, By, until} = require('..'); -var driver = new webdriver.Builder() +var driver = new Builder() .forBrowser('firefox') .build(); -driver.get('http://www.google.com/ncr'); -driver.findElement(By.name('q')).sendKeys('webdriver'); -driver.findElement(By.name('btnG')).click(); -driver.wait(until.titleIs('webdriver - Google Search'), 1000); -driver.quit(); \ No newline at end of file +driver.get('http://www.google.com/ncr') + .then(_ => driver.findElement(By.name('q')).sendKeys('webdriver')) + .then(_ => driver.findElement(By.name('btnG')).click()) + .then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000)) + .then(_ => driver.quit()); diff --git a/node_modules/selenium-webdriver/example/google_search_generator.js b/node_modules/selenium-webdriver/example/google_search_generator.js index 983c8d84f..25df93ab9 100644 --- a/node_modules/selenium-webdriver/example/google_search_generator.js +++ b/node_modules/selenium-webdriver/example/google_search_generator.js @@ -18,28 +18,33 @@ /** * @fileoverview An example WebDriver script using generator functions. * - * Usage: node selenium-webdriver/example/google_search_generator.js + * Before running this script, ensure that Mozilla's geckodriver is present on + * your system PATH: + * + * Usage: + * + * node selenium-webdriver/example/google_search_generator.js */ -var webdriver = require('..'), - By = webdriver.By; +'use strict'; + +const {Builder, By, promise, until} = require('..'); -var driver = new webdriver.Builder() - .forBrowser('firefox') - .build(); +promise.consume(function* () { + let driver; + try { + driver = yield new Builder().forBrowser('firefox').build(); -driver.get('http://www.google.com/ncr'); -driver.call(function* () { - var query = yield driver.findElement(By.name('q')); - query.sendKeys('webdriver'); + yield driver.get('http://www.google.com/ncr'); - var submit = yield driver.findElement(By.name('btnG')); - submit.click(); -}); + let q = yield driver.findElement(By.name('q')); + yield q.sendKeys('webdriver'); -driver.wait(function* () { - var title = yield driver.getTitle(); - return 'webdriver - Google Search' === title; -}, 1000); + let btnG = yield driver.findElement(By.name('btnG')); + yield btnG.click(); -driver.quit(); + yield driver.wait(until.titleIs('webdriver - Google Search'), 1000); + } finally { + yield driver && driver.quit(); + } +}).then(_ => console.log('SUCCESS'), err => console.error('ERROR: ' + err)); diff --git a/node_modules/selenium-webdriver/example/google_search_test.js b/node_modules/selenium-webdriver/example/google_search_test.js index 823e2c578..a29278258 100644 --- a/node_modules/selenium-webdriver/example/google_search_test.js +++ b/node_modules/selenium-webdriver/example/google_search_test.js @@ -17,31 +17,45 @@ /** * @fileoverview An example test that may be run using Mocha. - * Usage: mocha -t 10000 selenium-webdriver/example/google_search_test.js + * + * Usage: + * + * mocha -t 10000 selenium-webdriver/example/google_search_test.js + * + * You can change which browser is started with the SELENIUM_BROWSER environment + * variable: + * + * SELENIUM_BROWSER=chrome \ + * mocha -t 10000 selenium-webdriver/example/google_search_test.js */ -var webdriver = require('..'), - By = webdriver.By, - until = webdriver.until, - test = require('../testing'); +const {Builder, By, until} = require('..'); +const test = require('../testing'); test.describe('Google Search', function() { - var driver; + let driver; - test.before(function() { - driver = new webdriver.Builder() - .forBrowser('firefox') - .build(); + test.before(function *() { + driver = yield new Builder().forBrowser('firefox').build(); }); - test.it('should append query to title', function() { - driver.get('http://www.google.com'); - driver.findElement(By.name('q')).sendKeys('webdriver'); - driver.findElement(By.name('btnG')).click(); - driver.wait(until.titleIs('webdriver - Google Search'), 1000); + // You can write tests either using traditional promises. + it('works with promises', function() { + return driver.get('http://www.google.com') + .then(_ => driver.findElement(By.name('q')).sendKeys('webdriver')) + .then(_ => driver.findElement(By.name('btnG')).click()) + .then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000)); }); - test.after(function() { - driver.quit(); + // Or you can define the test as a generator function. The test will wait for + // any yielded promises to resolve before invoking the next step in the + // generator. + test.it('works with generators', function*() { + yield driver.get('http://www.google.com/ncr'); + yield driver.findElement(By.name('q')).sendKeys('webdriver'); + yield driver.findElement(By.name('btnG')).click(); + yield driver.wait(until.titleIs('webdriver - Google Search'), 1000); }); + + test.after(() => driver.quit()); }); diff --git a/node_modules/selenium-webdriver/example/logging.js b/node_modules/selenium-webdriver/example/logging.js index ae1d4cc2a..633ac90c2 100644 --- a/node_modules/selenium-webdriver/example/logging.js +++ b/node_modules/selenium-webdriver/example/logging.js @@ -21,23 +21,15 @@ 'use strict'; -var webdriver = require('..'), - By = webdriver.By, - until = webdriver.until; +const {Builder, By, logging, until} = require('..'); -webdriver.logging.installConsoleHandler(); -webdriver.logging.getLogger('webdriver.http') - .setLevel(webdriver.logging.Level.ALL); +logging.installConsoleHandler(); +logging.getLogger('webdriver.http').setLevel(logging.Level.ALL); -var driver = new webdriver.Builder() - .forBrowser('firefox') - .build(); +var driver = new Builder().forBrowser('firefox').build(); -driver.get('http://www.google.com/ncr'); - -var searchBox = driver.wait(until.elementLocated(By.name('q')), 3000); -searchBox.sendKeys('webdriver'); - -driver.findElement(By.name('btnG')).click(); -driver.wait(until.titleIs('webdriver - Google Search'), 1000); -driver.quit(); +driver.get('http://www.google.com/ncr') + .then(_ => driver.findElement(By.name('q')).sendKeys('webdriver')) + .then(_ => driver.findElement(By.name('btnG')).click()) + .then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000)) + .then(_ => driver.quit()); diff --git a/node_modules/selenium-webdriver/example/parallel_flows.js b/node_modules/selenium-webdriver/example/parallel_flows.js index f41692234..59ff103fb 100644 --- a/node_modules/selenium-webdriver/example/parallel_flows.js +++ b/node_modules/selenium-webdriver/example/parallel_flows.js @@ -18,6 +18,9 @@ /** * @fileoverview An example of starting multiple WebDriver clients that run * in parallel in separate control flows. + * + * This example will only work when the promise manager is enabled + * (see ). */ var webdriver = require('..'), diff --git a/node_modules/selenium-webdriver/firefox/binary.js b/node_modules/selenium-webdriver/firefox/binary.js index f31440b4e..b997b480d 100644 --- a/node_modules/selenium-webdriver/firefox/binary.js +++ b/node_modules/selenium-webdriver/firefox/binary.js @@ -181,6 +181,15 @@ class Binary { this.devEdition_ = false; } + /** + * @return {(string|undefined)} The path to the Firefox executable to use, or + * `undefined` if WebDriver should attempt to locate Firefox automatically + * on the current system. + */ + getExe() { + return this.exe_; + } + /** * Add arguments to the command line used to start Firefox. * @param {...(string|!Array.)} var_args Either the arguments to add @@ -196,6 +205,14 @@ class Binary { } } + /** + * @return {!Array} The command line arguments to use when starting + * the browser. + */ + getArguments() { + return this.args_; + } + /** * Specifies whether to use Firefox Developer Edition instead of the normal * stable channel. Setting this option has no effect if this instance was diff --git a/node_modules/selenium-webdriver/firefox/index.js b/node_modules/selenium-webdriver/firefox/index.js index 0adc97093..4ea1702a9 100644 --- a/node_modules/selenium-webdriver/firefox/index.js +++ b/node_modules/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,13 +454,37 @@ 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); + + if (binary) { + if (binary.getExe()) { + firefoxOptions['binary'] = binary.getExe(); + } + + let args = binary.getArguments(); + if (args.length) { + firefoxOptions['args'] = args; + } + } + if (profile) { - caps.set(Capability.PROFILE, profile.encode()); + // If the user specified a template directory or any extensions to install, + // we need to encode the profile as a base64 string (which requires writing + // it to disk first). Otherwise, if the user just specified some custom + // preferences, we can send those directly. + if (profile.getTemplateDir() || profile.getExtensions().length) { + firefoxOptions['profile'] = profile.encode(); + + } else { + let prefs = profile.getPreferences(); + if (Object.keys(prefs).length) { + firefoxOptions['prefs'] = prefs; + } + } } let sessionCaps = caps; @@ -473,7 +501,7 @@ function createGeckoDriver( sessionCaps = {required, desired: caps}; } - /** @type {(command.Executor|undefined)} */ + /** @type {!command.Executor} */ let cmdExecutor; let onQuit = function() {}; @@ -493,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 + }; } @@ -506,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) { @@ -529,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}; } @@ -549,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 @@ -569,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(); @@ -578,7 +607,6 @@ class Driver extends webdriver.WebDriver { caps = new capabilities.Capabilities(opt_config); } - let hasBinary = caps.has(Capability.BINARY); let binary = caps.get(Capability.BINARY) || new Binary(); caps.delete(Capability.BINARY); if (typeof binary === 'string') { @@ -591,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 = @@ -602,12 +628,7 @@ class Driver extends webdriver.WebDriver { let spec; if (useMarionette) { - spec = createGeckoDriver( - opt_executor, - caps, - profile, - hasBinary ? binary : null, - 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' @@ -616,14 +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 super.quit().finally(spec.onQuit); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + spec.executor, spec.capabilities, opt_flow, this, spec.onQuit)); } /** @@ -637,7 +652,7 @@ class Driver extends webdriver.WebDriver { /** * Get the context that is currently in effect. * - * @return {!promise.Promise} Current context. + * @return {!promise.Thenable} Current context. */ getContext() { return this.schedule( @@ -657,7 +672,7 @@ class Driver extends webdriver.WebDriver { * * Use your powers wisely. * - * @param {!promise.Promise} ctx The context to switch to. + * @param {!promise.Thenable} ctx The context to switch to. */ setContext(ctx) { return this.schedule( diff --git a/node_modules/selenium-webdriver/firefox/profile.js b/node_modules/selenium-webdriver/firefox/profile.js index b6b39086c..b7f6363f9 100644 --- a/node_modules/selenium-webdriver/firefox/profile.js +++ b/node_modules/selenium-webdriver/firefox/profile.js @@ -201,9 +201,6 @@ class Profile { /** @private {!Object} */ this.preferences_ = {}; - Object.assign(this.preferences_, getDefaultPreferences()['mutable']); - Object.assign(this.preferences_, getDefaultPreferences()['frozen']); - /** @private {boolean} */ this.nativeEventsEnabled_ = true; @@ -217,6 +214,14 @@ class Profile { this.extensions_ = []; } + /** + * @return {(string|undefined)} Path to an existing Firefox profile directory + * to use as a template when writing this Profile to disk. + */ + getTemplateDir() { + return this.template_; + } + /** * Registers an extension to be included with this profile. * @param {string} extension Path to the extension to include, as either an @@ -226,6 +231,13 @@ class Profile { this.extensions_.push(extension); } + /** + * @return {!Array} A list of extensions to install in this profile. + */ + getExtensions() { + return this.extensions_; + } + /** * Sets a desired preference for this profile. * @param {string} key The preference key. @@ -254,6 +266,13 @@ class Profile { return this.preferences_[key]; } + /** + * @return {!Object} A copy of all currently configured preferences. + */ + getPreferences() { + return Object.assign({}, this.preferences_); + } + /** * Specifies which host the driver should listen for commands on. If not * specified, the driver will default to "localhost". This option should be @@ -353,6 +372,8 @@ class Profile { // Freeze preferences for async operations. var prefs = {}; + Object.assign(prefs, getDefaultPreferences()['mutable']); + Object.assign(prefs, getDefaultPreferences()['frozen']); Object.assign(prefs, this.preferences_); // Freeze extensions for async operations. diff --git a/node_modules/selenium-webdriver/http/util.js b/node_modules/selenium-webdriver/http/util.js index 7564ba85e..8662bed28 100644 --- a/node_modules/selenium-webdriver/http/util.js +++ b/node_modules/selenium-webdriver/http/util.js @@ -61,38 +61,54 @@ exports.getStatus = getStatus; * Waits for a WebDriver server to be healthy and accepting requests. * @param {string} url Base URL of the server to query. * @param {number} timeout How long to wait for the server. - * @return {!promise.Promise} A promise that will resolve when the - * server is ready. + * @param {Promise=} opt_cancelToken A promise used as a cancellation signal: + * if resolved before the server is ready, the wait will be terminated + * early with a {@link promise.CancellationError}. + * @return {!Promise} A promise that will resolve when the server is ready, or + * if the wait is cancelled. */ -exports.waitForServer = function(url, timeout) { - var ready = promise.defer(), - start = Date.now(); - checkServerStatus(); - return ready.promise; - - function checkServerStatus() { - return getStatus(url).then(status => ready.fulfill(status), onError); - } - - function onError(e) { - // Some servers don't support the status command. If they are able to - // response with an error, then can consider the server ready. - if (e instanceof error.UnsupportedOperationError) { - ready.fulfill(); - return; +exports.waitForServer = function(url, timeout, opt_cancelToken) { + return new Promise((onResolve, onReject) => { + let start = Date.now(); + + let done = false; + let resolve = (status) => { + done = true; + onResolve(status); + }; + let reject = (err) => { + done = true; + onReject(err); + }; + + if (opt_cancelToken) { + opt_cancelToken.then(_ => reject(new promise.CancellationError)); } - if (Date.now() - start > timeout) { - ready.reject( - Error('Timed out waiting for the WebDriver server at ' + url)); - } else { - setTimeout(function() { - if (ready.promise.isPending()) { - checkServerStatus(); - } - }, 50); + checkServerStatus(); + function checkServerStatus() { + return getStatus(url).then(status => resolve(status), onError); } - } + + function onError(e) { + // Some servers don't support the status command. If they are able to + // response with an error, then can consider the server ready. + if (e instanceof error.UnsupportedOperationError) { + resolve({}); + return; + } + + if (Date.now() - start > timeout) { + reject(Error('Timed out waiting for the WebDriver server at ' + url)); + } else { + setTimeout(function() { + if (!done) { + checkServerStatus(); + } + }, 50); + } + } + }); }; @@ -101,39 +117,59 @@ exports.waitForServer = function(url, timeout) { * timeout expires. * @param {string} url The URL to poll. * @param {number} timeout How long to wait, in milliseconds. - * @return {!promise.Promise} A promise that will resolve when the - * URL responds with 2xx. + * @param {Promise=} opt_cancelToken A promise used as a cancellation signal: + * if resolved before the a 2xx response is received, the wait will be + * terminated early with a {@link promise.CancellationError}. + * @return {!Promise} A promise that will resolve when a 2xx is received from + * the given URL, or if the wait is cancelled. */ -exports.waitForUrl = function(url, timeout) { - var client = new HttpClient(url), - request = new HttpRequest('GET', ''), - ready = promise.defer(), - start = Date.now(); - testUrl(); - return ready.promise; - - function testUrl() { - client.send(request).then(onResponse, onError); - } - - function onError() { - if (Date.now() - start > timeout) { - ready.reject(Error( - 'Timed out waiting for the URL to return 2xx: ' + url)); - } else { - setTimeout(function() { - if (ready.promise.isPending()) { - testUrl(); - } - }, 50); +exports.waitForUrl = function(url, timeout, opt_cancelToken) { + return new Promise((onResolve, onReject) => { + let client = new HttpClient(url); + let request = new HttpRequest('GET', ''); + let start = Date.now(); + + let done = false; + let resolve = () => { + done = true; + onResolve(); + }; + let reject = (err) => { + done = true; + onReject(err); + }; + + if (opt_cancelToken) { + opt_cancelToken.then(_ => reject(new promise.CancellationError)); + } + + testUrl(); + + function testUrl() { + client.send(request).then(onResponse, onError); + } + + function onError() { + if (Date.now() - start > timeout) { + reject(Error('Timed out waiting for the URL to return 2xx: ' + url)); + } else { + setTimeout(function() { + if (!done) { + testUrl(); + } + }, 50); + } } - } - function onResponse(response) { - if (!ready.promise.isPending()) return; - if (response.status > 199 && response.status < 300) { - return ready.fulfill(); + function onResponse(response) { + if (done) { + return; + } + if (response.status > 199 && response.status < 300) { + resolve(); + return; + } + onError(); } - onError(); - } + }); }; diff --git a/node_modules/selenium-webdriver/ie.js b/node_modules/selenium-webdriver/ie.js index 40095a0bf..5b86fa58e 100644 --- a/node_modules/selenium-webdriver/ie.js +++ b/node_modules/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,16 +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()); - - let boundQuit = this.quit.bind(this); - /** @override */ - this.quit = function() { - return boundQuit().finally(service.kill.bind(service)); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } /** diff --git a/node_modules/selenium-webdriver/index.js b/node_modules/selenium-webdriver/index.js index 47f825738..3cde7f396 100644 --- a/node_modules/selenium-webdriver/index.js +++ b/node_modules/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, @@ -134,6 +209,9 @@ function ensureFileDetectorsAreEnabled(ctor) { */ class Builder { constructor() { + /** @private @const */ + this.log_ = logging.getLogger('webdriver.Builder'); + /** @private {promise.ControlFlow} */ this.flow_ = null; @@ -465,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 @@ -482,6 +559,7 @@ class Builder { var browser; if (!this.ignoreEnv_ && process.env.SELENIUM_BROWSER) { + this.log_.fine(`SELENIUM_BROWSER=${process.env.SELENIUM_BROWSER}`); browser = process.env.SELENIUM_BROWSER.split(/:/, 3); capabilities.set(Capability.BROWSER_NAME, browser[0]); capabilities.set(Capability.VERSION, browser[1] || null); @@ -524,76 +602,65 @@ class Builder { let url = this.url_; if (!this.ignoreEnv_) { if (process.env.SELENIUM_REMOTE_URL) { + this.log_.fine( + `SELENIUM_REMOTE_URL=${process.env.SELENIUM_REMOTE_URL}`); url = process.env.SELENIUM_REMOTE_URL; } else if (process.env.SELENIUM_SERVER_JAR) { + this.log_.fine( + `SELENIUM_SERVER_JAR=${process.env.SELENIUM_SERVER_JAR}`); url = startSeleniumServer(process.env.SELENIUM_SERVER_JAR); } } if (url) { + this.log_.fine('Creating session on remote server'); let client = Promise.resolve(url) .then(url => new _http.HttpClient(url, this.agent_, this.proxy_)); let executor = new _http.Executor(client); 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.Promise} 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); - } } @@ -612,6 +679,8 @@ 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; exports.WebElementCondition = webdriver.WebElementCondition; diff --git a/node_modules/selenium-webdriver/lib/actions.js b/node_modules/selenium-webdriver/lib/actions.js index 7200b08d6..1b059bbbf 100644 --- a/node_modules/selenium-webdriver/lib/actions.js +++ b/node_modules/selenium-webdriver/lib/actions.js @@ -65,9 +65,12 @@ function checkModifierKey(key) { * Class for defining sequences of complex user interactions. Each sequence * will not be executed until {@link #perform} is called. * - * Example: + * This class should not be instantiated directly. Instead, obtain an instance + * using {@link ./webdriver.WebDriver#actions() WebDriver.actions()}. * - * new ActionSequence(driver). + * Sample usage: + * + * driver.actions(). * keyDown(Key.SHIFT). * click(element1). * click(element2). @@ -107,7 +110,7 @@ class ActionSequence { /** * Executes this action sequence. * - * @return {!./promise.Promise} A promise that will be resolved once + * @return {!./promise.Thenable} A promise that will be resolved once * this sequence has completed. */ perform() { @@ -117,9 +120,10 @@ class ActionSequence { let actions = this.actions_.concat(); let driver = this.driver_; return driver.controlFlow().execute(function() { - actions.forEach(function(action) { - driver.schedule(action.command, action.description); + let results = actions.map(action => { + return driver.schedule(action.command, action.description); }); + return Promise.all(results); }, 'ActionSequence.perform'); } @@ -377,9 +381,12 @@ class ActionSequence { * Class for defining sequences of user touch interactions. Each sequence * will not be executed until {@link #perform} is called. * - * Example: + * This class should not be instantiated directly. Instead, obtain an instance + * using {@link ./webdriver.WebDriver#touchActions() WebDriver.touchActions()}. + * + * Sample usage: * - * new TouchSequence(driver). + * driver.touchActions(). * tapAndHold({x: 0, y: 0}). * move({x: 3, y: 4}). * release({x: 10, y: 10}). @@ -415,7 +422,7 @@ class TouchSequence { /** * Executes this action sequence. - * @return {!./promise.Promise} A promise that will be resolved once + * @return {!./promise.Thenable} A promise that will be resolved once * this sequence has completed. */ perform() { @@ -425,9 +432,10 @@ class TouchSequence { let actions = this.actions_.concat(); let driver = this.driver_; return driver.controlFlow().execute(function() { - actions.forEach(function(action) { - driver.schedule(action.command, action.description); + let results = actions.map(action => { + return driver.schedule(action.command, action.description); }); + return Promise.all(results); }, 'TouchSequence.perform'); } diff --git a/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so b/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so index 916e530f3..248c32db5 100644 Binary files a/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so and b/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so differ diff --git a/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so b/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so index 8e7db8de3..004062c7b 100644 Binary files a/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so and b/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so differ diff --git a/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi b/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi index 39aca6b62..f9a51cf4f 100644 Binary files a/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi and b/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi differ diff --git a/node_modules/selenium-webdriver/lib/http.js b/node_modules/selenium-webdriver/lib/http.js index a5675f81f..68bc43213 100644 --- a/node_modules/selenium-webdriver/lib/http.js +++ b/node_modules/selenium-webdriver/lib/http.js @@ -25,7 +25,11 @@ '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'); @@ -110,13 +114,77 @@ class Response { } +const DEV_ROOT = '../../../../buck-out/gen/javascript/'; + +/** @enum {string} */ +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'), +}; + + +const ATOMS = /** !Map> */new Map(); +const LOG = logging.getLogger('webdriver.http'); + +/** + * @param {Atom} file The atom file to load. + * @return {!Promise} 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 */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); } function get(path) { return resource('GET', path); } function resource(method, path) { return {method: method, path: path}; } -/** @const {!Map} */ +/** @typedef {{method: string, path: string}} */ +var CommandSpec; + + +/** @typedef {function(!cmd.Command): !Promise} */ +var CommandTransformer; + + +/** + * @param {!cmd.Command} command The initial command. + * @param {Atom} atom The name of the atom to execute. + * @return {!Promise} The transformed command to execute. + */ +function toExecuteAtomCommand(command, atom, ...params) { + return loadAtom(atom).then(atom => { + return 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))); + }); +} + + + +/** @const {!Map} */ const COMMAND_MAP = new Map([ [cmd.Name.GET_SERVER_STATUS, get('/status')], [cmd.Name.NEW_SESSION, post('/session')], @@ -195,8 +263,15 @@ const COMMAND_MAP = new Map([ ]); -/** @const {!Map} */ +/** @const {!Map} */ const W3C_COMMAND_MAP = new Map([ + [cmd.Name.GET_ACTIVE_ELEMENT, get('/session/:sessionId/element/active')], + [cmd.Name.GET_ELEMENT_ATTRIBUTE, (cmd) => { + return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name'); + }], + [cmd.Name.IS_ELEMENT_DISPLAYED, (cmd) => { + return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id'); + }], [cmd.Name.MAXIMIZE_WINDOW, post('/session/:sessionId/window/maximize')], [cmd.Name.GET_WINDOW_POSITION, get('/session/:sessionId/window/position')], [cmd.Name.SET_WINDOW_POSITION, post('/session/:sessionId/window/position')], @@ -248,6 +323,53 @@ function doSend(executor, request) { } +/** + * @param {Map} customCommands + * A map of custom command definitions. + * @param {boolean} w3c Whether to use W3C command mappings. + * @param {!cmd.Command} command The command to resolve. + * @return {!Promise} A promise that will resolve with the + * command to execute. + */ +function buildRequest(customCommands, w3c, command) { + LOG.finest(() => `Translating command: ${command.getName()}`); + let spec = customCommands && customCommands.get(command.getName()); + if (spec) { + return toHttpRequest(spec); + } + + if (w3c) { + spec = W3C_COMMAND_MAP.get(command.getName()); + if (typeof spec === 'function') { + LOG.finest(() => `Transforming command for W3C: ${command.getName()}`); + return spec(command) + .then(newCommand => buildRequest(customCommands, w3c, newCommand)); + } else if (spec) { + return toHttpRequest(spec); + } + } + + spec = COMMAND_MAP.get(command.getName()); + if (spec) { + return toHttpRequest(spec); + } + return Promise.reject( + new error.UnknownCommandError( + 'Unrecognized command: ' + command.getName())); + + /** + * @param {CommandSpec} resource + * @return {!Promise} + */ + function toHttpRequest(resource) { + LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`); + let parameters = command.getParameters(); + let path = buildPath(resource.path, parameters); + return Promise.resolve(new Request(resource.method, path, parameters)); + } +} + + /** * A command executor that communicates with the server using JSON over HTTP. * @@ -279,7 +401,7 @@ class Executor { */ this.w3c = false; - /** @private {Map} */ + /** @private {Map} */ this.customCommands_ = null; /** @private {!logging.Logger} */ @@ -308,51 +430,40 @@ class Executor { /** @override */ execute(command) { - let resource = - (this.customCommands_ && this.customCommands_.get(command.getName())) - || (this.w3c && W3C_COMMAND_MAP.get(command.getName())) - || COMMAND_MAP.get(command.getName()); - if (!resource) { - throw new error.UnknownCommandError( - 'Unrecognized command: ' + command.getName()); - } - - let parameters = command.getParameters(); - let path = buildPath(resource.path, parameters); - let request = new Request(resource.method, path, parameters); - - let log = this.log_; - log.finer(() => '>>>\n' + request); - return doSend(this, request).then(response => { - log.finer(() => '<<<\n' + response); - - let parsed = - parseHttpResponse(/** @type {!Response} */ (response), this.w3c); - - if (command.getName() === cmd.Name.NEW_SESSION - || command.getName() === cmd.Name.DESCRIBE_SESSION) { - if (!parsed || !parsed['sessionId']) { - throw new error.WebDriverError( - 'Unable to parse new session response: ' + response.body); + let request = buildRequest(this.customCommands_, this.w3c, command); + return request.then(request => { + this.log_.finer(() => `>>> ${request.method} ${request.path}`); + return doSend(this, request).then(response => { + this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`); + + let parsed = + parseHttpResponse(/** @type {!Response} */ (response), this.w3c); + + if (command.getName() === cmd.Name.NEW_SESSION + || command.getName() === cmd.Name.DESCRIBE_SESSION) { + if (!parsed || !parsed['sessionId']) { + throw new error.WebDriverError( + 'Unable to parse new session response: ' + response.body); + } + + // 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 + // command, which is not defined in the W3C spec. + if (command.getName() === cmd.Name.NEW_SESSION) { + this.w3c = this.w3c || !('status' in parsed); + } + + return new Session(parsed['sessionId'], parsed['value']); } - // 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 - // command, which is not defined in the W3C spec. - if (command.getName() === cmd.Name.NEW_SESSION) { - this.w3c = this.w3c || !('status' in parsed); + if (parsed + && typeof parsed === 'object' + && 'value' in parsed) { + let value = parsed['value']; + return typeof value === 'undefined' ? null : value; } - - return new Session(parsed['sessionId'], parsed['value']); - } - - if (parsed - && typeof parsed === 'object' - && 'value' in parsed) { - let value = parsed['value']; - return typeof value === 'undefined' ? null : value; - } - return parsed; + return parsed; + }); }); } } diff --git a/node_modules/selenium-webdriver/lib/logging.js b/node_modules/selenium-webdriver/lib/logging.js index 97f54924c..634a5cbc8 100644 --- a/node_modules/selenium-webdriver/lib/logging.js +++ b/node_modules/selenium-webdriver/lib/logging.js @@ -521,8 +521,14 @@ function getLogger(name) { } +/** + * Pads a number to ensure it has a minimum of two digits. + * + * @param {number} n the number to be padded. + * @return {string} the padded number. + */ function pad(n) { - if (n > 10) { + if (n >= 10) { return '' + n; } else { return '0' + n; diff --git a/node_modules/selenium-webdriver/lib/promise.js b/node_modules/selenium-webdriver/lib/promise.js index b98e7cf2e..32d0c98e6 100644 --- a/node_modules/selenium-webdriver/lib/promise.js +++ b/node_modules/selenium-webdriver/lib/promise.js @@ -17,6 +17,42 @@ /** * @fileoverview + * + * > ### IMPORTANT NOTICE + * > + * > The promise manager contained in this module is in the process of being + * > phased out in favor of native JavaScript promises. This will be a long + * > process and will not be completed until there have been two major LTS Node + * > releases (approx. Node v10.0) that support + * > [async functions](https://tc39.github.io/ecmascript-asyncawait/). + * > + * > At this time, the promise manager can be disabled by setting an environment + * > variable, `SELENIUM_PROMISE_MANAGER=0`. In the absence of async functions, + * > users may use generators with the + * > {@link ./promise.consume promise.consume()} function to write "synchronous" + * > style tests: + * > + * > ```js + * > const {Builder, By, promise, until} = require('selenium-webdriver'); + * > + * > let result = promise.consume(function* doGoogleSearch() { + * > let driver = new Builder().forBrowser('firefox').build(); + * > yield driver.get('http://www.google.com/ncr'); + * > yield driver.findElement(By.name('q')).sendKeys('webdriver'); + * > yield driver.findElement(By.name('btnG')).click(); + * > yield driver.wait(until.titleIs('webdriver - Google Search'), 1000); + * > yield driver.quit(); + * > }); + * > + * > result.then(_ => console.log('SUCCESS!'), + * > e => console.error('FAILURE: ' + e)); + * > ``` + * > + * > The motiviation behind this change and full deprecation plan are documented + * > in [issue 2969](https://github.com/SeleniumHQ/selenium/issues/2969). + * > + * > + * * The promise module is centered around the {@linkplain ControlFlow}, a class * that coordinates the execution of asynchronous tasks. The ControlFlow allows * users to focus on the imperative commands for their script without worrying @@ -592,6 +628,7 @@ 'use strict'; +const error = require('./error'); const events = require('./events'); const logging = require('./logging'); @@ -643,7 +680,6 @@ function asyncRun(fn) { }); } - /** * @param {number} level What level of verbosity to log with. * @param {(string|function(this: T): string)} loggable The message to log. @@ -799,32 +835,56 @@ class MultipleUnhandledRejectionError extends Error { * @const */ const IMPLEMENTED_BY_SYMBOL = Symbol('promise.Thenable'); +const CANCELLABLE_SYMBOL = Symbol('promise.CancellableThenable'); + + +/** + * @param {function(new: ?)} ctor + * @param {!Object} symbol + */ +function addMarkerSymbol(ctor, symbol) { + try { + ctor.prototype[symbol] = true; + } catch (ignored) { + // Property access denied? + } +} + + +/** + * @param {*} object + * @param {!Object} symbol + * @return {boolean} + */ +function hasMarkerSymbol(object, symbol) { + if (!object) { + return false; + } + try { + return !!object[symbol]; + } catch (e) { + return false; // Property access seems to be forbidden. + } +} /** * Thenable is a promise-like object with a {@code then} method which may be * used to schedule callbacks on a promised value. * - * @interface + * @record * @extends {IThenable} * @template T */ class Thenable { /** * Adds a property to a class prototype to allow runtime checks of whether - * instances of that class implement the Thenable interface. This function - * will also ensure the prototype's {@code then} function is exported from - * compiled code. + * instances of that class implement the Thenable interface. * @param {function(new: Thenable, ...?)} ctor The * constructor whose prototype to modify. */ static addImplementation(ctor) { - ctor.prototype['then'] = ctor.prototype.then; - try { - ctor.prototype[IMPLEMENTED_BY_SYMBOL] = true; - } catch (ignored) { - // Property access denied? - } + addMarkerSymbol(ctor, IMPLEMENTED_BY_SYMBOL); } /** @@ -835,29 +895,9 @@ class Thenable { * interface. */ static isImplementation(object) { - if (!object) { - return false; - } - try { - return !!object[IMPLEMENTED_BY_SYMBOL]; - } catch (e) { - return false; // Property access seems to be forbidden. - } + return hasMarkerSymbol(object, IMPLEMENTED_BY_SYMBOL); } - /** - * Cancels the computation of this promise's value, rejecting the promise in - * the process. This method is a no-op if the promise has already been - * resolved. - * - * @param {(string|Error)=} opt_reason The reason this promise is being - * cancelled. This value will be wrapped in a {@link CancellationError}. - */ - cancel(opt_reason) {} - - /** @return {boolean} Whether this promise's value is still being computed. */ - isPending() {} - /** * Registers listeners for when this instance is resolved. * @@ -867,8 +907,8 @@ class Thenable { * @param {?(function(*): (R|IThenable))=} opt_errback * The function to call if this promise is rejected. The function should * expect a single argument: the rejection reason. - * @return {!ManagedPromise} A new promise which will be - * resolved with the result of the invoked callback. + * @return {!Thenable} A new promise which will be resolved with the result + * of the invoked callback. * @template R */ then(opt_callback, opt_errback) {} @@ -892,49 +932,53 @@ class Thenable { * @param {function(*): (R|IThenable)} errback The * function to call if this promise is rejected. The function should * expect a single argument: the rejection reason. - * @return {!ManagedPromise} A new promise which will be - * resolved with the result of the invoked callback. + * @return {!Thenable} A new promise which will be resolved with the result + * of the invoked callback. * @template R */ catch(errback) {} +} + +/** + * Marker interface for objects that allow consumers to request the cancellation + * of a promies-based operation. A cancelled promise will be rejected with a + * {@link CancellationError}. + * + * This interface is considered package-private and should not be used outside + * of selenium-webdriver. + * + * @interface + * @extends {Thenable} + * @template T + * @package + */ +class CancellableThenable { /** - * Registers a listener to invoke when this promise is resolved, regardless - * of whether the promise's value was successfully computed. This function - * is synonymous with the {@code finally} clause in a synchronous API: - * - * // Synchronous API: - * try { - * doSynchronousWork(); - * } finally { - * cleanUp(); - * } - * - * // Asynchronous promise API: - * doAsynchronousWork().finally(cleanUp); - * - * __Note:__ similar to the {@code finally} clause, if the registered - * callback returns a rejected promise or throws an error, it will silently - * replace the rejection error (if any) from this promise: - * - * try { - * throw Error('one'); - * } finally { - * throw Error('two'); // Hides Error: one - * } - * - * promise.rejected(Error('one')) - * .finally(function() { - * throw Error('two'); // Hides Error: one - * }); + * @param {function(new: CancellableThenable, ...?)} ctor + */ + static addImplementation(ctor) { + Thenable.addImplementation(ctor); + addMarkerSymbol(ctor, CANCELLABLE_SYMBOL); + } + + /** + * @param {*} object + * @return {boolean} + */ + static isImplementation(object) { + return hasMarkerSymbol(object, CANCELLABLE_SYMBOL); + } + + /** + * Requests the cancellation of the computation of this promise's value, + * rejecting the promise in the process. This method is a no-op if the promise + * has already been resolved. * - * @param {function(): (R|IThenable)} callback The function to call when - * this promise is resolved. - * @return {!ManagedPromise} A promise that will be fulfilled - * with the callback result. - * @template R + * @param {(string|Error)=} opt_reason The reason this promise is being + * cancelled. This value will be wrapped in a {@link CancellationError}. */ - finally(callback) {} + cancel(opt_reason) {} } @@ -967,7 +1011,7 @@ const ON_CANCEL_HANDLER = new WeakMap; * fulfilled or rejected state, at which point the promise is considered * resolved. * - * @implements {Thenable} + * @implements {CancellableThenable} * @template T * @see http://promises-aplus.github.io/promises-spec/ */ @@ -983,6 +1027,12 @@ class ManagedPromise { * this instance was created under. Defaults to the currently active flow. */ constructor(resolver, opt_flow) { + 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']); + } getUid(this); /** @private {!ControlFlow} */ @@ -1024,6 +1074,30 @@ class ManagedPromise { } } + /** + * Creates a promise that is immediately resolved with the given value. + * + * @param {T=} opt_value The value to resolve. + * @return {!ManagedPromise} A promise resolved with the given value. + * @template T + */ + static resolve(opt_value) { + if (opt_value instanceof ManagedPromise) { + return opt_value; + } + return new ManagedPromise(resolve => resolve(opt_value)); + } + + /** + * Creates a promise that is immediately rejected with the given reason. + * + * @param {*=} opt_reason The rejection reason. + * @return {!ManagedPromise} A new rejected promise. + */ + static reject(opt_reason) { + return new ManagedPromise((_, reject) => reject(opt_reason)); + } + /** @override */ toString() { return 'ManagedPromise::' + getUid(this) + @@ -1176,7 +1250,7 @@ class ManagedPromise { } if (this.parent_ && canCancel(this.parent_)) { - this.parent_.cancel(opt_reason); + /** @type {!CancellableThenable} */(this.parent_).cancel(opt_reason); } else { var reason = CancellationError.wrap(opt_reason); let onCancel = ON_CANCEL_HANDLER.get(this); @@ -1194,18 +1268,13 @@ class ManagedPromise { function canCancel(promise) { if (!(promise instanceof ManagedPromise)) { - return Thenable.isImplementation(promise); + return CancellableThenable.isImplementation(promise); } return promise.state_ === PromiseState.PENDING || promise.state_ === PromiseState.BLOCKED; } } - /** @override */ - isPending() { - return this.state_ === PromiseState.PENDING; - } - /** @override */ then(opt_callback, opt_errback) { return this.addCallback_( @@ -1218,21 +1287,15 @@ class ManagedPromise { null, errback, 'catch', ManagedPromise.prototype.catch); } - /** @override */ + /** + * @param {function(): (R|IThenable)} callback + * @return {!ManagedPromise} + * @template R + * @see ./promise.finally() + */ finally(callback) { - var error; - var mustThrow = false; - return this.then(function() { - return callback(); - }, function(err) { - error = err; - mustThrow = true; - return callback(); - }).then(function() { - if (mustThrow) { - throw error; - } - }); + let result = thenFinally(this, callback); + return /** @type {!ManagedPromise} */(result); } /** @@ -1308,7 +1371,16 @@ class ManagedPromise { } } } -Thenable.addImplementation(ManagedPromise); +CancellableThenable.addImplementation(ManagedPromise); + + +/** + * @param {!ManagedPromise} promise + * @return {boolean} + */ +function isPending(promise) { + return promise.state_ === PromiseState.PENDING; +} /** @@ -1405,19 +1477,11 @@ function isPromise(value) { * Creates a promise that will be resolved at a set time in the future. * @param {number} ms The amount of time, in milliseconds, to wait before * resolving the promise. - * @return {!ManagedPromise} The promise. + * @return {!Thenable} The promise. */ function delayed(ms) { - var key; - return new ManagedPromise(function(fulfill) { - key = setTimeout(function() { - key = null; - fulfill(); - }, ms); - }).catch(function(e) { - clearTimeout(key); - key = null; - throw e; + return createPromise(resolve => { + setTimeout(() => resolve(), ms); }); } @@ -1436,15 +1500,11 @@ function defer() { * Creates a promise that has been resolved with the given value. * @param {T=} opt_value The resolved value. * @return {!ManagedPromise} The resolved promise. + * @deprecated Use {@link ManagedPromise#resolve Promise.resolve(value)}. * @template T */ function fulfilled(opt_value) { - if (opt_value instanceof ManagedPromise) { - return opt_value; - } - return new ManagedPromise(function(fulfill) { - fulfill(opt_value); - }); + return ManagedPromise.resolve(opt_value); } @@ -1452,16 +1512,11 @@ function fulfilled(opt_value) { * Creates a promise that has been rejected with the given reason. * @param {*=} opt_reason The rejection reason; may be any value, but is * usually an Error or a string. - * @return {!ManagedPromise} The rejected promise. - * @template T + * @return {!ManagedPromise} The rejected promise. + * @deprecated Use {@link ManagedPromise#reject Promise.reject(reason)}. */ function rejected(opt_reason) { - if (opt_reason instanceof ManagedPromise) { - return opt_reason; - } - return new ManagedPromise(function(_, reject) { - reject(opt_reason); - }); + return ManagedPromise.reject(opt_reason); } @@ -1474,12 +1529,12 @@ function rejected(opt_reason) { * @param {!Function} fn The function to wrap. * @param {...?} var_args The arguments to apply to the function, excluding the * final callback. - * @return {!ManagedPromise} A promise that will be resolved with the + * @return {!Thenable} A promise that will be resolved with the * result of the provided function's callback. */ function checkedNodeCall(fn, var_args) { let args = Array.prototype.slice.call(arguments, 1); - return new ManagedPromise(function(fulfill, reject) { + return createPromise(function(fulfill, reject) { try { args.push(function(error, value) { error ? reject(error) : fulfill(value); @@ -1491,6 +1546,59 @@ function checkedNodeCall(fn, var_args) { }); } +/** + * Registers a listener to invoke when a promise is resolved, regardless + * of whether the promise's value was successfully computed. This function + * is synonymous with the {@code finally} clause in a synchronous API: + * + * // Synchronous API: + * try { + * doSynchronousWork(); + * } finally { + * cleanUp(); + * } + * + * // Asynchronous promise API: + * doAsynchronousWork().finally(cleanUp); + * + * __Note:__ similar to the {@code finally} clause, if the registered + * callback returns a rejected promise or throws an error, it will silently + * replace the rejection error (if any) from this promise: + * + * try { + * throw Error('one'); + * } finally { + * throw Error('two'); // Hides Error: one + * } + * + * let p = Promise.reject(Error('one')); + * promise.finally(p, function() { + * throw Error('two'); // Hides Error: one + * }); + * + * @param {!IThenable} promise The promise to add the listener to. + * @param {function(): (R|IThenable)} callback The function to call when + * the promise is resolved. + * @return {!IThenable} A promise that will be resolved with the callback + * result. + * @template R + */ +function thenFinally(promise, callback) { + let error; + let mustThrow = false; + return promise.then(function() { + return callback(); + }, function(err) { + error = err; + mustThrow = true; + return callback(); + }).then(function() { + if (mustThrow) { + throw error; + } + }); +} + /** * Registers an observer on a promised {@code value}, returning a new promise @@ -1501,16 +1609,15 @@ function checkedNodeCall(fn, var_args) { * resolved successfully. * @param {Function=} opt_errback The function to call when the value is * rejected. - * @return {!ManagedPromise} A new promise. + * @return {!Thenable} A new promise. */ function when(value, opt_callback, opt_errback) { if (Thenable.isImplementation(value)) { return value.then(opt_callback, opt_errback); } - return new ManagedPromise(function(fulfill) { - fulfill(value); - }).then(opt_callback, opt_errback); + return createPromise(resolve => resolve(value)) + .then(opt_callback, opt_errback); } @@ -1542,14 +1649,14 @@ function asap(value, callback, opt_errback) { * * @param {!Array<(T|!ManagedPromise)>} arr An array of * promises to wait on. - * @return {!ManagedPromise>} A promise that is + * @return {!Thenable>} A promise that is * fulfilled with an array containing the fulfilled values of the * input array, or rejected with the same reason as the first * rejected value. * @template T */ function all(arr) { - return new ManagedPromise(function(fulfill, reject) { + return createPromise(function(fulfill, reject) { var n = arr.length; var values = []; @@ -1603,12 +1710,12 @@ function all(arr) { * @template TYPE, SELF */ function map(arr, fn, opt_self) { - return fulfilled(arr).then(function(v) { + return createPromise(resolve => resolve(arr)).then(v => { if (!Array.isArray(v)) { throw TypeError('not an array'); } var arr = /** @type {!Array} */(v); - return new ManagedPromise(function(fulfill, reject) { + return createPromise(function(fulfill, reject) { var n = arr.length; var values = new Array(n); (function processNext(i) { @@ -1661,12 +1768,12 @@ function map(arr, fn, opt_self) { * @template TYPE, SELF */ function filter(arr, fn, opt_self) { - return fulfilled(arr).then(function(v) { + return createPromise(resolve => resolve(arr)).then(v => { if (!Array.isArray(v)) { throw TypeError('not an array'); } var arr = /** @type {!Array} */(v); - return new ManagedPromise(function(fulfill, reject) { + return createPromise(function(fulfill, reject) { var n = arr.length; var values = []; var valuesLength = 0; @@ -1714,7 +1821,7 @@ function filter(arr, fn, opt_self) { * promise.fullyResolved(value); // Stack overflow. * * @param {*} value The value to fully resolve. - * @return {!ManagedPromise} A promise for a fully resolved version + * @return {!Thenable} A promise for a fully resolved version * of the input value. */ function fullyResolved(value) { @@ -1728,7 +1835,7 @@ function fullyResolved(value) { /** * @param {*} value The value to fully resolve. If a promise, assumed to * already be resolved. - * @return {!ManagedPromise} A promise for a fully resolved version + * @return {!Thenable} A promise for a fully resolved version * of the input value. */ function fullyResolveValue(value) { @@ -1755,13 +1862,13 @@ function fullyResolveValue(value) { return fullyResolveKeys(/** @type {!Object} */ (value)); } - return fulfilled(value); + return createPromise(resolve => resolve(value)); } /** * @param {!(Array|Object)} obj the object to resolve. - * @return {!ManagedPromise} A promise that will be resolved with the + * @return {!Thenable} A promise that will be resolved with the * input object once all of its values have been fully resolved. */ function fullyResolveKeys(obj) { @@ -1773,8 +1880,9 @@ function fullyResolveKeys(obj) { } return n; })(); + if (!numKeys) { - return fulfilled(obj); + return createPromise(resolve => resolve(obj)); } function forEachProperty(obj, fn) { @@ -1788,7 +1896,7 @@ function fullyResolveKeys(obj) { } var numResolved = 0; - return new ManagedPromise(function(fulfill, reject) { + return createPromise(function(fulfill, reject) { var forEachKey = isArray ? forEachElement: forEachProperty; forEachKey(obj, function(partialValue, key) { @@ -1822,6 +1930,236 @@ function fullyResolveKeys(obj) { ////////////////////////////////////////////////////////////////////////////// +/** + * Defines methods for coordinating the execution of asynchronous tasks. + * @record + */ +class Scheduler { + /** + * Schedules a task for execution. If the task function is a generator, the + * task will be executed using {@link ./promise.consume consume()}. + * + * @param {function(): (T|IThenable)} fn The function to call to start the + * task. + * @param {string=} opt_description A description of the task for debugging + * purposes. + * @return {!Thenable} A promise that will be resolved with the task + * result. + * @template T + */ + execute(fn, opt_description) {} + + /** + * Creates a new promise using the given resolver function. + * + * @param {function( + * function((T|IThenable|Thenable|null)=), + * function(*=))} resolver + * @return {!Thenable} + * @template T + */ + promise(resolver) {} + + /** + * Schedules a `setTimeout` call. + * + * @param {number} ms The timeout delay, in milliseconds. + * @param {string=} opt_description A description to accompany the timeout. + * @return {!Thenable} A promise that will be resolved when the timeout + * fires. + */ + timeout(ms, opt_description) {} + + /** + * Schedules a task to wait for a condition to hold. + * + * If the condition is defined as a function, it may return any value. Promies + * will be resolved before testing if the condition holds (resolution time + * counts towards the timeout). Once resolved, values are always evaluated as + * booleans. + * + * If the condition function throws, or returns a rejected promise, the + * wait task will fail. + * + * If the condition is defined as a promise, the scheduler will wait for it to + * settle. If the timeout expires before the promise settles, the promise + * returned by this function will be rejected. + * + * If this function is invoked with `timeout === 0`, or the timeout is + * omitted, this scheduler will wait indefinitely for the condition to be + * satisfied. + * + * @param {(!IThenable|function())} condition The condition to poll, + * or a promise to wait on. + * @param {number=} opt_timeout How long to wait, in milliseconds, for the + * condition to hold before timing out. If omitted, the flow will wait + * indefinitely. + * @param {string=} opt_message An optional error message to include if the + * wait times out; defaults to the empty string. + * @return {!Thenable} A promise that will be fulfilled + * when the condition has been satisified. 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. + * @template T + */ + wait(condition, opt_timeout, opt_message) {} +} + + +let USE_PROMISE_MANAGER; +function usePromiseManager() { + if (typeof USE_PROMISE_MANAGER !== 'undefined') { + return !!USE_PROMISE_MANAGER; + } + return process.env['SELENIUM_PROMISE_MANAGER'] === undefined + || !/^0|false$/i.test(process.env['SELENIUM_PROMISE_MANAGER']); +} + + +/** + * @param {function( + * function((T|IThenable|Thenable|null)=), + * function(*=))} resolver + * @return {!Thenable} + * @template T + */ +function createPromise(resolver) { + let ctor = usePromiseManager() ? ManagedPromise : NativePromise; + return new ctor(resolver); +} + + +/** + * @param {!Scheduler} scheduler The scheduler to use. + * @param {(!IThenable|function())} condition The condition to poll, + * or a promise to wait on. + * @param {number=} opt_timeout How long to wait, in milliseconds, for the + * condition to hold before timing out. If omitted, the flow will wait + * indefinitely. + * @param {string=} opt_message An optional error message to include if the + * wait times out; defaults to the empty string. + * @return {!Thenable} A promise that will be fulfilled + * when the condition has been satisified. 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. + * @template T + */ +function scheduleWait(scheduler, condition, opt_timeout, opt_message) { + let timeout = opt_timeout || 0; + if (typeof timeout !== 'number' || timeout < 0) { + throw TypeError('timeout must be a number >= 0: ' + timeout); + } + + if (isPromise(condition)) { + return scheduler.execute(function() { + if (!timeout) { + return condition; + } + return scheduler.promise(function(fulfill, reject) { + let start = Date.now(); + let timer = setTimeout(function() { + timer = null; + reject( + new error.TimeoutError( + (opt_message ? opt_message + '\n' : '') + + 'Timed out waiting for promise to resolve after ' + + (Date.now() - start) + 'ms')); + }, timeout); + + /** @type {Thenable} */(condition).then( + function(value) { + timer && clearTimeout(timer); + fulfill(value); + }, + function(error) { + timer && clearTimeout(timer); + reject(error); + }); + }); + }, opt_message || ''); + } + + if (typeof condition !== 'function') { + throw TypeError('Invalid condition; must be a function or promise: ' + + typeof condition); + } + + if (isGenerator(condition)) { + let original = condition; + condition = () => consume(original); + } + + return scheduler.execute(function() { + var startTime = Date.now(); + return scheduler.promise(function(fulfill, reject) { + pollCondition(); + + function pollCondition() { + var conditionFn = /** @type {function()} */(condition); + scheduler.execute(conditionFn).then(function(value) { + var elapsed = Date.now() - startTime; + if (!!value) { + fulfill(value); + } else if (timeout && elapsed >= timeout) { + reject( + new error.TimeoutError( + (opt_message ? opt_message + '\n' : '') + + `Wait timed out after ${elapsed}ms`)); + } else { + // Do not use asyncRun here because we need a non-micro yield + // here so the UI thread is given a chance when running in a + // browser. + setTimeout(pollCondition, 0); + } + }, reject); + } + }); + }, opt_message || ''); +} + + +/** + * A scheduler that executes all tasks immediately, with no coordination. This + * class is an event emitter for API compatibility with the {@link ControlFlow}, + * however, it emits no events. + * + * @implements {Scheduler} + */ +class SimpleScheduler extends events.EventEmitter { + /** @override */ + execute(fn) { + return this.promise((resolve, reject) => { + try { + if (isGenerator(fn)) { + consume(fn).then(resolve, reject); + } else { + resolve(fn.call(undefined)); + } + } catch (ex) { + reject(ex); + } + }); + } + + /** @override */ + promise(resolver) { + return new NativePromise(resolver); + } + + /** @override */ + timeout(ms) { + return this.promise(resolve => setTimeout(_ => resolve(), ms)); + } + + /** @override */ + wait(condition, opt_timeout, opt_message) { + return scheduleWait(this, condition, opt_timeout, opt_message); + } +} +const SIMPLE_SCHEDULER = new SimpleScheduler; + /** * Handles the execution of scheduled tasks, each of which may be an @@ -1848,13 +2186,20 @@ function fullyResolveKeys(obj) { * 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 fora detailed + * Refer to the {@link ./promise} module documentation for a detailed * explanation of how the ControlFlow coordinates task execution. * + * @implements {Scheduler} * @final */ class ControlFlow extends events.EventEmitter { constructor() { + if (!usePromiseManager()) { + throw TypeError( + 'Cannot instantiate control flow when the promise manager has' + + ' been disabled'); + } + super(); /** @private {boolean} */ @@ -2024,21 +2369,7 @@ class ControlFlow extends events.EventEmitter { return this.activeQueue_; } - /** - * Schedules a task for execution. If there is nothing currently in the - * queue, the task will be executed in the next turn of the event loop. If - * the task function is a generator, the task will be executed using - * {@link ./promise.consume consume()}. - * - * @param {function(): (T|ManagedPromise)} fn The function to - * call to start the task. If the function returns a - * {@link ManagedPromise}, this instance will wait for it to be - * resolved before starting the next task. - * @param {string=} opt_description A description of the task. - * @return {!ManagedPromise} A promise that will be resolved - * with the result of the action. - * @template T - */ + /** @override */ execute(fn, opt_description) { if (isGenerator(fn)) { let original = fn; @@ -2060,126 +2391,21 @@ class ControlFlow extends events.EventEmitter { return task.promise; } - /** - * Inserts a {@code setTimeout} into the command queue. This is equivalent to - * a thread sleep in a synchronous programming language. - * - * @param {number} ms The timeout delay, in milliseconds. - * @param {string=} opt_description A description to accompany the timeout. - * @return {!ManagedPromise} A promise that will be resolved with - * the result of the action. - */ + /** @override */ + promise(resolver) { + return new ManagedPromise(resolver, this); + } + + /** @override */ timeout(ms, opt_description) { - return this.execute(function() { - return delayed(ms); + return this.execute(() => { + return this.promise(resolve => setTimeout(() => resolve(), ms)); }, opt_description); } - /** - * Schedules a task that shall wait for a condition to hold. Each condition - * function may return any value, but it will always be evaluated as a - * boolean. - * - * Condition functions may schedule sub-tasks with this instance, however, - * their execution time will be factored into whether a wait has timed out. - * - * In the event a condition returns a ManagedPromise, the polling loop will wait for - * it to be resolved before evaluating whether the condition has been - * satisfied. The resolution time for a promise is factored into whether a - * wait has timed out. - * - * If the condition function throws, or returns a rejected promise, the - * wait task will fail. - * - * If the condition is defined as a promise, the flow will wait for it to - * settle. If the timeout expires before the promise settles, the promise - * returned by this function will be rejected. - * - * If this function is invoked with `timeout === 0`, or the timeout is - * omitted, the flow will wait indefinitely for the condition to be satisfied. - * - * @param {(!ManagedPromise|function())} condition The condition to poll, - * or a promise to wait on. - * @param {number=} opt_timeout How long to wait, in milliseconds, for the - * condition to hold before timing out. If omitted, the flow will wait - * indefinitely. - * @param {string=} opt_message An optional error message to include if the - * wait times out; defaults to the empty string. - * @return {!ManagedPromise} A promise that will be fulfilled - * when the condition has been satisified. 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. - * @template T - */ + /** @override */ wait(condition, opt_timeout, opt_message) { - var timeout = opt_timeout || 0; - if (typeof timeout !== 'number' || timeout < 0) { - throw TypeError('timeout must be a number >= 0: ' + timeout); - } - - if (isPromise(condition)) { - return this.execute(function() { - if (!timeout) { - return condition; - } - return new ManagedPromise(function(fulfill, reject) { - var start = Date.now(); - var timer = setTimeout(function() { - timer = null; - reject(Error((opt_message ? opt_message + '\n' : '') + - 'Timed out waiting for promise to resolve after ' + - (Date.now() - start) + 'ms')); - }, timeout); - - /** @type {Thenable} */(condition).then( - function(value) { - timer && clearTimeout(timer); - fulfill(value); - }, - function(error) { - timer && clearTimeout(timer); - reject(error); - }); - }); - }, opt_message || ''); - } - - if (typeof condition !== 'function') { - throw TypeError('Invalid condition; must be a function or promise: ' + - typeof condition); - } - - if (isGenerator(condition)) { - let original = condition; - condition = () => consume(original); - } - - var self = this; - return this.execute(function() { - var startTime = Date.now(); - return new ManagedPromise(function(fulfill, reject) { - pollCondition(); - - function pollCondition() { - var conditionFn = /** @type {function()} */(condition); - self.execute(conditionFn).then(function(value) { - var elapsed = Date.now() - startTime; - if (!!value) { - fulfill(value); - } else if (timeout && elapsed >= timeout) { - reject(new Error((opt_message ? opt_message + '\n' : '') + - 'Wait timed out after ' + elapsed + 'ms')); - } else { - // Do not use asyncRun here because we need a non-micro yield - // here so the UI thread is given a chance when running in a - // browser. - setTimeout(pollCondition, 0); - } - }, reject); - } - }); - }, opt_message || ''); + return scheduleWait(this, condition, opt_timeout, opt_message); } /** @@ -2510,6 +2736,9 @@ class TaskQueue extends events.EventEmitter { /** @private {({task: !Task, q: !TaskQueue}|null)} */ this.pending_ = null; + /** @private {TaskQueue} */ + this.subQ_ = null; + /** @private {TaskQueueState} */ this.state_ = TaskQueueState.NEW; @@ -2690,7 +2919,7 @@ class TaskQueue extends events.EventEmitter { var task; do { task = this.getNextTask_(); - } while (task && !task.promise.isPending()); + } while (task && !isPending(task.promise)); if (!task) { this.state_ = TaskQueueState.FINISHED; @@ -2701,20 +2930,30 @@ class TaskQueue extends events.EventEmitter { return; } - var self = this; - var subQ = new TaskQueue(this.flow_); - subQ.once('end', () => self.onTaskComplete_(result)) - .once('error', (e) => self.onTaskFailure_(result, e)); - vlog(2, () => self + ' created ' + subQ + ' for ' + task); + let result = undefined; + this.subQ_ = new TaskQueue(this.flow_); + + this.subQ_.once('end', () => { // On task completion. + this.subQ_ = null; + this.pending_ && this.pending_.task.fulfill(result); + }); + + this.subQ_.once('error', e => { // On task failure. + this.subQ_ = null; + if (Thenable.isImplementation(result)) { + result.cancel(CancellationError.wrap(e)); + } + this.pending_ && this.pending_.task.reject(e); + }); + vlog(2, () => `${this} created ${this.subQ_} for ${task}`); - var result = undefined; try { - this.pending_ = {task: task, q: subQ}; + this.pending_ = {task: task, q: this.subQ_}; task.promise.queue_ = this; - result = subQ.execute_(task.execute); - subQ.start(); + result = this.subQ_.execute_(task.execute); + this.subQ_.start(); } catch (ex) { - subQ.abort_(ex); + this.subQ_.abort_(ex); } } @@ -2804,28 +3043,6 @@ class TaskQueue extends events.EventEmitter { } } - /** - * @param {*} value the value originally returned by the task function. - * @private - */ - onTaskComplete_(value) { - if (this.pending_) { - this.pending_.task.fulfill(value); - } - } - - /** - * @param {*} taskFnResult the value originally returned by the task function. - * @param {*} error the error that caused the task function to terminate. - * @private - */ - onTaskFailure_(taskFnResult, error) { - if (Thenable.isImplementation(taskFnResult)) { - taskFnResult.cancel(CancellationError.wrap(error)); - } - this.pending_.task.reject(error); - } - /** * @return {(Task|undefined)} the next task scheduled within this queue, * if any. @@ -2857,9 +3074,9 @@ class TaskQueue extends events.EventEmitter { /** * The default flow to use if no others are active. - * @type {!ControlFlow} + * @type {ControlFlow} */ -var defaultFlow = new ControlFlow(); +var defaultFlow; /** @@ -2878,6 +3095,11 @@ var activeFlows = []; * @throws {Error} If the default flow is not currently active. */ function setDefaultFlow(flow) { + if (!usePromiseManager()) { + throw Error( + 'You may not change set the control flow when the promise' + +' manager is disabled'); + } if (activeFlows.length) { throw Error('You may only change the default flow while it is active'); } @@ -2887,10 +3109,21 @@ function setDefaultFlow(flow) { /** * @return {!ControlFlow} The currently active control flow. + * @suppress {checkTypes} */ function controlFlow() { - return /** @type {!ControlFlow} */ ( - activeFlows.length ? activeFlows[activeFlows.length - 1] : defaultFlow); + if (!usePromiseManager()) { + return SIMPLE_SCHEDULER; + } + + if (activeFlows.length) { + return activeFlows[activeFlows.length - 1]; + } + + if (!defaultFlow) { + defaultFlow = new ControlFlow; + } + return defaultFlow; } @@ -2900,8 +3133,7 @@ function controlFlow() { * a promise that resolves to the callback result. * @param {function(!ControlFlow)} callback The entry point * to the newly created flow. - * @return {!ManagedPromise} A promise that resolves to the callback - * result. + * @return {!Thenable} A promise that resolves to the callback result. */ function createFlow(callback) { var flow = new ControlFlow; @@ -2955,53 +3187,51 @@ function isGenerator(fn) { * @param {Object=} opt_self The object to use as "this" when invoking the * initial generator. * @param {...*} var_args Any arguments to pass to the initial generator. - * @return {!ManagedPromise} A promise that will resolve to the + * @return {!Thenable} A promise that will resolve to the * generator's final result. * @throws {TypeError} If the given function is not a generator. */ -function consume(generatorFn, opt_self, var_args) { +function consume(generatorFn, opt_self, ...var_args) { if (!isGenerator(generatorFn)) { throw new TypeError('Input is not a GeneratorFunction: ' + generatorFn.constructor.name); } - var deferred = defer(); - var generator = generatorFn.apply( - opt_self, Array.prototype.slice.call(arguments, 2)); - callNext(); - return deferred.promise; - - /** @param {*=} opt_value . */ - function callNext(opt_value) { - pump(generator.next, opt_value); - } - - /** @param {*=} opt_error . */ - function callThrow(opt_error) { - // Dictionary lookup required because Closure compiler's built-in - // externs does not include GeneratorFunction.prototype.throw. - pump(generator['throw'], opt_error); - } + let ret; + return ret = createPromise((resolve, reject) => { + let generator = generatorFn.apply(opt_self, var_args); + callNext(); - function pump(fn, opt_arg) { - if (!deferred.promise.isPending()) { - return; // Defererd was cancelled; silently abort. + /** @param {*=} opt_value . */ + function callNext(opt_value) { + pump(generator.next, opt_value); } - try { - var result = fn.call(generator, opt_arg); - } catch (ex) { - deferred.reject(ex); - return; + /** @param {*=} opt_error . */ + function callThrow(opt_error) { + pump(generator.throw, opt_error); } - if (result.done) { - deferred.fulfill(result.value); - return; - } + function pump(fn, opt_arg) { + if (ret instanceof ManagedPromise && !isPending(ret)) { + return; // Defererd was cancelled; silently abort. + } - asap(result.value, callNext, callThrow); - } + try { + var result = fn.call(generator, opt_arg); + } catch (ex) { + reject(ex); + return; + } + + if (result.done) { + resolve(result.value); + return; + } + + asap(result.value, callNext, callThrow); + } + }); } @@ -3009,12 +3239,14 @@ function consume(generatorFn, opt_self, var_args) { module.exports = { + CancellableThenable: CancellableThenable, CancellationError: CancellationError, ControlFlow: ControlFlow, Deferred: Deferred, MultipleUnhandledRejectionError: MultipleUnhandledRejectionError, Thenable: Thenable, Promise: ManagedPromise, + Scheduler: Scheduler, all: all, asap: asap, captureStackTrace: captureStackTrace, @@ -3025,6 +3257,7 @@ module.exports = { defer: defer, delayed: delayed, filter: filter, + finally: thenFinally, fulfilled: fulfilled, fullyResolved: fullyResolved, isGenerator: isGenerator, @@ -3034,6 +3267,22 @@ module.exports = { setDefaultFlow: setDefaultFlow, when: when, + /** + * Indicates whether the promise manager is currently enabled. When disabled, + * attempting to use the {@link ControlFlow} or {@link ManagedPromise Promise} + * classes will generate an error. + * + * 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. + * + * @return {boolean} Whether the promise manager is enabled. + * @see + */ + get USE_PROMISE_MANAGER() { return usePromiseManager(); }, + set USE_PROMISE_MANAGER(/** boolean */value) { USE_PROMISE_MANAGER = value; }, + get LONG_STACK_TRACES() { return LONG_STACK_TRACES; }, set LONG_STACK_TRACES(v) { LONG_STACK_TRACES = v; }, }; diff --git a/node_modules/selenium-webdriver/lib/safari/client.js b/node_modules/selenium-webdriver/lib/safari/client.js deleted file mode 100644 index 482c820fc..000000000 --- a/node_modules/selenium-webdriver/lib/safari/client.js +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -'use strict';var n=this; -function q(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"== -b&&"undefined"==typeof a.call)return"object";return b}function aa(a){var b=q(a);return"array"==b||"object"==b&&"number"==typeof a.length}function r(a){return"string"==typeof a}function ba(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}function ca(a,b,c){return a.call.apply(a.bind,arguments)} -function da(a,b,c){if(!a)throw Error();if(2")&&(a=a.replace(pa,">"));-1!=a.indexOf('"')&&(a=a.replace(qa,"""));-1!=a.indexOf("'")&&(a=a.replace(ra,"'"));-1!=a.indexOf("\x00")&&(a=a.replace(sa,"�"));return a}var na=/&/g,oa=//g,qa=/"/g,ra=/'/g,sa=/\x00/g,ma=/[\x00&<>"']/;function ta(a,b){return ab?1:0};function ua(a,b){b.unshift(a);t.call(this,ja.apply(null,b));b.shift()}ga(ua,t);ua.prototype.name="AssertionError";function u(a,b){throw new ua("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1));};var va=Array.prototype.indexOf?function(a,b,c){return Array.prototype.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(r(a))return r(b)&&1==b.length?a.indexOf(b,c):-1;for(;c=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};function Da(a,b){for(var c in a)b.call(void 0,a[c],c,a)};function v(a,b){this.b={};this.a=[];this.f=this.c=0;var c=arguments.length;if(1b)throw Error("Bad port number "+b);a.s=b}else a.s=null}function La(a,b,c){b instanceof B?(a.b=b,Qa(a.b,a.a)):(c||(b=C(b,Ra)),a.b=new B(b,0,a.a))}function A(a,b){return a?b?decodeURI(a.replace(/%25/g,"%2525")):decodeURIComponent(a):""}function C(a,b,c){return r(a)?(a=encodeURI(a).replace(b,Sa),c&&(a=a.replace(/%25([0-9a-fA-F]{2})/g,"%$1")),a):null} -function Sa(a){a=a.charCodeAt(0);return"%"+(a>>4&15).toString(16)+(a&15).toString(16)}var Ma=/[#\/\?@]/g,Oa=/[\#\?:]/g,Na=/[\#\?]/g,Ra=/[\#\?@]/g,Pa=/#/g;function B(a,b,c){this.c=this.a=null;this.b=a||null;this.f=!!c}function D(a){a.a||(a.a=new v,a.c=0,a.b&&Ia(a.b,function(b,c){var d=decodeURIComponent(b.replace(/\+/g," "));D(a);a.b=null;var d=E(a,d),f=Ga(a.a,d);f||Ea(a.a,d,f=[]);f.push(c);a.c=a.c+1}))} -function Ta(a,b){D(a);b=E(a,b);if(y(a.a.b,b)){a.b=null;a.c=a.c-Ga(a.a,b).length;var c=a.a;y(c.b,b)&&(delete c.b[b],c.c--,c.f++,c.a.length>2*c.c&&Fa(c))}}B.prototype.clear=function(){this.a=this.b=null;this.c=0};B.prototype.o=function(){D(this);for(var a=this.a.l(),b=this.a.o(),c=[],d=0;d");return N(b,a.j())}var mb=/^[a-zA-Z0-9-]+$/,nb={action:!0,cite:!0,data:!0,formaction:!0,href:!0,manifest:!0,poster:!0,src:!0},ob={APPLET:!0,BASE:!0,EMBED:!0,IFRAME:!0,LINK:!0,MATH:!0,META:!0,OBJECT:!0,SCRIPT:!0,STYLE:!0,SVG:!0,TEMPLATE:!0}; -function pb(a,b,c){if(!mb.test(a))throw Error("Invalid tag name <"+a+">.");if(a.toUpperCase()in ob)throw Error("Tag name <"+a+"> is not allowed for SafeHtml.");var d=null,f="<"+a;if(b)for(var g in b){if(!mb.test(g))throw Error('Invalid attribute name "'+g+'".');var e=b[g];if(null!=e){var l,m=a;l=g;if(e instanceof F)e=Xa(e);else if("style"==l.toLowerCase()){if(!ba(e))throw Error('The "style" attribute requires goog.html.SafeStyle or map of style properties, '+typeof e+" given: "+e);if(!(e instanceof -G)){var m="",w=void 0;for(w in e){if(!/^[-_a-zA-Z0-9]+$/.test(w))throw Error("Name allows only [-_a-zA-Z0-9], got: "+w);var k=e[w];if(null!=k){if(k instanceof F)k=Xa(k);else if(bb.test(k)){for(var h=!0,x=!0,p=0;p":(d=P(c),f+=">"+M(d)+"",d=d.j());(a=b&&b.dir)&&(/^(ltr|rtl|auto)$/i.test(a)?d=0:d=null);return N(f,d)}function P(a){function b(a){"array"==q(a)?wa(a,b):(a=lb(a),d+=M(a),a=a.j(),0==c?c=a:0!=a&&c!=a&&(c=null))}var c=0,d="";wa(arguments,b);return N(d,c)}var kb={}; -function N(a,b){var c=new L;c.a=a;c.b=b;return c}N("",0);var qb=N("",0),rb=N("
",0);function sb(a){var b;b=Error();if(Error.captureStackTrace)Error.captureStackTrace(b,a||sb),b=String(b.stack);else{try{throw b;}catch(c){b=c}b=(b=b.stack)?String(b):null}b||(b=tb(a||arguments.callee.caller,[]));return b} -function tb(a,b){var c=[];if(0<=va(b,a))c.push("[...circular reference...]");else if(a&&50>b.length){c.push(ub(a)+"(");for(var d=a.arguments,f=0;d&&f=Db(this).value)for("function"==q(b)&&(b=b()),a=new vb(a,String(b),this.g),c&&(a.a=c),c="log:"+a.b,n.console&&(n.console.timeStamp?n.console.timeStamp(c):n.console.markTimeline&&n.console.markTimeline(c)),n.msWriteProfilerMark&&n.msWriteProfilerMark(c),c=this;c;){b=c;var d=a;if(b.a)for(var f=0,g=void 0;g=b.a[f];f++)g(d);c=c.b}};var Eb={},S=null;function Fb(){S||(S=new xb(""),Eb[""]=S,S.f=Cb)} -function Gb(a){Fb();var b;if(!(b=Eb[a])){b=new xb(a);var c=a.lastIndexOf("."),d=a.substr(c+1),c=Gb(a.substr(0,c));c.c||(c.c={});c.c[d]=b;b.b=c;Eb[a]=b}return b};var Hb=new function(){this.a=fa()};function Ib(a){this.c=a||"";this.f=Hb}Ib.prototype.a=!0;Ib.prototype.b=!1;function T(a){return 10>a?"0"+a:String(a)}function Jb(a){Ib.call(this,a)}ga(Jb,Ib);Jb.prototype.b=!0;function Kb(a,b){Da(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:Lb.hasOwnProperty(d)?a.setAttribute(Lb[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})}var Lb={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"}; -function Mb(a,b,c){function d(c){c&&b.appendChild(r(c)?a.createTextNode(c):c)}for(var f=2;f=this.a.scrollHeight-this.a.scrollTop-this.a.clientHeight,c=this.g.createElement("DIV");c.className="logmsg";var d;var f=this.b;if(a){switch(a.f.value){case yb.value:d="dbg-sh";break;case zb.value:d="dbg-sev";break;case Ab.value:d="dbg-w";break;case Bb.value:d="dbg-i";break;default:d="dbg-f"}var g=[];g.push(f.c," ");if(f.a){var e=new Date(a.c);g.push("[",T(e.getFullYear()-2E3)+T(e.getMonth()+1)+T(e.getDate())+" "+T(e.getHours())+":"+T(e.getMinutes())+":"+ -T(e.getSeconds())+"."+T(Math.floor(e.getMilliseconds()/10)),"] ")}var e=(a.c-f.f.a)/1E3,l=e.toFixed(3),m=0;if(1>e)m=2;else for(;100>e;)m++,e*=10;for(;0 [end]\n\nJS stack traversal:\n"+sb(void 0)+"-> "))}catch(za){w=O("Exception trying to expose exception! You win, we lose. "+za)}e=P(rb,w)}a=O(a.b);d=pb("span",{"class":d},P(a,e));d=P(g,d,rb)}else d=qb;c.innerHTML=M(d);this.a.appendChild(c);b&&(this.a.scrollTop=this.a.scrollHeight)}}; -Qb.prototype.clear=function(){this.a&&(this.a.innerHTML=M(qb))};function U(a,b){a&&a.log(Bb,b,void 0)};var Sb;if(-1!=J.indexOf("iPhone")&&-1==J.indexOf("iPod")&&-1==J.indexOf("iPad")||-1!=J.indexOf("iPad")||-1!=J.indexOf("iPod"))Sb="";else{var Tb=/Version\/([0-9.]+)/.exec(J);Sb=Tb?Tb[1]:""};for(var Ub=0,Vb=ka(String(Sb)).split("."),Wb=ka("6").split("."),Xb=Math.max(Vb.length,Wb.length),Yb=0;0==Ub&&Ybf?setTimeout(a,250*f):c&&c.log(zb,"Unable to establish a connection with the SafariDriver extension",void 0)}var b=document.createElement("h2");b.innerHTML="SafariDriver Launcher";document.body.appendChild(b);b=document.createElement("div");document.body.appendChild(b);Rb(new Qb(b));var c=Gb("safaridriver.client"),d=Ua();if(d){d=new z(d);U(c,"Connecting to SafariDriver browser extension..."); -U(c,"This will fail if you have not installed the latest SafariDriver extension from\nhttp://selenium-release.storage.googleapis.com/index.html");U(c,"Extension logs may be viewed by clicking the Selenium [\u2713] button on the Safari toolbar");var f=0,g=new lc(d.toString());a()}else c&&c.log(zb,"No url specified. Please reload this page with the url parameter set",void 0)}var Y=["init"],Z=n;Y[0]in Z||!Z.execScript||Z.execScript("var "+Y[0]); -for(var nc;Y.length&&(nc=Y.shift());)Y.length||void 0===mc?Z[nc]?Z=Z[nc]:Z=Z[nc]={}:Z[nc]=mc;;window.onload = init; diff --git a/node_modules/selenium-webdriver/lib/session.js b/node_modules/selenium-webdriver/lib/session.js index 296291d4c..64ff6be76 100644 --- a/node_modules/selenium-webdriver/lib/session.js +++ b/node_modules/selenium-webdriver/lib/session.js @@ -17,7 +17,7 @@ 'use strict'; -const Capabilities = require('./capabilities').Capabilities; +const {Capabilities} = require('./capabilities'); /** diff --git a/node_modules/selenium-webdriver/lib/test/build.js b/node_modules/selenium-webdriver/lib/test/build.js index 59f0b1357..53b284ffb 100644 --- a/node_modules/selenium-webdriver/lib/test/build.js +++ b/node_modules/selenium-webdriver/lib/test/build.js @@ -21,8 +21,7 @@ const spawn = require('child_process').spawn, fs = require('fs'), path = require('path'); -const isDevMode = require('../devmode'), - promise = require('../promise'); +const isDevMode = require('../devmode'); var projectRoot = path.normalize(path.join(__dirname, '../../../../..')); @@ -69,7 +68,7 @@ Build.prototype.onlyOnce = function() { /** * Executes the build. - * @return {!webdriver.promise.Promise} A promise that will be resolved when + * @return {!Promise} A promise that will be resolved when * the build has completed. * @throws {Error} If no targets were specified. */ @@ -86,7 +85,7 @@ Build.prototype.go = function() { }); if (!targets.length) { - return promise.fulfilled(); + return Promise.resolve(); } } @@ -100,31 +99,30 @@ Build.prototype.go = function() { cmd = path.join(projectRoot, 'go'); } - var result = promise.defer(); - spawn(cmd, args, { - cwd: projectRoot, - env: process.env, - stdio: ['ignore', process.stdout, process.stderr] - }).on('exit', function(code, signal) { - if (code === 0) { - targets.forEach(function(target) { - builtTargets[target] = 1; - }); - return result.fulfill(); - } - - var msg = 'Unable to build artifacts'; - if (code) { // May be null. - msg += '; code=' + code; - } - if (signal) { - msg += '; signal=' + signal; - } - - result.reject(Error(msg)); + return new Promise((resolve, reject) => { + spawn(cmd, args, { + cwd: projectRoot, + env: process.env, + stdio: ['ignore', process.stdout, process.stderr] + }).on('exit', function(code, signal) { + if (code === 0) { + targets.forEach(function(target) { + builtTargets[target] = 1; + }); + return resolve(); + } + + var msg = 'Unable to build artifacts'; + if (code) { // May be null. + msg += '; code=' + code; + } + if (signal) { + msg += '; signal=' + signal; + } + + reject(Error(msg)); + }); }); - - return result.promise; }; diff --git a/node_modules/selenium-webdriver/lib/test/data/formPage.html b/node_modules/selenium-webdriver/lib/test/data/formPage.html index 7bcfea00f..45ae2b7d6 100644 --- a/node_modules/selenium-webdriver/lib/test/data/formPage.html +++ b/node_modules/selenium-webdriver/lib/test/data/formPage.html @@ -45,6 +45,7 @@ There should be a form here: