diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-11-16 01:59:39 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-11-16 02:00:31 +0100 |
commit | bd65bb67e25a79b019d745b7262b2008ce2adb15 (patch) | |
tree | 89e1b032103a63737f1a703e6a943832ef261704 /node_modules/selenium-webdriver | |
parent | f91466595b651721690133f58ab37f977539e95b (diff) | |
download | wallet-core-bd65bb67e25a79b019d745b7262b2008ce2adb15.tar.xz |
incrementally verify denoms
The denominations are not stored in a separate object store.
Diffstat (limited to 'node_modules/selenium-webdriver')
71 files changed, 7196 insertions, 6773 deletions
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 + <https://github.com/SeleniumHQ/selenium/issues/2969> +* 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<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} 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: <https://github.com/mozilla/geckodriver/releases> * * 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: <https://github.com/mozilla/geckodriver/releases> + * + * 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 <https://github.com/SeleniumHQ/selenium/issues/2969>). */ 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 @@ -182,6 +182,15 @@ class Binary { } /** + * @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.<string>)} var_args Either the arguments to add * as varargs, or the arguments as an array. @@ -197,6 +206,14 @@ class Binary { } /** + * @return {!Array<string>} 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 * created with a path to a specific Firefox binary. 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<Context>} Current context. + * @return {!promise.Thenable<Context>} Current context. */ getContext() { return this.schedule( @@ -657,7 +672,7 @@ class Driver extends webdriver.WebDriver { * * Use your powers wisely. * - * @param {!promise.Promise<void>} ctx The context to switch to. + * @param {!promise.Thenable<void>} 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; @@ -218,6 +215,14 @@ class Profile { } /** + * @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 * unpacked extension directory or the path to a xpi file. @@ -227,6 +232,13 @@ class Profile { } /** + * @return {!Array<string>} 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. * @param {(string|number|boolean)} value The preference value. @@ -255,6 +267,13 @@ class Profile { } /** + * @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 * specified when "localhost" is not mapped to the loopback address @@ -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; @@ -94,6 +95,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<!webdriver.IWebDriver>} + * @interface + */ +class ThenableWebDriver { + /** @param {...?} args */ + static createSession(...args) {} +} + + +/** + * @const {!Map<function(new: WebDriver, !IThenable<!Session>, ...?), + * function(new: ThenableWebDriver, !IThenable<!Session>, ...?)>} + */ +const THENABLE_DRIVERS = new Map; + + +/** + * @param {function(new: WebDriver, !IThenable<!Session>, ...?)} 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>} 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, * allowing quick runtime changes. @@ -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<!webdriver.WebDriver>} 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 Binary files differindex 916e530f3..248c32db5 100644 --- a/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so +++ b/node_modules/selenium-webdriver/lib/firefox/amd64/libnoblur64.so diff --git a/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so b/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so Binary files differindex 8e7db8de3..004062c7b 100644 --- a/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so +++ b/node_modules/selenium-webdriver/lib/firefox/i386/libnoblur.so diff --git a/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi b/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi Binary files differindex 39aca6b62..f9a51cf4f 100644 --- a/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi +++ b/node_modules/selenium-webdriver/lib/firefox/webdriver.xpi diff --git a/node_modules/selenium-webdriver/lib/http.js b/node_modules/selenium-webdriver/lib/http.js index 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<string, !Promise<string>> */new Map(); +const LOG = logging.getLogger('webdriver.http'); + +/** + * @param {Atom} file The atom file to load. + * @return {!Promise<string>} A promise that will resolve to the contents of the + * file. + */ +function loadAtom(file) { + if (ATOMS.has(file)) { + return ATOMS.get(file); + } + let contents = /** !Promise<string> */new Promise((resolve, reject) => { + LOG.finest(() => `Loading atom ${file}`); + fs.readFile(file, 'utf8', function(err, data) { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); + ATOMS.set(file, contents); + return contents; +} + + function post(path) { return resource('POST', path); } function del(path) { return resource('DELETE', path); } function get(path) { return resource('GET', path); } function resource(method, path) { return {method: method, path: path}; } -/** @const {!Map<string, {method: string, path: string}>} */ +/** @typedef {{method: string, path: string}} */ +var CommandSpec; + + +/** @typedef {function(!cmd.Command): !Promise<!cmd.Command>} */ +var CommandTransformer; + + +/** + * @param {!cmd.Command} command The initial command. + * @param {Atom} atom The name of the atom to execute. + * @return {!Promise<!cmd.Command>} The transformed command to execute. + */ +function toExecuteAtomCommand(command, atom, ...params) { + return loadAtom(atom).then(atom => { + return new cmd.Command(cmd.Name.EXECUTE_SCRIPT) + .setParameter('sessionId', command.getParameter('sessionId')) + .setParameter('script', `return (${atom}).apply(null, arguments)`) + .setParameter('args', params.map(param => command.getParameter(param))); + }); +} + + + +/** @const {!Map<string, CommandSpec>} */ 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<string, {method: string, path: string}>} */ +/** @const {!Map<string, (CommandSpec|CommandTransformer)>} */ 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')], @@ -249,6 +324,53 @@ function doSend(executor, request) { /** + * @param {Map<string, CommandSpec>} 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<!Request>} 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<!Request>} + */ + 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. * * By default, each instance of this class will use the legacy wire protocol @@ -279,7 +401,7 @@ class Executor { */ this.w3c = false; - /** @private {Map<string, {method: string, path: string}>} */ + /** @private {Map<string, CommandSpec>} */ 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<T>} * @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,30 +895,10 @@ 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. * * @param {?(function(T): (R|IThenable<R>))=} opt_callback The @@ -867,8 +907,8 @@ class Thenable { * @param {?(function(*): (R|IThenable<R>))=} opt_errback * The function to call if this promise is rejected. The function should * expect a single argument: the rejection reason. - * @return {!ManagedPromise<R>} A new promise which will be - * resolved with the result of the invoked callback. + * @return {!Thenable<R>} 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<R>)} errback The * function to call if this promise is rejected. The function should * expect a single argument: the rejection reason. - * @return {!ManagedPromise<R>} A new promise which will be - * resolved with the result of the invoked callback. + * @return {!Thenable<R>} 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<T>} + * @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<R>)} callback The function to call when - * this promise is resolved. - * @return {!ManagedPromise<R>} 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<T>} + * @implements {CancellableThenable<T>} * @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<T>} 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,7 +1268,7 @@ 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; @@ -1202,11 +1276,6 @@ class ManagedPromise { } /** @override */ - isPending() { - return this.state_ === PromiseState.PENDING; - } - - /** @override */ then(opt_callback, opt_errback) { return this.addCallback_( opt_callback, opt_errback, 'then', ManagedPromise.prototype.then); @@ -1218,21 +1287,15 @@ class ManagedPromise { null, errback, 'catch', ManagedPromise.prototype.catch); } - /** @override */ + /** + * @param {function(): (R|IThenable<R>)} callback + * @return {!ManagedPromise<R>} + * @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<T>} 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<T>} 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<R>)} callback The function to call when + * the promise is resolved. + * @return {!IThenable<R>} 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<T>)>} arr An array of * promises to wait on. - * @return {!ManagedPromise<!Array<T>>} A promise that is + * @return {!Thenable<!Array<T>>} 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<T>)} fn The function to call to start the + * task. + * @param {string=} opt_description A description of the task for debugging + * purposes. + * @return {!Thenable<T>} 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<T>|Thenable|null)=), + * function(*=))} resolver + * @return {!Thenable<T>} + * @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<void>} 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<T>|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<T>} 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<T>|Thenable|null)=), + * function(*=))} resolver + * @return {!Thenable<T>} + * @template T + */ +function createPromise(resolver) { + let ctor = usePromiseManager() ? ManagedPromise : NativePromise; + return new ctor(resolver); +} + + +/** + * @param {!Scheduler} scheduler The scheduler to use. + * @param {(!IThenable<T>|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<T>} 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 || '<anonymous wait: promise resolution>'); + } + + 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 || '<anonymous wait>'); +} + + +/** + * 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<T>)} 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<T>} 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<T>|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<T>} 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 || '<anonymous wait: promise resolution>'); - } - - 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 || '<anonymous wait>'); + 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); } } @@ -2805,28 +3044,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. * @private @@ -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 <https://github.com/SeleniumHQ/selenium/issues/2969> + */ + 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<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}function ea(a,b,c){ea=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?ca:da;return ea.apply(null,arguments)}var fa=Date.now||function(){return+new Date}; -function ga(a,b){function c(){}c.prototype=b.prototype;a.B=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.w=function(a,c,g){for(var e=Array(arguments.length-2),l=2;l<arguments.length;l++)e[l-2]=arguments[l];return b.prototype[c].apply(a,e)}};function t(a){if(Error.captureStackTrace)Error.captureStackTrace(this,t);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))}ga(t,Error);t.prototype.name="CustomError";var ia;function ja(a,b){for(var c=a.split("%s"),d="",f=Array.prototype.slice.call(arguments,1);f.length&&1<c.length;)d+=c.shift()+f.shift();return d+c.join("%s")}var ka=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}; -function la(a){if(!ma.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(na,"&"));-1!=a.indexOf("<")&&(a=a.replace(oa,"<"));-1!=a.indexOf(">")&&(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,pa=/>/g,qa=/"/g,ra=/'/g,sa=/\x00/g,ma=/[\x00&<>"']/;function ta(a,b){return a<b?-1:a>b?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<a.length;c++)if(c in a&&a[c]===b)return c;return-1},wa=Array.prototype.forEach?function(a,b,c){Array.prototype.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,f=r(a)?a.split(""):a,g=0;g<d;g++)g in f&&b.call(c,f[g],g,a)}; -function xa(a){return Array.prototype.concat.apply(Array.prototype,arguments)}function Ba(a){var b=a.length;if(0<b){for(var c=Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]}function Ca(a,b,c){return 2>=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(1<c){if(c%2)throw Error("Uneven number of arguments");for(var d=0;d<c;d+=2)Ea(this,arguments[d],arguments[d+1])}else if(a){var f;if(a instanceof v)f=a.o(),d=a.l();else{var c=[],g=0;for(f in a)c[g++]=f;f=c;c=[];g=0;for(d in a)c[g++]=a[d];d=c}for(c=0;c<f.length;c++)Ea(this,f[c],d[c])}}v.prototype.l=function(){Fa(this);for(var a=[],b=0;b<this.a.length;b++)a.push(this.b[this.a[b]]);return a}; -v.prototype.o=function(){Fa(this);return this.a.concat()};v.prototype.clear=function(){this.b={};this.f=this.c=this.a.length=0};function Fa(a){if(a.c!=a.a.length){for(var b=0,c=0;b<a.a.length;){var d=a.a[b];y(a.b,d)&&(a.a[c++]=d);b++}a.a.length=c}if(a.c!=a.a.length){for(var f={},c=b=0;b<a.a.length;)d=a.a[b],y(f,d)||(a.a[c++]=d,f[d]=1),b++;a.a.length=c}}function Ga(a,b){return y(a.b,b)?a.b[b]:void 0}function Ea(a,b,c){y(a.b,b)||(a.c++,a.a.push(b),a.f++);a.b[b]=c} -v.prototype.forEach=function(a,b){for(var c=this.o(),d=0;d<c.length;d++){var f=c[d];a.call(b,Ga(this,f),f,this)}};v.prototype.clone=function(){return new v(this)};function y(a,b){return Object.prototype.hasOwnProperty.call(a,b)};var Ha=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/;function Ia(a,b){if(a)for(var c=a.split("&"),d=0;d<c.length;d++){var f=c[d].indexOf("="),g=null,e=null;0<=f?(g=c[d].substring(0,f),e=c[d].substring(f+1)):g=c[d];b(g,e?decodeURIComponent(e.replace(/\+/g," ")):"")}};function z(a,b){this.f=this.u=this.c="";this.s=null;this.g=this.m="";this.a=!1;var c;a instanceof z?(this.a=void 0!==b?b:a.a,Ja(this,a.c),this.u=a.u,this.f=a.f,Ka(this,a.s),this.m=a.m,La(this,a.b.clone()),this.g=a.g):a&&(c=String(a).match(Ha))?(this.a=!!b,Ja(this,c[1]||"",!0),this.u=A(c[2]||""),this.f=A(c[3]||"",!0),Ka(this,c[4]),this.m=A(c[5]||"",!0),La(this,c[6]||"",!0),this.g=A(c[7]||"")):(this.a=!!b,this.b=new B(null,0,this.a))} -z.prototype.toString=function(){var a=[],b=this.c;b&&a.push(C(b,Ma,!0),":");var c=this.f;if(c||"file"==b)a.push("//"),(b=this.u)&&a.push(C(b,Ma,!0),"@"),a.push(encodeURIComponent(String(c)).replace(/%25([0-9a-fA-F]{2})/g,"%$1")),c=this.s,null!=c&&a.push(":",String(c));if(c=this.m)this.f&&"/"!=c.charAt(0)&&a.push("/"),a.push(C(c,"/"==c.charAt(0)?Na:Oa,!0));(c=this.b.toString())&&a.push("?",c);(c=this.g)&&a.push("#",C(c,Pa));return a.join("")};z.prototype.clone=function(){return new z(this)}; -function Ja(a,b,c){a.c=c?A(b,!0):b;a.c&&(a.c=a.c.replace(/:$/,""))}function Ka(a,b){if(b){b=Number(b);if(isNaN(b)||0>b)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<b.length;d++)for(var f=a[d],g=0;g<f.length;g++)c.push(b[d]);return c}; -B.prototype.l=function(a){D(this);var b=[];if(r(a)){var c=a;D(this);c=E(this,c);y(this.a.b,c)&&(b=xa(b,Ga(this.a,E(this,a))))}else for(a=this.a.l(),c=0;c<a.length;c++)b=xa(b,a[c]);return b};function Ua(){var a=(new z(window.location)).b.l("url");return 0<a.length?String(a[0]):void 0} -B.prototype.toString=function(){if(this.b)return this.b;if(!this.a)return"";for(var a=[],b=this.a.o(),c=0;c<b.length;c++)for(var d=b[c],f=encodeURIComponent(String(d)),d=this.l(d),g=0;g<d.length;g++){var e=f;""!==d[g]&&(e+="="+encodeURIComponent(String(d[g])));a.push(e)}return this.b=a.join("&")};B.prototype.clone=function(){var a=new B;a.b=this.b;this.a&&(a.a=this.a.clone(),a.c=this.c);return a};function E(a,b){var c=String(b);a.f&&(c=c.toLowerCase());return c} -function Qa(a,b){b&&!a.f&&(D(a),a.b=null,a.a.forEach(function(a,b){var f=b.toLowerCase();b!=f&&(Ta(this,b),Ta(this,f),0<a.length&&(this.b=null,Ea(this.a,E(this,f),Ba(a)),this.c=this.c+a.length))},a));a.f=b};var Va={area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0};function F(){this.a="";this.b=Wa}F.prototype.i=!0;F.prototype.h=function(){return this.a};F.prototype.toString=function(){return"Const{"+this.a+"}"};function Xa(a){if(a instanceof F&&a.constructor===F&&a.b===Wa)return a.a;u("expected object of type Const, got '"+a+"'");return"type_error:Const"}var Wa={};function Ya(a){var b=new F;b.a=a;return b};function G(){this.a="";this.b=Za}G.prototype.i=!0;var Za={};G.prototype.h=function(){return this.a};G.prototype.toString=function(){return"SafeStyle{"+this.a+"}"};function $a(a){var b=new G;b.a=a;return b}var ab=$a(""),bb=/^([-,."'%_!# a-zA-Z0-9]+|(?:rgb|hsl)a?\([0-9.%, ]+\))$/;function H(){this.a="";this.b=cb}H.prototype.i=!0;H.prototype.h=function(){return this.a};H.prototype.v=!0;H.prototype.j=function(){return 1};H.prototype.toString=function(){return"SafeUrl{"+this.a+"}"};function db(a){if(a instanceof H&&a.constructor===H&&a.b===cb)return a.a;u("expected object of type SafeUrl, got '"+a+"' of type "+q(a));return"type_error:SafeUrl"}var eb=/^(?:(?:https?|mailto|ftp):|[^&:/?#]*(?:[/?#]|$))/i; -function fb(a){if(a instanceof H)return a;a=a.i?a.h():String(a);eb.test(a)||(a="about:invalid#zClosurez");return gb(a)}var cb={};function gb(a){var b=new H;b.a=a;return b}gb("about:blank");function I(){this.a=hb}I.prototype.i=!0;I.prototype.h=function(){return""};I.prototype.v=!0;I.prototype.j=function(){return 1};I.prototype.toString=function(){return"TrustedResourceUrl{}"};var hb={};var J;a:{var ib=n.navigator;if(ib){var jb=ib.userAgent;if(jb){J=jb;break a}}J=""};function L(){this.a="";this.c=kb;this.b=null}L.prototype.v=!0;L.prototype.j=function(){return this.b};L.prototype.i=!0;L.prototype.h=function(){return this.a};L.prototype.toString=function(){return"SafeHtml{"+this.a+"}"};function M(a){if(a instanceof L&&a.constructor===L&&a.c===kb)return a.a;u("expected object of type SafeHtml, got '"+a+"' of type "+q(a));return"type_error:SafeHtml"}function lb(a){if(a instanceof L)return a;var b=null;a.v&&(b=a.j());a=la(a.i?a.h():String(a));return N(a,b)} -function O(a){if(a instanceof L)return a;a=lb(a);var b;b=M(a).replace(/ /g,"  ").replace(/(\r\n|\r|\n)/g,"<br>");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<k.length;p++){var K=k.charAt(p);"'"==K&&x?h=!h:'"'==K&&h&&(x=!x)}h&&x||(u("String value requires balanced quotes, got: "+k),k="zClosurez")}else u("String value allows only [-,.\"'%_!# a-zA-Z0-9], rgb() and rgba(), got: "+k),k="zClosurez";m+=w+":"+k+";"}}e=m?$a(m):ab}m=void 0;e instanceof -G&&e.constructor===G&&e.b===Za?m=e.a:(u("expected object of type SafeStyle, got '"+e+"' of type "+q(e)),m="type_error:SafeStyle");e=m}else{if(/^on/i.test(l))throw Error('Attribute "'+l+'" requires goog.string.Const value, "'+e+'" given.');if(l.toLowerCase()in nb)if(e instanceof I)e instanceof I&&e.constructor===I&&e.a===hb?e="":(u("expected object of type TrustedResourceUrl, got '"+e+"' of type "+q(e)),e="type_error:TrustedResourceUrl");else if(e instanceof H)e=db(e);else if(r(e))e=fb(e).h();else throw Error('Attribute "'+ -l+'" on tag "'+m+'" requires goog.html.SafeUrl, goog.string.Const, or string, value "'+e+'" given.');}e.i&&(e=e.h());l=l+'="'+la(String(e))+'"';f=f+(" "+l)}}null!=c?"array"==q(c)||(c=[c]):c=[];!0===Va[a.toLowerCase()]?f+=">":(d=P(c),f+=">"+M(d)+"</"+a+">",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("<!DOCTYPE html>",0);var qb=N("",0),rb=N("<br>",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<d.length;f++){0<f&&c.push(", ");var g;g=d[f];switch(typeof g){case "object":g=g?"object":"null";break;case "string":break;case "number":g=String(g);break;case "boolean":g=g?"true":"false";break;case "function":g=(g=ub(g))?g:"[fn]";break;default:g=typeof g}40<g.length&&(g=g.substr(0,40)+"...");c.push(g)}b.push(a);c.push(")\n");try{c.push(tb(a.caller, -b))}catch(e){c.push("[exception trying to get caller]\n")}}else a?c.push("[...long stack...]"):c.push("[end]");return c.join("")}function ub(a){if(Q[a])return Q[a];a=String(a);if(!Q[a]){var b=/function ([^\(]+)/.exec(a);Q[a]=b?b[1]:"[Anonymous]"}return Q[a]}var Q={};function vb(a,b,c,d,f){"number"==typeof f||wb++;this.c=d||fa();this.f=a;this.b=b;this.g=c;delete this.a}vb.prototype.a=null;var wb=0;function xb(a){this.g=a;this.a=this.c=this.f=this.b=null}function R(a,b){this.name=a;this.value=b}R.prototype.toString=function(){return this.name};var yb=new R("SHOUT",1200),zb=new R("SEVERE",1E3),Ab=new R("WARNING",900),Bb=new R("INFO",800),Cb=new R("CONFIG",700);function Db(a){if(a.f)return a.f;if(a.b)return Db(a.b);u("Root logger has no level set.");return null} -xb.prototype.log=function(a,b,c){if(a.value>=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<c.length;f++){var g=c[f];!aa(g)||ba(g)&&0<g.nodeType?d(g):wa(Nb(g)?Ba(g):g,d)}}function Nb(a){if(a&&"number"==typeof a.length){if(ba(a))return"function"==typeof a.item||"string"==typeof a.item;if("function"==q(a))return"function"==typeof a.item}return!1}function Ob(a){this.b=a||n.document||document} -function Pb(a,b){var c;c=a.b;var d=b&&"*"!=b?b.toUpperCase():"";c.querySelectorAll&&c.querySelector&&d?c=c.querySelectorAll(d+""):c=c.getElementsByTagName(d||"*");return c}Ob.prototype.a=function(a,b,c){var d=this.b,f=arguments,g=f[1],e=d.createElement(f[0]);g&&(r(g)?e.className=g:"array"==q(g)?e.className=g.join(" "):Kb(e,g));2<f.length&&Mb(d,e,f);return e}; -Ob.prototype.contains=function(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a};function Qb(a){this.m=ea(this.f,this);this.b=new Jb;this.c=this.b.a=!1;this.a=a;this.g=this.a.ownerDocument||this.a.document;a=(a=this.a)?new Ob(9==a.nodeType?a:a.ownerDocument||a.document):ia||(ia=new Ob);var b=null,c=Pb(a,"HEAD")[0];c||(b=Pb(a,"BODY")[0],c=a.a("HEAD"),b.parentNode.insertBefore(c,b));b=a.a("STYLE");b.innerHTML=".dbg-sev{color:#F00}.dbg-w{color:#C40}.dbg-sh{font-weight:bold;color:#000}.dbg-i{color:#444}.dbg-f{color:#999}.dbg-ev{color:#0A0}.dbg-m{color:#990}.logmsg{border-bottom:1px solid #CCC;padding:2px}.logsep{background-color: #8C8;}.logdiv{border:1px solid #CCC;background-color:#FCFCFC;font:medium monospace}"; -c.appendChild(b);this.a.className+=" logdiv"}function Rb(a){if(1!=a.c){var b;Fb();b=S;var c=a.m;b.a||(b.a=[]);b.a.push(c);a.c=!0}} -Qb.prototype.f=function(a){if(a){var b=100>=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<m--;)l=" "+l;g.push("[",l,"s] ");g.push("[",a.g,"] ");g=O(g.join(""));e=qb;if(f.b&&a.a){var w;try{var k;var h=a.a,x;d:{for(var f=["window","location","href"],e=n,p;p=f.shift();)if(null!=e[p])e=e[p];else{x=null;break d}x=e}if(r(h))k={message:h,name:"Unknown error",lineNumber:"Not available",fileName:x,stack:"Not available"};else{var K,ya;p=!1;try{K= -h.lineNumber||h.A||"Not available"}catch(za){K="Not available",p=!0}try{ya=h.fileName||h.filename||h.sourceURL||n.$googDebugFname||x}catch(za){ya="Not available",p=!0}k=!p&&h.lineNumber&&h.fileName&&h.stack&&h.message&&h.name?h:{message:h.message||"Not available",name:h.name||"UnknownError",lineNumber:K,fileName:ya,stack:h.stack||"Not available"}}var Aa;var ha=k.fileName;null!=ha||(ha="");if(/^https?:\/\//i.test(ha)){var cc=fb(ha);Ya("view-source scheme plus HTTP/HTTPS URL");var dc="view-source:"+ -db(cc);Aa=gb(dc)}else{var ec=Ya("sanitizedviewsrc");Aa=gb(Xa(ec))}w=P(O("Message: "+k.message+"\nUrl: "),pb("a",{href:Aa,target:"_new"},k.fileName),O("\nLine: "+k.lineNumber+"\n\nBrowser stack:\n"+k.stack+"-> [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&&Yb<Xb;Yb++){var Zb=Vb[Yb]||"",$b=Wb[Yb]||"",ac=/(\d*)(\D*)/g,bc=/(\d*)(\D*)/g;do{var V=ac.exec(Zb)||["","",""],W=bc.exec($b)||["","",""];if(0==V[0].length&&0==W[0].length)break;Ub=ta(0==V[1].length?0:parseInt(V[1],10),0==W[1].length?0:parseInt(W[1],10))||ta(0==V[2].length,0==W[2].length)||ta(V[2],W[2])}while(0==Ub)};var fc=JSON.stringify;function X(a,b,c){var d=a.constructor;if(a.window===a)try{var f=a.constructor;delete a.constructor;d=a.constructor;a.constructor=f}catch(g){}return d.prototype[b].apply(a,Ca(arguments,2))};Gb("safaridriver.message");function gc(a){this.a={};this.a[hc]="webdriver";this.a[ic]=a}var hc="origin",ic="type";function jc(a){var b=window;a.a[hc]="webdriver";if(b.postMessage){var c,b=function(a){c=a.data};X(window,"addEventListener","safaridriver.message.response",b,!1);kc(a.a);X(window,"removeEventListener","safaridriver.message.response",b,!1);return c}var d=X(document,"createEvent","Events");d.initEvent("beforeload",!1,!1);return b.canLoad(d,a.a)} -function kc(a){var b=X(document,"createEvent","MessageEvent");b.initMessageEvent("safaridriver.message",!1,!1,a,window.location.origin,"0",window,null);X(window,"dispatchEvent",b)}gc.prototype.toJSON=function(){return this.a};gc.prototype.toString=function(){return fc(this.toJSON())};function lc(a){gc.call(this,"connect");this.a.url=a}ga(lc,gc);function mc(){function a(){f+=1;jc(g)?(U(c,"Connected to extension"),U(c,"Requesting extension connect to client at "+d)):5>f?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: <select name="no-select" disabled="disabled"> <option value="foo">Foo</option> + <option value="bar">Bar</option> </select> <select name="select_empty_multiple" multiple> diff --git a/node_modules/selenium-webdriver/lib/test/fileserver.js b/node_modules/selenium-webdriver/lib/test/fileserver.js index 448b0c9a2..8194778ea 100644 --- a/node_modules/selenium-webdriver/lib/test/fileserver.js +++ b/node_modules/selenium-webdriver/lib/test/fileserver.js @@ -28,8 +28,7 @@ var serveIndex = require('serve-index'); var Server = require('./httpserver').Server, resources = require('./resources'), - isDevMode = require('../devmode'), - promise = require('../promise'); + isDevMode = require('../devmode'); var WEB_ROOT = '/common'; var JS_ROOT = '/javascript'; @@ -194,15 +193,18 @@ function sendDelayedResponse(request, response) { } -function handleUpload(request, response, next) { - multer({ - inMemory: true, - onFileUploadComplete: function(file) { +function handleUpload(request, response) { + let upload = multer({storage: multer.memoryStorage()}).any(); + upload(request, response, function(err) { + if (err) { + response.writeHead(500); + response.end(err + ''); + } else { response.writeHead(200); - response.write(file.buffer); + response.write(request.files[0].buffer); response.end('<script>window.top.window.onUploadDone();</script>'); } - })(request, response, function() {}); + }); } @@ -269,7 +271,7 @@ function sendIndex(request, response) { /** * Starts the server on the specified port. * @param {number=} opt_port The port to use, or 0 for any free port. - * @return {!webdriver.promise.Promise.<Host>} A promise that will resolve + * @return {!Promise<Host>} A promise that will resolve * with the server host when it has fully started. */ exports.start = server.start.bind(server); @@ -277,7 +279,7 @@ exports.start = server.start.bind(server); /** * Stops the server. - * @return {!webdriver.promise.Promise} A promise that will resolve when the + * @return {!Promise} A promise that will resolve when the * server has closed all connections. */ exports.stop = server.stop.bind(server); diff --git a/node_modules/selenium-webdriver/lib/test/httpserver.js b/node_modules/selenium-webdriver/lib/test/httpserver.js index 55b12551f..f7847f734 100644 --- a/node_modules/selenium-webdriver/lib/test/httpserver.js +++ b/node_modules/selenium-webdriver/lib/test/httpserver.js @@ -50,14 +50,14 @@ var Server = function(requestHandler) { * Starts the server on the given port. If no port, or 0, is provided, * the server will be started on a random port. * @param {number=} opt_port The port to start on. - * @return {!webdriver.promise.Promise.<Host>} A promise that will resolve + * @return {!Promise<Host>} A promise that will resolve * with the server host when it has fully started. */ this.start = function(opt_port) { assert(typeof opt_port !== 'function', "start invoked with function, not port (mocha callback)?"); var port = opt_port || portprober.findFreePort('localhost'); - return promise.when(port, function(port) { + return Promise.resolve(port).then(port => { return promise.checkedNodeCall( server.listen.bind(server, port, 'localhost')); }).then(function() { @@ -67,13 +67,11 @@ var Server = function(requestHandler) { /** * Stops the server. - * @return {!webdriver.promise.Promise} A promise that will resolve when the + * @return {!Promise} A promise that will resolve when the * server has closed all connections. */ this.stop = function() { - var d = promise.defer(); - server.close(d.fulfill); - return d.promise; + return new Promise(resolve => server.close(resolve)); }; /** diff --git a/node_modules/selenium-webdriver/lib/test/index.js b/node_modules/selenium-webdriver/lib/test/index.js index d702c5fb2..ba34ddab4 100644 --- a/node_modules/selenium-webdriver/lib/test/index.js +++ b/node_modules/selenium-webdriver/lib/test/index.js @@ -31,7 +31,6 @@ var build = require('./build'), const LEGACY_FIREFOX = 'legacy-' + webdriver.Browser.FIREFOX; -const LEGACY_SAFARI = 'legacy-' + webdriver.Browser.SAFARI; /** @@ -46,8 +45,7 @@ var NATIVE_BROWSERS = [ webdriver.Browser.IE, webdriver.Browser.OPERA, webdriver.Browser.PHANTOM_JS, - webdriver.Browser.SAFARI, - LEGACY_SAFARI + webdriver.Browser.SAFARI ]; @@ -83,8 +81,7 @@ var browsersToTest = (function() { parts[0] = webdriver.Browser.IE; } - if (parts[0] === LEGACY_FIREFOX || - parts[0] === LEGACY_SAFARI) { + if (parts[0] === LEGACY_FIREFOX) { return; } @@ -117,6 +114,8 @@ var browsersToTest = (function() { console.log('Running tests using loopback address') } } + console.log( + 'Promise manager is enabled? ' + webdriver.promise.USE_PROMISE_MANAGER); return browsers; })(); @@ -175,14 +174,6 @@ function TestEnvironment(browserName, server) { parts[0] = webdriver.Browser.FIREFOX; } - if (parts[0] === LEGACY_SAFARI) { - var options = builder.getSafariOptions() || new safari.Options(); - options.useLegacyDriver(true); - builder.setSafariOptions(options); - - parts[0] = webdriver.Browser.SAFARI; - } - builder.forBrowser(parts[0], parts[1], parts[2]); if (server) { builder.usingServer(server.address()); @@ -227,27 +218,31 @@ function suite(fn, opt_options) { try { + before(function() { + if (isDevMode) { + return build.of( + '//javascript/atoms/fragments:is-displayed', + '//javascript/webdriver/atoms:getAttribute') + .onlyOnce().go(); + } + }); + // Server is only started if required for a specific config. - testing.after(function() { + after(function() { if (seleniumServer) { return seleniumServer.stop(); } }); browsers.forEach(function(browser) { - testing.describe('[' + browser + ']', function() { + describe('[' + browser + ']', function() { if (isDevMode && nativeRun) { if (browser === LEGACY_FIREFOX) { - testing.before(function() { + before(function() { return build.of('//javascript/firefox-driver:webdriver') .onlyOnce().go(); }); - } else if (browser === LEGACY_SAFARI) { - testing.before(function() { - return build.of('//javascript/safari-driver:client') - .onlyOnce().go(); - }); } } @@ -259,7 +254,7 @@ function suite(fn, opt_options) { serverJar, {loopback: useLoopback}); } - testing.before(function() { + before(function() { this.timeout(0); return seleniumServer.start(60 * 1000); }); @@ -275,14 +270,14 @@ function suite(fn, opt_options) { // GLOBAL TEST SETUP -testing.before(function() { +before(function() { // Do not pass register fileserver.start directly with testing.before, // as start takes an optional port, which before assumes is an async // callback. return fileserver.start(); }); -testing.after(function() { +after(function() { return fileserver.stop(); }); diff --git a/node_modules/selenium-webdriver/lib/webdriver.js b/node_modules/selenium-webdriver/lib/webdriver.js index 13077b54e..081d77bda 100644 --- a/node_modules/selenium-webdriver/lib/webdriver.js +++ b/node_modules/selenium-webdriver/lib/webdriver.js @@ -28,7 +28,7 @@ const command = require('./command'); const error = require('./error'); const input = require('./input'); const logging = require('./logging'); -const Session = require('./session').Session; +const {Session} = require('./session'); const Symbols = require('./symbols'); const promise = require('./promise'); @@ -64,13 +64,13 @@ class Condition { /** * Defines a condition that will result in a {@link WebElement}. * - * @extends {Condition<!(WebElement|promise.Promise<!WebElement>)>} + * @extends {Condition<!(WebElement|IThenable<!WebElement>)>} */ class WebElementCondition extends Condition { /** * @param {string} message A descriptive error message. Should complete the * sentence "Waiting [...]" - * @param {function(!WebDriver): !(WebElement|promise.Promise<!WebElement>)} + * @param {function(!WebDriver): !(WebElement|IThenable<!WebElement>)} * fn The condition function to evaluate on each iteration of the wait * loop. */ @@ -237,145 +237,14 @@ function fromWireValue(driver, value) { /** - * Creates a new WebDriver client, which provides control over a browser. - * - * Every command.Command returns a {@link promise.Promise} that - * represents the result of that command. Callbacks may be registered on this - * object to manipulate the command result or catch an expected error. Any - * commands scheduled with a callback are considered sub-commands and will - * execute before the next command in the current frame. For example: - * - * var message = []; - * driver.call(message.push, message, 'a').then(function() { - * driver.call(message.push, message, 'b'); - * }); - * driver.call(message.push, message, 'c'); - * driver.call(function() { - * alert('message is abc? ' + (message.join('') == 'abc')); - * }); + * Structural interface for a WebDriver client. * + * @record */ -class WebDriver { - /** - * @param {!(Session|promise.Promise<!Session>)} session Either a - * known session or a promise that will be resolved to a session. - * @param {!command.Executor} executor The executor to use when sending - * commands to the browser. - * @param {promise.ControlFlow=} opt_flow The flow to - * schedule commands through. Defaults to the active flow object. - */ - constructor(session, executor, opt_flow) { - /** @private {!promise.Promise<!Session>} */ - this.session_ = promise.fulfilled(session); - - /** @private {!command.Executor} */ - this.executor_ = executor; - - /** @private {!promise.ControlFlow} */ - this.flow_ = opt_flow || promise.controlFlow(); - - /** @private {input.FileDetector} */ - this.fileDetector_ = null; - } - - /** - * Creates a new WebDriver client for an existing session. - * @param {!command.Executor} executor Command executor to use when querying - * for session details. - * @param {string} sessionId ID of the session to attach to. - * @param {promise.ControlFlow=} opt_flow The control flow all - * driver commands should execute under. Defaults to the - * {@link promise.controlFlow() currently active} control flow. - * @return {!WebDriver} A new client for the specified session. - */ - static attachToSession(executor, sessionId, opt_flow) { - let flow = opt_flow || promise.controlFlow(); - let cmd = new command.Command(command.Name.DESCRIBE_SESSION) - .setParameter('sessionId', sessionId); - let session = flow.execute( - () => executeCommand(executor, cmd).catch(err => { - // The DESCRIBE_SESSION command is not supported by the W3C spec, so - // if we get back an unknown command, just return a session with - // unknown capabilities. - if (err instanceof error.UnknownCommandError) { - return new Session(sessionId, new Capabilities); - } - throw err; - }), - 'WebDriver.attachToSession()'); - return new WebDriver(session, executor, flow); - } - - /** - * Creates a new WebDriver session. - * - * By default, the requested session `capabilities` are merely "desired" and - * the remote end will still create a new session even if it cannot satisfy - * all of the requested capabilities. You can query which capabilities a - * session actually has using the - * {@linkplain #getCapabilities() getCapabilities()} method on the returned - * WebDriver instance. - * - * To define _required capabilities_, provide the `capabilities` as an object - * literal with `required` and `desired` keys. The `desired` key may be - * omitted if all capabilities are required, and vice versa. If the server - * cannot create a session with all of the required capabilities, it will - * return an {@linkplain error.SessionNotCreatedError}. - * - * let required = new Capabilities().set('browserName', 'firefox'); - * let desired = new Capabilities().set('version', '45'); - * let driver = WebDriver.createSession(executor, {required, desired}); - * - * This function will always return a WebDriver instance. If there is an error - * creating the session, such as the aforementioned SessionNotCreatedError, - * the driver will have a rejected {@linkplain #getSession session} promise. - * It is recommended that this promise is left _unhandled_ so it will - * propagate through the {@linkplain promise.ControlFlow control flow} and - * cause subsequent commands to fail. - * - * let required = Capabilities.firefox(); - * let driver = WebDriver.createSession(executor, {required}); - * - * // If the createSession operation failed, then this command will also - * // also fail, propagating the creation failure. - * driver.get('http://www.google.com').catch(e => console.log(e)); - * - * @param {!command.Executor} executor The executor to create the new session - * with. - * @param {(!Capabilities| - * {desired: (Capabilities|undefined), - * required: (Capabilities|undefined)})} capabilities The desired - * capabilities for the new session. - * @param {promise.ControlFlow=} opt_flow The control flow all driver - * commands should execute under, including the initial session creation. - * Defaults to the {@link promise.controlFlow() currently active} - * control flow. - * @return {!WebDriver} The driver for the newly created session. - */ - static createSession(executor, capabilities, opt_flow) { - let flow = opt_flow || promise.controlFlow(); - let cmd = new command.Command(command.Name.NEW_SESSION); - - if (capabilities && (capabilities.desired || capabilities.required)) { - cmd.setParameter('desiredCapabilities', capabilities.desired); - cmd.setParameter('requiredCapabilities', capabilities.required); - } else { - cmd.setParameter('desiredCapabilities', capabilities); - } +class IWebDriver { - let session = flow.execute( - () => executeCommand(executor, cmd), - 'WebDriver.createSession()'); - return new WebDriver(session, executor, flow); - } - - /** - * @return {!promise.ControlFlow} The control flow used by this - * instance. - */ - controlFlow() { - return this.flow_; - } + /** @return {!promise.ControlFlow} The control flow used by this instance. */ + controlFlow() {} /** * Schedules a {@link command.Command} to be executed by this driver's @@ -383,107 +252,44 @@ class WebDriver { * * @param {!command.Command} command The command to schedule. * @param {string} description A description of the command for debugging. - * @return {!promise.Promise<T>} A promise that will be resolved + * @return {!promise.Thenable<T>} A promise that will be resolved * with the command result. * @template T */ - schedule(command, description) { - var self = this; - - checkHasNotQuit(); - command.setParameter('sessionId', this.session_); - - // If any of the command parameters are rejected promises, those - // rejections may be reported as unhandled before the control flow - // attempts to execute the command. To ensure parameters errors - // propagate through the command itself, we resolve all of the - // command parameters now, but suppress any errors until the ControlFlow - // actually executes the command. This addresses scenarios like catching - // an element not found error in: - // - // driver.findElement(By.id('foo')).click().catch(function(e) { - // if (e instanceof NoSuchElementError) { - // // Do something. - // } - // }); - var prepCommand = toWireValue(command.getParameters()); - prepCommand.catch(function() {}); - - var flow = this.flow_; - var executor = this.executor_; - return flow.execute(function() { - // A call to WebDriver.quit() may have been scheduled in the same event - // loop as this |command|, which would prevent us from detecting that the - // driver has quit above. Therefore, we need to make another quick check. - // We still check above so we can fail as early as possible. - checkHasNotQuit(); - - // Retrieve resolved command parameters; any previously suppressed errors - // will now propagate up through the control flow as part of the command - // execution. - return prepCommand.then(function(parameters) { - command.setParameters(parameters); - return executor.execute(command); - }).then(value => fromWireValue(self, value)); - }, description); - - function checkHasNotQuit() { - if (!self.session_) { - throw new error.NoSuchSessionError( - 'This driver instance does not have a valid session ID ' + - '(did you call WebDriver.quit()?) and may no longer be ' + - 'used.'); - } - } - } + schedule(command, description) {} /** * Sets the {@linkplain input.FileDetector file detector} that should be * used with this instance. * @param {input.FileDetector} detector The detector to use or {@code null}. */ - setFileDetector(detector) { - this.fileDetector_ = detector; - } + setFileDetector(detector) {} /** * @return {!command.Executor} The command executor used by this instance. */ - getExecutor() { - return this.executor_; - } + getExecutor() {} /** - * @return {!promise.Promise<!Session>} A promise for this client's - * session. + * @return {!promise.Thenable<!Session>} A promise for this client's session. */ - getSession() { - return this.session_; - } + getSession() {} /** - * @return {!promise.Promise<!Capabilities>} A promise - * that will resolve with the this instance's capabilities. + * @return {!promise.Thenable<!Capabilities>} A promise that will resolve with + * the this instance's capabilities. */ - getCapabilities() { - return this.session_.then(session => session.getCapabilities()); - } + getCapabilities() {} /** - * Schedules a command to quit the current session. After calling quit, this - * instance will be invalidated and may no longer be used to issue commands - * against the browser. - * @return {!promise.Promise<void>} A promise that will be resolved - * when the command has completed. + * Terminates the browser session. After calling quit, this instance will be + * invalidated and may no longer be used to issue commands against the + * browser. + * + * @return {!promise.Thenable<void>} A promise that will be resolved when the + * command has completed. */ - quit() { - var result = this.schedule( - new command.Command(command.Name.QUIT), - 'WebDriver.quit()'); - // Delete our session ID when the quit command finishes; this will allow us - // to throw an error when attemnpting to use a driver post-quit. - return result.finally(() => delete this.session_); - } + quit() {} /** * Creates a new action sequence using this driver. The sequence will not be @@ -498,9 +304,7 @@ class WebDriver { * * @return {!actions.ActionSequence} A new action sequence for this instance. */ - actions() { - return new actions.ActionSequence(this); - } + actions() {} /** * Creates a new touch sequence using this driver. The sequence will not be @@ -514,9 +318,7 @@ class WebDriver { * * @return {!actions.TouchSequence} A new touch sequence for this instance. */ - touchActions() { - return new actions.TouchSequence(this); - } + touchActions() {} /** * Schedules a command to execute JavaScript in the context of the currently @@ -550,22 +352,11 @@ class WebDriver { * * @param {!(string|Function)} script The script to execute. * @param {...*} var_args The arguments to pass to the script. - * @return {!promise.Promise<T>} A promise that will resolve to the + * @return {!promise.Thenable<T>} A promise that will resolve to the * scripts return value. * @template T */ - executeScript(script, var_args) { - if (typeof script === 'function') { - script = 'return (' + script + ').apply(null, arguments);'; - } - let args = - arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : []; - return this.schedule( - new command.Command(command.Name.EXECUTE_SCRIPT). - setParameter('script', script). - setParameter('args', args), - 'WebDriver.executeScript()'); - } + executeScript(script, var_args) {} /** * Schedules a command to execute asynchronous JavaScript in the context of the @@ -639,45 +430,22 @@ class WebDriver { * * @param {!(string|Function)} script The script to execute. * @param {...*} var_args The arguments to pass to the script. - * @return {!promise.Promise<T>} A promise that will resolve to the + * @return {!promise.Thenable<T>} A promise that will resolve to the * scripts return value. * @template T */ - executeAsyncScript(script, var_args) { - if (typeof script === 'function') { - script = 'return (' + script + ').apply(null, arguments);'; - } - let args = Array.prototype.slice.call(arguments, 1); - return this.schedule( - new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT). - setParameter('script', script). - setParameter('args', args), - 'WebDriver.executeScript()'); - } + executeAsyncScript(script, var_args) {} /** * Schedules a command to execute a custom function. - * @param {function(...): (T|promise.Promise<T>)} fn The function to - * execute. + * @param {function(...): (T|IThenable<T>)} fn The function to execute. * @param {Object=} opt_scope The object in whose scope to execute the function. * @param {...*} var_args Any arguments to pass to the function. - * @return {!promise.Promise<T>} A promise that will be resolved' + * @return {!promise.Thenable<T>} A promise that will be resolved' * with the function's result. * @template T */ - call(fn, opt_scope, var_args) { - let args = Array.prototype.slice.call(arguments, 2); - let flow = this.flow_; - return flow.execute(function() { - return promise.fullyResolved(args).then(function(args) { - if (promise.isGenerator(fn)) { - args.unshift(fn, opt_scope); - return promise.consume.apply(null, args); - } - return fn.apply(opt_scope, args); - }); - }, 'WebDriver.call(' + (fn.name || 'function') + ')'); - } + call(fn, opt_scope, var_args) {} /** * Schedules a command to wait for a condition to hold. The condition may be @@ -716,7 +484,7 @@ class WebDriver { * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds'); * driver.get(getServerUrl()); * - * @param {!(promise.Promise<T>| + * @param {!(IThenable<T>| * Condition<T>| * function(!WebDriver): T)} condition The condition to * wait on, defined as a promise, condition object, or a function to @@ -724,134 +492,76 @@ class WebDriver { * @param {number=} opt_timeout How long to wait for the condition to be true. * @param {string=} opt_message An optional message to use if the wait times * out. - * @return {!(promise.Promise<T>|WebElementPromise)} A promise that will be + * @return {!(promise.Thenable<T>|WebElementPromise)} A promise that will be * resolved with the first truthy value returned by the condition * function, or rejected if the condition times out. If the input * input condition is an instance of a {@link WebElementCondition}, * the returned value will be a {@link WebElementPromise}. + * @throws {TypeError} if the provided `condition` is not a valid type. * @template T */ - wait(condition, opt_timeout, opt_message) { - if (promise.isPromise(condition)) { - return this.flow_.wait( - /** @type {!promise.Promise} */(condition), - opt_timeout, opt_message); - } - - var message = opt_message; - var fn = /** @type {!Function} */(condition); - if (condition instanceof Condition) { - message = message || condition.description(); - fn = condition.fn; - } - - var driver = this; - var result = this.flow_.wait(function() { - if (promise.isGenerator(fn)) { - return promise.consume(fn, null, [driver]); - } - return fn(driver); - }, opt_timeout, message); - - if (condition instanceof WebElementCondition) { - result = new WebElementPromise(this, result.then(function(value) { - if (!(value instanceof WebElement)) { - throw TypeError( - 'WebElementCondition did not resolve to a WebElement: ' - + Object.prototype.toString.call(value)); - } - return value; - })); - } - return result; - } + wait(condition, opt_timeout, opt_message) {} /** * Schedules a command to make the driver sleep for the given amount of time. * @param {number} ms The amount of time, in milliseconds, to sleep. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the sleep has finished. */ - sleep(ms) { - return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')'); - } + sleep(ms) {} /** * Schedules a command to retrieve the current window handle. - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved with the current window handle. */ - getWindowHandle() { - return this.schedule( - new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE), - 'WebDriver.getWindowHandle()'); - } + getWindowHandle() {} /** * Schedules a command to retrieve the current list of available window handles. - * @return {!promise.Promise.<!Array<string>>} A promise that will + * @return {!promise.Thenable<!Array<string>>} A promise that will * be resolved with an array of window handles. */ - getAllWindowHandles() { - return this.schedule( - new command.Command(command.Name.GET_WINDOW_HANDLES), - 'WebDriver.getAllWindowHandles()'); - } + getAllWindowHandles() {} /** * Schedules a command to retrieve the current page's source. The page source * returned is a representation of the underlying DOM: do not expect it to be * formatted or escaped in the same way as the response sent from the web * server. - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved with the current page source. */ - getPageSource() { - return this.schedule( - new command.Command(command.Name.GET_PAGE_SOURCE), - 'WebDriver.getPageSource()'); - } + getPageSource() {} /** * Schedules a command to close the current window. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when this command has completed. */ - close() { - return this.schedule(new command.Command(command.Name.CLOSE), - 'WebDriver.close()'); - } + close() {} /** * Schedules a command to navigate to the given URL. * @param {string} url The fully qualified URL to open. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the document has finished loading. */ - get(url) { - return this.navigate().to(url); - } + get(url) {} /** * Schedules a command to retrieve the URL of the current page. - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved with the current URL. */ - getCurrentUrl() { - return this.schedule( - new command.Command(command.Name.GET_CURRENT_URL), - 'WebDriver.getCurrentUrl()'); - } + getCurrentUrl() {} /** * Schedules a command to retrieve the current page's title. - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved with the current page's title. */ - getTitle() { - return this.schedule(new command.Command(command.Name.GET_TITLE), - 'WebDriver.getTitle()'); - } + getTitle() {} /** * Schedule a command to find an element on the page. If the element cannot be @@ -859,7 +569,10 @@ class WebDriver { * by the driver. Unlike other commands, this error cannot be suppressed. In * other words, scheduling a command to find an element doubles as an assert * that the element is present on the page. To test whether an element is - * present on the page, use {@link #isElementPresent} instead. + * present on the page, use {@link #findElements}: + * + * driver.findElements(By.id('foo')) + * .then(found => console.log('Element found? %s', !!found.length)); * * The search criteria for an element may be defined using one of the * factories in the {@link webdriver.By} namespace, or as a short-hand @@ -889,6 +602,415 @@ class WebDriver { * commands against the located element. If the element is not found, the * element will be invalidated and all scheduled commands aborted. */ + findElement(locator) {} + + /** + * Schedule a command to search for multiple elements on the page. + * + * @param {!(by.By|Function)} locator The locator to use. + * @return {!promise.Thenable<!Array<!WebElement>>} A + * promise that will resolve to an array of WebElements. + */ + findElements(locator) {} + + /** + * Schedule a command to take a screenshot. The driver makes a best effort to + * return a screenshot of the following, in order of preference: + * + * 1. Entire page + * 2. Current window + * 3. Visible portion of the current frame + * 4. The entire display containing the browser + * + * @return {!promise.Thenable<string>} A promise that will be + * resolved to the screenshot as a base-64 encoded PNG. + */ + takeScreenshot() {} + + /** + * @return {!Options} The options interface for this instance. + */ + manage() {} + + /** + * @return {!Navigation} The navigation interface for this instance. + */ + navigate() {} + + /** + * @return {!TargetLocator} The target locator interface for this + * instance. + */ + switchTo() {} +} + + +/** + * Each WebDriver instance provides automated control over a browser session. + * + * @implements {IWebDriver} + */ +class WebDriver { + /** + * @param {!(Session|IThenable<!Session>)} session Either a known session or a + * promise that will be resolved to a session. + * @param {!command.Executor} executor The executor to use when sending + * commands to the browser. + * @param {promise.ControlFlow=} opt_flow The flow to + * schedule commands through. Defaults to the active flow object. + * @param {(function(this: void): ?)=} opt_onQuit A function to call, if any, + * when the session is terminated. + */ + constructor(session, executor, opt_flow, opt_onQuit) { + /** @private {!promise.ControlFlow} */ + this.flow_ = opt_flow || promise.controlFlow(); + + /** @private {!promise.Thenable<!Session>} */ + this.session_ = this.flow_.promise(resolve => resolve(session)); + + /** @private {!command.Executor} */ + this.executor_ = executor; + + /** @private {input.FileDetector} */ + this.fileDetector_ = null; + + /** @private @const {(function(this: void): ?|undefined)} */ + this.onQuit_ = opt_onQuit; + } + + /** + * Creates a new WebDriver client for an existing session. + * @param {!command.Executor} executor Command executor to use when querying + * for session details. + * @param {string} sessionId ID of the session to attach to. + * @param {promise.ControlFlow=} opt_flow The control flow all + * driver commands should execute under. Defaults to the + * {@link promise.controlFlow() currently active} control flow. + * @return {!WebDriver} A new client for the specified session. + */ + static attachToSession(executor, sessionId, opt_flow) { + let flow = opt_flow || promise.controlFlow(); + let cmd = new command.Command(command.Name.DESCRIBE_SESSION) + .setParameter('sessionId', sessionId); + let session = flow.execute( + () => executeCommand(executor, cmd).catch(err => { + // The DESCRIBE_SESSION command is not supported by the W3C spec, so + // if we get back an unknown command, just return a session with + // unknown capabilities. + if (err instanceof error.UnknownCommandError) { + return new Session(sessionId, new Capabilities); + } + throw err; + }), + 'WebDriver.attachToSession()'); + return new WebDriver(session, executor, flow); + } + + /** + * Creates a new WebDriver session. + * + * By default, the requested session `capabilities` are merely "desired" and + * the remote end will still create a new session even if it cannot satisfy + * all of the requested capabilities. You can query which capabilities a + * session actually has using the + * {@linkplain #getCapabilities() getCapabilities()} method on the returned + * WebDriver instance. + * + * To define _required capabilities_, provide the `capabilities` as an object + * literal with `required` and `desired` keys. The `desired` key may be + * omitted if all capabilities are required, and vice versa. If the server + * cannot create a session with all of the required capabilities, it will + * return an {@linkplain error.SessionNotCreatedError}. + * + * let required = new Capabilities().set('browserName', 'firefox'); + * let desired = new Capabilities().set('version', '45'); + * let driver = WebDriver.createSession(executor, {required, desired}); + * + * This function will always return a WebDriver instance. If there is an error + * creating the session, such as the aforementioned SessionNotCreatedError, + * the driver will have a rejected {@linkplain #getSession session} promise. + * It is recommended that this promise is left _unhandled_ so it will + * propagate through the {@linkplain promise.ControlFlow control flow} and + * cause subsequent commands to fail. + * + * let required = Capabilities.firefox(); + * let driver = WebDriver.createSession(executor, {required}); + * + * // If the createSession operation failed, then this command will also + * // also fail, propagating the creation failure. + * driver.get('http://www.google.com').catch(e => console.log(e)); + * + * @param {!command.Executor} executor The executor to create the new session + * with. + * @param {(!Capabilities| + * {desired: (Capabilities|undefined), + * required: (Capabilities|undefined)})} capabilities The desired + * capabilities for the new session. + * @param {promise.ControlFlow=} opt_flow The control flow all driver + * commands should execute under, including the initial session creation. + * Defaults to the {@link promise.controlFlow() currently active} + * control flow. + * @param {(function(new: WebDriver, + * !IThenable<!Session>, + * !command.Executor, + * promise.ControlFlow=))=} opt_ctor + * A reference to the constructor of the specific type of WebDriver client + * to instantiate. Will create a vanilla {@linkplain WebDriver} instance + * if a constructor is not provided. + * @param {(function(this: void): ?)=} opt_onQuit A callback to invoke when + * the newly created session is terminated. This should be used to clean + * up any resources associated with the session. + * @return {!WebDriver} The driver for the newly created session. + */ + static createSession( + executor, capabilities, opt_flow, opt_ctor, opt_onQuit) { + let flow = opt_flow || promise.controlFlow(); + let cmd = new command.Command(command.Name.NEW_SESSION); + + if (capabilities && (capabilities.desired || capabilities.required)) { + cmd.setParameter('desiredCapabilities', capabilities.desired); + cmd.setParameter('requiredCapabilities', capabilities.required); + } else { + cmd.setParameter('desiredCapabilities', capabilities); + } + + let session = flow.execute( + () => executeCommand(executor, cmd), + 'WebDriver.createSession()'); + if (typeof opt_onQuit === 'function') { + session = session.catch(err => { + return Promise.resolve(opt_onQuit.call(void 0)).then(_ => {throw err}); + }); + } + const ctor = opt_ctor || WebDriver; + return new ctor(session, executor, flow, opt_onQuit); + } + + /** @override */ + controlFlow() { + return this.flow_; + } + + /** @override */ + schedule(command, description) { + command.setParameter('sessionId', this.session_); + + // If any of the command parameters are rejected promises, those + // rejections may be reported as unhandled before the control flow + // attempts to execute the command. To ensure parameters errors + // propagate through the command itself, we resolve all of the + // command parameters now, but suppress any errors until the ControlFlow + // actually executes the command. This addresses scenarios like catching + // an element not found error in: + // + // driver.findElement(By.id('foo')).click().catch(function(e) { + // if (e instanceof NoSuchElementError) { + // // Do something. + // } + // }); + var prepCommand = toWireValue(command.getParameters()); + prepCommand.catch(function() {}); + + var flow = this.flow_; + var executor = this.executor_; + return flow.execute(() => { + // Retrieve resolved command parameters; any previously suppressed errors + // will now propagate up through the control flow as part of the command + // execution. + return prepCommand.then(function(parameters) { + command.setParameters(parameters); + return executor.execute(command); + }).then(value => fromWireValue(this, value)); + }, description); + } + + /** @override */ + setFileDetector(detector) { + this.fileDetector_ = detector; + } + + /** @override */ + getExecutor() { + return this.executor_; + } + + /** @override */ + getSession() { + return this.session_; + } + + /** @override */ + getCapabilities() { + return this.session_.then(s => s.getCapabilities()); + } + + /** @override */ + quit() { + var result = this.schedule( + new command.Command(command.Name.QUIT), + 'WebDriver.quit()'); + // Delete our session ID when the quit command finishes; this will allow us + // to throw an error when attemnpting to use a driver post-quit. + return /** @type {!promise.Thenable} */(promise.finally(result, () => { + this.session_ = this.flow_.promise((_, reject) => { + reject(new error.NoSuchSessionError( + 'This driver instance does not have a valid session ID ' + + '(did you call WebDriver.quit()?) and may no longer be used.')); + }); + + // Only want the session rejection to bubble if accessed. + this.session_.catch(function() {}); + + if (this.onQuit_) { + return this.onQuit_.call(void 0); + } + })); + } + + /** @override */ + actions() { + return new actions.ActionSequence(this); + } + + /** @override */ + touchActions() { + return new actions.TouchSequence(this); + } + + /** @override */ + executeScript(script, var_args) { + if (typeof script === 'function') { + script = 'return (' + script + ').apply(null, arguments);'; + } + let args = + arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : []; + return this.schedule( + new command.Command(command.Name.EXECUTE_SCRIPT). + setParameter('script', script). + setParameter('args', args), + 'WebDriver.executeScript()'); + } + + /** @override */ + executeAsyncScript(script, var_args) { + if (typeof script === 'function') { + script = 'return (' + script + ').apply(null, arguments);'; + } + let args = Array.prototype.slice.call(arguments, 1); + return this.schedule( + new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT). + setParameter('script', script). + setParameter('args', args), + 'WebDriver.executeScript()'); + } + + /** @override */ + call(fn, opt_scope, var_args) { + let args = Array.prototype.slice.call(arguments, 2); + return this.flow_.execute(function() { + return promise.fullyResolved(args).then(function(args) { + if (promise.isGenerator(fn)) { + args.unshift(fn, opt_scope); + return promise.consume.apply(null, args); + } + return fn.apply(opt_scope, args); + }); + }, 'WebDriver.call(' + (fn.name || 'function') + ')'); + } + + /** @override */ + wait(condition, opt_timeout, opt_message) { + if (promise.isPromise(condition)) { + return this.flow_.wait( + /** @type {!IThenable} */(condition), + opt_timeout, opt_message); + } + + var message = opt_message; + var fn = /** @type {!Function} */(condition); + if (condition instanceof Condition) { + message = message || condition.description(); + fn = condition.fn; + } + + if (typeof fn !== 'function') { + throw TypeError( + 'Wait condition must be a promise-like object, function, or a ' + + 'Condition object'); + } + + var driver = this; + var result = this.flow_.wait(function() { + if (promise.isGenerator(fn)) { + return promise.consume(fn, null, [driver]); + } + return fn(driver); + }, opt_timeout, message); + + if (condition instanceof WebElementCondition) { + result = new WebElementPromise(this, result.then(function(value) { + if (!(value instanceof WebElement)) { + throw TypeError( + 'WebElementCondition did not resolve to a WebElement: ' + + Object.prototype.toString.call(value)); + } + return value; + })); + } + return result; + } + + /** @override */ + sleep(ms) { + return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')'); + } + + /** @override */ + getWindowHandle() { + return this.schedule( + new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE), + 'WebDriver.getWindowHandle()'); + } + + /** @override */ + getAllWindowHandles() { + return this.schedule( + new command.Command(command.Name.GET_WINDOW_HANDLES), + 'WebDriver.getAllWindowHandles()'); + } + + /** @override */ + getPageSource() { + return this.schedule( + new command.Command(command.Name.GET_PAGE_SOURCE), + 'WebDriver.getPageSource()'); + } + + /** @override */ + close() { + return this.schedule(new command.Command(command.Name.CLOSE), + 'WebDriver.close()'); + } + + /** @override */ + get(url) { + return this.navigate().to(url); + } + + /** @override */ + getCurrentUrl() { + return this.schedule( + new command.Command(command.Name.GET_CURRENT_URL), + 'WebDriver.getCurrentUrl()'); + } + + /** @override */ + getTitle() { + return this.schedule(new command.Command(command.Name.GET_TITLE), + 'WebDriver.getTitle()'); + } + + /** @override */ findElement(locator) { let id; locator = by.checkedLocator(locator); @@ -907,7 +1029,7 @@ class WebDriver { * @param {!Function} locatorFn The locator function to use. * @param {!(WebDriver|WebElement)} context The search * context. - * @return {!promise.Promise.<!WebElement>} A + * @return {!promise.Thenable<!WebElement>} A * promise that will resolve to a list of WebElements. * @private */ @@ -923,13 +1045,7 @@ class WebDriver { }); } - /** - * Schedule a command to search for multiple elements on the page. - * - * @param {!(by.By|Function)} locator The locator to use. - * @return {!promise.Promise.<!Array.<!WebElement>>} A - * promise that will resolve to an array of WebElements. - */ + /** @override */ findElements(locator) { locator = by.checkedLocator(locator); if (typeof locator === 'function') { @@ -951,7 +1067,7 @@ class WebDriver { /** * @param {!Function} locatorFn The locator function to use. * @param {!(WebDriver|WebElement)} context The search context. - * @return {!promise.Promise<!Array<!WebElement>>} A promise that + * @return {!promise.Thenable<!Array<!WebElement>>} A promise that * will resolve to an array of WebElements. * @private */ @@ -971,41 +1087,23 @@ class WebDriver { }); } - /** - * Schedule a command to take a screenshot. The driver makes a best effort to - * return a screenshot of the following, in order of preference: - * - * 1. Entire page - * 2. Current window - * 3. Visible portion of the current frame - * 4. The entire display containing the browser - * - * @return {!promise.Promise<string>} A promise that will be - * resolved to the screenshot as a base-64 encoded PNG. - */ + /** @override */ takeScreenshot() { return this.schedule(new command.Command(command.Name.SCREENSHOT), 'WebDriver.takeScreenshot()'); } - /** - * @return {!Options} The options interface for this instance. - */ + /** @override */ manage() { return new Options(this); } - /** - * @return {!Navigation} The navigation interface for this instance. - */ + /** @override */ navigate() { return new Navigation(this); } - /** - * @return {!TargetLocator} The target locator interface for this - * instance. - */ + /** @override */ switchTo() { return new TargetLocator(this); } @@ -1015,7 +1113,7 @@ class WebDriver { /** * Interface for navigating back and forth in the browser history. * - * This class should never be instantiated directly. Insead, obtain an instance + * This class should never be instantiated directly. Instead, obtain an instance * with * * webdriver.navigate() @@ -1035,7 +1133,7 @@ class Navigation { /** * Schedules a command to navigate to a new URL. * @param {string} url The URL to navigate to. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the URL has been loaded. */ to(url) { @@ -1047,7 +1145,7 @@ class Navigation { /** * Schedules a command to move backwards in the browser history. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the navigation event has completed. */ back() { @@ -1058,7 +1156,7 @@ class Navigation { /** * Schedules a command to move forwards in the browser history. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the navigation event has completed. */ forward() { @@ -1069,7 +1167,7 @@ class Navigation { /** * Schedules a command to refresh the current page. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the navigation event has completed. */ refresh() { @@ -1116,7 +1214,7 @@ class Options { * }); * * @param {!Options.Cookie} spec Defines the cookie to add. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the cookie has been added to the page. * @throws {error.InvalidArgumentError} if any of the cookie parameters are * invalid. @@ -1171,7 +1269,7 @@ class Options { /** * Schedules a command to delete all cookies visible to the current page. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when all cookies have been deleted. */ deleteAllCookies() { @@ -1185,7 +1283,7 @@ class Options { * is a no-op if there is no cookie with the given name visible to the current * page. * @param {string} name The name of the cookie to delete. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the cookie has been deleted. */ deleteCookie(name) { @@ -1199,7 +1297,7 @@ class Options { * Schedules a command to retrieve all cookies visible to the current page. * Each cookie will be returned as a JSON object as described by the WebDriver * wire protocol. - * @return {!promise.Promise<!Array<!Options.Cookie>>} A promise that will be + * @return {!promise.Thenable<!Array<!Options.Cookie>>} A promise that will be * resolved with the cookies visible to the current browsing context. */ getCookies() { @@ -1214,7 +1312,7 @@ class Options { * described by the WebDriver wire protocol. * * @param {string} name The name of the cookie to retrieve. - * @return {!promise.Promise<?Options.Cookie>} A promise that will be resolved + * @return {!promise.Thenable<?Options.Cookie>} A promise that will be resolved * with the named cookie, or `null` if there is no such cookie. */ getCookie(name) { @@ -1365,7 +1463,7 @@ class Timeouts { * slower location strategies like XPath. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the implicit wait timeout has been set. */ implicitlyWait(ms) { @@ -1378,7 +1476,7 @@ class Timeouts { * less than or equal to 0, the script will be allowed to run indefinitely. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the script timeout has been set. */ setScriptTimeout(ms) { @@ -1391,7 +1489,7 @@ class Timeouts { * indefinite. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the timeout has been set. */ pageLoadTimeout(ms) { @@ -1411,7 +1509,7 @@ class Timeouts { /** * An interface for managing the current window. * - * This class should never be instantiated directly. Insead, obtain an instance + * This class should never be instantiated directly. Instead, obtain an instance * with * * webdriver.manage().window() @@ -1432,7 +1530,7 @@ class Window { /** * Retrieves the window's current position, relative to the top left corner of * the screen. - * @return {!promise.Promise.<{x: number, y: number}>} A promise + * @return {!promise.Thenable<{x: number, y: number}>} A promise * that will be resolved with the window's position in the form of a * {x:number, y:number} object literal. */ @@ -1449,7 +1547,7 @@ class Window { * side of the screen. * @param {number} y The desired vertical position, relative to the top of the * of the screen. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the command has completed. */ setPosition(x, y) { @@ -1463,7 +1561,7 @@ class Window { /** * Retrieves the window's current size. - * @return {!promise.Promise<{width: number, height: number}>} A + * @return {!promise.Thenable<{width: number, height: number}>} A * promise that will be resolved with the window's size in the form of a * {width:number, height:number} object literal. */ @@ -1478,7 +1576,7 @@ class Window { * Resizes the current window. * @param {number} width The desired window width. * @param {number} height The desired window height. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the command has completed. */ setSize(width, height) { @@ -1492,7 +1590,7 @@ class Window { /** * Maximizes the current window. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the command has completed. */ maximize() { @@ -1534,7 +1632,7 @@ class Logs { * entries since the last call, or from the start of the session. * * @param {!logging.Type} type The desired log type. - * @return {!promise.Promise.<!Array.<!logging.Entry>>} A + * @return {!promise.Thenable<!Array.<!logging.Entry>>} A * promise that will resolve to a list of log entries for the specified * type. */ @@ -1557,7 +1655,7 @@ class Logs { /** * Retrieves the log types available to this driver. - * @return {!promise.Promise<!Array<!logging.Type>>} A + * @return {!promise.Thenable<!Array<!logging.Type>>} A * promise that will resolve to a list of available log types. */ getAvailableLogTypes() { @@ -1604,7 +1702,7 @@ class TargetLocator { /** * Schedules a command to switch focus of all future commands to the topmost * frame on the page. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the driver has changed focus to the default content. */ defaultContent() { @@ -1630,7 +1728,7 @@ class TargetLocator { * rejected with a {@linkplain error.NoSuchFrameError}. * * @param {(number|WebElement|null)} id The frame locator. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the driver has changed focus to the specified frame. */ frame(id) { @@ -1650,7 +1748,7 @@ class TargetLocator { * * @param {string} nameOrHandle The name or window handle of the window to * switch focus to. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the driver has changed focus to the specified window. */ window(nameOrHandle) { @@ -1714,8 +1812,8 @@ class WebElement { /** @private {!WebDriver} */ this.driver_ = driver; - /** @private {!promise.Promise<string>} */ - this.id_ = promise.fulfilled(id); + /** @private {!promise.Thenable<string>} */ + this.id_ = driver.controlFlow().promise(resolve => resolve(id)); } /** @@ -1762,12 +1860,12 @@ class WebElement { * * @param {!WebElement} a A WebElement. * @param {!WebElement} b A WebElement. - * @return {!promise.Promise<boolean>} A promise that will be + * @return {!promise.Thenable<boolean>} A promise that will be * resolved to whether the two WebElements are equal. */ static equals(a, b) { if (a === b) { - return promise.fulfilled(true); + return a.driver_.controlFlow().promise(resolve => resolve(true)); } let ids = [a.getId(), b.getId()]; return promise.all(ids).then(function(ids) { @@ -1791,7 +1889,7 @@ class WebElement { } /** - * @return {!promise.Promise<string>} A promise that resolves to + * @return {!promise.Thenable<string>} A promise that resolves to * the server-assigned opaque ID assigned to this element. */ getId() { @@ -1812,14 +1910,14 @@ class WebElement { * * @param {!command.Command} command The command to schedule. * @param {string} description A description of the command for debugging. - * @return {!promise.Promise<T>} A promise that will be resolved + * @return {!promise.Thenable<T>} A promise that will be resolved * with the command result. * @template T * @see WebDriver#schedule * @private */ schedule_(command, description) { - command.setParameter('id', this.getId()); + command.setParameter('id', this); return this.driver_.schedule(command, description); } @@ -1878,7 +1976,7 @@ class WebElement { * * @param {!(by.By|Function)} locator The locator strategy to use when * searching for the element. - * @return {!promise.Promise<!Array<!WebElement>>} A + * @return {!promise.Thenable<!Array<!WebElement>>} A * promise that will resolve to an array of WebElements. */ findElements(locator) { @@ -1897,7 +1995,7 @@ class WebElement { /** * Schedules a command to click on this element. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the click command has completed. */ click() { @@ -1959,7 +2057,7 @@ class WebElement { * sequence of keys to type. Number keys may be referenced numerically or * by string (1 or '1'). All arguments will be joined into a single * sequence. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when all keys have been typed. */ sendKeys(var_args) { @@ -1993,7 +2091,7 @@ class WebElement { keys.catch(function() {}); var element = this; - return this.driver_.flow_.execute(function() { + return this.getDriver().controlFlow().execute(function() { return keys.then(function(keys) { return element.driver_.fileDetector_ .handleFile(element.driver_, keys.join('')); @@ -2008,7 +2106,7 @@ class WebElement { /** * Schedules a command to query for the tag/node name of this element. - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved with the element's tag name. */ getTagName() { @@ -2029,7 +2127,7 @@ class WebElement { * * @param {string} cssStyleProperty The name of the CSS style property to look * up. - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved with the requested CSS value. */ getCssValue(cssStyleProperty) { @@ -2065,7 +2163,7 @@ class WebElement { * - "readonly" * * @param {string} attributeName The name of the attribute to query. - * @return {!promise.Promise<?string>} A promise that will be + * @return {!promise.Thenable<?string>} A promise that will be * resolved with the attribute's value. The returned value will always be * either a string or null. */ @@ -2080,7 +2178,7 @@ class WebElement { * Get the visible (i.e. not hidden by CSS) innerText of this element, * including sub-elements, without any leading or trailing whitespace. * - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved with the element's visible text. */ getText() { @@ -2092,7 +2190,7 @@ class WebElement { /** * Schedules a command to compute the size of this element's bounding box, in * pixels. - * @return {!promise.Promise.<{width: number, height: number}>} A + * @return {!promise.Thenable<{width: number, height: number}>} A * promise that will be resolved with the element's size as a * {@code {width:number, height:number}} object. */ @@ -2104,7 +2202,7 @@ class WebElement { /** * Schedules a command to compute the location of this element in page space. - * @return {!promise.Promise.<{x: number, y: number}>} A promise that + * @return {!promise.Thenable<{x: number, y: number}>} A promise that * will be resolved to the element's location as a * {@code {x:number, y:number}} object. */ @@ -2117,7 +2215,7 @@ class WebElement { /** * Schedules a command to query whether the DOM element represented by this * instance is enabled, as dicted by the {@code disabled} attribute. - * @return {!promise.Promise<boolean>} A promise that will be + * @return {!promise.Thenable<boolean>} A promise that will be * resolved with whether this element is currently enabled. */ isEnabled() { @@ -2128,7 +2226,7 @@ class WebElement { /** * Schedules a command to query whether this element is selected. - * @return {!promise.Promise<boolean>} A promise that will be + * @return {!promise.Thenable<boolean>} A promise that will be * resolved with whether this element is currently selected. */ isSelected() { @@ -2141,7 +2239,7 @@ class WebElement { * Schedules a command to submit the form containing this element (or this * element if it is a FORM element). This command is a no-op if the element is * not contained in a form. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the form has been submitted. */ submit() { @@ -2154,7 +2252,7 @@ class WebElement { * Schedules a command to clear the `value` of this element. This command has * no effect if the underlying DOM element is neither a text INPUT element * nor a TEXTAREA element. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when the element has been cleared. */ clear() { @@ -2165,7 +2263,7 @@ class WebElement { /** * Schedules a command to test whether this element is currently displayed. - * @return {!promise.Promise<boolean>} A promise that will be + * @return {!promise.Thenable<boolean>} A promise that will be * resolved with whether this element is currently visible on the page. */ isDisplayed() { @@ -2181,7 +2279,7 @@ class WebElement { * @param {boolean=} opt_scroll Optional argument that indicates whether the * element should be scrolled into view before taking a screenshot. * Defaults to false. - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved to the screenshot as a base-64 encoded PNG. */ takeScreenshot(opt_scroll) { @@ -2206,24 +2304,30 @@ class WebElement { * return el.click(); * }); * - * @implements {promise.Thenable<!WebElement>} + * @implements {promise.CancellableThenable<!WebElement>} * @final */ class WebElementPromise extends WebElement { /** * @param {!WebDriver} driver The parent WebDriver instance for this * element. - * @param {!promise.Promise<!WebElement>} el A promise + * @param {!promise.Thenable<!WebElement>} el A promise * that will resolve to the promised element. */ constructor(driver, el) { super(driver, 'unused'); - /** @override */ - this.cancel = el.cancel.bind(el); - - /** @override */ - this.isPending = el.isPending.bind(el); + /** + * Cancel operation is only supported if the wrapped thenable is also + * cancellable. + * @param {(string|Error)=} opt_reason + * @override + */ + this.cancel = function(opt_reason) { + if (promise.CancellableThenable.isImplementation(el)) { + /** @type {!promise.CancellableThenable} */(el).cancel(opt_reason); + } + } /** @override */ this.then = el.then.bind(el); @@ -2231,9 +2335,6 @@ class WebElementPromise extends WebElement { /** @override */ this.catch = el.catch.bind(el); - /** @override */ - this.finally = el.finally.bind(el); - /** * Defers returning the element ID until the wrapped WebElement has been * resolved. @@ -2246,7 +2347,7 @@ class WebElementPromise extends WebElement { }; } } -promise.Thenable.addImplementation(WebElementPromise); +promise.CancellableThenable.addImplementation(WebElementPromise); ////////////////////////////////////////////////////////////////////////////// @@ -2272,15 +2373,15 @@ class Alert { /** @private {!WebDriver} */ this.driver_ = driver; - /** @private {!promise.Promise<string>} */ - this.text_ = promise.fulfilled(text); + /** @private {!promise.Thenable<string>} */ + this.text_ = driver.controlFlow().promise(resolve => resolve(text)); } /** * Retrieves the message text displayed with this alert. For instance, if the * alert were opened with alert("hello"), then this would return "hello". * - * @return {!promise.Promise<string>} A promise that will be + * @return {!promise.Thenable<string>} A promise that will be * resolved to the text displayed with this alert. */ getText() { @@ -2294,7 +2395,7 @@ class Alert { * * @param {string} username The username to send. * @param {string} password The password to send. - * @return {!promise.Promise<void>} A promise that will be resolved when this + * @return {!promise.Thenable<void>} A promise that will be resolved when this * command has completed. */ authenticateAs(username, password) { @@ -2307,7 +2408,7 @@ class Alert { /** * Accepts this alert. * - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when this command has completed. */ accept() { @@ -2319,7 +2420,7 @@ class Alert { /** * Dismisses this alert. * - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when this command has completed. */ dismiss() { @@ -2334,7 +2435,7 @@ class Alert { * window.confirm). * * @param {string} text The text to set. - * @return {!promise.Promise<void>} A promise that will be resolved + * @return {!promise.Thenable<void>} A promise that will be resolved * when this command has completed. */ sendKeys(text) { @@ -2357,7 +2458,7 @@ class Alert { * return alert.dismiss(); * }); * - * @implements {promise.Thenable.<!webdriver.Alert>} + * @implements {promise.CancellableThenable<!webdriver.Alert>} * @final */ class AlertPromise extends Alert { @@ -2370,11 +2471,17 @@ class AlertPromise extends Alert { constructor(driver, alert) { super(driver, 'unused'); - /** @override */ - this.cancel = alert.cancel.bind(alert); - - /** @override */ - this.isPending = alert.isPending.bind(alert); + /** + * Cancel operation is only supported if the wrapped thenable is also + * cancellable. + * @param {(string|Error)=} opt_reason + * @override + */ + this.cancel = function(opt_reason) { + if (promise.CancellableThenable.isImplementation(alert)) { + /** @type {!promise.CancellableThenable} */(alert).cancel(opt_reason); + } + }; /** @override */ this.then = alert.then.bind(alert); @@ -2382,9 +2489,6 @@ class AlertPromise extends Alert { /** @override */ this.catch = alert.catch.bind(alert); - /** @override */ - this.finally = alert.finally.bind(alert); - /** * Defer returning text until the promised alert has been resolved. * @override @@ -2436,7 +2540,7 @@ class AlertPromise extends Alert { }; } } -promise.Thenable.addImplementation(AlertPromise); +promise.CancellableThenable.addImplementation(AlertPromise); // PUBLIC API @@ -2451,6 +2555,7 @@ module.exports = { Options: Options, TargetLocator: TargetLocator, Timeouts: Timeouts, + IWebDriver: IWebDriver, WebDriver: WebDriver, WebElement: WebElement, WebElementCondition: WebElementCondition, diff --git a/node_modules/selenium-webdriver/opera.js b/node_modules/selenium-webdriver/opera.js index 7106511f7..cc84f2af5 100644 --- a/node_modules/selenium-webdriver/opera.js +++ b/node_modules/selenium-webdriver/opera.js @@ -348,14 +348,17 @@ class Options { */ class Driver extends webdriver.WebDriver { /** + * Creates a new session for Opera. + * * @param {(capabilities.Capabilities|Options)=} opt_config The configuration * options. * @param {remote.DriverService=} opt_service The session to use; will use * the {@link getDefaultService default service} by default. * @param {promise.ControlFlow=} opt_flow The control flow to use, * or {@code null} to use the currently active flow. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_service, opt_flow) { + static createSession(opt_config, opt_service, opt_flow) { var service = opt_service || getDefaultService(); var client = service.start().then(url => new http.HttpClient(url)); var executor = new http.Executor(client); @@ -379,8 +382,8 @@ class Driver extends webdriver.WebDriver { caps = options.toCapabilities(caps); } - var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - super(driver.getSession(), executor, driver.controlFlow()); + return /** @type {!Driver} */( + webdriver.WebDriver.createSession(executor, caps, opt_flow, this)); } /** diff --git a/node_modules/selenium-webdriver/package.json b/node_modules/selenium-webdriver/package.json index 8f94d5300..5c3935833 100644 --- a/node_modules/selenium-webdriver/package.json +++ b/node_modules/selenium-webdriver/package.json @@ -1,6 +1,6 @@ { "name": "selenium-webdriver", - "version": "3.0.0-beta-3", + "version": "3.0.1", "description": "The official WebDriver JavaScript bindings from the Selenium project", "license": "Apache-2.0", "keywords": [ @@ -20,24 +20,23 @@ "url": "https://github.com/SeleniumHQ/selenium.git" }, "engines": { - "node": ">= 4.2.x" + "node": ">= 6.9.0" }, "dependencies": { - "adm-zip": "0.4.4", - "rimraf": "^2.2.8", - "tmp": "0.0.24", - "ws": "^1.0.1", - "xml2js": "0.4.4" + "adm-zip": "^0.4.7", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" }, "devDependencies": { - "express": "^4.11.2", - "mocha": ">= 1.21.x", - "multer": "^0.1.7", - "promises-aplus-tests": "^2.1.0", - "serve-index": "^1.6.1", - "sinon": "^1.17.2" + "express": "^4.14.0", + "mocha": "^3.1.2", + "multer": "^1.2.0", + "promises-aplus-tests": "^2.1.2", + "serve-index": "^1.8.0", + "sinon": "^1.17.6" }, "scripts": { - "test": "mocha --harmony -t 600000 --recursive test" + "test": "mocha -t 600000 --recursive test" } } diff --git a/node_modules/selenium-webdriver/phantomjs.js b/node_modules/selenium-webdriver/phantomjs.js index 2de5364de..baa7cb378 100644 --- a/node_modules/selenium-webdriver/phantomjs.js +++ b/node_modules/selenium-webdriver/phantomjs.js @@ -148,6 +148,8 @@ function createExecutor(url) { */ class Driver extends webdriver.WebDriver { /** + * Creates a new PhantomJS session. + * * @param {capabilities.Capabilities=} opt_capabilities The desired * capabilities. * @param {promise.ControlFlow=} opt_flow The control flow to use, @@ -155,8 +157,9 @@ class Driver extends webdriver.WebDriver { * @param {string=} opt_logFile Path to the log file for the phantomjs * executable's output. For convenience, this may be set at runtime with * the `SELENIUM_PHANTOMJS_LOG` environment variable. + * @return {!Driver} A new driver reference. */ - constructor(opt_capabilities, opt_flow, opt_logFile) { + static createSession(opt_capabilities, opt_flow, opt_logFile) { // TODO: add an Options class for consistency with the other driver types. var caps = opt_capabilities || capabilities.Capabilities.phantomjs(); @@ -207,7 +210,6 @@ class Driver extends webdriver.WebDriver { var port = portprober.findFreePort(); var service = new remote.DriverService(exe, { port: port, - stdio: 'inherit', args: Promise.resolve(port).then(function(port) { args.push('--webdriver=' + port); return args; @@ -215,17 +217,8 @@ class Driver extends webdriver.WebDriver { }); var executor = createExecutor(service.start()); - var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - - super(driver.getSession(), executor, driver.controlFlow()); - - var boundQuit = this.quit.bind(this); - - /** @override */ - this.quit = function() { - let killService = () => service.kill(); - return boundQuit().then(killService, killService); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } /** @@ -265,7 +258,7 @@ class Driver extends webdriver.WebDriver { * * @param {(string|!Function)} script The script to execute. * @param {...*} var_args The arguments to pass to the script. - * @return {!promise.Promise<T>} A promise that resolve to the + * @return {!promise.Thenable<T>} A promise that resolve to the * script's return value. * @template T */ diff --git a/node_modules/selenium-webdriver/remote/index.js b/node_modules/selenium-webdriver/remote/index.js index ab76b4476..87dee7e8d 100644 --- a/node_modules/selenium-webdriver/remote/index.js +++ b/node_modules/selenium-webdriver/remote/index.js @@ -247,15 +247,18 @@ class DriverService { pathname: self.path_ }); - return new Promise(function(fulfill, reject) { - var ready = httpUtil.waitForServer(serverUrl, timeout) - .then(fulfill, reject); - earlyTermination.catch(function(e) { - ready.cancel(/** @type {Error} */(e)); - reject(Error(e.message)); - }); - }).then(function() { - return serverUrl; + return new Promise((fulfill, reject) => { + let cancelToken = + earlyTermination.catch(e => reject(Error(e.message))); + + httpUtil.waitForServer(serverUrl, timeout, cancelToken) + .then(_ => fulfill(serverUrl), err => { + if (err instanceof promise.CancellationError) { + fulfill(serverUrl); + } else { + reject(err); + } + }); }); }); })); @@ -283,7 +286,7 @@ class DriverService { /** * Schedules a task in the current control flow to stop the server if it is * currently running. - * @return {!promise.Promise} A promise that will be resolved when + * @return {!promise.Thenable} A promise that will be resolved when * the server has been stopped. */ stop() { @@ -297,8 +300,9 @@ class DriverService { * @return {!Promise<!Array<string>>} */ function resolveCommandLineFlags(args) { - return Promise.resolve(args) // Resolve the outer array. - .then(args => Promise.all(args)); // Then resolve the individual flags. + // Resolve the outer array, then the individual flags. + return Promise.resolve(args) + .then(/** !Array<CommandLineFlag> */args => Promise.all(args)); } diff --git a/node_modules/selenium-webdriver/safari.js b/node_modules/selenium-webdriver/safari.js index bbfff06e8..97d512bc7 100644 --- a/node_modules/selenium-webdriver/safari.js +++ b/node_modules/selenium-webdriver/safari.js @@ -17,452 +17,21 @@ /** * @fileoverview Defines a WebDriver client for Safari. - * - * - * __Testing Older Versions of Safari__ - * - * To test versions of Safari prior to Safari 10.0, you must install the - * [latest version](http://selenium-release.storage.googleapis.com/index.html) - * of the SafariDriver browser extension; using Safari for normal browsing is - * not recommended once the extension has been installed. You can, and should, - * disable the extension when the browser is not being used with WebDriver. - * - * You must also enable the use of legacy driver using the {@link Options} class. - * - * let options = new safari.Options() - * .useLegacyDriver(true); - * - * let driver = new (require('selenium-webdriver')).Builder() - * .forBrowser('safari') - * .setSafariOptions(options) - * .build(); */ 'use strict'; -const events = require('events'); -const fs = require('fs'); -const http = require('http'); -const path = require('path'); -const url = require('url'); -const util = require('util'); -const ws = require('ws'); - +const http = require('./http'); const io = require('./io'); -const exec = require('./io/exec'); -const isDevMode = require('./lib/devmode'); -const Capabilities = require('./lib/capabilities').Capabilities; -const Capability = require('./lib/capabilities').Capability; +const {Capabilities, Capability} = require('./lib/capabilities'); const command = require('./lib/command'); const error = require('./lib/error'); const logging = require('./lib/logging'); const promise = require('./lib/promise'); -const Session = require('./lib/session').Session; const Symbols = require('./lib/symbols'); const webdriver = require('./lib/webdriver'); const portprober = require('./net/portprober'); const remote = require('./remote'); -const http_ = require('./http'); - - -/** @const */ -const CLIENT_PATH = isDevMode - ? path.join(__dirname, - '../../../buck-out/gen/javascript/safari-driver/client.js') - : path.join(__dirname, 'lib/safari/client.js'); - - -/** @const */ -const LIBRARY_DIR = (function() { - if (process.platform === 'darwin') { - return path.join('/Users', process.env['USER'], 'Library/Safari'); - } else if (process.platform === 'win32') { - return path.join(process.env['APPDATA'], 'Apple Computer', 'Safari'); - } else { - return '/dev/null'; - } -})(); - - -/** @const */ -const SESSION_DATA_FILES = (function() { - if (process.platform === 'darwin') { - var libraryDir = path.join('/Users', process.env['USER'], 'Library'); - return [ - path.join(libraryDir, 'Caches/com.apple.Safari/Cache.db'), - path.join(libraryDir, 'Cookies/Cookies.binarycookies'), - path.join(libraryDir, 'Cookies/Cookies.plist'), - path.join(libraryDir, 'Safari/History.plist'), - path.join(libraryDir, 'Safari/LastSession.plist'), - path.join(libraryDir, 'Safari/LocalStorage'), - path.join(libraryDir, 'Safari/Databases') - ]; - } else if (process.platform === 'win32') { - var appDataDir = path.join(process.env['APPDATA'], - 'Apple Computer', 'Safari'); - var localDataDir = path.join(process.env['LOCALAPPDATA'], - 'Apple Computer', 'Safari'); - return [ - path.join(appDataDir, 'History.plist'), - path.join(appDataDir, 'LastSession.plist'), - path.join(appDataDir, 'Cookies/Cookies.plist'), - path.join(appDataDir, 'Cookies/Cookies.binarycookies'), - path.join(localDataDir, 'Cache.db'), - path.join(localDataDir, 'Databases'), - path.join(localDataDir, 'LocalStorage') - ]; - } else { - return []; - } -})(); - - -/** @typedef {{port: number, address: string, family: string}} */ -var Host; - - -/** - * A basic HTTP/WebSocket server used to communicate with the legacy SafariDriver - * browser extension. - */ -class Server extends events.EventEmitter { - constructor() { - super(); - var server = http.createServer(function(req, res) { - if (req.url === '/favicon.ico') { - res.writeHead(204); - res.end(); - return; - } - - var query = url.parse(/** @type {string} */(req.url)).query || ''; - if (query.indexOf('url=') == -1) { - var address = server.address() - var host = address.address + ':' + address.port; - res.writeHead( - 302, {'Location': 'http://' + host + '?url=ws://' + host}); - res.end(); - } - - fs.readFile(CLIENT_PATH, 'utf8', function(err, data) { - if (err) { - res.writeHead(500, {'Content-Type': 'text/plain'}); - res.end(err.stack); - return; - } - var content = '<!DOCTYPE html><body><script>' + data + '</script>'; - res.writeHead(200, { - 'Content-Type': 'text/html; charset=utf-8', - 'Content-Length': Buffer.byteLength(content, 'utf8'), - }); - res.end(content); - }); - }); - - var wss = new ws.Server({server: server}); - wss.on('connection', this.emit.bind(this, 'connection')); - - /** - * Starts the server on a random port. - * @return {!Promise<Host>} A promise that will resolve with the server host - * when it has fully started. - */ - this.start = function() { - if (server.address()) { - return Promise.resolve(server.address()); - } - return portprober.findFreePort('localhost').then(function(port) { - return promise.checkedNodeCall( - server.listen.bind(server, port, 'localhost')); - }).then(function() { - return server.address(); - }); - }; - - /** - * Stops the server. - * @return {!Promise} A promise that will resolve when the server has closed - * all connections. - */ - this.stop = function() { - return new Promise(fulfill => server.close(fulfill)); - }; - - /** - * @return {Host} This server's host info. - * @throws {Error} If the server is not running. - */ - this.address = function() { - var addr = server.address(); - if (!addr) { - throw Error('There server is not running!'); - } - return addr; - }; - } -} - - -/** - * @return {!Promise<string>} A promise that will resolve with the path - * to Safari on the current system. - */ -function findSafariExecutable() { - switch (process.platform) { - case 'darwin': - return Promise.resolve('/Applications/Safari.app/Contents/MacOS/Safari'); - - case 'win32': - var files = [ - process.env['PROGRAMFILES'] || '\\Program Files', - process.env['PROGRAMFILES(X86)'] || '\\Program Files (x86)' - ].map(function(prefix) { - return path.join(prefix, 'Safari\\Safari.exe'); - }); - return io.exists(files[0]).then(function(exists) { - return exists ? files[0] : io.exists(files[1]).then(function(exists) { - if (exists) { - return files[1]; - } - throw Error('Unable to find Safari on the current system'); - }); - }); - - default: - return Promise.reject( - Error('Safari is not supported on the current platform: ' + - process.platform)); - } -} - - -/** - * @param {string} serverUrl The URL to connect to. - * @return {!Promise<string>} A promise for the path to a file that Safari can - * open on start-up to trigger a new connection to the WebSocket server. - */ -function createConnectFile(serverUrl) { - return io.tmpFile({postfix: '.html'}).then(function(f) { - let contents = - `<!DOCTYPE html><script>window.location = "${serverUrl}";</script>`; - return io.write(f, contents).then(() => f); - }); -} - - -/** - * Deletes all session data files if so desired. - * @param {!Object} desiredCapabilities . - * @return {!Array<!Promise>} A list of promises for the deleted files. - */ -function cleanSession(desiredCapabilities) { - if (!desiredCapabilities) { - return []; - } - var options = desiredCapabilities[OPTIONS_CAPABILITY_KEY]; - if (!options) { - return []; - } - if (!options['cleanSession']) { - return []; - } - return SESSION_DATA_FILES.map(function(file) { - return io.unlink(file); - }); -} - - -/** @return {string} . */ -function getRandomString() { - let seed = Date.now(); - return Math.floor(Math.random() * seed).toString(36) - + Math.abs(Math.floor(Math.random() * seed) ^ Date.now()).toString(36); -} - - -/** - * @implements {command.Executor} - */ -class CommandExecutor { - constructor() { - this.server_ = null; - - /** @private {ws.WebSocket} */ - this.socket_ = null; - - /** @private {?string} 8*/ - this.sessionId_ = null; - - /** @private {Promise<!exec.Command>} */ - this.safari_ = null; - - /** @private {!logging.Logger} */ - this.log_ = logging.getLogger('webdriver.safari'); - } - - /** @override */ - execute(cmd) { - var self = this; - return new promise.Promise(function(fulfill, reject) { - var safariCommand = JSON.stringify({ - 'origin': 'webdriver', - 'type': 'command', - 'command': { - 'id': getRandomString(), - 'name': cmd.getName(), - 'parameters': cmd.getParameters() - } - }); - - switch (cmd.getName()) { - case command.Name.NEW_SESSION: - self.startSafari_(cmd) - .then(() => self.sendCommand_(safariCommand)) - .then(caps => new Session(self.sessionId(), caps)) - .then(fulfill, reject); - break; - - case command.Name.DESCRIBE_SESSION: - self.sendCommand_(safariCommand) - .then(caps => new Session(self.sessionId(), caps)) - .then(fulfill, reject); - break; - - case command.Name.QUIT: - self.destroySession_().then(() => fulfill(null), reject); - break; - - default: - self.sendCommand_(safariCommand).then(fulfill, reject); - break; - } - }); - } - - /** - * @return {string} The static session ID for this executor's current - * connection. - */ - sessionId() { - if (!this.sessionId_) { - throw Error('not currently connected') - } - return this.sessionId_; - } - - /** - * @param {string} data . - * @return {!promise.Promise} . - * @private - */ - sendCommand_(data) { - let self = this; - return new promise.Promise(function(fulfill, reject) { - // TODO: support reconnecting with the extension. - if (!self.socket_) { - self.destroySession_().finally(function() { - reject(Error('The connection to the SafariDriver was closed')); - }); - return; - } - - self.log_.fine(() => '>>> ' + data); - self.socket_.send(data, function(err) { - if (err) { - reject(err); - return; - } - }); - - self.socket_.once('message', function(data) { - try { - self.log_.fine(() => '<<< ' + data); - data = JSON.parse(data); - } catch (ex) { - reject(Error('Failed to parse driver message: ' + data)); - return; - } - - try { - error.checkLegacyResponse(data['response']); - fulfill(data['response']['value']); - } catch (ex) { - reject(ex); - } - }); - }); - } - - /** - * @param {!command.Command} command . - * @private - */ - startSafari_(command) { - this.server_ = new Server(); - - this.safari_ = this.server_.start().then(function(address) { - var tasks = cleanSession( - /** @type {!Object} */( - command.getParameters()['desiredCapabilities'])); - tasks.push( - findSafariExecutable(), - createConnectFile( - 'http://' + address.address + ':' + address.port)); - - return Promise.all(tasks).then(function(/** !Array<string> */tasks) { - var exe = tasks[tasks.length - 2]; - var html = tasks[tasks.length - 1]; - return exec(exe, {args: [html]}); - }); - }); - - return new Promise((resolve, reject) => { - let start = Date.now(); - let timer = setTimeout(function() { - let elapsed = Date.now() - start; - reject(Error( - 'Failed to connect to the SafariDriver after ' + elapsed + - ' ms; Have you installed the latest extension from ' + - 'http://selenium-release.storage.googleapis.com/index.html?')); - }, 10 * 1000); - - this.server_.once('connection', socket => { - clearTimeout(timer); - this.socket_ = socket; - this.sessionId_ = getRandomString(); - socket.once('close', () => { - this.socket_ = null; - this.sessionId_ = null; - }); - resolve(); - }); - }); - } - - /** - * Destroys the active session by stopping the WebSocket server and killing the - * Safari subprocess. - * @private - */ - destroySession_() { - var tasks = []; - if (this.server_) { - tasks.push(this.server_.stop()); - } - if (this.safari_) { - tasks.push(this.safari_.then(function(safari) { - safari.kill(); - return safari.result(); - })); - } - var self = this; - return promise.all(tasks).finally(function() { - self.server_ = null; - self.socket_ = null; - self.safari_ = null; - }); - } -} /** @@ -498,10 +67,7 @@ class ServiceBuilder extends remote.DriverService.Builder { } -/** @const */ const OPTIONS_CAPABILITY_KEY = 'safari.options'; -const LEGACY_DRIVER_CAPABILITY_KEY = 'legacyDriver' - /** @@ -517,9 +83,6 @@ class Options { /** @private {?./lib/capabilities.ProxyConfig} */ this.proxy_ = null; - - /** @private {boolean} */ - this.legacyDriver_ = false; } /** @@ -546,10 +109,6 @@ class Options { options.setLoggingPrefs(capabilities.get(Capability.LOGGING_PREFS)); } - if (capabilities.has(LEGACY_DRIVER_CAPABILITY_KEY)) { - options.useLegacyDriver(capabilities.get(LEGACY_DRIVER_CAPABILITY_KEY)); - } - return options; } @@ -569,18 +128,6 @@ class Options { } /** - * Sets whether to use the legacy driver from the Selenium project. This option - * is disabled by default. - * - * @param {boolean} enable Whether to enable the legacy driver. - * @return {!Options} A self reference. - */ - useLegacyDriver(enable) { - this.legacyDriver_ = enable; - return this; - } - - /** * Sets the logging preferences for the new session. * @param {!./lib/logging.Preferences} prefs The logging preferences. * @return {!Options} A self reference. @@ -618,7 +165,6 @@ class Options { if (this.options_) { caps.set(OPTIONS_CAPABILITY_KEY, this); } - caps.set(LEGACY_DRIVER_CAPABILITY_KEY, this.legacyDriver_); return caps; } @@ -645,49 +191,28 @@ class Options { */ class Driver extends webdriver.WebDriver { /** + * Creates a new Safari session. + * * @param {(Options|Capabilities)=} opt_config The configuration * options for the new session. * @param {promise.ControlFlow=} opt_flow The control flow to create * the driver under. + * @return {!Driver} A new driver instance. */ - constructor(opt_config, opt_flow) { - let caps, - executor, - useLegacyDriver = false, - onQuit = () => {}; - + static createSession(opt_config, opt_flow) { + let caps; if (opt_config instanceof Options) { caps = opt_config.toCapabilities(); } else { caps = opt_config || Capabilities.safari() } - if (caps.has(LEGACY_DRIVER_CAPABILITY_KEY)) { - useLegacyDriver = caps.get(LEGACY_DRIVER_CAPABILITY_KEY); - caps.delete(LEGACY_DRIVER_CAPABILITY_KEY); - } - - if (useLegacyDriver) { - executor = new CommandExecutor(); - } else { - let service = new ServiceBuilder().build(); - - executor = new http_.Executor( - service.start() - .then(url => new http_.HttpClient(url)) - ); - - onQuit = () => service.kill(); - } - - let driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); - - super(driver.getSession(), executor, driver.controlFlow()); + let service = new ServiceBuilder().build(); + let executor = new http.Executor( + service.start().then(url => new http.HttpClient(url))); - /** @override */ - this.quit = () => { - return super.quit().finally(onQuit); - }; + return /** @type {!Driver} */(webdriver.WebDriver.createSession( + executor, caps, opt_flow, this, () => service.kill())); } } diff --git a/node_modules/selenium-webdriver/test/actions_test.js b/node_modules/selenium-webdriver/test/actions_test.js index 7ea0047ad..ef218f7d7 100644 --- a/node_modules/selenium-webdriver/test/actions_test.js +++ b/node_modules/selenium-webdriver/test/actions_test.js @@ -26,28 +26,26 @@ var Browser = require('..').Browser, test.suite(function(env) { var driver; - test.beforeEach(function() { driver = env.builder().build(); }); - test.afterEach(function() { driver.quit(); }); + test.beforeEach(function*() { driver = yield env.builder().build(); }); + test.afterEach(function() { return driver.quit(); }); test.ignore( env.browsers(Browser.FIREFOX, Browser.PHANTOM_JS, Browser.SAFARI)). describe('WebDriver.actions()', function() { - test.it('can move to and click element in an iframe', function() { - driver.get(fileServer.whereIs('click_tests/click_in_iframe.html')); + test.it('can move to and click element in an iframe', function*() { + yield driver.get(fileServer.whereIs('click_tests/click_in_iframe.html')); - driver.wait(until.elementLocated(By.id('ifr')), 5000) - .then(function(frame) { - driver.switchTo().frame(frame); - }); + yield driver.wait(until.elementLocated(By.id('ifr')), 5000) + .then(frame => driver.switchTo().frame(frame)); - var link = driver.findElement(By.id('link')); - driver.actions() + let link = yield driver.findElement(By.id('link')); + yield driver.actions() .mouseMove(link) .click() .perform(); - driver.wait(until.titleIs('Submitted Successfully!'), 5000); + return driver.wait(until.titleIs('Submitted Successfully!'), 5000); }); }); diff --git a/node_modules/selenium-webdriver/test/chrome/options_test.js b/node_modules/selenium-webdriver/test/chrome/options_test.js index 28c4faa91..d36a044e4 100644 --- a/node_modules/selenium-webdriver/test/chrome/options_test.js +++ b/node_modules/selenium-webdriver/test/chrome/options_test.js @@ -205,22 +205,22 @@ test.suite(function(env) { var driver; test.afterEach(function() { - driver.quit(); + return driver.quit(); }); describe('Chrome options', function() { - test.it('can start Chrome with custom args', function() { + test.it('can start Chrome with custom args', function*() { var options = new chrome.Options(). addArguments('user-agent=foo;bar'); - driver = env.builder(). - setChromeOptions(options). - build(); + driver = yield env.builder() + .setChromeOptions(options) + .build(); - driver.get(test.Pages.ajaxyPage); + yield driver.get(test.Pages.ajaxyPage); - var userAgent = driver.executeScript( - 'return window.navigator.userAgent'); + var userAgent = + yield driver.executeScript('return window.navigator.userAgent'); assert(userAgent).equalTo('foo;bar'); }); }); diff --git a/node_modules/selenium-webdriver/test/cookie_test.js b/node_modules/selenium-webdriver/test/cookie_test.js index 3912fdbee..40f7d9b57 100644 --- a/node_modules/selenium-webdriver/test/cookie_test.js +++ b/node_modules/selenium-webdriver/test/cookie_test.js @@ -29,126 +29,127 @@ var test = require('../lib/test'), test.suite(function(env) { var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().build(); }); test.after(function() { - driver.quit(); + return driver.quit(); }); - test.ignore(env.browsers(Browser.SAFARI)). // Cookie handling is broken. + // Cookie handling is broken. + test.ignore(env.browsers(Browser.PHANTOM_JS, Browser.SAFARI)). describe('Cookie Management;', function() { - test.beforeEach(function() { - driver.get(fileserver.Pages.ajaxyPage); - driver.manage().deleteAllCookies(); - assertHasCookies(); + test.beforeEach(function*() { + yield driver.get(fileserver.Pages.ajaxyPage); + yield driver.manage().deleteAllCookies(); + return assertHasCookies(); }); - test.it('can add new cookies', function() { + test.it('can add new cookies', function*() { var cookie = createCookieSpec(); - driver.manage().addCookie(cookie); - driver.manage().getCookie(cookie.name).then(function(actual) { + yield driver.manage().addCookie(cookie); + yield driver.manage().getCookie(cookie.name).then(function(actual) { assert.equal(actual.value, cookie.value); }); }); - test.it('can get all cookies', function() { + test.it('can get all cookies', function*() { var cookie1 = createCookieSpec(); var cookie2 = createCookieSpec(); - driver.manage().addCookie(cookie1); - driver.manage().addCookie(cookie2); + yield driver.manage().addCookie(cookie1); + yield driver.manage().addCookie(cookie2); - assertHasCookies(cookie1, cookie2); + return assertHasCookies(cookie1, cookie2); }); test.ignore(env.browsers(Browser.IE)). - it('only returns cookies visible to the current page', function() { + it('only returns cookies visible to the current page', function*() { var cookie1 = createCookieSpec(); - driver.manage().addCookie(cookie1); + yield driver.manage().addCookie(cookie1); var pageUrl = fileserver.whereIs('page/1'); var cookie2 = createCookieSpec({ path: url.parse(pageUrl).pathname }); - driver.get(pageUrl); - driver.manage().addCookie(cookie2); - assertHasCookies(cookie1, cookie2); + yield driver.get(pageUrl); + yield driver.manage().addCookie(cookie2); + yield assertHasCookies(cookie1, cookie2); - driver.get(fileserver.Pages.ajaxyPage); - assertHasCookies(cookie1); + yield driver.get(fileserver.Pages.ajaxyPage); + yield assertHasCookies(cookie1); - driver.get(pageUrl); - assertHasCookies(cookie1, cookie2); + yield driver.get(pageUrl); + yield assertHasCookies(cookie1, cookie2); }); - test.it('can delete all cookies', function() { + test.it('can delete all cookies', function*() { var cookie1 = createCookieSpec(); var cookie2 = createCookieSpec(); - driver.executeScript( + yield driver.executeScript( 'document.cookie = arguments[0] + "=" + arguments[1];' + 'document.cookie = arguments[2] + "=" + arguments[3];', cookie1.name, cookie1.value, cookie2.name, cookie2.value); - assertHasCookies(cookie1, cookie2); + yield assertHasCookies(cookie1, cookie2); - driver.manage().deleteAllCookies(); - assertHasCookies(); + yield driver.manage().deleteAllCookies(); + yield assertHasCookies(); }); - test.it('can delete cookies by name', function() { + test.it('can delete cookies by name', function*() { var cookie1 = createCookieSpec(); var cookie2 = createCookieSpec(); - driver.executeScript( + yield driver.executeScript( 'document.cookie = arguments[0] + "=" + arguments[1];' + 'document.cookie = arguments[2] + "=" + arguments[3];', cookie1.name, cookie1.value, cookie2.name, cookie2.value); - assertHasCookies(cookie1, cookie2); + yield assertHasCookies(cookie1, cookie2); - driver.manage().deleteCookie(cookie1.name); - assertHasCookies(cookie2); + yield driver.manage().deleteCookie(cookie1.name); + yield assertHasCookies(cookie2); }); - test.it('should only delete cookie with exact name', function() { + test.it('should only delete cookie with exact name', function*() { var cookie1 = createCookieSpec(); var cookie2 = createCookieSpec(); var cookie3 = {name: cookie1.name + 'xx', value: cookie1.value}; - driver.executeScript( + yield driver.executeScript( 'document.cookie = arguments[0] + "=" + arguments[1];' + 'document.cookie = arguments[2] + "=" + arguments[3];' + 'document.cookie = arguments[4] + "=" + arguments[5];', cookie1.name, cookie1.value, cookie2.name, cookie2.value, cookie3.name, cookie3.value); - assertHasCookies(cookie1, cookie2, cookie3); + yield assertHasCookies(cookie1, cookie2, cookie3); - driver.manage().deleteCookie(cookie1.name); - assertHasCookies(cookie2, cookie3); + yield driver.manage().deleteCookie(cookie1.name); + yield assertHasCookies(cookie2, cookie3); }); - test.it('can delete cookies set higher in the path', function() { + test.it('can delete cookies set higher in the path', function*() { var cookie = createCookieSpec(); var childUrl = fileserver.whereIs('child/childPage.html'); var grandchildUrl = fileserver.whereIs( 'child/grandchild/grandchildPage.html'); - driver.get(childUrl); - driver.manage().addCookie(cookie); - assertHasCookies(cookie); + yield driver.get(childUrl); + yield driver.manage().addCookie(cookie); + yield assertHasCookies(cookie); - driver.get(grandchildUrl); - assertHasCookies(cookie); + yield driver.get(grandchildUrl); + yield assertHasCookies(cookie); - driver.manage().deleteCookie(cookie.name); - assertHasCookies(); + yield driver.manage().deleteCookie(cookie.name); + yield assertHasCookies(); - driver.get(childUrl); - assertHasCookies(); + yield driver.get(childUrl); + yield assertHasCookies(); }); test.ignore(env.browsers( @@ -156,20 +157,20 @@ test.suite(function(env) { Browser.FIREFOX, 'legacy-' + Browser.FIREFOX, Browser.IE)). - it('should retain cookie expiry', function() { + it('should retain cookie expiry', function*() { let expirationDelay = 5 * 1000; let expiry = new Date(Date.now() + expirationDelay); let cookie = createCookieSpec({expiry}); - driver.manage().addCookie(cookie); - driver.manage().getCookie(cookie.name).then(function(actual) { + yield driver.manage().addCookie(cookie); + yield driver.manage().getCookie(cookie.name).then(function(actual) { assert.equal(actual.value, cookie.value); // expiry times are exchanged in seconds since January 1, 1970 UTC. assert.equal(actual.expiry, Math.floor(expiry.getTime() / 1000)); }); - driver.sleep(expirationDelay); - assertHasCookies(); + yield driver.sleep(expirationDelay); + yield assertHasCookies(); }); }); @@ -192,9 +193,8 @@ test.suite(function(env) { return map; } - function assertHasCookies(var_args) { - var expected = Array.prototype.slice.call(arguments, 0); - driver.manage().getCookies().then(function(cookies) { + function assertHasCookies(...expected) { + return driver.manage().getCookies().then(function(cookies) { assert.equal(cookies.length, expected.length, 'Wrong # of cookies.' + '\n Expected: ' + JSON.stringify(expected) + diff --git a/node_modules/selenium-webdriver/test/element_finding_test.js b/node_modules/selenium-webdriver/test/element_finding_test.js index 819e15655..9f4568b8b 100644 --- a/node_modules/selenium-webdriver/test/element_finding_test.js +++ b/node_modules/selenium-webdriver/test/element_finding_test.js @@ -34,34 +34,34 @@ test.suite(function(env) { var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().build(); }); - test.after(function() { - driver.quit(); + after(function() { + return driver.quit(); }); describe('finding elements', function() { test.it( 'should work after loading multiple pages in a row', - function() { - driver.get(Pages.formPage); - driver.get(Pages.xhtmlTestPage); - driver.findElement(By.linkText('click me')).click(); - driver.wait(until.titleIs('We Arrive Here'), 5000); + function*() { + yield driver.get(Pages.formPage); + yield driver.get(Pages.xhtmlTestPage); + yield driver.findElement(By.linkText('click me')).click(); + yield driver.wait(until.titleIs('We Arrive Here'), 5000); }); describe('By.id()', function() { - test.it('should work', function() { - driver.get(Pages.xhtmlTestPage); - driver.findElement(By.id('linkId')).click(); - driver.wait(until.titleIs('We Arrive Here'), 5000); + test.it('should work', function*() { + yield driver.get(Pages.xhtmlTestPage); + yield driver.findElement(By.id('linkId')).click(); + yield driver.wait(until.titleIs('We Arrive Here'), 5000); }); - test.it('should fail if ID not present on page', function() { - driver.get(Pages.formPage); - driver.findElement(By.id('nonExistantButton')). + test.it('should fail if ID not present on page', function*() { + yield driver.get(Pages.formPage); + return driver.findElement(By.id('nonExistantButton')). then(fail, function(e) { assert(e).instanceOf(error.NoSuchElementError); }); @@ -70,182 +70,178 @@ test.suite(function(env) { test.it( 'should find multiple elements by ID even though that is ' + 'malformed HTML', - function() { - driver.get(Pages.nestedPage); - driver.findElements(By.id('2')).then(function(elements) { - assert(elements.length).equalTo(8); - }); + function*() { + yield driver.get(Pages.nestedPage); + + let elements = yield driver.findElements(By.id('2')); + assert(elements.length).equalTo(8); }); }); describe('By.linkText()', function() { - test.it('should be able to click on link identified by text', function() { - driver.get(Pages.xhtmlTestPage); - driver.findElement(By.linkText('click me')).click(); - driver.wait(until.titleIs('We Arrive Here'), 5000); + test.it('should be able to click on link identified by text', function*() { + yield driver.get(Pages.xhtmlTestPage); + yield driver.findElement(By.linkText('click me')).click(); + yield driver.wait(until.titleIs('We Arrive Here'), 5000); }); test.it( 'should be able to find elements by partial link text', - function() { - driver.get(Pages.xhtmlTestPage); - driver.findElement(By.partialLinkText('ick me')).click(); - driver.wait(until.titleIs('We Arrive Here'), 5000); + function*() { + yield driver.get(Pages.xhtmlTestPage); + yield driver.findElement(By.partialLinkText('ick me')).click(); + yield driver.wait(until.titleIs('We Arrive Here'), 5000); }); - test.it('should work when link text contains equals sign', function() { - driver.get(Pages.xhtmlTestPage); - var id = driver.findElement(By.linkText('Link=equalssign')). - getAttribute('id'); + test.it('should work when link text contains equals sign', function*() { + yield driver.get(Pages.xhtmlTestPage); + let el = yield driver.findElement(By.linkText('Link=equalssign')); + + let id = yield el.getAttribute('id'); assert(id).equalTo('linkWithEqualsSign'); }); test.it('matches by partial text when containing equals sign', - function() { - driver.get(Pages.xhtmlTestPage); - var id = driver.findElement(By.partialLinkText('Link=')). - getAttribute('id'); + function*() { + yield driver.get(Pages.xhtmlTestPage); + let link = yield driver.findElement(By.partialLinkText('Link=')); + + let id = yield link.getAttribute('id'); assert(id).equalTo('linkWithEqualsSign'); }); test.it('works when searching for multiple and text contains =', - function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.linkText('Link=equalssign')). - then(function(elements) { - assert(elements.length).equalTo(1); - return elements[0].getAttribute('id'); - }). - then(function(id) { - assert(id).equalTo('linkWithEqualsSign'); - }); + function*() { + yield driver.get(Pages.xhtmlTestPage); + let elements = + yield driver.findElements(By.linkText('Link=equalssign')); + + assert(elements.length).equalTo(1); + + let id = yield elements[0].getAttribute('id'); + assert(id).equalTo('linkWithEqualsSign'); }); test.it( 'works when searching for multiple with partial text containing =', - function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.partialLinkText('Link=')). - then(function(elements) { - assert(elements.length).equalTo(1); - return elements[0].getAttribute('id'); - }). - then(function(id) { - assert(id).equalTo('linkWithEqualsSign'); - }); - }); + function*() { + yield driver.get(Pages.xhtmlTestPage); + let elements = + yield driver.findElements(By.partialLinkText('Link=')); + + assert(elements.length).equalTo(1); + + let id = yield elements[0].getAttribute('id'); + assert(id).equalTo('linkWithEqualsSign'); + }); test.it('should be able to find multiple exact matches', - function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.linkText('click me')). - then(function(elements) { - assert(elements.length).equalTo(2); - }); + function*() { + yield driver.get(Pages.xhtmlTestPage); + let elements = yield driver.findElements(By.linkText('click me')); + assert(elements.length).equalTo(2); }); test.it('should be able to find multiple partial matches', - function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.partialLinkText('ick me')). - then(function(elements) { - assert(elements.length).equalTo(2); - }); + function*() { + yield driver.get(Pages.xhtmlTestPage); + let elements = + yield driver.findElements(By.partialLinkText('ick me')); + assert(elements.length).equalTo(2); }); - // See https://github.com/mozilla/geckodriver/issues/137 - test.ignore(browsers(Browser.FIREFOX)). - it('works on XHTML pages', function() { - driver.get(test.whereIs('actualXhtmlPage.xhtml')); + test.ignore(browsers(Browser.SAFARI)). + it('works on XHTML pages', function*() { + yield driver.get(test.whereIs('actualXhtmlPage.xhtml')); - var el = driver.findElement(By.linkText('Foo')); - assert(el.getText()).equalTo('Foo'); + let el = yield driver.findElement(By.linkText('Foo')); + return assert(el.getText()).equalTo('Foo'); }); }); describe('By.name()', function() { - test.it('should work', function() { - driver.get(Pages.formPage); + test.it('should work', function*() { + yield driver.get(Pages.formPage); - var el = driver.findElement(By.name('checky')); - assert(el.getAttribute('value')).equalTo('furrfu'); + let el = yield driver.findElement(By.name('checky')); + yield assert(el.getAttribute('value')).equalTo('furrfu'); }); - test.it('should find multiple elements with same name', function() { - driver.get(Pages.nestedPage); - driver.findElements(By.name('checky')).then(function(elements) { - assert(elements.length).greaterThan(1); - }); + test.it('should find multiple elements with same name', function*() { + yield driver.get(Pages.nestedPage); + + let elements = yield driver.findElements(By.name('checky')); + assert(elements.length).greaterThan(1); }); test.it( 'should be able to find elements that do not support name property', - function() { - driver.get(Pages.nestedPage); - driver.findElement(By.name('div1')); + function*() { + yield driver.get(Pages.nestedPage); + yield driver.findElement(By.name('div1')); // Pass if this does not return an error. }); - test.it('shoudl be able to find hidden elements by name', function() { - driver.get(Pages.formPage); - driver.findElement(By.name('hidden')); + test.it('shoudl be able to find hidden elements by name', function*() { + yield driver.get(Pages.formPage); + yield driver.findElement(By.name('hidden')); // Pass if this does not return an error. }); }); describe('By.className()', function() { - test.it('should work', function() { - driver.get(Pages.xhtmlTestPage); + test.it('should work', function*() { + yield driver.get(Pages.xhtmlTestPage); - var el = driver.findElement(By.className('extraDiv')); - assert(el.getText()).startsWith('Another div starts here.'); + let el = yield driver.findElement(By.className('extraDiv')); + yield assert(el.getText()).startsWith('Another div starts here.'); }); - test.it('should work when name is first name among many', function() { - driver.get(Pages.xhtmlTestPage); + test.it('should work when name is first name among many', function*() { + yield driver.get(Pages.xhtmlTestPage); - var el = driver.findElement(By.className('nameA')); - assert(el.getText()).equalTo('An H2 title'); + let el = yield driver.findElement(By.className('nameA')); + yield assert(el.getText()).equalTo('An H2 title'); }); - test.it('should work when name is last name among many', function() { - driver.get(Pages.xhtmlTestPage); + test.it('should work when name is last name among many', function*() { + yield driver.get(Pages.xhtmlTestPage); - var el = driver.findElement(By.className('nameC')); - assert(el.getText()).equalTo('An H2 title'); + let el = yield driver.findElement(By.className('nameC')); + yield assert(el.getText()).equalTo('An H2 title'); }); - test.it('should work when name is middle of many', function() { - driver.get(Pages.xhtmlTestPage); + test.it('should work when name is middle of many', function*() { + yield driver.get(Pages.xhtmlTestPage); - var el = driver.findElement(By.className('nameBnoise')); - assert(el.getText()).equalTo('An H2 title'); + let el = yield driver.findElement(By.className('nameBnoise')); + yield assert(el.getText()).equalTo('An H2 title'); }); - test.it('should work when name surrounded by whitespace', function() { - driver.get(Pages.xhtmlTestPage); + test.it('should work when name surrounded by whitespace', function*() { + yield driver.get(Pages.xhtmlTestPage); - var el = driver.findElement(By.className('spaceAround')); - assert(el.getText()).equalTo('Spaced out'); + let el = yield driver.findElement(By.className('spaceAround')); + yield assert(el.getText()).equalTo('Spaced out'); }); - test.it('should fail if queried name only partially matches', function() { - driver.get(Pages.xhtmlTestPage); - driver.findElement(By.className('nameB')). + test.it('should fail if queried name only partially matches', function*() { + yield driver.get(Pages.xhtmlTestPage); + return driver.findElement(By.className('nameB')). then(fail, function(e) { assert(e).instanceOf(error.NoSuchElementError); }); }); - test.it('should implicitly wait', function() { + test.it('should implicitly wait', function*() { var TIMEOUT_IN_MS = 1000; var EPSILON = TIMEOUT_IN_MS / 2; - driver.manage().timeouts().implicitlyWait(TIMEOUT_IN_MS); - driver.get(Pages.formPage); + yield driver.manage().timeouts().implicitlyWait(TIMEOUT_IN_MS); + yield driver.get(Pages.formPage); var start = new Date(); - driver.findElement(By.id('nonExistantButton')). + return driver.findElement(By.id('nonExistantButton')). then(fail, function(e) { var end = new Date(); assert(e).instanceOf(error.NoSuchElementError); @@ -253,11 +249,11 @@ test.suite(function(env) { }); }); - test.it('should be able to find multiple matches', function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.className('nameC')).then(function(elements) { - assert(elements.length).greaterThan(1); - }); + test.it('should be able to find multiple matches', function*() { + yield driver.get(Pages.xhtmlTestPage); + + let elements = yield driver.findElements(By.className('nameC')); + assert(elements.length).greaterThan(1); }); test.it('permits compound class names', function() { @@ -269,133 +265,136 @@ test.suite(function(env) { }); describe('By.xpath()', function() { - test.it('should work with multiple matches', function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.xpath('//div')).then(function(elements) { - assert(elements.length).greaterThan(1); - }); + test.it('should work with multiple matches', function*() { + yield driver.get(Pages.xhtmlTestPage); + let elements = yield driver.findElements(By.xpath('//div')); + assert(elements.length).greaterThan(1); }); - test.it('should work for selectors using contains keyword', function() { - driver.get(Pages.nestedPage); - driver.findElement(By.xpath('//a[contains(., "hello world")]')); + test.it('should work for selectors using contains keyword', function*() { + yield driver.get(Pages.nestedPage); + yield driver.findElement(By.xpath('//a[contains(., "hello world")]')); // Pass if no error. }); }); describe('By.tagName()', function() { - test.it('works', function() { - driver.get(Pages.formPage); + test.it('works', function*() { + yield driver.get(Pages.formPage); - var el = driver.findElement(By.tagName('input')); - assert(el.getTagName()).equalTo('input'); + let el = yield driver.findElement(By.tagName('input')); + yield assert(el.getTagName()).equalTo('input'); }); - test.it('can find multiple elements', function() { - driver.get(Pages.formPage); - driver.findElements(By.tagName('input')).then(function(elements) { - assert(elements.length).greaterThan(1); - }); + test.it('can find multiple elements', function*() { + yield driver.get(Pages.formPage); + + let elements = yield driver.findElements(By.tagName('input')); + assert(elements.length).greaterThan(1); }); }); describe('By.css()', function() { - test.it('works', function() { - driver.get(Pages.xhtmlTestPage); - driver.findElement(By.css('div.content')); + test.it('works', function*() { + yield driver.get(Pages.xhtmlTestPage); + yield driver.findElement(By.css('div.content')); // Pass if no error. }); - test.it('can find multiple elements', function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.css('p')).then(function(elements) { - assert(elements.length).greaterThan(1); - }); + test.it('can find multiple elements', function*() { + yield driver.get(Pages.xhtmlTestPage); + + let elements = yield driver.findElements(By.css('p')); + assert(elements.length).greaterThan(1); // Pass if no error. }); test.it( 'should find first matching element when searching by ' + 'compound CSS selector', - function() { - driver.get(Pages.xhtmlTestPage); - var el = driver.findElement(By.css('div.extraDiv, div.content')); - assert(el.getAttribute('class')).equalTo('content'); + function*() { + yield driver.get(Pages.xhtmlTestPage); + + let el = + yield driver.findElement(By.css('div.extraDiv, div.content')); + yield assert(el.getAttribute('class')).equalTo('content'); }); test.it('should be able to find multiple elements by compound selector', - function() { - driver.get(Pages.xhtmlTestPage); - driver.findElements(By.css('div.extraDiv, div.content')). - then(function(elements) { - assertClassIs(elements[0], 'content'); - assertClassIs(elements[1], 'extraDiv'); - - function assertClassIs(el, expected) { - assert(el.getAttribute('class')).equalTo(expected); - } - }); + function*() { + yield driver.get(Pages.xhtmlTestPage); + let elements = + yield driver.findElements(By.css('div.extraDiv, div.content')); + + return Promise.all([ + assertClassIs(elements[0], 'content'), + assertClassIs(elements[1], 'extraDiv') + ]); + + function assertClassIs(el, expected) { + return assert(el.getAttribute('class')).equalTo(expected); + } }); // IE only supports short version option[selected]. test.ignore(browsers(Browser.IE)). - it('should be able to find element by boolean attribute', function() { - driver.get(test.whereIs( + it('should be able to find element by boolean attribute', function*() { + yield driver.get(test.whereIs( 'locators_tests/boolean_attribute_selected.html')); - var el = driver.findElement(By.css('option[selected="selected"]')); - assert(el.getAttribute('value')).equalTo('two'); + let el = yield driver.findElement(By.css('option[selected="selected"]')); + yield assert(el.getAttribute('value')).equalTo('two'); }); test.it( 'should be able to find element with short ' + 'boolean attribute selector', - function() { - driver.get(test.whereIs( + function*() { + yield driver.get(test.whereIs( 'locators_tests/boolean_attribute_selected.html')); - var el = driver.findElement(By.css('option[selected]')); - assert(el.getAttribute('value')).equalTo('two'); + let el = yield driver.findElement(By.css('option[selected]')); + yield assert(el.getAttribute('value')).equalTo('two'); }); test.it( 'should be able to find element with short boolean attribute ' + 'selector on HTML4 page', - function() { - driver.get(test.whereIs( + function*() { + yield driver.get(test.whereIs( 'locators_tests/boolean_attribute_selected_html4.html')); - var el = driver.findElement(By.css('option[selected]')); - assert(el.getAttribute('value')).equalTo('two'); + let el = yield driver.findElement(By.css('option[selected]')); + yield assert(el.getAttribute('value')).equalTo('two'); }); }); describe('by custom locator', function() { - test.it('handles single element result', function() { - driver.get(Pages.javascriptPage); + test.it('handles single element result', function*() { + yield driver.get(Pages.javascriptPage); - let link = driver.findElement(function(driver) { + let link = yield driver.findElement(function(driver) { let links = driver.findElements(By.tagName('a')); return promise.filter(links, function(link) { return link.getAttribute('id').then(id => id === 'updatediv'); }).then(links => links[0]); }); - assert(link.getText()).isEqualTo('Update a div'); + yield assert(link.getText()).matches(/Update\s+a\s+div/); }); - test.it('uses first element if locator resolves to list', function() { - driver.get(Pages.javascriptPage); + test.it('uses first element if locator resolves to list', function*() { + yield driver.get(Pages.javascriptPage); - let link = driver.findElement(function() { + let link = yield driver.findElement(function() { return driver.findElements(By.tagName('a')); }); - assert(link.getText()).isEqualTo('Change the page title!'); + yield assert(link.getText()).isEqualTo('Change the page title!'); }); - test.it('fails if locator returns non-webelement value', function() { - driver.get(Pages.javascriptPage); + test.it('fails if locator returns non-webelement value', function*() { + yield driver.get(Pages.javascriptPage); let link = driver.findElement(function() { return driver.getTitle(); @@ -406,5 +405,22 @@ test.suite(function(env) { (e) => assert(e).instanceOf(TypeError)); }); }); + + describe('switchTo().activeElement()', function() { + // SAFARI's new session response does not identify it as a W3C browser, + // so the command is sent in the unsupported wire protocol format. + test.ignore(browsers(Browser.SAFARI)). + it('returns document.activeElement', function*() { + yield driver.get(Pages.formPage); + + let email = yield driver.findElement(By.css('#email')); + yield driver.executeScript('arguments[0].focus()', email); + + let ae = yield driver.switchTo().activeElement(); + let equal = yield driver.executeScript( + 'return arguments[0] === arguments[1]', email, ae); + assert(equal).isTrue(); + }); + }); }); }); diff --git a/node_modules/selenium-webdriver/test/execute_script_test.js b/node_modules/selenium-webdriver/test/execute_script_test.js index 2ee150ed8..97eaa1ed1 100644 --- a/node_modules/selenium-webdriver/test/execute_script_test.js +++ b/node_modules/selenium-webdriver/test/execute_script_test.js @@ -29,23 +29,23 @@ var webdriver = require('..'), test.suite(function(env) { var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().build(); }); test.after(function() { - driver.quit(); + return driver.quit(); }); test.beforeEach(function() { - driver.get(test.Pages.echoPage); + return driver.get(test.Pages.echoPage); }); describe('executeScript;', function() { var shouldHaveFailed = new Error('Should have failed'); test.it('fails if script throws', function() { - execute('throw new Error("boom")') + return execute('throw new Error("boom")') .then(function() { throw shouldHaveFailed; }) .catch(function(e) { // The java WebDriver server adds a bunch of crap to error messages. @@ -55,7 +55,7 @@ test.suite(function(env) { }); test.it('fails if script does not parse', function() { - execute('throw function\\*') + return execute('throw function\\*') .then(function() { throw shouldHaveFailed; }) .catch(function(e) { assert(e).notEqualTo(shouldHaveFailed); @@ -63,64 +63,64 @@ test.suite(function(env) { }); describe('scripts;', function() { - test.it('do not pollute the global scope', function() { - execute('var x = 1;'); - assert(execute('return typeof x;')).equalTo('undefined'); + test.it('do not pollute the global scope', function*() { + yield execute('var x = 1;'); + yield assert(execute('return typeof x;')).equalTo('undefined'); }); - test.it('can set global variables', function() { - execute('window.x = 1234;'); - assert(execute('return x;')).equalTo(1234); + test.it('can set global variables', function*() { + yield execute('window.x = 1234;'); + yield assert(execute('return x;')).equalTo(1234); }); - test.it('may be defined as a function expression', function() { - assert(execute(function() { + test.it('may be defined as a function expression', function*() { + let result = yield execute(function() { return 1234 + 'abc'; - })).equalTo('1234abc'); + }); + assert(result).equalTo('1234abc'); }); }); describe('return values;', function() { test.it('returns undefined as null', function() { - assert(execute('var x; return x;')).isNull(); + return assert(execute('var x; return x;')).isNull(); }); test.it('can return null', function() { - assert(execute('return null;')).isNull(); + return assert(execute('return null;')).isNull(); }); - test.it('can return numbers', function() { - assert(execute('return 1234')).equalTo(1234); - assert(execute('return 3.1456')).equalTo(3.1456); + test.it('can return numbers', function*() { + yield assert(execute('return 1234')).equalTo(1234); + yield assert(execute('return 3.1456')).equalTo(3.1456); }); test.it('can return strings', function() { - assert(execute('return "hello"')).equalTo('hello'); + return assert(execute('return "hello"')).equalTo('hello'); }); - test.it('can return booleans', function() { - assert(execute('return true')).equalTo(true); - assert(execute('return false')).equalTo(false); + test.it('can return booleans', function*() { + yield assert(execute('return true')).equalTo(true); + yield assert(execute('return false')).equalTo(false); }); test.it('can return an array of primitives', function() { - execute('var x; return [1, false, null, 3.14, x]') + return execute('var x; return [1, false, null, 3.14, x]') .then(verifyJson([1, false, null, 3.14, null])); }); test.it('can return nested arrays', function() { - execute('return [[1, 2, [3]]]') - .then(verifyJson([[1, 2, [3]]])); + return execute('return [[1, 2, [3]]]').then(verifyJson([[1, 2, [3]]])); }); - test.ignore(env.browsers(Browser.IE, Browser.SAFARI)). + test.ignore(env.browsers(Browser.IE)). it('can return empty object literal', function() { - execute('return {}').then(verifyJson({})); + return execute('return {}').then(verifyJson({})); }); test.it('can return object literals', function() { - execute('return {a: 1, b: false, c: null}').then(function(result) { + return execute('return {a: 1, b: false, c: null}').then(result => { verifyJson(['a', 'b', 'c'])(Object.keys(result).sort()); assert(result.a).equalTo(1); assert(result.b).equalTo(false); @@ -129,118 +129,116 @@ test.suite(function(env) { }); test.it('can return complex object literals', function() { - execute('return {a:{b: "hello"}}').then(verifyJson({a:{b: 'hello'}})); + return execute('return {a:{b: "hello"}}') + .then(verifyJson({a:{b: 'hello'}})); }); - test.it('can return dom elements as web elements', function() { - execute('return document.querySelector(".header.host")') - .then(function(result) { - assert(result).instanceOf(webdriver.WebElement); - assert(result.getText()).startsWith('host: '); - }); + test.it('can return dom elements as web elements', function*() { + let result = + yield execute('return document.querySelector(".header.host")'); + assert(result).instanceOf(webdriver.WebElement); + + return assert(result.getText()).startsWith('host: '); }); - test.it('can return array of dom elements', function() { - execute('var nodes = document.querySelectorAll(".request,.host");' + - 'return [nodes[0], nodes[1]];') - .then(function(result) { - assert(result.length).equalTo(2); + test.it('can return array of dom elements', function*() { + let result = yield execute( + 'var nodes = document.querySelectorAll(".request,.host");' + + 'return [nodes[0], nodes[1]];'); + assert(result.length).equalTo(2); - assert(result[0]).instanceOf(webdriver.WebElement); - assert(result[0].getText()).startsWith('GET '); + assert(result[0]).instanceOf(webdriver.WebElement); + yield assert(result[0].getText()).startsWith('GET '); - assert(result[1]).instanceOf(webdriver.WebElement); - assert(result[1].getText()).startsWith('host: '); - }); + assert(result[1]).instanceOf(webdriver.WebElement); + yield assert(result[1].getText()).startsWith('host: '); }); - test.it('can return a NodeList as an array of web elements', function() { - execute('return document.querySelectorAll(".request,.host");') - .then(function(result) { - assert(result.length).equalTo(2); + test.it('can return a NodeList as an array of web elements', function*() { + let result = + yield execute('return document.querySelectorAll(".request,.host");') - assert(result[0]).instanceOf(webdriver.WebElement); - assert(result[0].getText()).startsWith('GET '); + assert(result.length).equalTo(2); - assert(result[1]).instanceOf(webdriver.WebElement); - assert(result[1].getText()).startsWith('host: '); - }); + assert(result[0]).instanceOf(webdriver.WebElement); + yield assert(result[0].getText()).startsWith('GET '); + + assert(result[1]).instanceOf(webdriver.WebElement); + yield assert(result[1].getText()).startsWith('host: '); }); - test.it('can return object literal with element property', function() { - execute('return {a: document.body}').then(function(result) { - assert(result.a).instanceOf(webdriver.WebElement); - assert(result.a.getTagName()).equalTo('body'); - }); + test.it('can return object literal with element property', function*() { + let result = yield execute('return {a: document.body}'); + + assert(result.a).instanceOf(webdriver.WebElement); + yield assert(result.a.getTagName()).equalTo('body'); }); }); describe('parameters;', function() { - test.it('can pass numeric arguments', function() { - assert(execute('return arguments[0]', 12)).equalTo(12); - assert(execute('return arguments[0]', 3.14)).equalTo(3.14); + test.it('can pass numeric arguments', function*() { + yield assert(execute('return arguments[0]', 12)).equalTo(12); + yield assert(execute('return arguments[0]', 3.14)).equalTo(3.14); }); - test.it('can pass boolean arguments', function() { - assert(execute('return arguments[0]', true)).equalTo(true); - assert(execute('return arguments[0]', false)).equalTo(false); + test.it('can pass boolean arguments', function*() { + yield assert(execute('return arguments[0]', true)).equalTo(true); + yield assert(execute('return arguments[0]', false)).equalTo(false); }); - test.it('can pass string arguments', function() { - assert(execute('return arguments[0]', 'hi')).equalTo('hi'); + test.it('can pass string arguments', function*() { + yield assert(execute('return arguments[0]', 'hi')).equalTo('hi'); }); - test.it('can pass null arguments', function() { - assert(execute('return arguments[0] === null', null)).equalTo(true); - assert(execute('return arguments[0]', null)).equalTo(null); + test.it('can pass null arguments', function*() { + yield assert(execute('return arguments[0] === null', null)).equalTo(true); + yield assert(execute('return arguments[0]', null)).equalTo(null); }); - test.it('passes undefined as a null argument', function() { + test.it('passes undefined as a null argument', function*() { var x; - assert(execute('return arguments[0] === null', x)).equalTo(true); - assert(execute('return arguments[0]', x)).equalTo(null); + yield assert(execute('return arguments[0] === null', x)).equalTo(true); + yield assert(execute('return arguments[0]', x)).equalTo(null); }); - test.it('can pass multiple arguments', function() { - assert(execute('return arguments.length')).equalTo(0); - assert(execute('return arguments.length', 1, 'a', false)).equalTo(3); + test.it('can pass multiple arguments', function*() { + yield assert(execute('return arguments.length')).equalTo(0); + yield assert(execute('return arguments.length', 1, 'a', false)).equalTo(3); }); - test.ignore(env.browsers(Browser.FIREFOX)). - it('can return arguments object as array', function() { - execute('return arguments', 1, 'a', false).then(function(val) { - assert(val.length).equalTo(3); - assert(val[0]).equalTo(1); - assert(val[1]).equalTo('a'); - assert(val[2]).equalTo(false); - }); + test.ignore(env.browsers(Browser.FIREFOX, Browser.SAFARI)). + it('can return arguments object as array', function*() { + let val = yield execute('return arguments', 1, 'a', false); + + assert(val.length).equalTo(3); + assert(val[0]).equalTo(1); + assert(val[1]).equalTo('a'); + assert(val[2]).equalTo(false); }); - test.it('can pass object literal', function() { - execute( + test.it('can pass object literal', function*() { + let result = yield execute( 'return [typeof arguments[0], arguments[0].a]', {a: 'hello'}) - .then(function(result) { - assert(result[0]).equalTo('object'); - assert(result[1]).equalTo('hello'); - }); + assert(result[0]).equalTo('object'); + assert(result[1]).equalTo('hello'); }); - test.it('WebElement arguments are passed as DOM elements', function() { - var el = driver.findElement(By.tagName('div')); - assert(execute('return arguments[0].tagName.toLowerCase();', el)) - .equalTo('div'); + test.it('WebElement arguments are passed as DOM elements', function*() { + let el = yield driver.findElement(By.tagName('div')); + let result = + yield execute('return arguments[0].tagName.toLowerCase();', el); + assert(result).equalTo('div'); }); - test.it('can pass array containing object literals', function() { - execute('return arguments[0]', [{color: "red"}]).then(function(result) { - assert(result.length).equalTo(1); - assert(result[0].color).equalTo('red'); - }); + test.it('can pass array containing object literals', function*() { + let result = yield execute('return arguments[0]', [{color: "red"}]); + assert(result.length).equalTo(1); + assert(result[0].color).equalTo('red'); }); test.it('does not modify object literal parameters', function() { var input = {color: 'red'}; - execute('return arguments[0];', input).then(verifyJson(input)); + return execute('return arguments[0];', input).then(verifyJson(input)); }); }); @@ -248,7 +246,7 @@ test.suite(function(env) { describe('issue 8223;', function() { describe('using for..in loops;', function() { test.it('can return array built from for-loop index', function() { - execute(function() { + return execute(function() { var ret = []; for (var i = 0; i < 3; i++) { ret.push(i); @@ -258,7 +256,7 @@ test.suite(function(env) { }); test.it('can copy input array contents', function() { - execute(function(input) { + return execute(function(input) { var ret = []; for (var i in input) { ret.push(input[i]); @@ -268,7 +266,7 @@ test.suite(function(env) { }); test.it('can iterate over input object keys', function() { - execute(function(thing) { + return execute(function(thing) { var ret = []; for (var w in thing.words) { ret.push(thing.words[w].word); @@ -281,7 +279,7 @@ test.suite(function(env) { describe('recursive functions;', function() { test.it('can build array from input', function() { var input = ['fa', 'fe', 'fi']; - execute(function(thearray) { + return execute(function(thearray) { var ret = []; function build_response(thearray, ret) { ret.push(thearray.shift()); @@ -294,7 +292,7 @@ test.suite(function(env) { test.it('can build array from elements in object', function() { var input = {words: [{word: 'fa'}, {word: 'fe'}, {word: 'fi'}]}; - execute(function(thing) { + return execute(function(thing) { var ret = []; function build_response(thing, ret) { var item = thing.words.shift(); @@ -342,7 +340,7 @@ test.suite(function(env) { function verifyJson(expected) { return function(actual) { - assert(JSON.stringify(actual)).equalTo(JSON.stringify(expected)); + return assert(JSON.stringify(actual)).equalTo(JSON.stringify(expected)); }; } diff --git a/node_modules/selenium-webdriver/test/fingerprint_test.js b/node_modules/selenium-webdriver/test/fingerprint_test.js index ca36ce142..0a13dd748 100644 --- a/node_modules/selenium-webdriver/test/fingerprint_test.js +++ b/node_modules/selenium-webdriver/test/fingerprint_test.js @@ -35,21 +35,26 @@ test.suite(function(env) { }); describe('fingerprinting', function() { - test.it('it should fingerprint the navigator object', function() { - driver.get(Pages.simpleTestPage); - assert(driver.executeScript('return navigator.webdriver')).equalTo(true); + test.it('it should fingerprint the navigator object', function*() { + yield driver.get(Pages.simpleTestPage); + + let wd = yield driver.executeScript('return navigator.webdriver'); + assert(wd).equalTo(true); }); - test.it('fingerprint must not be writable', function() { - driver.get(Pages.simpleTestPage); - assert(driver.executeScript( - 'navigator.webdriver = "ohai"; return navigator.webdriver')) - .equalTo(true); + test.it('fingerprint must not be writable', function*() { + yield driver.get(Pages.simpleTestPage); + + let wd = yield driver.executeScript( + 'navigator.webdriver = "ohai"; return navigator.webdriver'); + assert(wd).equalTo(true); }); - test.it('leaves fingerprint on svg pages', function() { - driver.get(Pages.svgPage); - assert(driver.executeScript('return navigator.webdriver')).equalTo(true); + test.it('leaves fingerprint on svg pages', function*() { + yield driver.get(Pages.svgPage); + + let wd = yield driver.executeScript('return navigator.webdriver'); + assert(wd).equalTo(true); }); }); diff --git a/node_modules/selenium-webdriver/test/firefox/firefox_test.js b/node_modules/selenium-webdriver/test/firefox/firefox_test.js index 6f3f4b3d9..485964f91 100644 --- a/node_modules/selenium-webdriver/test/firefox/firefox_test.js +++ b/node_modules/selenium-webdriver/test/firefox/firefox_test.js @@ -44,11 +44,30 @@ test.suite(function(env) { test.afterEach(function() { if (driver) { - driver.quit(); + return driver.quit(); } }); - test.it('can start Firefox with custom preferences', function() { + /** + * Runs a test that requires Firefox Developer Edition. The test will be + * skipped if dev cannot be found on the current system. + */ + function runWithFirefoxDev(options, testFn) { + let binary = new firefox.Binary(); + binary.useDevEdition(); + return binary.locate().then(exe => { + options.setBinary(exe); + driver = env.builder() + .setFirefoxOptions(options) + .build(); + return driver.call(testFn); + }, err => { + console.warn( + 'Skipping test: could not find Firefox Dev Edition: ' + err); + }); + } + + test.it('can start Firefox with custom preferences', function*() { var profile = new firefox.Profile(); profile.setPreference('general.useragent.override', 'foo;bar'); @@ -58,68 +77,72 @@ test.suite(function(env) { setFirefoxOptions(options). build(); - driver.get('data:text/html,<html><div>content</div></html>'); + yield driver.get('data:text/html,<html><div>content</div></html>'); - var userAgent = driver.executeScript( + var userAgent = yield driver.executeScript( 'return window.navigator.userAgent'); assert(userAgent).equalTo('foo;bar'); }); test.it('can start Firefox with a jetpack extension', function() { - var profile = new firefox.Profile(); + let profile = new firefox.Profile(); profile.addExtension(JETPACK_EXTENSION); - var options = new firefox.Options().setProfile(profile); + let options = new firefox.Options().setProfile(profile); - driver = env.builder(). - setFirefoxOptions(options). - build(); + return runWithFirefoxDev(options, function*() { + yield loadJetpackPage(driver, + 'data:text/html;charset=UTF-8,<html><div>content</div></html>'); - loadJetpackPage(driver, - 'data:text/html;charset=UTF-8,<html><div>content</div></html>'); - assert(driver.findElement({id: 'jetpack-sample-banner'}).getText()) - .equalTo('Hello, world!'); + let text = + yield driver.findElement({id: 'jetpack-sample-banner'}).getText(); + assert(text).equalTo('Hello, world!'); + }); }); test.it('can start Firefox with a normal extension', function() { - var profile = new firefox.Profile(); + let profile = new firefox.Profile(); profile.addExtension(NORMAL_EXTENSION); - var options = new firefox.Options().setProfile(profile); + let options = new firefox.Options().setProfile(profile); - driver = env.builder(). - setFirefoxOptions(options). - build(); + return runWithFirefoxDev(options, function*() { + yield driver.get('data:text/html,<html><div>content</div></html>'); - driver.get('data:text/html,<html><div>content</div></html>'); - assert(driver.findElement({id: 'sample-extension-footer'}).getText()) - .equalTo('Goodbye'); + let footer = + yield driver.findElement({id: 'sample-extension-footer'}); + let text = yield footer.getText(); + assert(text).equalTo('Goodbye'); + }); }); test.it('can start Firefox with multiple extensions', function() { - var profile = new firefox.Profile(); + let profile = new firefox.Profile(); profile.addExtension(JETPACK_EXTENSION); profile.addExtension(NORMAL_EXTENSION); - var options = new firefox.Options().setProfile(profile); + let options = new firefox.Options().setProfile(profile); - driver = env.builder(). - setFirefoxOptions(options). - build(); + return runWithFirefoxDev(options, function*() { + yield loadJetpackPage(driver, + 'data:text/html;charset=UTF-8,<html><div>content</div></html>'); + + let banner = + yield driver.findElement({id: 'jetpack-sample-banner'}).getText(); + assert(banner).equalTo('Hello, world!'); - loadJetpackPage(driver, - 'data:text/html;charset=UTF-8,<html><div>content</div></html>'); - assert(driver.findElement({id: 'jetpack-sample-banner'}).getText()) - .equalTo('Hello, world!'); - assert(driver.findElement({id: 'sample-extension-footer'}).getText()) - .equalTo('Goodbye'); + let footer = + yield driver.findElement({id: 'sample-extension-footer'}) + .getText(); + assert(footer).equalTo('Goodbye'); + }); }); function loadJetpackPage(driver, url) { // On linux the jetpack extension does not always run the first time // we load a page. If this happens, just reload the page (a simple // refresh doesn't appear to work). - driver.wait(function() { + return driver.wait(function() { driver.get(url); return driver.findElements({id: 'jetpack-sample-banner'}) .then(found => found.length > 0); @@ -131,21 +154,21 @@ test.suite(function(env) { var driver1, driver2; test.ignore(env.isRemote). - it('can start multiple sessions with single binary instance', function() { + it('can start multiple sessions with single binary instance', function*() { var options = new firefox.Options().setBinary(new firefox.Binary); env.builder().setFirefoxOptions(options); - driver1 = env.builder().build(); - driver2 = env.builder().build(); + driver1 = yield env.builder().build(); + driver2 = yield env.builder().build(); // Ok if this doesn't fail. }); - test.afterEach(function() { + test.afterEach(function*() { if (driver1) { - driver1.quit(); + yield driver1.quit(); } if (driver2) { - driver2.quit(); + yield driver2.quit(); } }); }); @@ -153,32 +176,35 @@ test.suite(function(env) { describe('context switching', function() { var driver; - test.beforeEach(function() { - driver = env.builder().build(); + test.beforeEach(function*() { + driver = yield env.builder().build(); }); test.afterEach(function() { if (driver) { - driver.quit(); + return driver.quit(); } }); test.ignore(() => !env.isMarionette). it('can get context', function() { - assert(driver.getContext()).equalTo(Context.CONTENT); + return assert(driver.getContext()).equalTo(Context.CONTENT); }); test.ignore(() => !env.isMarionette). - it('can set context', function() { - driver.setContext(Context.CHROME); - assert(driver.getContext()).equalTo(Context.CHROME); - driver.setContext(Context.CONTENT); - assert(driver.getContext()).equalTo(Context.CONTENT); + it('can set context', function*() { + yield driver.setContext(Context.CHROME); + let ctxt = yield driver.getContext(); + assert(ctxt).equalTo(Context.CHROME); + + yield driver.setContext(Context.CONTENT); + ctxt = yield driver.getContext(); + assert(ctxt).equalTo(Context.CONTENT); }); test.ignore(() => !env.isMarionette). it('throws on unknown context', function() { - driver.setContext("foo").then(assert.fail, function(e) { + return driver.setContext("foo").then(assert.fail, function(e) { assert(e).instanceOf(error.InvalidArgumentError); }); }); diff --git a/node_modules/selenium-webdriver/test/firefox/profile_test.js b/node_modules/selenium-webdriver/test/firefox/profile_test.js index 807c07b72..de61c26b5 100644 --- a/node_modules/selenium-webdriver/test/firefox/profile_test.js +++ b/node_modules/selenium-webdriver/test/firefox/profile_test.js @@ -52,7 +52,6 @@ describe('Profile', function() { it('allows overriding mutable properties', function() { var profile = new Profile(); - assert.equal('about:blank', profile.getPreference('browser.newtab.url')); profile.setPreference('browser.newtab.url', 'http://www.example.com'); assert.equal('http://www.example.com', diff --git a/node_modules/selenium-webdriver/test/http/util_test.js b/node_modules/selenium-webdriver/test/http/util_test.js index aa7a9158a..6361c0650 100644 --- a/node_modules/selenium-webdriver/test/http/util_test.js +++ b/node_modules/selenium-webdriver/test/http/util_test.js @@ -17,11 +17,12 @@ 'use strict'; -var assert = require('assert'), - http = require('http'); +const assert = require('assert'); +const http = require('http'); -var error = require('../../lib/error'); -var util = require('../../http/util'); +const error = require('../../lib/error'); +const util = require('../../http/util'); +const promise = require('../../lib/promise'); describe('selenium-webdriver/http/util', function() { @@ -123,19 +124,15 @@ describe('selenium-webdriver/http/util', function() { function() {}); }); - it('can cancel wait', function(done) { + it('can cancel wait', function() { status = 1; - var err = Error('cancelled!'); - var isReady = util.waitForServer(baseUrl, 200). - then(function() { done('Did not expect to succeed'); }). - then(null, function(e) { - assert.equal('cancelled!', e.message); - }). - then(function() { done(); }, done); - - setTimeout(function() { - isReady.cancel('cancelled!'); - }, 50); + let cancel = new Promise(resolve => { + setTimeout(_ => resolve(), 50) + }); + return util.waitForServer(baseUrl, 200, cancel) + .then( + () => { throw Error('Did not expect to succeed!'); }, + (e) => assert.ok(e instanceof promise.CancellationError)); }); }); @@ -167,18 +164,15 @@ describe('selenium-webdriver/http/util', function() { }); }); - it('can cancel wait', function(done) { + it('can cancel wait', function() { responseCode = 404; - var isReady = util.waitForUrl(baseUrl, 200). - then(function() { done('Did not expect to succeed'); }). - then(null, function(e) { - assert.equal('cancelled!', e.message); - }). - then(function() { done(); }, done); - - setTimeout(function() { - isReady.cancel('cancelled!'); - }, 50); + let cancel = new Promise(resolve => { + setTimeout(_ => resolve(), 50); + }); + return util.waitForUrl(baseUrl, 200, cancel) + .then( + () => { throw Error('Did not expect to succeed!'); }, + (e) => assert.ok(e instanceof promise.CancellationError)); }); }); }); diff --git a/node_modules/selenium-webdriver/test/io_test.js b/node_modules/selenium-webdriver/test/io_test.js index 42ec8d3a3..840d7644b 100644 --- a/node_modules/selenium-webdriver/test/io_test.js +++ b/node_modules/selenium-webdriver/test/io_test.js @@ -307,7 +307,7 @@ describe('io', function() { }); it('catches errors from invalid input', function() { - return io.read(1234) + return io.read({}) .then(() => assert.fail('should have failed'), (e) => assert.ok(e instanceof TypeError)); }); diff --git a/node_modules/selenium-webdriver/test/lib/http_test.js b/node_modules/selenium-webdriver/test/lib/http_test.js index 343a04800..1c6c073ad 100644 --- a/node_modules/selenium-webdriver/test/lib/http_test.js +++ b/node_modules/selenium-webdriver/test/lib/http_test.js @@ -84,12 +84,14 @@ describe('http', function() { describe('command routing', function() { it('rejects unrecognized commands', function() { - assert.throws( - () => executor.execute(new Command('fake-name')), - function (err) { - return err instanceof error.UnknownCommandError - && 'Unrecognized command: fake-name' === err.message; - }); + return executor.execute(new Command('fake-name')) + .then(assert.fail, err => { + if (err instanceof error.UnknownCommandError + && 'Unrecognized command: fake-name' === err.message) { + return; + } + throw err; + }) }); it('rejects promise if client fails to send request', function() { diff --git a/node_modules/selenium-webdriver/test/lib/promise_aplus_test.js b/node_modules/selenium-webdriver/test/lib/promise_aplus_test.js index a89391590..207f490a1 100644 --- a/node_modules/selenium-webdriver/test/lib/promise_aplus_test.js +++ b/node_modules/selenium-webdriver/test/lib/promise_aplus_test.js @@ -18,57 +18,61 @@ 'use strict'; const promise = require('../../lib/promise'); +const {enablePromiseManager} = require('../../lib/test/promise'); describe('Promises/A+ Compliance Tests', function() { - // The promise spec does not define behavior for unhandled rejections and - // assumes they are effectively swallowed. This is not the case with our - // implementation, so we have to disable error propagation to test that the - // rest of our behavior is compliant. - // We run the tests with a separate instance of the control flow to ensure - // disablign error propagation does not impact other tests. - var flow = new promise.ControlFlow(); - flow.setPropagateUnhandledRejections(false); + enablePromiseManager(() => { + // The promise spec does not define behavior for unhandled rejections and + // assumes they are effectively swallowed. This is not the case with our + // implementation, so we have to disable error propagation to test that the + // rest of our behavior is compliant. + // We run the tests with a separate instance of the control flow to ensure + // disablign error propagation does not impact other tests. + var flow = new promise.ControlFlow(); + flow.setPropagateUnhandledRejections(false); - // Skip the tests in 2.2.6.1/2. We are not compliant in these scenarios. - var realDescribe = global.describe; - global.describe = function(name, fn) { - realDescribe(name, function() { - var prefix = 'Promises/A+ Compliance Tests 2.2.6: ' - + '`then` may be called multiple times on the same promise.'; - var suffix = 'even when one handler is added inside another handler'; - if (this.fullTitle().startsWith(prefix) - && this.fullTitle().endsWith(suffix)) { - var realSpecify = global.specify; - try { - global.specify = function(name) { - realSpecify(name); - }; + // Skip the tests in 2.2.6.1/2. We are not compliant in these scenarios. + var realDescribe = global.describe; + global.describe = function(name, fn) { + realDescribe(name, function() { + var prefix = 'Promises/A+ Compliance Tests ' + + 'SELENIUM_PROMISE_MANAGER=true 2.2.6: ' + + '`then` may be called multiple times on the same promise.'; + var suffix = 'even when one handler is added inside another handler'; + if (this.fullTitle().startsWith(prefix) + && this.fullTitle().endsWith(suffix)) { + var realSpecify = global.specify; + try { + global.specify = function(name) { + realSpecify(name); + }; + fn(); + } finally { + global.specify = realSpecify; + } + } else { fn(); - } finally { - global.specify = realSpecify; } - } else { - fn(); + }); + }; + + require('promises-aplus-tests').mocha({ + resolved: function(value) { + return new promise.Promise((fulfill) => fulfill(value), flow); + }, + rejected: function(error) { + return new promise.Promise((_, reject) => reject(error), flow); + }, + deferred: function() { + var d = new promise.Deferred(flow); + return { + resolve: d.fulfill, + reject: d.reject, + promise: d.promise + }; } }); - }; - require('promises-aplus-tests').mocha({ - resolved: function(value) { - return new promise.Promise((fulfill) => fulfill(value), flow); - }, - rejected: function(error) { - return new promise.Promise((_, reject) => reject(error), flow); - }, - deferred: function() { - var d = new promise.Deferred(flow); - return { - resolve: d.fulfill, - reject: d.reject, - promise: d.promise - }; - } + global.describe = realDescribe; }); - - global.describe = realDescribe; }); diff --git a/node_modules/selenium-webdriver/test/lib/promise_error_test.js b/node_modules/selenium-webdriver/test/lib/promise_error_test.js index e6de3cb92..b89a2f875 100644 --- a/node_modules/selenium-webdriver/test/lib/promise_error_test.js +++ b/node_modules/selenium-webdriver/test/lib/promise_error_test.js @@ -27,6 +27,7 @@ const testutil = require('./testutil'); const assert = require('assert'); const promise = require('../../lib/promise'); +const {enablePromiseManager} = require('../../lib/test/promise'); const NativePromise = Promise; const StubError = testutil.StubError; @@ -34,244 +35,178 @@ const throwStubError = testutil.throwStubError; const assertIsStubError = testutil.assertIsStubError; describe('promise error handling', function() { - var flow, uncaughtExceptions; - - beforeEach(function setUp() { - flow = promise.controlFlow(); - uncaughtExceptions = []; - flow.on('uncaughtException', onUncaughtException); - }); - - afterEach(function tearDown() { - return waitForIdle(flow).then(function() { - assert.deepEqual( - [], uncaughtExceptions, 'There were uncaught exceptions'); - flow.reset(); - }); - }); - - function onUncaughtException(e) { - uncaughtExceptions.push(e); - } - - function waitForAbort(opt_flow, opt_n) { - var n = opt_n || 1; - var theFlow = opt_flow || flow; - theFlow.removeAllListeners( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); - return new NativePromise(function(fulfill, reject) { - theFlow.once('idle', function() { - reject(Error('expected flow to report an unhandled error')); - }); - - var errors = []; - theFlow.on('uncaughtException', onError); - function onError(e) { - errors.push(e); - if (errors.length === n) { - theFlow.removeListener('uncaughtException', onError); - fulfill(n === 1 ? errors[0] : errors); - } + enablePromiseManager(() => { + var flow, uncaughtExceptions; + + beforeEach(function setUp() { + if (promise.USE_PROMISE_MANAGER) { + flow = promise.controlFlow(); + uncaughtExceptions = []; + flow.on('uncaughtException', onUncaughtException); } }); - } - - function waitForIdle(opt_flow) { - var theFlow = opt_flow || flow; - return new NativePromise(function(fulfill, reject) { - if (theFlow.isIdle()) { - fulfill(); - return; - } - theFlow.once('idle', fulfill); - theFlow.once('uncaughtException', reject); - }); - } - - it('testRejectedPromiseTriggersErrorCallback', function() { - return promise.rejected(new StubError). - then(assert.fail, assertIsStubError); - }); - describe('callback throws trigger subsequent error callback', function() { - it('fulfilled promise', function() { - return promise.fulfilled(). - then(throwStubError). - then(assert.fail, assertIsStubError); - }); - - it('rejected promise', function() { - var e = Error('not the droids you are looking for'); - return promise.rejected(e). - then(assert.fail, throwStubError). - then(assert.fail, assertIsStubError); - }); - }); - - describe('callback returns rejected promise triggers subsequent errback', function() { - it('from fulfilled callback', function() { - return promise.fulfilled().then(function() { - return promise.rejected(new StubError); - }).then(assert.fail, assertIsStubError); - }); - - it('from rejected callback', function() { - var e = Error('not the droids you are looking for'); - return promise.rejected(e). - then(assert.fail, function() { - return promise.rejected(new StubError); - }). - then(assert.fail, assertIsStubError); + afterEach(function tearDown() { + if (promise.USE_PROMISE_MANAGER) { + return waitForIdle(flow).then(function() { + assert.deepEqual( + [], uncaughtExceptions, 'There were uncaught exceptions'); + flow.reset(); + }); + } }); - }); - - it('testReportsUnhandledRejectionsThroughTheControlFlow', function() { - promise.rejected(new StubError); - return waitForAbort().then(assertIsStubError); - }); - describe('multiple unhandled rejections outside a task', function() { - it('are reported in order they occurred', function() { - var e1 = Error('error 1'); - var e2 = Error('error 2'); + function onUncaughtException(e) { + uncaughtExceptions.push(e); + } - promise.rejected(e1); - promise.rejected(e2); + function waitForAbort(opt_flow, opt_n) { + var n = opt_n || 1; + var theFlow = opt_flow || flow; + theFlow.removeAllListeners( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + return new NativePromise(function(fulfill, reject) { + theFlow.once('idle', function() { + reject(Error('expected flow to report an unhandled error')); + }); - return waitForAbort(flow).then(function(error) { - assert.ok( - error instanceof promise.MultipleUnhandledRejectionError); - // TODO: switch to Array.from when we drop node 0.12.x var errors = []; - for (var e of error.errors) { + theFlow.on('uncaughtException', onError); + function onError(e) { errors.push(e); + if (errors.length === n) { + theFlow.removeListener('uncaughtException', onError); + fulfill(n === 1 ? errors[0] : errors); + } } - assert.deepEqual([e1, e2], errors); }); - }); - }); - - describe('does not report unhandled rejection when', function() { - it('handler added before next tick', function() { - promise.rejected(new StubError).then(assert.fail, assertIsStubError); - return waitForIdle(); - }); + } - it('added async but before next tick', function() { - var called = false; + function waitForIdle(opt_flow) { + var theFlow = opt_flow || flow; return new NativePromise(function(fulfill, reject) { - var aPromise; - NativePromise.resolve().then(function() { - aPromise.then(assert.fail, function(e) { - called = true; - assertIsStubError(e); - }); - waitForIdle().then(fulfill, reject); - }); - aPromise = promise.rejected(new StubError); - }).then(function() { - assert.ok(called); - }) + if (theFlow.isIdle()) { + fulfill(); + return; + } + theFlow.once('idle', fulfill); + theFlow.once('uncaughtException', reject); + }); + } + + it('testRejectedPromiseTriggersErrorCallback', function() { + return promise.rejected(new StubError). + then(assert.fail, assertIsStubError); }); - }); - it('testTaskThrows', function() { - return flow.execute(throwStubError).then(assert.fail, assertIsStubError); - }); + describe('callback throws trigger subsequent error callback', function() { + it('fulfilled promise', function() { + return promise.fulfilled(). + then(throwStubError). + then(assert.fail, assertIsStubError); + }); - it('testTaskReturnsRejectedPromise', function() { - return flow.execute(function() { - return promise.rejected(new StubError) - }).then(assert.fail, assertIsStubError); - }); + it('rejected promise', function() { + var e = Error('not the droids you are looking for'); + return promise.rejected(e). + then(assert.fail, throwStubError). + then(assert.fail, assertIsStubError); + }); + }); - it('testTaskHasUnhandledRejection', function() { - return flow.execute(function() { - promise.rejected(new StubError) - }).then(assert.fail, assertIsStubError); - }); + describe('callback returns rejected promise triggers subsequent errback', function() { + it('from fulfilled callback', function() { + return promise.fulfilled().then(function() { + return promise.rejected(new StubError); + }).then(assert.fail, assertIsStubError); + }); - it('testTaskfails_returnedPromiseIsUnhandled', function() { - flow.execute(throwStubError); - return waitForAbort().then(assertIsStubError); - }); + it('from rejected callback', function() { + var e = Error('not the droids you are looking for'); + return promise.rejected(e). + then(assert.fail, function() { + return promise.rejected(new StubError); + }). + then(assert.fail, assertIsStubError); + }); + }); - it('testTaskHasUnhandledRejection_cancelsRemainingSubTasks', function() { - var seen = []; - flow.execute(function() { + it('testReportsUnhandledRejectionsThroughTheControlFlow', function() { promise.rejected(new StubError); - - flow.execute(() => seen.push('a')) - .then(() => seen.push('b'), (e) => seen.push(e)); - flow.execute(() => seen.push('c')) - .then(() => seen.push('b'), (e) => seen.push(e)); + return waitForAbort().then(assertIsStubError); }); - return waitForAbort() - .then(assertIsStubError) - .then(() => assert.deepEqual([], seen)); - }); + describe('multiple unhandled rejections outside a task', function() { + it('are reported in order they occurred', function() { + var e1 = Error('error 1'); + var e2 = Error('error 2'); - describe('nested task failures', function() { - it('returns up to paren', function() { - return flow.execute(function() { - return flow.execute(function() { - return flow.execute(throwStubError); - }); - }).then(assert.fail, assertIsStubError); - }); + promise.rejected(e1); + promise.rejected(e2); - it('task throws; uncaught error bubbles up', function() { - flow.execute(function() { - flow.execute(function() { - flow.execute(throwStubError); + return waitForAbort(flow).then(function(error) { + assert.ok( + error instanceof promise.MultipleUnhandledRejectionError); + // TODO: switch to Array.from when we drop node 0.12.x + var errors = []; + for (var e of error.errors) { + errors.push(e); + } + assert.deepEqual([e1, e2], errors); }); }); - return waitForAbort().then(assertIsStubError); }); - it('task throws; uncaught error bubbles up; is caught at root', function() { - flow.execute(function() { - flow.execute(function() { - flow.execute(throwStubError); - }); - }).then(assert.fail, assertIsStubError); - return waitForIdle(); - }); + describe('does not report unhandled rejection when', function() { + it('handler added before next tick', function() { + promise.rejected(new StubError).then(assert.fail, assertIsStubError); + return waitForIdle(); + }); - it('unhandled rejection bubbles up', function() { - flow.execute(function() { - flow.execute(function() { - flow.execute(function() { - promise.rejected(new StubError); + it('added async but before next tick', function() { + var called = false; + return new NativePromise(function(fulfill, reject) { + var aPromise; + NativePromise.resolve().then(function() { + aPromise.then(assert.fail, function(e) { + called = true; + assertIsStubError(e); + }); + waitForIdle().then(fulfill, reject); }); - }); + aPromise = promise.rejected(new StubError); + }).then(function() { + assert.ok(called); + }) }); - return waitForAbort().then(assertIsStubError); }); - it('unhandled rejection bubbles up; caught at root', function() { - flow.execute(function() { - flow.execute(function() { - promise.rejected(new StubError); - }); + it('testTaskThrows', function() { + return flow.execute(throwStubError).then(assert.fail, assertIsStubError); + }); + + it('testTaskReturnsRejectedPromise', function() { + return flow.execute(function() { + return promise.rejected(new StubError) }).then(assert.fail, assertIsStubError); - return waitForIdle(); }); - it('mixtureof hanging and free subtasks', function() { - flow.execute(function() { - return flow.execute(function() { - flow.execute(throwStubError); - }); - }); + it('testTaskHasUnhandledRejection', function() { + return flow.execute(function() { + promise.rejected(new StubError) + }).then(assert.fail, assertIsStubError); + }); + + it('testTaskfails_returnedPromiseIsUnhandled', function() { + flow.execute(throwStubError); return waitForAbort().then(assertIsStubError); }); - it('cancels remaining tasks', function() { + it('testTaskHasUnhandledRejection_cancelsRemainingSubTasks', function() { var seen = []; flow.execute(function() { - flow.execute(() => promise.rejected(new StubError)); + promise.rejected(new StubError); + flow.execute(() => seen.push('a')) .then(() => seen.push('b'), (e) => seen.push(e)); flow.execute(() => seen.push('c')) @@ -282,598 +217,667 @@ describe('promise error handling', function() { .then(assertIsStubError) .then(() => assert.deepEqual([], seen)); }); - }); - - it('testTaskReturnsPromiseLikeObjectThatInvokesErrback', function() { - return flow.execute(function() { - return { - 'then': function(_, errback) { - errback('abc123'); - } - }; - }).then(assert.fail, function(value) { - assert.equal('abc123', value); - }); - }); - - describe('ControlFlow#wait();', function() { - describe('condition throws;', function() { - it('failure is caught', function() { - return flow.wait(throwStubError, 50).then(assert.fail, assertIsStubError); - }); - - it('failure is not caught', function() { - flow.wait(throwStubError, 50); - return waitForAbort().then(assertIsStubError); - }); - }); - describe('condition returns promise', function() { - it('failure is caught', function() { - return flow.wait(function() { - return promise.rejected(new StubError); - }, 50).then(assert.fail, assertIsStubError); + describe('nested task failures', function() { + it('returns up to paren', function() { + return flow.execute(function() { + return flow.execute(function() { + return flow.execute(throwStubError); + }); + }).then(assert.fail, assertIsStubError); }); - it('failure is not caught', function() { - flow.wait(function() { - return promise.rejected(new StubError); - }, 50); + it('task throws; uncaught error bubbles up', function() { + flow.execute(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }); return waitForAbort().then(assertIsStubError); }); - }); - describe('condition has unhandled promise rejection', function() { - it('failure is caught', function() { - return flow.wait(function() { - promise.rejected(new StubError); - }, 50).then(assert.fail, assertIsStubError); + it('task throws; uncaught error bubbles up; is caught at root', function() { + flow.execute(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }).then(assert.fail, assertIsStubError); + return waitForIdle(); }); - it('failure is not caught', function() { - flow.wait(function() { - promise.rejected(new StubError); - }, 50); + it('unhandled rejection bubbles up', function() { + flow.execute(function() { + flow.execute(function() { + flow.execute(function() { + promise.rejected(new StubError); + }); + }); + }); return waitForAbort().then(assertIsStubError); }); - }); - describe('condition has subtask failure', function() { - it('failure is caught', function() { - return flow.wait(function() { + it('unhandled rejection bubbles up; caught at root', function() { + flow.execute(function() { flow.execute(function() { - flow.execute(throwStubError); + promise.rejected(new StubError); }); - }, 50).then(assert.fail, assertIsStubError); + }).then(assert.fail, assertIsStubError); + return waitForIdle(); }); - it('failure is not caught', function() { - flow.wait(function() { - flow.execute(function() { + it('mixtureof hanging and free subtasks', function() { + flow.execute(function() { + return flow.execute(function() { flow.execute(throwStubError); }); - }, 50); + }); return waitForAbort().then(assertIsStubError); }); - }); - }); - describe('errback throws a new error', function() { - it('start with normal promise', function() { - var error = Error('an error'); - return promise.rejected(error). - catch(function(e) { - assert.equal(e, error); - throw new StubError; - }). - catch(assertIsStubError); + it('cancels remaining tasks', function() { + var seen = []; + flow.execute(function() { + flow.execute(() => promise.rejected(new StubError)); + flow.execute(() => seen.push('a')) + .then(() => seen.push('b'), (e) => seen.push(e)); + flow.execute(() => seen.push('c')) + .then(() => seen.push('b'), (e) => seen.push(e)); + }); + + return waitForAbort() + .then(assertIsStubError) + .then(() => assert.deepEqual([], seen)); + }); }); - it('start with task result', function() { - var error = Error('an error'); + it('testTaskReturnsPromiseLikeObjectThatInvokesErrback', function() { return flow.execute(function() { - throw error; - }). - catch(function(e) { - assert.equal(e, error); - throw new StubError; - }). - catch(assertIsStubError); + return { + 'then': function(_, errback) { + errback('abc123'); + } + }; + }).then(assert.fail, function(value) { + assert.equal('abc123', value); + }); }); - it('start with normal promise; uncaught error', function() { - var error = Error('an error'); - promise.rejected(error). - catch(function(e) { - assert.equal(e, error); - throw new StubError; - }); - return waitForAbort().then(assertIsStubError); - }); + describe('ControlFlow#wait();', function() { + describe('condition throws;', function() { + it('failure is caught', function() { + return flow.wait(throwStubError, 50).then(assert.fail, assertIsStubError); + }); - it('start with task result; uncaught error', function() { - var error = Error('an error'); - flow.execute(function() { - throw error; - }). - catch(function(e) { - assert.equal(e, error); - throw new StubError; + it('failure is not caught', function() { + flow.wait(throwStubError, 50); + return waitForAbort().then(assertIsStubError); + }); }); - return waitForAbort().then(assertIsStubError); - }); - }); - it('thrownPromiseCausesCallbackRejection', function() { - let p = promise.fulfilled(1234); - return promise.fulfilled().then(function() { - throw p; - }).then(assert.fail, function(value) { - assert.strictEqual(p, value); - }); - }); + describe('condition returns promise', function() { + it('failure is caught', function() { + return flow.wait(function() { + return promise.rejected(new StubError); + }, 50).then(assert.fail, assertIsStubError); + }); - describe('task throws promise', function() { - it('promise was fulfilled', function() { - var toThrow = promise.fulfilled(1234); - flow.execute(function() { - throw toThrow; - }).then(assert.fail, function(value) { - assert.equal(toThrow, value); - return toThrow; - }).then(function(value) { - assert.equal(1234, value); + it('failure is not caught', function() { + flow.wait(function() { + return promise.rejected(new StubError); + }, 50); + return waitForAbort().then(assertIsStubError); + }); }); - return waitForIdle(); - }); - - it('promise was rejected', function() { - var toThrow = promise.rejected(new StubError); - toThrow.catch(function() {}); // For tearDown. - flow.execute(function() { - throw toThrow; - }).then(assert.fail, function(e) { - assert.equal(toThrow, e); - return e; - }).then(assert.fail, assertIsStubError); - return waitForIdle(); - }); - }); - it('testFailsTaskIfThereIsAnUnhandledErrorWhileWaitingOnTaskResult', function() { - var d = promise.defer(); - flow.execute(function() { - promise.rejected(new StubError); - return d.promise; - }).then(assert.fail, assertIsStubError); + describe('condition has unhandled promise rejection', function() { + it('failure is caught', function() { + return flow.wait(function() { + promise.rejected(new StubError); + }, 50).then(assert.fail, assertIsStubError); + }); - return waitForIdle().then(function() { - return d.promise; - }).then(assert.fail, function(e) { - assert.equal('CancellationError: StubError', e.toString()); - }); - }); + it('failure is not caught', function() { + flow.wait(function() { + promise.rejected(new StubError); + }, 50); + return waitForAbort().then(assertIsStubError); + }); + }); - it('testFailsParentTaskIfAsyncScheduledTaskFails', function() { - var d = promise.defer(); - flow.execute(function() { - flow.execute(throwStubError); - return d.promise; - }).then(assert.fail, assertIsStubError); + describe('condition has subtask failure', function() { + it('failure is caught', function() { + return flow.wait(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }, 50).then(assert.fail, assertIsStubError); + }); - return waitForIdle().then(function() { - return d.promise; - }).then(assert.fail, function(e) { - assert.equal('CancellationError: StubError', e.toString()); + it('failure is not caught', function() { + flow.wait(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }, 50); + return waitForAbort().then(assertIsStubError); + }); + }); }); - }); - describe('long stack traces', function() { - afterEach(() => promise.LONG_STACK_TRACES = false); + describe('errback throws a new error', function() { + it('start with normal promise', function() { + var error = Error('an error'); + return promise.rejected(error). + catch(function(e) { + assert.equal(e, error); + throw new StubError; + }). + catch(assertIsStubError); + }); - it('always includes task stacks in failures', function() { - promise.LONG_STACK_TRACES = false; - flow.execute(function() { - flow.execute(function() { - flow.execute(throwStubError, 'throw error'); - }, 'two'); - }, 'three'). - then(assert.fail, function(e) { - assertIsStubError(e); - if (typeof e.stack !== 'string') { - return; - } - var messages = e.stack.split(/\n/).filter(function(line, index) { - return /^From: /.test(line); - }); - assert.deepEqual([ - 'From: Task: throw error', - 'From: Task: two', - 'From: Task: three' - ], messages); + it('start with task result', function() { + var error = Error('an error'); + return flow.execute(function() { + throw error; + }). + catch(function(e) { + assert.equal(e, error); + throw new StubError; + }). + catch(assertIsStubError); + }); + + it('start with normal promise; uncaught error', function() { + var error = Error('an error'); + promise.rejected(error). + catch(function(e) { + assert.equal(e, error); + throw new StubError; + }); + return waitForAbort().then(assertIsStubError); }); - return waitForIdle(); - }); - it('does not include completed tasks', function () { - flow.execute(function() {}, 'succeeds'); - flow.execute(throwStubError, 'kaboom').then(assert.fail, function(e) { - assertIsStubError(e); - if (typeof e.stack !== 'string') { - return; - } - var messages = e.stack.split(/\n/).filter(function(line, index) { - return /^From: /.test(line); + it('start with task result; uncaught error', function() { + var error = Error('an error'); + flow.execute(function() { + throw error; + }). + catch(function(e) { + assert.equal(e, error); + throw new StubError; }); - assert.deepEqual(['From: Task: kaboom'], messages); + return waitForAbort().then(assertIsStubError); }); - return waitForIdle(); }); - it('does not include promise chain when disabled', function() { - promise.LONG_STACK_TRACES = false; - flow.execute(function() { - flow.execute(function() { - return promise.fulfilled(). - then(function() {}). - then(function() {}). - then(throwStubError); - }, 'eventually assert.fails'); - }, 'start'). - then(assert.fail, function(e) { - assertIsStubError(e); - if (typeof e.stack !== 'string') { - return; - } - var messages = e.stack.split(/\n/).filter(function(line, index) { - return /^From: /.test(line); - }); - assert.deepEqual([ - 'From: Task: eventually assert.fails', - 'From: Task: start' - ], messages); + it('thrownPromiseCausesCallbackRejection', function() { + let p = promise.fulfilled(1234); + return promise.fulfilled().then(function() { + throw p; + }).then(assert.fail, function(value) { + assert.strictEqual(p, value); }); - return waitForIdle(); }); - it('includes promise chain when enabled', function() { - promise.LONG_STACK_TRACES = true; - flow.execute(function() { + describe('task throws promise', function() { + it('promise was fulfilled', function() { + var toThrow = promise.fulfilled(1234); flow.execute(function() { - return promise.fulfilled(). - then(function() {}). - then(function() {}). - then(throwStubError); - }, 'eventually assert.fails'); - }, 'start'). - then(assert.fail, function(e) { - assertIsStubError(e); - if (typeof e.stack !== 'string') { - return; - } - var messages = e.stack.split(/\n/).filter(function(line, index) { - return /^From: /.test(line); + throw toThrow; + }).then(assert.fail, function(value) { + assert.equal(toThrow, value); + return toThrow; + }).then(function(value) { + assert.equal(1234, value); }); - assert.deepEqual([ - 'From: Promise: then', - 'From: Task: eventually assert.fails', - 'From: Task: start' - ], messages); + return waitForIdle(); }); - return waitForIdle(); - }); - }); - describe('frame cancels remaining tasks', function() { - it('on unhandled task failure', function() { - var run = false; - return flow.execute(function() { - flow.execute(throwStubError); - flow.execute(function() { run = true; }); - }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.ok(!run); + it('promise was rejected', function() { + var toThrow = promise.rejected(new StubError); + toThrow.catch(function() {}); // For tearDown. + flow.execute(function() { + throw toThrow; + }).then(assert.fail, function(e) { + assert.equal(toThrow, e); + return e; + }).then(assert.fail, assertIsStubError); + return waitForIdle(); }); }); - it('on unhandled promise rejection', function() { - var run = false; - return flow.execute(function() { + it('testFailsTaskIfThereIsAnUnhandledErrorWhileWaitingOnTaskResult', function() { + var d = promise.defer(); + flow.execute(function() { promise.rejected(new StubError); - flow.execute(function() { run = true; }); + return d.promise; + }).then(assert.fail, assertIsStubError); + + return waitForIdle().then(function() { + return d.promise; }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.ok(!run); + assert.equal('CancellationError: StubError', e.toString()); }); }); - it('if task throws', function() { - var run = false; - return flow.execute(function() { - flow.execute(function() { run = true; }); - throw new StubError; + it('testFailsParentTaskIfAsyncScheduledTaskFails', function() { + var d = promise.defer(); + flow.execute(function() { + flow.execute(throwStubError); + return d.promise; + }).then(assert.fail, assertIsStubError); + + return waitForIdle().then(function() { + return d.promise; }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.ok(!run); + assert.equal('CancellationError: StubError', e.toString()); }); }); - describe('task callbacks scheduled in another frame', function() { - flow = promise.controlFlow(); - function noop() {} - - let subTask; + describe('long stack traces', function() { + afterEach(() => promise.LONG_STACK_TRACES = false); - before(function() { + it('always includes task stacks in failures', function() { + promise.LONG_STACK_TRACES = false; flow.execute(function() { - // This task will be discarded and never run because of the error below. - subTask = flow.execute(() => 'abc'); - throw new StubError('stub'); - }).catch(noop); - }); - - function assertCancellation(e) { - assert.ok(e instanceof promise.CancellationError); - assert.equal( - 'Task was discarded due to a previous failure: stub', e.message); - } - - it('are rejected with cancellation error', function() { - let result; - return Promise.resolve().then(function() { - return flow.execute(function() { - result = subTask.then(assert.fail); + flow.execute(function() { + flow.execute(throwStubError, 'throw error'); + }, 'two'); + }, 'three'). + then(assert.fail, function(e) { + assertIsStubError(e); + if (typeof e.stack !== 'string') { + return; + } + var messages = e.stack.split(/\n/).filter(function(line, index) { + return /^From: /.test(line); }); - }) - .then(() => result) - .then(assert.fail, assertCancellation); + assert.deepEqual([ + 'From: Task: throw error', + 'From: Task: two', + 'From: Task: three' + ], messages); + }); + return waitForIdle(); + }); + + it('does not include completed tasks', function () { + flow.execute(function() {}, 'succeeds'); + flow.execute(throwStubError, 'kaboom').then(assert.fail, function(e) { + assertIsStubError(e); + if (typeof e.stack !== 'string') { + return; + } + var messages = e.stack.split(/\n/).filter(function(line, index) { + return /^From: /.test(line); + }); + assert.deepEqual(['From: Task: kaboom'], messages); + }); + return waitForIdle(); }); - it('cancellation errors propagate through callbacks (1)', function() { - let result; - return Promise.resolve().then(function() { - return flow.execute(function() { - result = subTask - .then(assert.fail, assertCancellation) - .then(() => 'abc123'); + it('does not include promise chain when disabled', function() { + promise.LONG_STACK_TRACES = false; + flow.execute(function() { + flow.execute(function() { + return promise.fulfilled(). + then(function() {}). + then(function() {}). + then(throwStubError); + }, 'eventually assert.fails'); + }, 'start'). + then(assert.fail, function(e) { + assertIsStubError(e); + if (typeof e.stack !== 'string') { + return; + } + var messages = e.stack.split(/\n/).filter(function(line, index) { + return /^From: /.test(line); }); - }) - .then(() => result) - .then(value => assert.equal('abc123', value)); + assert.deepEqual([ + 'From: Task: eventually assert.fails', + 'From: Task: start' + ], messages); + }); + return waitForIdle(); }); - it('cancellation errors propagate through callbacks (2)', function() { - let result; - return Promise.resolve().then(function() { - return flow.execute(function() { - result = subTask.then(assert.fail) - .then(noop, assertCancellation) - .then(() => 'fin'); + it('includes promise chain when enabled', function() { + promise.LONG_STACK_TRACES = true; + flow.execute(function() { + flow.execute(function() { + return promise.fulfilled(). + then(function() {}). + then(function() {}). + then(throwStubError); + }, 'eventually assert.fails'); + }, 'start'). + then(assert.fail, function(e) { + assertIsStubError(e); + if (typeof e.stack !== 'string') { + return; + } + var messages = e.stack.split(/\n/).filter(function(line, index) { + return /^From: /.test(line); }); - }) - // Verify result actually computed successfully all the way through. - .then(() => result) - .then(value => assert.equal('fin', value)); + assert.deepEqual([ + 'From: Promise: then', + 'From: Task: eventually assert.fails', + 'From: Task: start' + ], messages); + }); + return waitForIdle(); }); }); - }); - it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_return', function() { - var seen = []; - return flow.execute(function() { - flow.execute(throwStubError); + describe('frame cancels remaining tasks', function() { + it('on unhandled task failure', function() { + var run = false; + return flow.execute(function() { + flow.execute(throwStubError); + flow.execute(function() { run = true; }); + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.ok(!run); + }); + }); - flow.execute(function() { - seen.push(1); - }).then(function() { - seen.push(2); - }, function() { - seen.push(3); - }); - }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.deepEqual([], seen); - }); - }); + it('on unhandled promise rejection', function() { + var run = false; + return flow.execute(function() { + promise.rejected(new StubError); + flow.execute(function() { run = true; }); + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.ok(!run); + }); + }); - it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_withReturn', function() { - var seen = []; - return flow.execute(function() { - flow.execute(throwStubError); + it('if task throws', function() { + var run = false; + return flow.execute(function() { + flow.execute(function() { run = true; }); + throw new StubError; + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.ok(!run); + }); + }); - return flow.execute(function() { - seen.push(1); - }).then(function() { - seen.push(2); - }, function() { - seen.push(3); - }); - }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.deepEqual([], seen); - }); - }); + describe('task callbacks scheduled in another frame', function() { + flow = promise.controlFlow(); + function noop() {} - it('testTasksWithinACallbackAreDroppedIfContainingTaskIsAborted', function() { - var seen = []; - return flow.execute(function() { - flow.execute(throwStubError); + let subTask; - // None of the callbacks on this promise should execute because the - // task assert.failure above is never handled, causing the containing task to - // abort. - promise.fulfilled().then(function() { - seen.push(1); - return flow.execute(function() { - seen.push(2); + before(function() { + flow.execute(function() { + // This task will be discarded and never run because of the error below. + subTask = flow.execute(() => 'abc'); + throw new StubError('stub'); + }).catch(noop); }); - }).finally(function() { - seen.push(3); - }); - }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.deepEqual([], seen); - }); - }); + function assertCancellation(e) { + assert.ok(e instanceof promise.CancellationError); + assert.equal( + 'Task was discarded due to a previous failure: stub', e.message); + } - it('testTaskIsCancelledAfterWaitTimeout', function() { - var seen = []; - return flow.execute(function() { - flow.wait(function() { - return promise.delayed(50); - }, 5); + it('are rejected with cancellation error', function() { + let result; + return Promise.resolve().then(function() { + return flow.execute(function() { + result = subTask.then(assert.fail); + }); + }) + .then(() => result) + .then(assert.fail, assertCancellation); + }); - return flow.execute(function() { - seen.push(1); - }).then(function() { - seen.push(2); - }, function() { - seen.push(3); - }); - }).then(assert.fail, function() { - assert.deepEqual([], seen); + it('cancellation errors propagate through callbacks (1)', function() { + let result; + return Promise.resolve().then(function() { + return flow.execute(function() { + result = subTask + .then(assert.fail, assertCancellation) + .then(() => 'abc123'); + }); + }) + .then(() => result) + .then(value => assert.equal('abc123', value)); + }); + + it('cancellation errors propagate through callbacks (2)', function() { + let result; + return Promise.resolve().then(function() { + return flow.execute(function() { + result = subTask.then(assert.fail) + .then(noop, assertCancellation) + .then(() => 'fin'); + }); + }) + // Verify result actually computed successfully all the way through. + .then(() => result) + .then(value => assert.equal('fin', value)); + }); + }); }); - }); - describe('task callbacks get cancellation error if registered after task was cancelled', function() { - it('(a)', function() { - var task; - flow.execute(function() { + it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_return', function() { + var seen = []; + return flow.execute(function() { flow.execute(throwStubError); - task = flow.execute(function() {}); - }).then(assert.fail, assertIsStubError); - return waitForIdle().then(function() { - return task.then(assert.fail, function(e) { - assert.ok(e instanceof promise.CancellationError); + + flow.execute(function() { + seen.push(1); + }).then(function() { + seen.push(2); + }, function() { + seen.push(3); }); + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.deepEqual([], seen); }); }); - it('(b)', function() { + it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_withReturn', function() { var seen = []; - - var task; - flow.execute(function() { + return flow.execute(function() { flow.execute(throwStubError); - task = flow.execute(function() {}); - task.then(() => seen.push(1)) - .then(() => seen.push(2)); - task.then(() => seen.push(3)) - .then(() => seen.push(4)); + return flow.execute(function() { + seen.push(1); + }).then(function() { + seen.push(2); + }, function() { + seen.push(3); + }); + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.deepEqual([], seen); + }); + }); - }).then(assert.fail, assertIsStubError); + it('testTasksWithinACallbackAreDroppedIfContainingTaskIsAborted', function() { + var seen = []; + return flow.execute(function() { + flow.execute(throwStubError); - return waitForIdle().then(function() { - return task.then(assert.fail, function(e) { - seen.push(5); - assert.ok(e instanceof promise.CancellationError); + // None of the callbacks on this promise should execute because the + // task assert.failure above is never handled, causing the containing task to + // abort. + promise.fulfilled().then(function() { + seen.push(1); + return flow.execute(function() { + seen.push(2); + }); + }).finally(function() { + seen.push(3); }); - }).then(() => assert.deepEqual([5], seen)); + + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.deepEqual([], seen); + }); }); - }); - it('unhandledRejectionInParallelTaskQueue', function() { - var seen = []; - function schedule(name) { - return flow.execute(() => seen.push(name), name); - } + it('testTaskIsCancelledAfterWaitTimeout', function() { + var seen = []; + return flow.execute(function() { + flow.wait(function() { + return promise.delayed(50); + }, 5); - flow.async(function() { - schedule('a.1'); - flow.execute(throwStubError, 'a.2 (throws)'); + return flow.execute(function() { + seen.push(1); + }).then(function() { + seen.push(2); + }, function() { + seen.push(3); + }); + }).then(assert.fail, function() { + assert.deepEqual([], seen); + }); }); - var b3; - flow.async(function() { - schedule('b.1'); - schedule('b.2'); - b3 = schedule('b.3'); - }); + describe('task callbacks get cancellation error if registered after task was cancelled', function() { + it('(a)', function() { + var task; + flow.execute(function() { + flow.execute(throwStubError); + task = flow.execute(function() {}); + }).then(assert.fail, assertIsStubError); + return waitForIdle().then(function() { + return task.then(assert.fail, function(e) { + assert.ok(e instanceof promise.CancellationError); + }); + }); + }); - var c3; - flow.async(function() { - schedule('c.1'); - schedule('c.2'); - c3 = schedule('c.3'); - }); + it('(b)', function() { + var seen = []; + + var task; + flow.execute(function() { + flow.execute(throwStubError); + task = flow.execute(function() {}); + + task.then(() => seen.push(1)) + .then(() => seen.push(2)); + task.then(() => seen.push(3)) + .then(() => seen.push(4)); + + }).then(assert.fail, assertIsStubError); - function assertWasCancelled(p) { - return p.then(assert.fail, function(e) { - assert.ok(e instanceof promise.CancellationError); + return waitForIdle().then(function() { + return task.then(assert.fail, function(e) { + seen.push(5); + assert.ok(e instanceof promise.CancellationError); + }); + }).then(() => assert.deepEqual([5], seen)); }); - } + }); - return waitForAbort() - .then(function() { - assert.deepEqual(['a.1', 'b.1', 'c.1', 'b.2', 'c.2'], seen); - assert.ok(!b3.isPending()); - assert.ok(!c3.isPending()); - }) - .then(() => assertWasCancelled(b3)) - .then(() => assertWasCancelled(c3)); - }); + it('unhandledRejectionInParallelTaskQueue', function() { + var seen = []; + function schedule(name) { + return flow.execute(() => seen.push(name), name); + } - it('errorsInAsyncFunctionsAreReportedAsUnhandledRejection', function() { - flow.removeAllListeners(); // For tearDown. + flow.async(function() { + schedule('a.1'); + flow.execute(throwStubError, 'a.2 (throws)'); + }); - var task; - return new Promise(function(fulfill) { - flow.once('uncaughtException', fulfill); + var b3; flow.async(function() { - task = flow.execute(function() {}); - throw Error('boom'); + schedule('b.1'); + schedule('b.2'); + b3 = schedule('b.3'); }); - }).then(function(error) { - assert.ok(error instanceof promise.CancellationError); - assert.ok(!task.isPending()); - return task.catch(function(error) { - assert.ok(error instanceof promise.CancellationError); + + var c3; + flow.async(function() { + schedule('c.1'); + schedule('c.2'); + c3 = schedule('c.3'); }); + + function assertWasCancelled(p) { + return p.then(assert.fail, function(e) { + assert.ok(e instanceof promise.CancellationError); + }); + } + + return waitForAbort() + .then(function() { + assert.deepEqual(['a.1', 'b.1', 'c.1', 'b.2', 'c.2'], seen); + }) + .then(() => assertWasCancelled(b3)) + .then(() => assertWasCancelled(c3)); }); - }); - describe('does not wait for values thrown from callbacks to be resolved', function() { - it('(a)', function() { - var p1 = promise.fulfilled(); - var reason = promise.fulfilled('should not see me'); - return p1.then(function() { - throw reason; - }).then(assert.fail, function(e) { - assert.equal(reason, e); + it('errorsInAsyncFunctionsAreReportedAsUnhandledRejection', function() { + flow.removeAllListeners(); // For tearDown. + + var task; + return new Promise(function(fulfill) { + flow.once('uncaughtException', fulfill); + flow.async(function() { + task = flow.execute(function() {}); + throw Error('boom'); + }); + }).then(function(error) { + assert.ok(error instanceof promise.CancellationError); + return task.catch(function(error) { + assert.ok(error instanceof promise.CancellationError); + }); }); }); - it('(b)', function() { - var p1 = promise.fulfilled(); - var reason = promise.rejected('should not see me'); - reason.catch(function() {}); // For tearDown. - return p1.then(function() { - throw reason; - }).then(assert.fail, function(e) { - assert.equal(reason, e); + describe('does not wait for values thrown from callbacks to be resolved', function() { + it('(a)', function() { + var p1 = promise.fulfilled(); + var reason = promise.fulfilled('should not see me'); + return p1.then(function() { + throw reason; + }).then(assert.fail, function(e) { + assert.equal(reason, e); + }); }); - }); - it('(c)', function() { - var p1 = promise.fulfilled(); - var reason = promise.defer(); - setTimeout(() => reason.fulfill('should not see me'), 100); - return p1.then(function() { - throw reason.promise; - }).then(assert.fail, function(e) { - assert.equal(reason.promise, e); + it('(b)', function() { + var p1 = promise.fulfilled(); + var reason = promise.rejected('should not see me'); + reason.catch(function() {}); // For tearDown. + return p1.then(function() { + throw reason; + }).then(assert.fail, function(e) { + assert.equal(reason, e); + }); }); - }); - it('(d)', function() { - var p1 = promise.fulfilled(); - var reason = {then: function() {}}; // A thenable like object. - return p1.then(function() { - throw reason; - }).then(assert.fail, function(e) { - assert.equal(reason, e); + it('(c)', function() { + var p1 = promise.fulfilled(); + var reason = promise.defer(); + setTimeout(() => reason.fulfill('should not see me'), 100); + return p1.then(function() { + throw reason.promise; + }).then(assert.fail, function(e) { + assert.equal(reason.promise, e); + }); + }); + + it('(d)', function() { + var p1 = promise.fulfilled(); + var reason = {then: function() {}}; // A thenable like object. + return p1.then(function() { + throw reason; + }).then(assert.fail, function(e) { + assert.equal(reason, e); + }); }); }); }); diff --git a/node_modules/selenium-webdriver/test/lib/promise_flow_test.js b/node_modules/selenium-webdriver/test/lib/promise_flow_test.js index b42ac5209..407fd547e 100644 --- a/node_modules/selenium-webdriver/test/lib/promise_flow_test.js +++ b/node_modules/selenium-webdriver/test/lib/promise_flow_test.js @@ -22,7 +22,9 @@ const fail = assert.fail; const sinon = require('sinon'); const testutil = require('./testutil'); +const {TimeoutError} = require('../../lib/error'); const promise = require('../../lib/promise'); +const {enablePromiseManager} = require('../../lib/test/promise'); const NativePromise = Promise; @@ -33,2252 +35,2254 @@ const callbackPair = testutil.callbackPair; const throwStubError = testutil.throwStubError; describe('promise control flow', function() { - let flow, flowHistory, messages, uncaughtExceptions; - - beforeEach(function setUp() { - promise.LONG_STACK_TRACES = false; - flow = new promise.ControlFlow(); - promise.setDefaultFlow(flow); - messages = []; - flowHistory = []; - - uncaughtExceptions = []; - flow.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, - onUncaughtException); - }); - - afterEach(function tearDown() { - flow.removeAllListeners( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); - assert.deepEqual([], uncaughtExceptions, - 'There were uncaught exceptions'); - flow.reset(); - promise.LONG_STACK_TRACES = false; - }); + enablePromiseManager(() => { + let flow, flowHistory, messages, uncaughtExceptions; - function onUncaughtException(e) { - uncaughtExceptions.push(e); - } - - function waitForAbort(opt_flow) { - var theFlow = opt_flow || flow; - theFlow.removeAllListeners( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); - return new NativePromise(function(fulfill, reject) { - theFlow.once(promise.ControlFlow.EventType.IDLE, function() { - reject(Error('expected flow to report an unhandled error')); - }); - theFlow.once( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, - fulfill); - }); - } - - function waitForIdle(opt_flow) { - var theFlow = opt_flow || flow; - return new NativePromise(function(fulfill, reject) { - theFlow.once(promise.ControlFlow.EventType.IDLE, fulfill); - theFlow.once( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, reject); - }); - } + beforeEach(function setUp() { + promise.LONG_STACK_TRACES = false; + flow = new promise.ControlFlow(); + promise.setDefaultFlow(flow); + messages = []; + flowHistory = []; - function timeout(ms) { - return new NativePromise(function(fulfill) { - setTimeout(fulfill, ms); + uncaughtExceptions = []; + flow.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + onUncaughtException); }); - } - - function schedule(msg, opt_return) { - return scheduleAction(msg, function() { - return opt_return; - }); - } - - /** - * @param {string} value The value to push. - * @param {promise.Promise=} opt_taskPromise Promise to return from - * the task. - * @return {!promise.Promise} The result. - */ - function schedulePush(value, opt_taskPromise) { - return scheduleAction(value, function() { - messages.push(value); - return opt_taskPromise; - }); - } - - /** - * @param {string} msg Debug message. - * @param {!Function} actionFn The function. - * @return {!promise.Promise} The function result. - */ - function scheduleAction(msg, actionFn) { - return promise.controlFlow().execute(function() { - flowHistory.push(msg); - return actionFn(); - }, msg); - } - - /** - * @param {!Function} condition The condition function. - * @param {number=} opt_timeout The timeout. - * @param {string=} opt_message Optional message. - * @return {!promise.Promise} The wait result. - */ - function scheduleWait(condition, opt_timeout, opt_message) { - var msg = opt_message || ''; - // It's not possible to hook into when the wait itself is scheduled, so - // we record each iteration of the wait loop. - var count = 0; - return promise.controlFlow().wait(function() { - flowHistory.push((count++) + ': ' + msg); - return condition(); - }, opt_timeout, msg); - } - - function asyncRun(fn, opt_self) { - NativePromise.resolve().then(() => fn.call(opt_self)); - } - - function assertFlowHistory(var_args) { - var expected = Array.prototype.slice.call(arguments, 0); - assert.deepEqual(expected, flowHistory); - } - - function assertMessages(var_args) { - var expected = Array.prototype.slice.call(arguments, 0); - assert.deepEqual(expected, messages); - } - - function assertingMessages(var_args) { - var args = Array.prototype.slice.call(arguments, 0); - return () => assertMessages.apply(null, args); - } - - function assertFlowIs(flow) { - assert.equal(flow, promise.controlFlow()); - } - - describe('testScheduling', function() { - it('aSimpleFunction', function() { - schedule('go'); - return waitForIdle().then(function() { - assertFlowHistory('go'); - }); - }); - - it('aSimpleFunctionWithANonPromiseReturnValue', function() { - schedule('go', 123).then(function(value) { - assert.equal(123, value); - }); - return waitForIdle().then(function() { - assertFlowHistory('go'); - }); + afterEach(function tearDown() { + flow.removeAllListeners( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + assert.deepEqual([], uncaughtExceptions, + 'There were uncaught exceptions'); + flow.reset(); + promise.LONG_STACK_TRACES = false; }); - it('aSimpleSequence', function() { - schedule('a'); - schedule('b'); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); + function onUncaughtException(e) { + uncaughtExceptions.push(e); + } - it('invokesCallbacksWhenTaskIsDone', function() { - var d = new promise.Deferred(); - var called = false; - var done = schedule('a', d.promise).then(function(value) { - called = true; - assert.equal(123, value); - }); - return timeout(5).then(function() { - assert.ok(!called); - d.fulfill(123); - return done; - }). - then(function() { - assertFlowHistory('a'); + function defer() { + let d = {}; + let promise = new Promise((resolve, reject) => { + Object.assign(d, {resolve, reject}); }); - }); - - it('blocksUntilPromiseReturnedByTaskIsResolved', function() { - var done = promise.defer(); - schedulePush('a', done.promise); - schedulePush('b'); - setTimeout(function() { - done.fulfill(); - messages.push('c'); - }, 25); - return waitForIdle().then(assertingMessages('a', 'c', 'b')); - }); + d.promise = promise; + return d; + } - it('waitsForReturnedPromisesToResolve', function() { - var d1 = new promise.Deferred(); - var d2 = new promise.Deferred(); - - var callback = sinon.spy(); - schedule('a', d1.promise).then(callback); - - return timeout(5).then(function() { - assert(!callback.called); - d1.fulfill(d2.promise); - return timeout(5); - }).then(function() { - assert(!callback.called); - d2.fulfill('fluffy bunny'); - return waitForIdle(); - }).then(function() { - assert(callback.called); - assert.equal('fluffy bunny', callback.getCall(0).args[0]); - assertFlowHistory('a'); + function waitForAbort(opt_flow) { + var theFlow = opt_flow || flow; + theFlow.removeAllListeners( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + return new NativePromise(function(fulfill, reject) { + theFlow.once(promise.ControlFlow.EventType.IDLE, function() { + reject(Error('expected flow to report an unhandled error')); + }); + theFlow.once( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + fulfill); }); - }); + } - it('executesTasksInAFutureTurnAfterTheyAreScheduled', function() { + function waitForIdle(opt_flow) { + var theFlow = opt_flow || flow; + return new NativePromise(function(fulfill, reject) { + theFlow.once(promise.ControlFlow.EventType.IDLE, fulfill); + theFlow.once( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, reject); + }); + } + + function timeout(ms) { + return new NativePromise(function(fulfill) { + setTimeout(fulfill, ms); + }); + } + + + function schedule(msg, opt_return) { + return scheduleAction(msg, function() { + return opt_return; + }); + } + + /** + * @param {string} value The value to push. + * @param {promise.Promise=} opt_taskPromise Promise to return from + * the task. + * @return {!promise.Promise} The result. + */ + function schedulePush(value, opt_taskPromise) { + return scheduleAction(value, function() { + messages.push(value); + return opt_taskPromise; + }); + } + + /** + * @param {string} msg Debug message. + * @param {!Function} actionFn The function. + * @return {!promise.Promise} The function result. + */ + function scheduleAction(msg, actionFn) { + return promise.controlFlow().execute(function() { + flowHistory.push(msg); + return actionFn(); + }, msg); + } + + /** + * @param {!Function} condition The condition function. + * @param {number=} opt_timeout The timeout. + * @param {string=} opt_message Optional message. + * @return {!promise.Promise} The wait result. + */ + function scheduleWait(condition, opt_timeout, opt_message) { + var msg = opt_message || ''; + // It's not possible to hook into when the wait itself is scheduled, so + // we record each iteration of the wait loop. var count = 0; - function incr() { count++; } - - scheduleAction('', incr); - assert.equal(0, count); - return waitForIdle().then(function() { - assert.equal(1, count); + return promise.controlFlow().wait(function() { + flowHistory.push((count++) + ': ' + msg); + return condition(); + }, opt_timeout, msg); + } + + function asyncRun(fn, opt_self) { + NativePromise.resolve().then(() => fn.call(opt_self)); + } + + function assertFlowHistory(var_args) { + var expected = Array.prototype.slice.call(arguments, 0); + assert.deepEqual(expected, flowHistory); + } + + function assertMessages(var_args) { + var expected = Array.prototype.slice.call(arguments, 0); + assert.deepEqual(expected, messages); + } + + function assertingMessages(var_args) { + var args = Array.prototype.slice.call(arguments, 0); + return () => assertMessages.apply(null, args); + } + + function assertFlowIs(flow) { + assert.equal(flow, promise.controlFlow()); + } + + describe('testScheduling', function() { + it('aSimpleFunction', function() { + schedule('go'); + return waitForIdle().then(function() { + assertFlowHistory('go'); + }); }); - }); - it('executesOneTaskPerTurnOfTheEventLoop', function() { - var order = []; - function go() { - order.push(order.length / 2); - asyncRun(function() { - order.push('-'); + it('aSimpleFunctionWithANonPromiseReturnValue', function() { + schedule('go', 123).then(function(value) { + assert.equal(123, value); }); - } - - scheduleAction('', go); - scheduleAction('', go); - return waitForIdle().then(function() { - assert.deepEqual([0, '-', 1, '-'], order); - }) - }); + return waitForIdle().then(function() { + assertFlowHistory('go'); + }); + }); - it('firstScheduledTaskIsWithinACallback', function() { - promise.fulfilled().then(function() { + it('aSimpleSequence', function() { schedule('a'); schedule('b'); schedule('c'); - }).then(function() { - assertFlowHistory('a', 'b', 'c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - return waitForIdle(); - }); - it('newTasksAddedWhileWaitingOnTaskReturnedPromise1', function() { - scheduleAction('a', function() { - var d = promise.defer(); - setTimeout(function() { - schedule('c'); - d.fulfill(); - }, 10); - return d.promise; - }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + it('invokesCallbacksWhenTaskIsDone', function() { + var d = new promise.Deferred(); + var called = false; + var done = schedule('a', d.promise).then(function(value) { + called = true; + assert.equal(123, value); + }); + return timeout(5).then(function() { + assert.ok(!called); + d.fulfill(123); + return done; + }). + then(function() { + assertFlowHistory('a'); + }); }); - }); - it('newTasksAddedWhileWaitingOnTaskReturnedPromise2', function() { - scheduleAction('a', function() { - var d = promise.defer(); + it('blocksUntilPromiseReturnedByTaskIsResolved', function() { + var done = promise.defer(); + schedulePush('a', done.promise); + schedulePush('b'); setTimeout(function() { - schedule('c'); - asyncRun(d.fulfill); - }, 10); - return d.promise; - }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'b'); + done.fulfill(); + messages.push('c'); + }, 25); + return waitForIdle().then(assertingMessages('a', 'c', 'b')); }); - }); - }); - describe('testFraming', function() { - it('callbacksRunInANewFrame', function() { - schedule('a').then(function() { - schedule('c'); + it('waitsForReturnedPromisesToResolve', function() { + var d1 = new promise.Deferred(); + var d2 = new promise.Deferred(); + + var callback = sinon.spy(); + schedule('a', d1.promise).then(callback); + + return timeout(5).then(function() { + assert(!callback.called); + d1.fulfill(d2.promise); + return timeout(5); + }).then(function() { + assert(!callback.called); + d2.fulfill('fluffy bunny'); + return waitForIdle(); + }).then(function() { + assert(callback.called); + assert.equal('fluffy bunny', callback.getCall(0).args[0]); + assertFlowHistory('a'); + }); }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'b'); + + it('executesTasksInAFutureTurnAfterTheyAreScheduled', function() { + var count = 0; + function incr() { count++; } + + scheduleAction('', incr); + assert.equal(0, count); + return waitForIdle().then(function() { + assert.equal(1, count); + }); }); - }); - it('lotsOfNesting', function() { - schedule('a').then(function() { - schedule('c').then(function() { - schedule('e').then(function() { - schedule('g'); + it('executesOneTaskPerTurnOfTheEventLoop', function() { + var order = []; + function go() { + order.push(order.length / 2); + asyncRun(function() { + order.push('-'); }); - schedule('f'); - }); - schedule('d'); + } + + scheduleAction('', go); + scheduleAction('', go); + return waitForIdle().then(function() { + assert.deepEqual([0, '-', 1, '-'], order); + }) }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'e', 'g', 'f', 'd', 'b'); + it('firstScheduledTaskIsWithinACallback', function() { + promise.fulfilled().then(function() { + schedule('a'); + schedule('b'); + schedule('c'); + }).then(function() { + assertFlowHistory('a', 'b', 'c'); + }); + return waitForIdle(); }); - }); - it('callbackReturnsPromiseThatDependsOnATask_1', function() { - schedule('a').then(function() { + it('newTasksAddedWhileWaitingOnTaskReturnedPromise1', function() { + scheduleAction('a', function() { + var d = promise.defer(); + setTimeout(function() { + schedule('c'); + d.fulfill(); + }, 10); + return d.promise; + }); schedule('b'); - return promise.delayed(5).then(function() { - return schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); }); }); - schedule('d'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd'); + it('newTasksAddedWhileWaitingOnTaskReturnedPromise2', function() { + scheduleAction('a', function() { + var d = promise.defer(); + setTimeout(function() { + schedule('c'); + asyncRun(d.fulfill); + }, 10); + return d.promise; + }); + schedule('b'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); }); }); - it('callbackReturnsPromiseThatDependsOnATask_2', function() { - schedule('a').then(function() { + describe('testFraming', function() { + it('callbacksRunInANewFrame', function() { + schedule('a').then(function() { + schedule('c'); + }); schedule('b'); - return promise.delayed(5). - then(function() { return promise.delayed(5) }). - then(function() { return promise.delayed(5) }). - then(function() { return promise.delayed(5) }). - then(function() { return schedule('c'); }); - }); - schedule('d'); - - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); }); - }); - it('eachCallbackWaitsForAllScheduledTasksToComplete', function() { - schedule('a'). - then(function() { - schedule('b'); - schedule('c'); - }). - then(function() { - schedule('d'); + it('lotsOfNesting', function() { + schedule('a').then(function() { + schedule('c').then(function() { + schedule('e').then(function() { + schedule('g'); + }); + schedule('f'); }); - schedule('e'); + schedule('d'); + }); + schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'e', 'g', 'f', 'd', 'b'); + }); }); - }); - it('eachCallbackWaitsForReturnTasksToComplete', function() { - schedule('a'). - then(function() { - schedule('b'); + it('callbackReturnsPromiseThatDependsOnATask_1', function() { + schedule('a').then(function() { + schedule('b'); + return promise.delayed(5).then(function() { return schedule('c'); - }). - then(function() { - schedule('d'); }); - schedule('e'); + }); + schedule('d'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); }); - }); - it('callbacksOnAResolvedPromiseInsertIntoTheCurrentFlow', function() { - promise.fulfilled().then(function() { - schedule('b'); - }); - schedule('a'); + it('callbackReturnsPromiseThatDependsOnATask_2', function() { + schedule('a').then(function() { + schedule('b'); + return promise.delayed(5). + then(function() { return promise.delayed(5) }). + then(function() { return promise.delayed(5) }). + then(function() { return promise.delayed(5) }). + then(function() { return schedule('c'); }); + }); + schedule('d'); - return waitForIdle().then(function() { - assertFlowHistory('b', 'a'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); }); - }); - it('callbacksInterruptTheFlowWhenPromiseIsResolved', function() { - schedule('a').then(function() { - schedule('c'); - }); - schedule('b'); + it('eachCallbackWaitsForAllScheduledTasksToComplete', function() { + schedule('a'). + then(function() { + schedule('b'); + schedule('c'); + }). + then(function() { + schedule('d'); + }); + schedule('e'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'b'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); }); - }); - it('allCallbacksInAFrameAreScheduledWhenPromiseIsResolved', function() { - var a = schedule('a'); - a.then(function() { schedule('b'); }); - schedule('c'); - a.then(function() { schedule('d'); }); - schedule('e'); + it('eachCallbackWaitsForReturnTasksToComplete', function() { + schedule('a'). + then(function() { + schedule('b'); + return schedule('c'); + }). + then(function() { + schedule('d'); + }); + schedule('e'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); }); - }); - it('tasksScheduledInInActiveFrameDoNotGetPrecedence', function() { - var d = promise.fulfilled(); - schedule('a'); - schedule('b'); - d.then(function() { schedule('c'); }); + it('callbacksOnAResolvedPromiseInsertIntoTheCurrentFlow', function() { + promise.fulfilled().then(function() { + schedule('b'); + }); + schedule('a'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + return waitForIdle().then(function() { + assertFlowHistory('b', 'a'); + }); }); - }); - it('tasksScheduledInAFrameGetPrecedence_1', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { + it('callbacksInterruptTheFlowWhenPromiseIsResolved', function() { + schedule('a').then(function() { schedule('c'); - schedule('d'); - }); - var e = schedule('e'); - a.then(function() { - schedule('f'); - e.then(function() { - schedule('g'); - }); - schedule('h'); }); - schedule('i'); - }); - schedule('j'); + schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); }); - }); - }); - describe('testErrorHandling', function() { - it('thrownErrorsArePassedToTaskErrback', function() { - scheduleAction('function that throws', throwStubError). - then(fail, assertIsStubError); - return waitForIdle(); - }); + it('allCallbacksInAFrameAreScheduledWhenPromiseIsResolved', function() { + var a = schedule('a'); + a.then(function() { schedule('b'); }); + schedule('c'); + a.then(function() { schedule('d'); }); + schedule('e'); - it('thrownErrorsPropagateThroughPromiseChain', function() { - scheduleAction('function that throws', throwStubError). - then(fail). - then(fail, assertIsStubError); - return waitForIdle(); - }); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); + }); - it('catchesErrorsFromFailedTasksInAFrame', function() { - schedule('a').then(function() { + it('tasksScheduledInInActiveFrameDoNotGetPrecedence', function() { + var d = promise.fulfilled(); + schedule('a'); schedule('b'); - scheduleAction('function that throws', throwStubError); - }). - then(fail, assertIsStubError); - return waitForIdle(); - }); + d.then(function() { schedule('c'); }); - it('abortsIfOnlyTaskReturnsAnUnhandledRejection', function() { - scheduleAction('function that returns rejected promise', function() { - return promise.rejected(new StubError); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - return waitForAbort().then(assertIsStubError); - }); - it('abortsIfThereIsAnUnhandledRejection', function() { - promise.rejected(new StubError); - schedule('this should not run'); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory(/* none */); - }); - }); - - it('abortsSequenceIfATaskFails', function() { - schedule('a'); - schedule('b'); - scheduleAction('c', throwStubError); - schedule('d'); // Should never execute. - - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'b', 'c'); + it('tasksScheduledInAFrameGetPrecedence_1', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { + schedule('c'); + schedule('d'); }); - }); - - it('abortsFromUnhandledFramedTaskFailures_1', function() { - schedule('outer task').then(function() { - scheduleAction('inner task', throwStubError); - }); - schedule('this should not run'); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('outer task', 'inner task'); + var e = schedule('e'); + a.then(function() { + schedule('f'); + e.then(function() { + schedule('g'); + }); + schedule('h'); }); - }); + schedule('i'); + }); + schedule('j'); - it('abortsFromUnhandledFramedTaskFailures_2', function() { - schedule('a').then(function() { - schedule('b').then(function() { - scheduleAction('c', throwStubError); - // This should not execute. - schedule('d'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'); }); }); - - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'b', 'c'); - }); }); - it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject', function() { - var callback = sinon.spy(); + describe('testErrorHandling', function() { + it('thrownErrorsArePassedToTaskErrback', function() { + scheduleAction('function that throws', throwStubError). + then(fail, assertIsStubError); + return waitForIdle(); + }); - scheduleAction('', function() { - var obj = {'foo': promise.rejected(new StubError)}; - return promise.fullyResolved(obj).then(callback); + it('thrownErrorsPropagateThroughPromiseChain', function() { + scheduleAction('function that throws', throwStubError). + then(fail). + then(fail, assertIsStubError); + return waitForIdle(); }); - return waitForAbort(). - then(assertIsStubError). - then(() => assert(!callback.called)); - }); + it('catchesErrorsFromFailedTasksInAFrame', function() { + schedule('a').then(function() { + schedule('b'); + scheduleAction('function that throws', throwStubError); + }). + then(fail, assertIsStubError); + return waitForIdle(); + }); - it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject_withCallback', function() { - var callback1 = sinon.spy(); - var callback2 = sinon.spy(); + it('abortsIfOnlyTaskReturnsAnUnhandledRejection', function() { + scheduleAction('function that returns rejected promise', function() { + return promise.rejected(new StubError); + }); + return waitForAbort().then(assertIsStubError); + }); - scheduleAction('', function() { - var obj = {'foo': promise.rejected(new StubError)}; - return promise.fullyResolved(obj).then(callback1); - }).then(callback2); + it('abortsIfThereIsAnUnhandledRejection', function() { + promise.rejected(new StubError); + schedule('this should not run'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory(/* none */); + }); + }); - return waitForAbort(). - then(assertIsStubError). - then(() => assert(!callback1.called)). - then(() => assert(!callback2.called)); - }); + it('abortsSequenceIfATaskFails', function() { + schedule('a'); + schedule('b'); + scheduleAction('c', throwStubError); + schedule('d'); // Should never execute. - it('canCatchErrorsFromNestedTasks', function() { - var errback = sinon.spy(); - schedule('a'). - then(function() { - return scheduleAction('b', throwStubError); - }). - catch(errback); - return waitForIdle().then(function() { - assert(errback.called); - assertIsStubError(errback.getCall(0).args[0]); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - }); - it('nestedCommandFailuresCanBeCaughtAndSuppressed', function() { - var errback = sinon.spy(); - schedule('a').then(function() { - return schedule('b').then(function() { - return schedule('c').then(function() { - throw new StubError; - }); + it('abortsFromUnhandledFramedTaskFailures_1', function() { + schedule('outer task').then(function() { + scheduleAction('inner task', throwStubError); }); - }).catch(errback); - schedule('d'); - return waitForIdle(). - then(function() { - assert(errback.called); - assertIsStubError(errback.getCall(0).args[0]); - assertFlowHistory('a', 'b', 'c', 'd'); - }); - }); - - it('aTaskWithAnUnhandledPromiseRejection', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - promise.rejected(new StubError); + schedule('this should not run'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('outer task', 'inner task'); + }); }); - schedule('should never run'); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'sub-tasks'); + it('abortsFromUnhandledFramedTaskFailures_2', function() { + schedule('a').then(function() { + schedule('b').then(function() { + scheduleAction('c', throwStubError); + // This should not execute. + schedule('d'); }); - }); + }); - it('aTaskThatReutrnsARejectedPromise', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - return promise.rejected(new StubError); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - schedule('should never run'); - - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'sub-tasks'); - }); - }); - it('discardsSubtasksIfTaskThrows', function() { - var pair = callbackPair(null, assertIsStubError); - scheduleAction('a', function() { - schedule('b'); - schedule('c'); - throwStubError(); - }).then(pair.callback, pair.errback); - schedule('d'); + it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject', function() { + var callback = sinon.spy(); - return waitForIdle(). - then(pair.assertErrback). - then(function() { - assertFlowHistory('a', 'd'); - }); - }); + scheduleAction('', function() { + var obj = {'foo': promise.rejected(new StubError)}; + return promise.fullyResolved(obj).then(callback); + }); - it('discardsRemainingSubtasksIfASubtaskFails', function() { - var pair = callbackPair(null, assertIsStubError); - scheduleAction('a', function() { - schedule('b'); - scheduleAction('c', throwStubError); - schedule('d'); - }).then(pair.callback, pair.errback); - schedule('e'); + return waitForAbort(). + then(assertIsStubError). + then(() => assert(!callback.called)); + }); - return waitForIdle(). - then(pair.assertErrback). - then(function() { - assertFlowHistory('a', 'b', 'c', 'e'); - }); - }); - }); + it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject_withCallback', function() { + var callback1 = sinon.spy(); + var callback2 = sinon.spy(); - describe('testTryModelingFinally', function() { - it('happyPath', function() { - /* Model: - try { - doFoo(); - doBar(); - } finally { - doBaz(); - } - */ - schedulePush('foo'). - then(() => schedulePush('bar')). - finally(() => schedulePush('baz')); - return waitForIdle().then(assertingMessages('foo', 'bar', 'baz')); - }); + scheduleAction('', function() { + var obj = {'foo': promise.rejected(new StubError)}; + return promise.fullyResolved(obj).then(callback1); + }).then(callback2); - it('firstTryFails', function() { - /* Model: - try { - doFoo(); - doBar(); - } finally { - doBaz(); - } - */ - - scheduleAction('doFoo and throw', function() { - messages.push('foo'); - throw new StubError; - }). - then(function() { schedulePush('bar'); }). - finally(function() { schedulePush('baz'); }); + return waitForAbort(). + then(assertIsStubError). + then(() => assert(!callback1.called)). + then(() => assert(!callback2.called)); + }); - return waitForAbort(). - then(assertIsStubError). - then(assertingMessages('foo', 'baz')); - }); + it('canCatchErrorsFromNestedTasks', function() { + var errback = sinon.spy(); + schedule('a'). + then(function() { + return scheduleAction('b', throwStubError); + }). + catch(errback); + return waitForIdle().then(function() { + assert(errback.called); + assertIsStubError(errback.getCall(0).args[0]); + }); + }); - it('secondTryFails', function() { - /* Model: - try { - doFoo(); - doBar(); - } finally { - doBaz(); - } - */ - - schedulePush('foo'). - then(function() { - return scheduleAction('doBar and throw', function() { - messages.push('bar'); + it('nestedCommandFailuresCanBeCaughtAndSuppressed', function() { + var errback = sinon.spy(); + schedule('a').then(function() { + return schedule('b').then(function() { + return schedule('c').then(function() { throw new StubError; }); - }). - finally(function() { - return schedulePush('baz'); }); - return waitForAbort(). - then(assertIsStubError). - then(assertingMessages('foo', 'bar', 'baz')); - }); - }); - - describe('testTaskCallbacksInterruptFlow', function() { - it('(base case)', function() { - schedule('a').then(function() { - schedule('b'); - }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + }).catch(errback); + schedule('d'); + return waitForIdle(). + then(function() { + assert(errback.called); + assertIsStubError(errback.getCall(0).args[0]); + assertFlowHistory('a', 'b', 'c', 'd'); + }); }); - }); - it('taskDependsOnImmediatelyFulfilledPromise', function() { - scheduleAction('a', function() { - return promise.fulfilled(); - }).then(function() { - schedule('b'); - }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); + it('aTaskWithAnUnhandledPromiseRejection', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + promise.rejected(new StubError); + }); + schedule('should never run'); - it('taskDependsOnPreviouslyFulfilledPromise', function() { - var aPromise = promise.fulfilled(123); - scheduleAction('a', function() { - return aPromise; - }).then(function(value) { - assert.equal(123, value); - schedule('b'); - }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks'); + }); }); - }); - it('taskDependsOnAsyncPromise', function() { - scheduleAction('a', function() { - return promise.delayed(25); - }).then(function() { - schedule('b'); - }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); + it('aTaskThatReutrnsARejectedPromise', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + return promise.rejected(new StubError); + }); + schedule('should never run'); - it('promiseChainedToTaskInterruptFlow', function() { - schedule('a').then(function() { - return promise.fulfilled(); - }).then(function() { - return promise.fulfilled(); - }).then(function() { - schedule('b'); - }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks'); + }); }); - }); - it('nestedTaskCallbacksInterruptFlowWhenResolved', function() { - schedule('a').then(function() { - schedule('b').then(function() { + it('discardsSubtasksIfTaskThrows', function() { + var pair = callbackPair(null, assertIsStubError); + scheduleAction('a', function() { + schedule('b'); schedule('c'); - }); - }); - schedule('d'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd'); + throwStubError(); + }).then(pair.callback, pair.errback); + schedule('d'); + + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory('a', 'd'); + }); }); - }); - }); - describe('testDelayedNesting', function() { + it('discardsRemainingSubtasksIfASubtaskFails', function() { + var pair = callbackPair(null, assertIsStubError); + scheduleAction('a', function() { + schedule('b'); + scheduleAction('c', throwStubError); + schedule('d'); + }).then(pair.callback, pair.errback); + schedule('e'); - it('1', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }); - schedule('d'); + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory('a', 'b', 'c', 'e'); + }); }); - schedule('e'); + }); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + describe('testTryModelingFinally', function() { + it('happyPath', function() { + /* Model: + try { + doFoo(); + doBar(); + } finally { + doBaz(); + } + */ + schedulePush('foo'). + then(() => schedulePush('bar')). + finally(() => schedulePush('baz')); + return waitForIdle().then(assertingMessages('foo', 'bar', 'baz')); + }); + + it('firstTryFails', function() { + /* Model: + try { + doFoo(); + doBar(); + } finally { + doBaz(); + } + */ + + scheduleAction('doFoo and throw', function() { + messages.push('foo'); + throw new StubError; + }). + then(function() { schedulePush('bar'); }). + finally(function() { schedulePush('baz'); }); + + return waitForAbort(). + then(assertIsStubError). + then(assertingMessages('foo', 'baz')); + }); + + it('secondTryFails', function() { + /* Model: + try { + doFoo(); + doBar(); + } finally { + doBaz(); + } + */ + + schedulePush('foo'). + then(function() { + return scheduleAction('doBar and throw', function() { + messages.push('bar'); + throw new StubError; + }); + }). + finally(function() { + return schedulePush('baz'); + }); + return waitForAbort(). + then(assertIsStubError). + then(assertingMessages('foo', 'bar', 'baz')); }); }); - it('2', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }); - schedule('d'); - a.then(function() { schedule('e'); }); + describe('testTaskCallbacksInterruptFlow', function() { + it('(base case)', function() { + schedule('a').then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - schedule('f'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + it('taskDependsOnImmediatelyFulfilledPromise', function() { + scheduleAction('a', function() { + return promise.fulfilled(); + }).then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - }); - it('3', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }); - a.then(function() { schedule('d'); }); + it('taskDependsOnPreviouslyFulfilledPromise', function() { + var aPromise = promise.fulfilled(123); + scheduleAction('a', function() { + return aPromise; + }).then(function(value) { + assert.equal(123, value); + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - schedule('e'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + it('taskDependsOnAsyncPromise', function() { + scheduleAction('a', function() { + return promise.delayed(25); + }).then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - }); - it('4', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }).then(function() { - schedule('d'); + it('promiseChainedToTaskInterruptFlow', function() { + schedule('a').then(function() { + return promise.fulfilled(); + }).then(function() { + return promise.fulfilled(); + }).then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); }); - a.then(function() { schedule('e'); }); }); - schedule('f'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + it('nestedTaskCallbacksInterruptFlowWhenResolved', function() { + schedule('a').then(function() { + schedule('b').then(function() { + schedule('c'); + }); + }); + schedule('d'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); }); }); - it('5', function() { - var a = schedule('a'); - schedule('b').then(function() { - var c; - a.then(function() { c = schedule('c'); }).then(function() { + describe('testDelayedNesting', function() { + + it('1', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); schedule('d'); - a.then(function() { schedule('e'); }); - c.then(function() { schedule('f'); }); - schedule('g'); }); - a.then(function() { schedule('h'); }); - }); - schedule('i'); - - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'); - }); - }); - }); + schedule('e'); - describe('testWaiting', function() { - it('onAConditionThatIsAlwaysTrue', function() { - scheduleWait(function() { return true;}, 0, 'waiting on true'); - return waitForIdle().then(function() { - assertFlowHistory('0: waiting on true'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); }); - }); - it('aSimpleCountingCondition', function() { - var count = 0; - scheduleWait(function() { - return ++count == 3; - }, 100, 'counting to 3'); + it('2', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); + schedule('d'); + a.then(function() { schedule('e'); }); + }); + schedule('f'); - return waitForIdle().then(function() { - assert.equal(3, count); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + }); }); - }); - - it('aConditionThatReturnsAPromise', function() { - var d = new promise.Deferred(); - var count = 0; - scheduleWait(function() { - count += 1; - return d.promise; - }, 0, 'waiting for promise'); + it('3', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); + a.then(function() { schedule('d'); }); + }); + schedule('e'); - return timeout(50).then(function() { - assert.equal(1, count); - d.fulfill(123); - return waitForIdle(); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); }); - }); - it('aConditionThatReturnsAPromise_2', function() { - var count = 0; - scheduleWait(function() { - return promise.fulfilled(++count == 3); - }, 100, 'waiting for promise'); + it('4', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }).then(function() { + schedule('d'); + }); + a.then(function() { schedule('e'); }); + }); + schedule('f'); - return waitForIdle().then(function() { - assert.equal(3, count); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + }); }); - }); - it('aConditionThatReturnsATaskResult', function() { - var count = 0; - scheduleWait(function() { - return scheduleAction('increment count', function() { - return ++count == 3; + it('5', function() { + var a = schedule('a'); + schedule('b').then(function() { + var c; + a.then(function() { c = schedule('c'); }).then(function() { + schedule('d'); + a.then(function() { schedule('e'); }); + c.then(function() { schedule('f'); }); + schedule('g'); + }); + a.then(function() { schedule('h'); }); }); - }, 100, 'counting to 3'); - schedule('post wait'); + schedule('i'); - return waitForIdle().then(function() { - assert.equal(3, count); - assertFlowHistory( - '0: counting to 3', 'increment count', - '1: counting to 3', 'increment count', - '2: counting to 3', 'increment count', - 'post wait'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'); + }); }); }); - it('conditionContainsASubtask', function() { - var count = 0; - scheduleWait(function() { - schedule('sub task'); - return ++count == 3; - }, 100, 'counting to 3'); - schedule('post wait'); - - return waitForIdle().then(function() { - assert.equal(3, count); - assertFlowHistory( - '0: counting to 3', 'sub task', - '1: counting to 3', 'sub task', - '2: counting to 3', 'sub task', - 'post wait'); + describe('testWaiting', function() { + it('onAConditionThatIsAlwaysTrue', function() { + scheduleWait(function() { return true;}, 0, 'waiting on true'); + return waitForIdle().then(function() { + assertFlowHistory('0: waiting on true'); + }); }); - }); - it('cancelsWaitIfScheduledTaskFails', function() { - var pair = callbackPair(null, assertIsStubError); - scheduleWait(function() { - scheduleAction('boom', throwStubError); - schedule('this should not run'); - return true; - }, 100, 'waiting to go boom').then(pair.callback, pair.errback); - schedule('post wait'); + it('aSimpleCountingCondition', function() { + var count = 0; + scheduleWait(function() { + return ++count == 3; + }, 100, 'counting to 3'); - return waitForIdle(). - then(pair.assertErrback). - then(function() { - assertFlowHistory( - '0: waiting to go boom', 'boom', - 'post wait'); - }); - }); + return waitForIdle().then(function() { + assert.equal(3, count); + }); + }); - it('failsIfConditionThrows', function() { - var callbacks = callbackPair(null, assertIsStubError); - scheduleWait(throwStubError, 0, 'goes boom'). - then(callbacks.callback, callbacks.errback); - schedule('post wait'); + it('aConditionThatReturnsAPromise', function() { + var d = new promise.Deferred(); + var count = 0; - return waitForIdle(). - then(callbacks.assertErrback). - then(function() { - assertFlowHistory('0: goes boom', 'post wait'); - }); - }); + scheduleWait(function() { + count += 1; + return d.promise; + }, 0, 'waiting for promise'); - it('failsIfConditionReturnsARejectedPromise', function() { - var callbacks = callbackPair(null, assertIsStubError); - scheduleWait(function() { - return promise.rejected(new StubError); - }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); - schedule('post wait'); + return timeout(50).then(function() { + assert.equal(1, count); + d.fulfill(123); + return waitForIdle(); + }); + }); - return waitForIdle(). - then(callbacks.assertErrback). - then(function() { - assertFlowHistory('0: goes boom', 'post wait'); - }); - }); + it('aConditionThatReturnsAPromise_2', function() { + var count = 0; + scheduleWait(function() { + return promise.fulfilled(++count == 3); + }, 100, 'waiting for promise'); - it('failsIfConditionHasUnhandledRejection', function() { - var callbacks = callbackPair(null, assertIsStubError); - scheduleWait(function() { - promise.controlFlow().execute(throwStubError); - }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); - schedule('post wait'); + return waitForIdle().then(function() { + assert.equal(3, count); + }); + }); - return waitForIdle(). - then(callbacks.assertErrback). - then(function() { - assertFlowHistory('0: goes boom', 'post wait'); + it('aConditionThatReturnsATaskResult', function() { + var count = 0; + scheduleWait(function() { + return scheduleAction('increment count', function() { + return ++count == 3; }); - }); + }, 100, 'counting to 3'); + schedule('post wait'); + + return waitForIdle().then(function() { + assert.equal(3, count); + assertFlowHistory( + '0: counting to 3', 'increment count', + '1: counting to 3', 'increment count', + '2: counting to 3', 'increment count', + 'post wait'); + }); + }); - it('failsIfConditionHasAFailedSubtask', function() { - var callbacks = callbackPair(null, assertIsStubError); - var count = 0; - scheduleWait(function() { - scheduleAction('maybe throw', function() { - if (++count == 2) { - throw new StubError; - } + it('conditionContainsASubtask', function() { + var count = 0; + scheduleWait(function() { + schedule('sub task'); + return ++count == 3; + }, 100, 'counting to 3'); + schedule('post wait'); + + return waitForIdle().then(function() { + assert.equal(3, count); + assertFlowHistory( + '0: counting to 3', 'sub task', + '1: counting to 3', 'sub task', + '2: counting to 3', 'sub task', + 'post wait'); }); - }, 100, 'waiting').then(callbacks.callback, callbacks.errback); - schedule('post wait'); + }); - return waitForIdle().then(function() { - assert.equal(2, count); - assertFlowHistory( - '0: waiting', 'maybe throw', - '1: waiting', 'maybe throw', - 'post wait'); + it('cancelsWaitIfScheduledTaskFails', function() { + var pair = callbackPair(null, assertIsStubError); + scheduleWait(function() { + scheduleAction('boom', throwStubError); + schedule('this should not run'); + return true; + }, 100, 'waiting to go boom').then(pair.callback, pair.errback); + schedule('post wait'); + + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory( + '0: waiting to go boom', 'boom', + 'post wait'); + }); }); - }); - it('pollingLoopWaitsForAllScheduledTasksInCondition', function() { - var count = 0; - scheduleWait(function() { - scheduleAction('increment count', function() { ++count; }); - return count >= 3; - }, 100, 'counting to 3'); - schedule('post wait'); + it('failsIfConditionThrows', function() { + var callbacks = callbackPair(null, assertIsStubError); + scheduleWait(throwStubError, 0, 'goes boom'). + then(callbacks.callback, callbacks.errback); + schedule('post wait'); - return waitForIdle().then(function() { - assert.equal(4, count); - assertFlowHistory( - '0: counting to 3', 'increment count', - '1: counting to 3', 'increment count', - '2: counting to 3', 'increment count', - '3: counting to 3', 'increment count', - 'post wait'); + return waitForIdle(). + then(callbacks.assertErrback). + then(function() { + assertFlowHistory('0: goes boom', 'post wait'); + }); }); - }); - it('waitsForeverOnAZeroTimeout', function() { - var done = false; - setTimeout(function() { - done = true; - }, 150); - var waitResult = scheduleWait(function() { - return done; - }, 0); + it('failsIfConditionReturnsARejectedPromise', function() { + var callbacks = callbackPair(null, assertIsStubError); + scheduleWait(function() { + return promise.rejected(new StubError); + }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); + schedule('post wait'); - return timeout(75).then(function() { - assert.ok(!done); - return timeout(100); - }).then(function() { - assert.ok(done); - return waitResult; + return waitForIdle(). + then(callbacks.assertErrback). + then(function() { + assertFlowHistory('0: goes boom', 'post wait'); + }); }); - }); - it('waitsForeverIfTimeoutOmitted', function() { - var done = false; - setTimeout(function() { - done = true; - }, 150); - var waitResult = scheduleWait(function() { - return done; + it('failsIfConditionHasUnhandledRejection', function() { + var callbacks = callbackPair(null, assertIsStubError); + scheduleWait(function() { + promise.controlFlow().execute(throwStubError); + }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); + schedule('post wait'); + + return waitForIdle(). + then(callbacks.assertErrback). + then(function() { + assertFlowHistory('0: goes boom', 'post wait'); + }); }); - return timeout(75).then(function() { - assert.ok(!done); - return timeout(100); - }).then(function() { - assert.ok(done); - return waitResult; + it('failsIfConditionHasAFailedSubtask', function() { + var callbacks = callbackPair(null, assertIsStubError); + var count = 0; + scheduleWait(function() { + scheduleAction('maybe throw', function() { + if (++count == 2) { + throw new StubError; + } + }); + }, 100, 'waiting').then(callbacks.callback, callbacks.errback); + schedule('post wait'); + + return waitForIdle().then(function() { + assert.equal(2, count); + assertFlowHistory( + '0: waiting', 'maybe throw', + '1: waiting', 'maybe throw', + 'post wait'); + }); }); - }); - it('timesOut_nonZeroTimeout', function() { - var count = 0; - scheduleWait(function() { - count += 1; - var ms = count === 2 ? 65 : 5; - return promise.delayed(ms).then(function() { - return false; - }); - }, 60, 'counting to 3'); - return waitForAbort().then(function(e) { - switch (count) { - case 1: - assertFlowHistory('0: counting to 3'); - break; - case 2: - assertFlowHistory('0: counting to 3', '1: counting to 3'); - break; - default: - fail('unexpected polling count: ' + count); - } - assert.ok( - /^counting to 3\nWait timed out after \d+ms$/.test(e.message)); + it('pollingLoopWaitsForAllScheduledTasksInCondition', function() { + var count = 0; + scheduleWait(function() { + scheduleAction('increment count', function() { ++count; }); + return count >= 3; + }, 100, 'counting to 3'); + schedule('post wait'); + + return waitForIdle().then(function() { + assert.equal(4, count); + assertFlowHistory( + '0: counting to 3', 'increment count', + '1: counting to 3', 'increment count', + '2: counting to 3', 'increment count', + '3: counting to 3', 'increment count', + 'post wait'); + }); }); - }); - it('shouldFailIfConditionReturnsARejectedPromise', function() { - scheduleWait(function() { - return promise.rejected(new StubError); - }, 100, 'returns rejected promise on first pass'); - return waitForAbort().then(assertIsStubError); - }); + it('waitsForeverOnAZeroTimeout', function() { + var done = false; + setTimeout(function() { + done = true; + }, 150); + var waitResult = scheduleWait(function() { + return done; + }, 0); + + return timeout(75).then(function() { + assert.ok(!done); + return timeout(100); + }).then(function() { + assert.ok(done); + return waitResult; + }); + }); - it('scheduleWithIntermittentWaits', function() { - schedule('a'); - scheduleWait(function() { return true; }, 0, 'wait 1'); - schedule('b'); - scheduleWait(function() { return true; }, 0, 'wait 2'); - schedule('c'); - scheduleWait(function() { return true; }, 0, 'wait 3'); + it('waitsForeverIfTimeoutOmitted', function() { + var done = false; + setTimeout(function() { + done = true; + }, 150); + var waitResult = scheduleWait(function() { + return done; + }); - return waitForIdle().then(function() { - assertFlowHistory('a', '0: wait 1', 'b', '0: wait 2', 'c', '0: wait 3'); + return timeout(75).then(function() { + assert.ok(!done); + return timeout(100); + }).then(function() { + assert.ok(done); + return waitResult; + }); }); - }); - it('scheduleWithIntermittentAndNestedWaits', function() { - schedule('a'); - scheduleWait(function() { return true; }, 0, 'wait 1'). - then(function() { - schedule('d'); - scheduleWait(function() { return true; }, 0, 'wait 2'); - schedule('e'); + it('timesOut_nonZeroTimeout', function() { + var count = 0; + scheduleWait(function() { + count += 1; + var ms = count === 2 ? 65 : 5; + return promise.delayed(ms).then(function() { + return false; }); - schedule('b'); - scheduleWait(function() { return true; }, 0, 'wait 3'); - schedule('c'); - scheduleWait(function() { return true; }, 0, 'wait 4'); - - return waitForIdle().then(function() { - assertFlowHistory( - 'a', '0: wait 1', 'd', '0: wait 2', 'e', 'b', '0: wait 3', 'c', - '0: wait 4'); + }, 60, 'counting to 3'); + return waitForAbort().then(function(e) { + switch (count) { + case 1: + assertFlowHistory('0: counting to 3'); + break; + case 2: + assertFlowHistory('0: counting to 3', '1: counting to 3'); + break; + default: + fail('unexpected polling count: ' + count); + } + assert.ok(e instanceof TimeoutError, 'Unexpected error: ' + e); + assert.ok( + /^counting to 3\nWait timed out after \d+ms$/.test(e.message)); + }); }); - }); - it('requiresConditionToBeAPromiseOrFunction', function() { - assert.throws(function() { - flow.wait(1234, 0); + it('shouldFailIfConditionReturnsARejectedPromise', function() { + scheduleWait(function() { + return promise.rejected(new StubError); + }, 100, 'returns rejected promise on first pass'); + return waitForAbort().then(assertIsStubError); }); - flow.wait(function() { return true;}, 0); - flow.wait(promise.fulfilled(), 0); - return waitForIdle(); - }); - it('promiseThatDoesNotResolveBeforeTimeout', function() { - var d = promise.defer(); - flow.wait(d.promise, 5).then(fail, function(e) { - assert.ok( - /Timed out waiting for promise to resolve after \d+ms/ - .test(e.message), - 'unexpected error message: ' + e.message); + it('scheduleWithIntermittentWaits', function() { + schedule('a'); + scheduleWait(function() { return true; }, 0, 'wait 1'); + schedule('b'); + scheduleWait(function() { return true; }, 0, 'wait 2'); + schedule('c'); + scheduleWait(function() { return true; }, 0, 'wait 3'); + + return waitForIdle().then(function() { + assertFlowHistory('a', '0: wait 1', 'b', '0: wait 2', 'c', '0: wait 3'); + }); }); - return waitForIdle().then(function() { - assert.ok('Promise should not be cancelled', d.promise.isPending()); + + it('scheduleWithIntermittentAndNestedWaits', function() { + schedule('a'); + scheduleWait(function() { return true; }, 0, 'wait 1'). + then(function() { + schedule('d'); + scheduleWait(function() { return true; }, 0, 'wait 2'); + schedule('e'); + }); + schedule('b'); + scheduleWait(function() { return true; }, 0, 'wait 3'); + schedule('c'); + scheduleWait(function() { return true; }, 0, 'wait 4'); + + return waitForIdle().then(function() { + assertFlowHistory( + 'a', '0: wait 1', 'd', '0: wait 2', 'e', 'b', '0: wait 3', 'c', + '0: wait 4'); + }); }); - }); - it('unboundedWaitOnPromiseResolution', function() { - var messages = []; - var d = promise.defer(); - var waitResult = flow.wait(d.promise).then(function(value) { - messages.push('b'); - assert.equal(1234, value); + it('requiresConditionToBeAPromiseOrFunction', function() { + assert.throws(function() { + flow.wait(1234, 0); + }); + flow.wait(function() { return true;}, 0); + flow.wait(promise.fulfilled(), 0); + return waitForIdle(); }); - setTimeout(function() { - messages.push('a'); - }, 5); - - timeout(10).then(function() { - assert.deepEqual(['a'], messages); - assert.ok(waitResult.isPending()); - d.fulfill(1234); - return waitResult; - }).then(function() { - assert.deepEqual(['a', 'b'], messages); + + it('promiseThatDoesNotResolveBeforeTimeout', function() { + var d = promise.defer(); + flow.wait(d.promise, 5).then(fail, function(e) { + assert.ok(e instanceof TimeoutError, 'Unexpected error: ' + e); + assert.ok( + /Timed out waiting for promise to resolve after \d+ms/ + .test(e.message), + 'unexpected error message: ' + e.message); + }); + return waitForIdle(); }); - return waitForIdle(); - }); - }); + it('unboundedWaitOnPromiseResolution', function() { + var messages = []; + var d = promise.defer(); + var waitResult = flow.wait(d.promise).then(function(value) { + messages.push('b'); + assert.equal(1234, value); + }); + setTimeout(function() { + messages.push('a'); + }, 5); - describe('testSubtasks', function() { - it('(base case)', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - schedule('c'); - schedule('d'); - }); - schedule('b'); + timeout(10).then(function() { + assert.deepEqual(['a'], messages); + d.fulfill(1234); + return waitResult; + }).then(function() { + assert.deepEqual(['a', 'b'], messages); + }); - return waitForIdle().then(function() { - assertFlowHistory('a', 'sub-tasks', 'c', 'd', 'b'); + return waitForIdle(); }); }); - it('nesting', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - schedule('b'); - scheduleAction('sub-sub-tasks', function() { + describe('testSubtasks', function() { + it('(base case)', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { schedule('c'); schedule('d'); }); - schedule('e'); - }); - schedule('f'); + schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory( - 'a', 'sub-tasks', 'b', 'sub-sub-tasks', 'c', 'd', 'e', 'f'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'sub-tasks', 'c', 'd', 'b'); + }); }); - }); - it('taskReturnsSubTaskResult_1', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - return schedule('c'); - }); - schedule('b'); + it('nesting', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + schedule('b'); + scheduleAction('sub-sub-tasks', function() { + schedule('c'); + schedule('d'); + }); + schedule('e'); + }); + schedule('f'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'sub-tasks', 'c', 'b'); + return waitForIdle().then(function() { + assertFlowHistory( + 'a', 'sub-tasks', 'b', 'sub-sub-tasks', 'c', 'd', 'e', 'f'); + }); }); - }); - it('taskReturnsSubTaskResult_2', function() { - let pair = callbackPair((value) => assert.equal(123, value)); - schedule('a'); - schedule('sub-tasks', promise.fulfilled(123)).then(pair.callback); - schedule('b'); + it('taskReturnsSubTaskResult_1', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + return schedule('c'); + }); + schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'sub-tasks','b'); - pair.assertCallback(); + return waitForIdle().then(function() { + assertFlowHistory('a', 'sub-tasks', 'c', 'b'); + }); }); - }); - it('taskReturnsPromiseThatDependsOnSubtask_1', function() { - scheduleAction('a', function() { - return promise.delayed(10).then(function() { - schedule('b'); + it('taskReturnsSubTaskResult_2', function() { + let pair = callbackPair((value) => assert.equal(123, value)); + schedule('a'); + schedule('sub-tasks', promise.fulfilled(123)).then(pair.callback); + schedule('b'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'sub-tasks','b'); + pair.assertCallback(); }); }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); - it('taskReturnsPromiseThatDependsOnSubtask_2', function() { - scheduleAction('a', function() { - return promise.fulfilled().then(function() { - schedule('b'); + it('taskReturnsPromiseThatDependsOnSubtask_1', function() { + scheduleAction('a', function() { + return promise.delayed(10).then(function() { + schedule('b'); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); }); }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); - it('taskReturnsPromiseThatDependsOnSubtask_3', function() { - scheduleAction('a', function() { - return promise.delayed(10).then(function() { - return schedule('b'); + it('taskReturnsPromiseThatDependsOnSubtask_2', function() { + scheduleAction('a', function() { + return promise.fulfilled().then(function() { + schedule('b'); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); }); }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); - it('taskReturnsPromiseThatDependsOnSubtask_4', function() { - scheduleAction('a', function() { - return promise.delayed(5).then(function() { - return promise.delayed(5).then(function() { + it('taskReturnsPromiseThatDependsOnSubtask_3', function() { + scheduleAction('a', function() { + return promise.delayed(10).then(function() { return schedule('b'); }); }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + + it('taskReturnsPromiseThatDependsOnSubtask_4', function() { + scheduleAction('a', function() { + return promise.delayed(5).then(function() { + return promise.delayed(5).then(function() { + return schedule('b'); + }); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - }); - it('taskReturnsPromiseThatDependsOnSubtask_5', function() { - scheduleAction('a', function() { - return promise.delayed(5).then(function() { + it('taskReturnsPromiseThatDependsOnSubtask_5', function() { + scheduleAction('a', function() { return promise.delayed(5).then(function() { return promise.delayed(5).then(function() { return promise.delayed(5).then(function() { - return schedule('b'); + return promise.delayed(5).then(function() { + return schedule('b'); + }); }); }); }); }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); - it('taskReturnsPromiseThatDependsOnSubtask_6', function() { - scheduleAction('a', function() { - return promise.delayed(5). - then(function() { return promise.delayed(5) }). - then(function() { return promise.delayed(5) }). - then(function() { return promise.delayed(5) }). - then(function() { return schedule('b'); }); - }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + it('taskReturnsPromiseThatDependsOnSubtask_6', function() { + scheduleAction('a', function() { + return promise.delayed(5). + then(function() { return promise.delayed(5) }). + then(function() { return promise.delayed(5) }). + then(function() { return promise.delayed(5) }). + then(function() { return schedule('b'); }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - }); - it('subTaskFails_1', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - scheduleAction('sub-task that fails', throwStubError); + it('subTaskFails_1', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + scheduleAction('sub-task that fails', throwStubError); + }); + schedule('should never execute'); + + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks', 'sub-task that fails'); + }); }); - schedule('should never execute'); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'sub-tasks', 'sub-task that fails'); - }); - }); + it('subTaskFails_2', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + return promise.rejected(new StubError); + }); + schedule('should never execute'); - it('subTaskFails_2', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - return promise.rejected(new StubError); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks'); + }); }); - schedule('should never execute'); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'sub-tasks'); - }); - }); - - it('subTaskFails_3', function() { - var callbacks = callbackPair(null, assertIsStubError); + it('subTaskFails_3', function() { + var callbacks = callbackPair(null, assertIsStubError); - schedule('a'); - scheduleAction('sub-tasks', function() { - return promise.rejected(new StubError); - }).then(callbacks.callback, callbacks.errback); - schedule('b'); + schedule('a'); + scheduleAction('sub-tasks', function() { + return promise.rejected(new StubError); + }).then(callbacks.callback, callbacks.errback); + schedule('b'); - return waitForIdle(). - then(function() { - assertFlowHistory('a', 'sub-tasks', 'b'); - callbacks.assertErrback(); - }); + return waitForIdle(). + then(function() { + assertFlowHistory('a', 'sub-tasks', 'b'); + callbacks.assertErrback(); + }); + }); }); - }); - describe('testEventLoopWaitsOnPendingPromiseRejections', function() { - it('oneRejection', function() { - var d = new promise.Deferred; - scheduleAction('one', function() { - return d.promise; + describe('testEventLoopWaitsOnPendingPromiseRejections', function() { + it('oneRejection', function() { + var d = new promise.Deferred; + scheduleAction('one', function() { + return d.promise; + }); + scheduleAction('two', function() {}); + + return timeout(50).then(function() { + assertFlowHistory('one'); + d.reject(new StubError); + return waitForAbort(); + }). + then(assertIsStubError). + then(function() { + assertFlowHistory('one'); + }); }); - scheduleAction('two', function() {}); - return timeout(50).then(function() { - assertFlowHistory('one'); - d.reject(new StubError); - return waitForAbort(); - }). - then(assertIsStubError). - then(function() { - assertFlowHistory('one'); + it('multipleRejections', function() { + var once = Error('once'); + var twice = Error('twice'); + + scheduleAction('one', function() { + promise.rejected(once); + promise.rejected(twice); + }); + var twoResult = scheduleAction('two', function() {}); + + flow.removeAllListeners( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + return new NativePromise(function(fulfill, reject) { + setTimeout(function() { + reject(Error('Should have reported the two errors by now')); + }, 50); + flow.on( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + fulfill); + }).then(function(e) { + assert.ok(e instanceof promise.MultipleUnhandledRejectionError, + 'Not a MultipleUnhandledRejectionError'); + let errors = Array.from(e.errors); + assert.deepEqual([once, twice], errors); + assertFlowHistory('one'); + }); }); }); - it('multipleRejections', function() { - var once = Error('once'); - var twice = Error('twice'); + describe('testCancelsPromiseReturnedByCallbackIfFrameFails', function() { + it('promiseCallback', function() { + var chainPair = callbackPair(null, assertIsStubError); + var deferredPair = callbackPair(null, function(e) { + assert.equal('CancellationError: StubError', e.toString(), + 'callback result should be cancelled'); + }); - scheduleAction('one', function() { - promise.rejected(once); - promise.rejected(twice); + var d = new promise.Deferred(); + d.promise.then(deferredPair.callback, deferredPair.errback); + + promise.fulfilled(). + then(function() { + scheduleAction('boom', throwStubError); + schedule('this should not run'); + return d.promise; + }). + then(chainPair.callback, chainPair.errback); + + return waitForIdle().then(function() { + assertFlowHistory('boom'); + chainPair.assertErrback('chain errback not invoked'); + deferredPair.assertErrback('deferred errback not invoked'); + }); }); - var twoResult = scheduleAction('two', function() {}); - flow.removeAllListeners( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); - return new NativePromise(function(fulfill, reject) { - setTimeout(function() { - reject(Error('Should have reported the two errors by now')); - }, 50); - flow.on( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, - fulfill); - }).then(function(e) { - assert.ok(e instanceof promise.MultipleUnhandledRejectionError, - 'Not a MultipleUnhandledRejectionError'); - let errors = Array.from(e.errors); - assert.deepEqual([once, twice], errors); - assertFlowHistory('one'); - assert.ok(!twoResult.isPending(), 'Did not cancel the second task'); - }); - }); - }); + it('taskCallback', function() { + var chainPair = callbackPair(null, assertIsStubError); + var deferredPair = callbackPair(null, function(e) { + assert.equal('CancellationError: StubError', e.toString(), + 'callback result should be cancelled'); + }); - describe('testCancelsPromiseReturnedByCallbackIfFrameFails', function() { - it('promiseCallback', function() { - var chainPair = callbackPair(null, assertIsStubError); - var deferredPair = callbackPair(null, function(e) { - assert.equal('CancellationError: StubError', e.toString(), - 'callback result should be cancelled'); + var d = new promise.Deferred(); + d.promise.then(deferredPair.callback, deferredPair.errback); + + schedule('a'). + then(function() { + scheduleAction('boom', throwStubError); + schedule('this should not run'); + return d.promise; + }). + then(chainPair.callback, chainPair.errback); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'boom'); + chainPair.assertErrback('chain errback not invoked'); + deferredPair.assertErrback('deferred errback not invoked'); + }); }); + }); - var d = new promise.Deferred(); - d.promise.then(deferredPair.callback, deferredPair.errback); - - promise.fulfilled(). + it('testMaintainsOrderInCallbacksWhenATaskReturnsAPromise', function() { + schedule('__start__', promise.fulfilled()). then(function() { - scheduleAction('boom', throwStubError); - schedule('this should not run'); - return d.promise; + messages.push('a'); + schedulePush('b'); + messages.push('c'); }). - then(chainPair.callback, chainPair.errback); + then(function() { + messages.push('d'); + }); + schedulePush('e'); return waitForIdle().then(function() { - assertFlowHistory('boom'); - chainPair.assertErrback('chain errback not invoked'); - deferredPair.assertErrback('deferred errback not invoked'); + assertFlowHistory('__start__', 'b', 'e'); + assertMessages('a', 'c', 'b', 'd', 'e'); }); }); - it('taskCallback', function() { - var chainPair = callbackPair(null, assertIsStubError); - var deferredPair = callbackPair(null, function(e) { - assert.equal('CancellationError: StubError', e.toString(), - 'callback result should be cancelled'); - }); + it('testOwningFlowIsActivatedForExecutingTasks', function() { + var defaultFlow = promise.controlFlow(); + var order = []; - var d = new promise.Deferred(); - d.promise.then(deferredPair.callback, deferredPair.errback); + promise.createFlow(function(flow) { + assertFlowIs(flow); + order.push(0); - schedule('a'). - then(function() { - scheduleAction('boom', throwStubError); - schedule('this should not run'); - return d.promise; - }). - then(chainPair.callback, chainPair.errback); + defaultFlow.execute(function() { + assertFlowIs(defaultFlow); + order.push(1); + }); + }); return waitForIdle().then(function() { - assertFlowHistory('a', 'boom'); - chainPair.assertErrback('chain errback not invoked'); - deferredPair.assertErrback('deferred errback not invoked'); + assertFlowIs(defaultFlow); + assert.deepEqual([0, 1], order); }); }); - }); - it('testMaintainsOrderInCallbacksWhenATaskReturnsAPromise', function() { - schedule('__start__', promise.fulfilled()). - then(function() { - messages.push('a'); - schedulePush('b'); - messages.push('c'); - }). - then(function() { - messages.push('d'); + it('testCreateFlowReturnsPromisePairedWithCreatedFlow', function() { + return new NativePromise(function(fulfill, reject) { + var newFlow; + promise.createFlow(function(flow) { + newFlow = flow; + assertFlowIs(newFlow); + }).then(function() { + assertFlowIs(newFlow); + waitForIdle(newFlow).then(fulfill, reject); }); - schedulePush('e'); - - return waitForIdle().then(function() { - assertFlowHistory('__start__', 'b', 'e'); - assertMessages('a', 'c', 'b', 'd', 'e'); + }); }); - }); - it('testOwningFlowIsActivatedForExecutingTasks', function() { - var defaultFlow = promise.controlFlow(); - var order = []; - - promise.createFlow(function(flow) { - assertFlowIs(flow); - order.push(0); - - defaultFlow.execute(function() { + it('testDeferredFactoriesCreateForActiveFlow_defaultFlow', function() { + var e = Error(); + var defaultFlow = promise.controlFlow(); + promise.fulfilled().then(function() { assertFlowIs(defaultFlow); - order.push(1); }); - }); - - return waitForIdle().then(function() { - assertFlowIs(defaultFlow); - assert.deepEqual([0, 1], order); - }); - }); - - it('testCreateFlowReturnsPromisePairedWithCreatedFlow', function() { - return new NativePromise(function(fulfill, reject) { - var newFlow; - promise.createFlow(function(flow) { - newFlow = flow; - assertFlowIs(newFlow); - }).then(function() { - assertFlowIs(newFlow); - waitForIdle(newFlow).then(fulfill, reject); + promise.rejected(e).then(null, function(err) { + assert.equal(e, err); + assertFlowIs(defaultFlow); + }); + promise.defer().promise.then(function() { + assertFlowIs(defaultFlow); }); - }); - }); - it('testDeferredFactoriesCreateForActiveFlow_defaultFlow', function() { - var e = Error(); - var defaultFlow = promise.controlFlow(); - promise.fulfilled().then(function() { - assertFlowIs(defaultFlow); - }); - promise.rejected(e).then(null, function(err) { - assert.equal(e, err); - assertFlowIs(defaultFlow); - }); - promise.defer().promise.then(function() { - assertFlowIs(defaultFlow); + return waitForIdle(); }); - return waitForIdle(); - }); + it('testDeferredFactoriesCreateForActiveFlow_newFlow', function() { + var e = Error(); + var newFlow = new promise.ControlFlow; + newFlow.execute(function() { + promise.fulfilled().then(function() { + assertFlowIs(newFlow); + }); - it('testDeferredFactoriesCreateForActiveFlow_newFlow', function() { - var e = Error(); - var newFlow = new promise.ControlFlow; - newFlow.execute(function() { - promise.fulfilled().then(function() { - assertFlowIs(newFlow); - }); + promise.rejected(e).then(null, function(err) { + assert.equal(e, err); + assertFlowIs(newFlow); + }); - promise.rejected(e).then(null, function(err) { - assert.equal(e, err); + let d = promise.defer(); + d.promise.then(function() { + assertFlowIs(newFlow); + }); + d.fulfill(); + }).then(function() { assertFlowIs(newFlow); }); - let d = promise.defer(); - d.promise.then(function() { - assertFlowIs(newFlow); - }); - d.fulfill(); - }).then(function() { - assertFlowIs(newFlow); + return waitForIdle(newFlow); }); - return waitForIdle(newFlow); - }); - - it('testFlowsSynchronizeWithThemselvesNotEachOther', function() { - var defaultFlow = promise.controlFlow(); - schedulePush('a', 'a'); - promise.controlFlow().timeout(250); - schedulePush('b', 'b'); + it('testFlowsSynchronizeWithThemselvesNotEachOther', function() { + var defaultFlow = promise.controlFlow(); + schedulePush('a', 'a'); + promise.controlFlow().timeout(500); + schedulePush('b', 'b'); - promise.createFlow(function() { - schedulePush('c', 'c'); - schedulePush('d', 'd'); - }); + promise.createFlow(function(flow2) { + assertFlowIs(flow2); + schedulePush('c', 'c'); + schedulePush('d', 'd'); + }); - return waitForIdle().then(function() { - assertMessages('a', 'c', 'd', 'b'); + return waitForIdle().then(function() { + assertMessages('a', 'c', 'd', 'b'); + }); }); - }); - it('testUnhandledErrorsAreReportedToTheOwningFlow', function() { - var error1 = Error('e1'); - var error2 = Error('e2'); + it('testUnhandledErrorsAreReportedToTheOwningFlow', function() { + var error1 = Error('e1'); + var error2 = Error('e2'); - var defaultFlow = promise.controlFlow(); - defaultFlow.removeAllListeners('uncaughtException'); + var defaultFlow = promise.controlFlow(); + defaultFlow.removeAllListeners('uncaughtException'); - var flow1Error = NativePromise.defer(); - flow1Error.promise.then(function(value) { - assert.equal(error2, value); - }); + var flow1Error = defer(); + flow1Error.promise.then(function(value) { + assert.equal(error2, value); + }); - var flow2Error = NativePromise.defer(); - flow2Error.promise.then(function(value) { - assert.equal(error1, value); - }); + var flow2Error = defer(); + flow2Error.promise.then(function(value) { + assert.equal(error1, value); + }); - promise.createFlow(function(flow) { - flow.once('uncaughtException', flow2Error.resolve); - promise.rejected(error1); + promise.createFlow(function(flow) { + flow.once('uncaughtException', flow2Error.resolve); + promise.rejected(error1); - defaultFlow.once('uncaughtException', flow1Error.resolve); - defaultFlow.execute(function() { - promise.rejected(error2); + defaultFlow.once('uncaughtException', flow1Error.resolve); + defaultFlow.execute(function() { + promise.rejected(error2); + }); }); - }); - return NativePromise.all([flow1Error.promise, flow2Error.promise]); - }); + return NativePromise.all([flow1Error.promise, flow2Error.promise]); + }); - it('testCanSynchronizeFlowsByReturningPromiseFromOneToAnother', function() { - var flow1 = new promise.ControlFlow; - var flow1Done = NativePromise.defer(); - flow1.once('idle', flow1Done.resolve); - flow1.once('uncaughtException', flow1Done.reject); + it('testCanSynchronizeFlowsByReturningPromiseFromOneToAnother', function() { + var flow1 = new promise.ControlFlow; + var flow1Done = defer(); + flow1.once('idle', flow1Done.resolve); + flow1.once('uncaughtException', flow1Done.reject); - var flow2 = new promise.ControlFlow; - var flow2Done = NativePromise.defer(); - flow2.once('idle', flow2Done.resolve); - flow2.once('uncaughtException', flow2Done.reject); + var flow2 = new promise.ControlFlow; + var flow2Done = defer(); + flow2.once('idle', flow2Done.resolve); + flow2.once('uncaughtException', flow2Done.reject); - flow1.execute(function() { - schedulePush('a', 'a'); - return promise.delayed(25); - }, 'start flow 1'); + flow1.execute(function() { + schedulePush('a', 'a'); + return promise.delayed(25); + }, 'start flow 1'); - flow2.execute(function() { - schedulePush('b', 'b'); - schedulePush('c', 'c'); flow2.execute(function() { - return flow1.execute(function() { - schedulePush('d', 'd'); - }, 'flow 1 task'); - }, 'inject flow1 result into flow2'); - schedulePush('e', 'e'); - }, 'start flow 2'); - - return NativePromise.all([flow1Done.promise, flow2Done.promise]). - then(function() { - assertMessages('a', 'b', 'c', 'd', 'e'); - }); - }); + schedulePush('b', 'b'); + schedulePush('c', 'c'); + flow2.execute(function() { + return flow1.execute(function() { + schedulePush('d', 'd'); + }, 'flow 1 task'); + }, 'inject flow1 result into flow2'); + schedulePush('e', 'e'); + }, 'start flow 2'); + + return NativePromise.all([flow1Done.promise, flow2Done.promise]). + then(function() { + assertMessages('a', 'b', 'c', 'd', 'e'); + }); + }); - it('testFramesWaitToCompleteForPendingRejections', function() { - return new NativePromise(function(fulfill, reject) { + it('testFramesWaitToCompleteForPendingRejections', function() { + return new NativePromise(function(fulfill, reject) { - promise.controlFlow().execute(function() { - promise.rejected(new StubError); - }).then(fulfill, reject); + promise.controlFlow().execute(function() { + promise.rejected(new StubError); + }).then(fulfill, reject); - }). - then(() => fail('expected to fail'), assertIsStubError); - }); + }). + then(() => fail('expected to fail'), assertIsStubError); + }); - it('testSynchronizeErrorsPropagateToOuterFlow', function() { - var outerFlow = new promise.ControlFlow; - var innerFlow = new promise.ControlFlow; + it('testSynchronizeErrorsPropagateToOuterFlow', function() { + var outerFlow = new promise.ControlFlow; + var innerFlow = new promise.ControlFlow; - var block = NativePromise.defer(); - innerFlow.execute(function() { - return block.promise; - }, 'block inner flow'); + var block = defer(); + innerFlow.execute(function() { + return block.promise; + }, 'block inner flow'); - outerFlow.execute(function() { - block.resolve(); - return innerFlow.execute(function() { - promise.rejected(new StubError); - }, 'trigger unhandled rejection error'); - }, 'run test'); + outerFlow.execute(function() { + block.resolve(); + return innerFlow.execute(function() { + promise.rejected(new StubError); + }, 'trigger unhandled rejection error'); + }, 'run test'); - return NativePromise.all([ - waitForIdle(innerFlow), - waitForAbort(outerFlow).then(assertIsStubError) - ]); - }); + return NativePromise.all([ + waitForIdle(innerFlow), + waitForAbort(outerFlow).then(assertIsStubError) + ]); + }); - it('testFailsIfErrbackThrows', function() { - promise.rejected('').then(null, throwStubError); - return waitForAbort().then(assertIsStubError); - }); + it('testFailsIfErrbackThrows', function() { + promise.rejected('').then(null, throwStubError); + return waitForAbort().then(assertIsStubError); + }); - it('testFailsIfCallbackReturnsRejectedPromise', function() { - promise.fulfilled().then(function() { - return promise.rejected(new StubError); + it('testFailsIfCallbackReturnsRejectedPromise', function() { + promise.fulfilled().then(function() { + return promise.rejected(new StubError); + }); + return waitForAbort().then(assertIsStubError); }); - return waitForAbort().then(assertIsStubError); - }); - it('testAbortsFrameIfTaskFails', function() { - promise.fulfilled().then(function() { - promise.controlFlow().execute(throwStubError); + it('testAbortsFrameIfTaskFails', function() { + promise.fulfilled().then(function() { + promise.controlFlow().execute(throwStubError); + }); + return waitForAbort().then(assertIsStubError); }); - return waitForAbort().then(assertIsStubError); - }); - it('testAbortsFramePromisedChainedFromTaskIsNotHandled', function() { - promise.fulfilled().then(function() { - promise.controlFlow().execute(function() {}). - then(throwStubError); + it('testAbortsFramePromisedChainedFromTaskIsNotHandled', function() { + promise.fulfilled().then(function() { + promise.controlFlow().execute(function() {}). + then(throwStubError); + }); + return waitForAbort().then(assertIsStubError); }); - return waitForAbort().then(assertIsStubError); - }); - it('testTrapsChainedUnhandledRejectionsWithinAFrame', function() { - var pair = callbackPair(null, assertIsStubError); - promise.fulfilled().then(function() { - promise.controlFlow().execute(function() {}). - then(throwStubError); - }).then(pair.callback, pair.errback); + it('testTrapsChainedUnhandledRejectionsWithinAFrame', function() { + var pair = callbackPair(null, assertIsStubError); + promise.fulfilled().then(function() { + promise.controlFlow().execute(function() {}). + then(throwStubError); + }).then(pair.callback, pair.errback); - return waitForIdle().then(pair.assertErrback); - }); + return waitForIdle().then(pair.assertErrback); + }); - it('testCancelsRemainingTasksIfFrameThrowsDuringScheduling', function() { - var task1, task2; - var pair = callbackPair(null, assertIsStubError); - var flow = promise.controlFlow(); - flow.execute(function() { - task1 = flow.execute(function() {}); - task2 = flow.execute(function() {}); - throw new StubError; - }).then(pair.callback, pair.errback); - - return waitForIdle(). - then(pair.assertErrback). - then(function() { - assert.ok(!task1.isPending()); - pair = callbackPair(); - return task1.then(pair.callback, pair.errback); - }). - then(function() { - pair.assertErrback(); - assert.ok(!task2.isPending()); - pair = callbackPair(); - return task2.then(pair.callback, pair.errback); - }). - then(function() { - pair.assertErrback(); - }); - }); + it('testCancelsRemainingTasksIfFrameThrowsDuringScheduling', function() { + var task1, task2; + var pair = callbackPair(null, assertIsStubError); + var flow = promise.controlFlow(); + flow.execute(function() { + task1 = flow.execute(function() {}); + task2 = flow.execute(function() {}); + throw new StubError; + }).then(pair.callback, pair.errback); - it('testCancelsRemainingTasksInFrameIfATaskFails', function() { - var task; - var pair = callbackPair(null, assertIsStubError); - var flow = promise.controlFlow(); - flow.execute(function() { - flow.execute(throwStubError); - task = flow.execute(function() {}); - }).then(pair.callback, pair.errback); - - return waitForIdle().then(pair.assertErrback).then(function() { - assert.ok(!task.isPending()); - pair = callbackPair(); - task.then(pair.callback, pair.errback); - }).then(function() { - pair.assertErrback(); + return waitForIdle(). + then(pair.assertErrback). + then(function() { + pair = callbackPair(); + return task1.then(pair.callback, pair.errback); + }). + then(function() { + pair.assertErrback(); + pair = callbackPair(); + return task2.then(pair.callback, pair.errback); + }). + then(function() { + pair.assertErrback(); + }); }); - }); - it('testDoesNotModifyRejectionErrorIfPromiseNotInsideAFlow', function() { - var error = Error('original message'); - var originalStack = error.stack; - var originalStr = error.toString(); + it('testCancelsRemainingTasksInFrameIfATaskFails', function() { + var task; + var pair = callbackPair(null, assertIsStubError); + var flow = promise.controlFlow(); + flow.execute(function() { + flow.execute(throwStubError); + task = flow.execute(function() {}); + }).then(pair.callback, pair.errback); - var pair = callbackPair(null, function(e) { - assert.equal(error, e); - assert.equal('original message', e.message); - assert.equal(originalStack, e.stack); - assert.equal(originalStr, e.toString()); + return waitForIdle().then(pair.assertErrback).then(function() { + pair = callbackPair(); + task.then(pair.callback, pair.errback); + }).then(function() { + pair.assertErrback(); + }); }); - promise.rejected(error).then(pair.callback, pair.errback); - return waitForIdle().then(pair.assertErrback); - }); + it('testDoesNotModifyRejectionErrorIfPromiseNotInsideAFlow', function() { + var error = Error('original message'); + var originalStack = error.stack; + var originalStr = error.toString(); - /** See https://github.com/SeleniumHQ/selenium/issues/444 */ - it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_1', function() { - var messages = []; - flow.execute(function() { - return promise.fulfilled(['a', 'b', 'c', 'd']); - }, 'start').then(function(steps) { - steps.forEach(function(step) { - promise.fulfilled(step) - .then(function() { - messages.push(step + '.1'); - }).then(function() { - messages.push(step + '.2'); - }); - }) - }); - return waitForIdle().then(function() { - assert.deepEqual( - ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], - messages); + var pair = callbackPair(null, function(e) { + assert.equal(error, e); + assert.equal('original message', e.message); + assert.equal(originalStack, e.stack); + assert.equal(originalStr, e.toString()); + }); + + promise.rejected(error).then(pair.callback, pair.errback); + return waitForIdle().then(pair.assertErrback); }); - }); - /** See https://github.com/SeleniumHQ/selenium/issues/444 */ - it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_2', function() { - var messages = []; - flow.execute(function() { - return promise.fulfilled(['a', 'b', 'c', 'd']); - }, 'start').then(function(steps) { - steps.forEach(function(step) { - promise.fulfilled(step) - .then(function() { - messages.push(step + '.1'); - }).then(function() { - flow.execute(function() {}, step + '.2').then(function() { + /** See https://github.com/SeleniumHQ/selenium/issues/444 */ + it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_1', function() { + var messages = []; + flow.execute(function() { + return promise.fulfilled(['a', 'b', 'c', 'd']); + }, 'start').then(function(steps) { + steps.forEach(function(step) { + promise.fulfilled(step) + .then(function() { + messages.push(step + '.1'); + }).then(function() { messages.push(step + '.2'); }); - }); - }) - }); - return waitForIdle().then(function() { - assert.deepEqual( - ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], - messages); + }) + }); + return waitForIdle().then(function() { + assert.deepEqual( + ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], + messages); + }); }); - }); - /** See https://github.com/SeleniumHQ/selenium/issues/444 */ - it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_3', function() { - var messages = []; - flow.execute(function() { - return promise.fulfilled(['a', 'b', 'c', 'd']); - }, 'start').then(function(steps) { - steps.forEach(function(step) { - promise.fulfilled(step) - .then(function(){}) - .then(function() { - messages.push(step + '.1'); - return flow.execute(function() {}, step + '.1'); - }).then(function() { - flow.execute(function() {}, step + '.2').then(function(text) { - messages.push(step + '.2'); + /** See https://github.com/SeleniumHQ/selenium/issues/444 */ + it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_2', function() { + var messages = []; + flow.execute(function() { + return promise.fulfilled(['a', 'b', 'c', 'd']); + }, 'start').then(function(steps) { + steps.forEach(function(step) { + promise.fulfilled(step) + .then(function() { + messages.push(step + '.1'); + }).then(function() { + flow.execute(function() {}, step + '.2').then(function() { + messages.push(step + '.2'); + }); }); - }); - }) + }) + }); + return waitForIdle().then(function() { + assert.deepEqual( + ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], + messages); + }); }); - return waitForIdle().then(function() { - assert.deepEqual( - ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], - messages); + + /** See https://github.com/SeleniumHQ/selenium/issues/444 */ + it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_3', function() { + var messages = []; + flow.execute(function() { + return promise.fulfilled(['a', 'b', 'c', 'd']); + }, 'start').then(function(steps) { + steps.forEach(function(step) { + promise.fulfilled(step) + .then(function(){}) + .then(function() { + messages.push(step + '.1'); + return flow.execute(function() {}, step + '.1'); + }).then(function() { + flow.execute(function() {}, step + '.2').then(function(text) { + messages.push(step + '.2'); + }); + }); + }) + }); + return waitForIdle().then(function() { + assert.deepEqual( + ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'], + messages); + }); }); - }); - /** See https://github.com/SeleniumHQ/selenium/issues/363 */ - it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() { - scheduleAction('a', () => promise.delayed(10)); - schedule('b'); - setTimeout(() => schedule('c'), 0); + /** See https://github.com/SeleniumHQ/selenium/issues/363 */ + it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() { + scheduleAction('a', () => promise.delayed(10)); + schedule('b'); + setTimeout(() => schedule('c'), 0); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'b'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); }); - }); - /** See https://github.com/SeleniumHQ/selenium/issues/363 */ - it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() { - scheduleAction('a', () => promise.delayed(10)); - schedule('b'); - schedule('c'); - setTimeout(function() { - schedule('d'); - scheduleAction('e', () => promise.delayed(10)); - schedule('f'); - }, 0); - - return waitForIdle().then(function() { - assertFlowHistory('a', 'd', 'e', 'b', 'c', 'f'); - }); - }); + /** See https://github.com/SeleniumHQ/selenium/issues/363 */ + it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() { + scheduleAction('a', () => promise.delayed(10)); + schedule('b'); + schedule('c'); + setTimeout(function() { + schedule('d'); + scheduleAction('e', () => promise.delayed(10)); + schedule('f'); + }, 0); - /** See https://github.com/SeleniumHQ/selenium/issues/363 */ - it('testCanSynchronizeTasksFromAdjacentTaskQueues', function() { - var task1 = scheduleAction('a', () => promise.delayed(10)); - schedule('b'); - setTimeout(function() { - scheduleAction('c', () => task1); - schedule('d'); - }, 0); - - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'd', 'b'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'd', 'e', 'b', 'c', 'f'); + }); }); - }); - describe('testCancellingAScheduledTask', function() { - it('1', function() { - var called = false; - var task1 = scheduleAction('a', () => called = true); - task1.cancel('no soup for you'); + /** See https://github.com/SeleniumHQ/selenium/issues/363 */ + it('testCanSynchronizeTasksFromAdjacentTaskQueues', function() { + var task1 = scheduleAction('a', () => promise.delayed(10)); + schedule('b'); + setTimeout(function() { + scheduleAction('c', () => task1); + schedule('d'); + }, 0); return waitForIdle().then(function() { - assert.ok(!called); - assertFlowHistory(); - return task1.catch(function(e) { - assert.ok(e instanceof promise.CancellationError); - assert.equal('no soup for you', e.message); - }); + assertFlowHistory('a', 'c', 'd', 'b'); }); }); - it('2', function() { - schedule('a'); - var called = false; - var task2 = scheduleAction('b', () => called = true); - schedule('c'); + describe('testCancellingAScheduledTask', function() { + it('1', function() { + var called = false; + var task1 = scheduleAction('a', () => called = true); + task1.cancel('no soup for you'); - task2.cancel('no soup for you'); - - return waitForIdle().then(function() { - assert.ok(!called); - assertFlowHistory('a', 'c'); - return task2.catch(function(e) { - assert.ok(e instanceof promise.CancellationError); - assert.equal('no soup for you', e.message); + return waitForIdle().then(function() { + assert.ok(!called); + assertFlowHistory(); + return task1.catch(function(e) { + assert.ok(e instanceof promise.CancellationError); + assert.equal('no soup for you', e.message); + }); }); }); - }); - it('3', function() { - var called = false; - var task = scheduleAction('a', () => called = true); - task.cancel(new StubError); + it('2', function() { + schedule('a'); + var called = false; + var task2 = scheduleAction('b', () => called = true); + schedule('c'); - return waitForIdle().then(function() { - assert.ok(!called); - assertFlowHistory(); - return task.catch(function(e) { - assert.ok(e instanceof promise.CancellationError); + task2.cancel('no soup for you'); + + return waitForIdle().then(function() { + assert.ok(!called); + assertFlowHistory('a', 'c'); + return task2.catch(function(e) { + assert.ok(e instanceof promise.CancellationError); + assert.equal('no soup for you', e.message); + }); }); }); - }); - it('4', function() { - var seen = []; - var task = scheduleAction('a', () => seen.push(1)) - .then(() => seen.push(2)) - .then(() => seen.push(3)) - .then(() => seen.push(4)) - .then(() => seen.push(5)); - task.cancel(new StubError); + it('3', function() { + var called = false; + var task = scheduleAction('a', () => called = true); + task.cancel(new StubError); - return waitForIdle().then(function() { - assert.deepEqual([], seen); - assertFlowHistory(); - return task.catch(function(e) { - assert.ok(e instanceof promise.CancellationError); + return waitForIdle().then(function() { + assert.ok(!called); + assertFlowHistory(); + return task.catch(function(e) { + assert.ok(e instanceof promise.CancellationError); + }); }); }); - }); - it('fromWithinAnExecutingTask', function() { - var called = false; - var task; - scheduleAction('a', function() { - task.cancel('no soup for you'); + it('4', function() { + var seen = []; + var task = scheduleAction('a', () => seen.push(1)) + .then(() => seen.push(2)) + .then(() => seen.push(3)) + .then(() => seen.push(4)) + .then(() => seen.push(5)); + task.cancel(new StubError); + + return waitForIdle().then(function() { + assert.deepEqual([], seen); + assertFlowHistory(); + return task.catch(function(e) { + assert.ok(e instanceof promise.CancellationError); + }); + }); }); - task = scheduleAction('b', () => called = true); - schedule('c'); - return waitForIdle().then(function() { - assert.ok(!called); - assertFlowHistory('a', 'c'); - return task.catch(function(e) { - assert.ok(e instanceof promise.CancellationError); - assert.equal('no soup for you', e.message); + it('fromWithinAnExecutingTask', function() { + var called = false; + var task; + scheduleAction('a', function() { + task.cancel('no soup for you'); + }); + task = scheduleAction('b', () => called = true); + schedule('c'); + + return waitForIdle().then(function() { + assert.ok(!called); + assertFlowHistory('a', 'c'); + return task.catch(function(e) { + assert.ok(e instanceof promise.CancellationError); + assert.equal('no soup for you', e.message); + }); }); }); }); - }); - it('testCancellingAPendingTask', function() { - var order = []; - var unresolved = promise.defer(); + it('testCancellingAPendingTask', function() { + var order = []; + var unresolved = promise.defer(); - var innerTask; - var outerTask = scheduleAction('a', function() { - order.push(1); + var innerTask; + var outerTask = scheduleAction('a', function() { + order.push(1); - // Schedule a task that will never finish. - innerTask = scheduleAction('a.1', function() { - return unresolved.promise; - }); + // Schedule a task that will never finish. + innerTask = scheduleAction('a.1', function() { + return unresolved.promise; + }); - // Since the outerTask is cancelled below, innerTask should be cancelled - // with a DiscardedTaskError, which means its callbacks are silently - // dropped - so this should never execute. - innerTask.catch(function(e) { - order.push(2); + // Since the outerTask is cancelled below, innerTask should be cancelled + // with a DiscardedTaskError, which means its callbacks are silently + // dropped - so this should never execute. + innerTask.catch(function(e) { + order.push(2); + }); }); - }); - schedule('b'); + schedule('b'); - outerTask.catch(function(e) { - order.push(3); - assert.ok(e instanceof promise.CancellationError); - assert.equal('no soup for you', e.message); - }); + outerTask.catch(function(e) { + order.push(3); + assert.ok(e instanceof promise.CancellationError); + assert.equal('no soup for you', e.message); + }); - unresolved.promise.catch(function(e) { - order.push(4); - assert.ok(e instanceof promise.CancellationError); - }); + unresolved.promise.catch(function(e) { + order.push(4); + assert.ok(e instanceof promise.CancellationError); + }); - return timeout(10).then(function() { - assert.deepEqual([1], order); - assert.ok(unresolved.promise.isPending()); + return timeout(10).then(function() { + assert.deepEqual([1], order); - outerTask.cancel('no soup for you'); - return waitForIdle(); - }).then(function() { - assertFlowHistory('a', 'a.1', 'b'); - assert.deepEqual([1, 3, 4], order); + outerTask.cancel('no soup for you'); + return waitForIdle(); + }).then(function() { + assertFlowHistory('a', 'a.1', 'b'); + assert.deepEqual([1, 3, 4], order); + }); }); - }); - it('testCancellingAPendingPromiseCallback', function() { - var called = false; + it('testCancellingAPendingPromiseCallback', function() { + var called = false; - var root = promise.fulfilled(); - root.then(function() { - cb2.cancel('no soup for you'); - }); + var root = promise.fulfilled(); + root.then(function() { + cb2.cancel('no soup for you'); + }); - var cb2 = root.then(fail, fail); // These callbacks should never be called. - cb2.then(fail, function(e) { - called = true; - assert.ok(e instanceof promise.CancellationError); - assert.equal('no soup for you', e.message); - }); + var cb2 = root.then(fail, fail); // These callbacks should never be called. + cb2.then(fail, function(e) { + called = true; + assert.ok(e instanceof promise.CancellationError); + assert.equal('no soup for you', e.message); + }); - return waitForIdle().then(function() { - assert.ok(called); + return waitForIdle().then(function() { + assert.ok(called); + }); }); - }); - describe('testResetFlow', function() { - it('1', function() { - var called = 0; - var task = flow.execute(() => called++); - task.finally(() => called++); + describe('testResetFlow', function() { + it('1', function() { + var called = 0; + var task = flow.execute(() => called++); + task.finally(() => called++); - return new Promise(function(fulfill) { - flow.once('reset', fulfill); - flow.reset(); + return new Promise(function(fulfill) { + flow.once('reset', fulfill); + flow.reset(); - }).then(function() { - assert.equal(0, called); - assert.ok(!task.isPending()); - return task; + }).then(function() { + assert.equal(0, called); + return task; - }).then(fail, function(e) { - assert.ok(e instanceof promise.CancellationError); - assert.equal('ControlFlow was reset', e.message); + }).then(fail, function(e) { + assert.ok(e instanceof promise.CancellationError); + assert.equal('ControlFlow was reset', e.message); + }); }); - }); - it('2', function() { - var called = 0; - var task1 = flow.execute(() => called++); - task1.finally(() => called++); + it('2', function() { + var called = 0; + var task1 = flow.execute(() => called++); + task1.finally(() => called++); - var task2 = flow.execute(() => called++); - task2.finally(() => called++); + var task2 = flow.execute(() => called++); + task2.finally(() => called++); - var task3 = flow.execute(() => called++); - task3.finally(() => called++); + var task3 = flow.execute(() => called++); + task3.finally(() => called++); - return new Promise(function(fulfill) { - flow.once('reset', fulfill); - flow.reset(); + return new Promise(function(fulfill) { + flow.once('reset', fulfill); + flow.reset(); - }).then(function() { - assert.equal(0, called); - assert.ok(!task1.isPending()); - assert.ok(!task2.isPending()); - assert.ok(!task3.isPending()); + }).then(function() { + assert.equal(0, called); + }); }); }); - }); - describe('testPromiseFulfilledInsideTask', function() { - it('1', function() { - var order = []; + describe('testPromiseFulfilledInsideTask', function() { + it('1', function() { + var order = []; - flow.execute(function() { - var d = promise.defer(); + flow.execute(function() { + var d = promise.defer(); - d.promise.then(() => order.push('a')); - d.promise.then(() => order.push('b')); - d.promise.then(() => order.push('c')); - d.fulfill(); + d.promise.then(() => order.push('a')); + d.promise.then(() => order.push('b')); + d.promise.then(() => order.push('c')); + d.fulfill(); - flow.execute(() => order.push('d')); + flow.execute(() => order.push('d')); - }).then(() => order.push('fin')); + }).then(() => order.push('fin')); - return waitForIdle().then(function() { - assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); + return waitForIdle().then(function() { + assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); + }); }); - }); - it('2', function() { - var order = []; + it('2', function() { + var order = []; - flow.execute(function() { - flow.execute(() => order.push('a')); - flow.execute(() => order.push('b')); + flow.execute(function() { + flow.execute(() => order.push('a')); + flow.execute(() => order.push('b')); + + var d = promise.defer(); + d.promise.then(() => order.push('c')); + d.promise.then(() => order.push('d')); + d.fulfill(); + flow.execute(() => order.push('e')); + + }).then(() => order.push('fin')); + + return waitForIdle().then(function() { + assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'fin'], order); + }); + }); + + it('3', function() { + var order = []; var d = promise.defer(); d.promise.then(() => order.push('c')); d.promise.then(() => order.push('d')); - d.fulfill(); - flow.execute(() => order.push('e')); + flow.execute(function() { + flow.execute(() => order.push('a')); + flow.execute(() => order.push('b')); - }).then(() => order.push('fin')); + d.promise.then(() => order.push('e')); + d.fulfill(); - return waitForIdle().then(function() { - assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'fin'], order); + flow.execute(() => order.push('f')); + + }).then(() => order.push('fin')); + + return waitForIdle().then(function() { + assert.deepEqual(['c', 'd', 'a', 'b', 'e', 'f', 'fin'], order); + }); }); - }); - it('3', function() { - var order = []; - var d = promise.defer(); - d.promise.then(() => order.push('c')); - d.promise.then(() => order.push('d')); + it('4', function() { + var order = []; + var d = promise.defer(); + d.promise.then(() => order.push('a')); + d.promise.then(() => order.push('b')); - flow.execute(function() { - flow.execute(() => order.push('a')); - flow.execute(() => order.push('b')); + flow.execute(function() { + flow.execute(function() { + order.push('c'); + flow.execute(() => order.push('d')); + d.promise.then(() => order.push('e')); + }); + flow.execute(() => order.push('f')); - d.promise.then(() => order.push('e')); - d.fulfill(); + d.promise.then(() => order.push('g')); + d.fulfill(); - flow.execute(() => order.push('f')); + flow.execute(() => order.push('h')); - }).then(() => order.push('fin')); + }).then(() => order.push('fin')); - return waitForIdle().then(function() { - assert.deepEqual(['c', 'd', 'a', 'b', 'e', 'f', 'fin'], order); + return waitForIdle().then(function() { + assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'fin'], order); + }); }); }); - it('4', function() { - var order = []; - var d = promise.defer(); - d.promise.then(() => order.push('a')); - d.promise.then(() => order.push('b')); + describe('testSettledPromiseCallbacksInsideATask', function() { + it('1', function() { + var order = []; + var p = promise.fulfilled(); - flow.execute(function() { flow.execute(function() { - order.push('c'); - flow.execute(() => order.push('d')); - d.promise.then(() => order.push('e')); + flow.execute(() => order.push('a')); + p.then(() => order.push('b')); + flow.execute(() => order.push('c')); + p.then(() => order.push('d')); + }).then(() => order.push('fin')); + + return waitForIdle().then(function() { + assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); }); - flow.execute(() => order.push('f')); - - d.promise.then(() => order.push('g')); - d.fulfill(); + }); - flow.execute(() => order.push('h')); + it('2', function() { + var order = []; - }).then(() => order.push('fin')); + flow.execute(function() { + flow.execute(() => order.push('a')) + .then( () => order.push('c')); + flow.execute(() => order.push('b')); + }).then(() => order.push('fin')); - return waitForIdle().then(function() { - assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'fin'], order); + return waitForIdle().then(function() { + assert.deepEqual(['a', 'c', 'b', 'fin'], order); + }); }); }); - }); - describe('testSettledPromiseCallbacksInsideATask', function() { - it('1', function() { + it('testTasksDoNotWaitForNewlyCreatedPromises', function() { var order = []; - var p = promise.fulfilled(); flow.execute(function() { - flow.execute(() => order.push('a')); - p.then(() => order.push('b')); - flow.execute(() => order.push('c')); - p.then(() => order.push('d')); - }).then(() => order.push('fin')); - - return waitForIdle().then(function() { - assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); - }); - }); + var d = promise.defer(); - it('2', function() { - var order = []; + // This is a normal promise, not a task, so the task for this callback is + // considered volatile. Volatile tasks should be skipped when they reach + // the front of the task queue. + d.promise.then(() => order.push('a')); - flow.execute(function() { - flow.execute(() => order.push('a')) - .then( () => order.push('c')); flow.execute(() => order.push('b')); + flow.execute(function() { + flow.execute(() => order.push('c')); + d.promise.then(() => order.push('d')); + d.fulfill(); + }); + flow.execute(() => order.push('e')); + }).then(() => order.push('fin')); return waitForIdle().then(function() { - assert.deepEqual(['a', 'c', 'b', 'fin'], order); + assert.deepEqual(['b', 'a', 'c', 'd', 'e', 'fin'], order); }); }); - }); - - it('testTasksDoNotWaitForNewlyCreatedPromises', function() { - var order = []; - flow.execute(function() { - var d = promise.defer(); - - // This is a normal promise, not a task, so the task for this callback is - // considered volatile. Volatile tasks should be skipped when they reach - // the front of the task queue. - d.promise.then(() => order.push('a')); - - flow.execute(() => order.push('b')); - flow.execute(function() { - flow.execute(() => order.push('c')); - d.promise.then(() => order.push('d')); - d.fulfill(); + it('testCallbackDependenciesDoNotDeadlock', function() { + var order = []; + var root = promise.defer(); + var dep = promise.fulfilled().then(function() { + order.push('a'); + return root.promise.then(function() { + order.push('b'); + }); }); - flow.execute(() => order.push('e')); - - }).then(() => order.push('fin')); + // This callback depends on |dep|, which depends on another callback + // attached to |root| via a chain. + root.promise.then(function() { + order.push('c'); + return dep.then(() => order.push('d')); + }).then(() => order.push('fin')); - return waitForIdle().then(function() { - assert.deepEqual(['b', 'a', 'c', 'd', 'e', 'fin'], order); - }); - }); + setTimeout(() => root.fulfill(), 20); - it('testCallbackDependenciesDoNotDeadlock', function() { - var order = []; - var root = promise.defer(); - var dep = promise.fulfilled().then(function() { - order.push('a'); - return root.promise.then(function() { - order.push('b'); + return waitForIdle().then(function() { + assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); }); }); - // This callback depends on |dep|, which depends on another callback - // attached to |root| via a chain. - root.promise.then(function() { - order.push('c'); - return dep.then(() => order.push('d')); - }).then(() => order.push('fin')); - - setTimeout(() => root.fulfill(), 20); - - return waitForIdle().then(function() { - assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order); - }); }); }); diff --git a/node_modules/selenium-webdriver/test/lib/promise_generator_test.js b/node_modules/selenium-webdriver/test/lib/promise_generator_test.js index 5fdff9f80..b3388da78 100644 --- a/node_modules/selenium-webdriver/test/lib/promise_generator_test.js +++ b/node_modules/selenium-webdriver/test/lib/promise_generator_test.js @@ -19,237 +19,165 @@ const assert = require('assert'); const promise = require('../../lib/promise'); +const {enablePromiseManager, promiseManagerSuite} = require('../../lib/test/promise'); describe('promise.consume()', function() { - it('requires inputs to be generator functions', function() { - assert.throws(function() { - promise.consume(function() {}); + promiseManagerSuite(() => { + it('requires inputs to be generator functions', function() { + assert.throws(function() { + promise.consume(function() {}); + }); }); - }); - it('handles a basic generator with no yielded promises', function() { - var values = []; - return promise.consume(function* () { - var i = 0; - while (i < 4) { - i = yield i + 1; - values.push(i); - } - }).then(function() { - assert.deepEqual([1, 2, 3, 4], values); + it('handles a basic generator with no yielded promises', function() { + var values = []; + return promise.consume(function* () { + var i = 0; + while (i < 4) { + i = yield i + 1; + values.push(i); + } + }).then(function() { + assert.deepEqual([1, 2, 3, 4], values); + }); }); - }); - it('handles a promise yielding generator', function() { - var values = []; - return promise.consume(function* () { - var i = 0; - while (i < 4) { - // Test that things are actually async here. - setTimeout(function() { - values.push(i * 2); - }, 10); - - yield promise.delayed(10).then(function() { - values.push(i++); - }); - } - }).then(function() { - assert.deepEqual([0, 0, 2, 1, 4, 2, 6, 3], values); + it('handles a promise yielding generator', function() { + var values = []; + return promise.consume(function* () { + var i = 0; + while (i < 4) { + // Test that things are actually async here. + setTimeout(function() { + values.push(i * 2); + }, 10); + + yield promise.delayed(10).then(function() { + values.push(i++); + }); + } + }).then(function() { + assert.deepEqual([0, 0, 2, 1, 4, 2, 6, 3], values); + }); }); - }); - it('assignemnts to yielded promises get fulfilled value', function() { - return promise.consume(function* () { - var p = promise.fulfilled(2); - var x = yield p; - assert.equal(2, x); + it('assignments to yielded promises get fulfilled value', function() { + return promise.consume(function* () { + let x = yield Promise.resolve(2); + assert.equal(2, x); + }); }); - }); - it('is possible to cancel promise generators', function() { - var values = []; - var p = promise.consume(function* () { - var i = 0; - while (i < 3) { - yield promise.delayed(100).then(function() { - values.push(i++); - }); - } - }); - return promise.delayed(75).then(function() { - p.cancel(); - return p.catch(function() { - return promise.delayed(300); + it('uses final return value as fulfillment value', function() { + return promise.consume(function* () { + yield 1; + yield 2; + return 3; + }).then(function(value) { + assert.equal(3, value); }); - }).then(function() { - assert.deepEqual([0], values); }); - }); - it('uses final return value as fulfillment value', function() { - return promise.consume(function* () { - yield 1; - yield 2; - return 3; - }).then(function(value) { - assert.equal(3, value); + it('throws rejected promise errors within the generator', function() { + var values = []; + return promise.consume(function* () { + values.push('a'); + var e = Error('stub error'); + try { + yield Promise.reject(e); + values.push('b'); + } catch (ex) { + assert.equal(e, ex); + values.push('c'); + } + values.push('d'); + }).then(function() { + assert.deepEqual(['a', 'c', 'd'], values); + }); }); - }); - it('throws rejected promise errors within the generator', function() { - var values = []; - return promise.consume(function* () { - values.push('a'); + it('aborts the generator if there is an unhandled rejection', function() { + var values = []; var e = Error('stub error'); - try { + return promise.consume(function* () { + values.push(1); yield promise.rejected(e); - values.push('b'); - } catch (ex) { - assert.equal(e, ex); - values.push('c'); - } - values.push('d'); - }).then(function() { - assert.deepEqual(['a', 'c', 'd'], values); - }); - }); - - it('aborts the generator if there is an unhandled rejection', function() { - var values = []; - var e = Error('stub error'); - return promise.consume(function* () { - values.push(1); - yield promise.rejected(e); - values.push(2); - }).catch(function() { - assert.deepEqual([1], values); - }); - }); - - it('yield waits for promises', function() { - var values = []; - var d = promise.defer(); - - setTimeout(function() { - assert.deepEqual([1], values); - d.fulfill(2); - }, 100); - - return promise.consume(function* () { - values.push(1); - values.push((yield d.promise), 3); - }).then(function() { - assert.deepEqual([1, 2, 3], values); - }); - }); - - it('accepts custom scopes', function() { - return promise.consume(function* () { - return this.name; - }, {name: 'Bob'}).then(function(value) { - assert.equal('Bob', value); + values.push(2); + }).catch(function() { + assert.deepEqual([1], values); + }); }); - }); - it('accepts initial generator arguments', function() { - return promise.consume(function* (a, b) { - assert.equal('red', a); - assert.equal('apples', b); - }, null, 'red', 'apples'); - }); - - it('executes generator within the control flow', function() { - var promises = [ - promise.defer(), - promise.defer() - ]; - var values = []; - - setTimeout(function() { - assert.deepEqual([], values); - promises[0].fulfill(1); - }, 100); - - setTimeout(function() { - assert.deepEqual([1], values); - promises[1].fulfill(2); - }, 200); - - return promise.controlFlow().execute(function* () { - values.push(yield promises[0].promise); - values.push(yield promises[1].promise); - values.push('fin'); - }).then(function() { - assert.deepEqual([1, 2, 'fin'], values); - }); - }); - - it('handles tasks scheduled in generator', function() { - var flow = promise.controlFlow(); - return flow.execute(function* () { - var x = yield flow.execute(function() { - return promise.delayed(10).then(function() { - return 1; - }); + it('yield waits for promises', function() { + let values = []; + let blocker = promise.delayed(100).then(() => { + assert.deepEqual([1], values); + return 2; }); - var y = yield flow.execute(function() { - return 2; + return promise.consume(function* () { + values.push(1); + values.push(yield blocker, 3); + }).then(function() { + assert.deepEqual([1, 2, 3], values); }); + }); - return x + y; - }).then(function(value) { - assert.equal(3, value); + it('accepts custom scopes', function() { + return promise.consume(function* () { + return this.name; + }, {name: 'Bob'}).then(function(value) { + assert.equal('Bob', value); + }); }); - }); - it('blocks the control flow while processing generator', function() { - var values = []; - return promise.controlFlow().wait(function* () { - yield values.push(1); - values.push(yield promise.delayed(10).then(function() { - return 2; - })); - yield values.push(3); - return values.length === 6; - }, 250).then(function() { - assert.deepEqual([1, 2, 3, 1, 2, 3], values); + it('accepts initial generator arguments', function() { + return promise.consume(function* (a, b) { + assert.equal('red', a); + assert.equal('apples', b); + }, null, 'red', 'apples'); }); }); - it('ControlFlow.wait() will timeout on long generator', function() { - var values = []; - return promise.controlFlow().wait(function* () { - var i = 0; - while (i < 3) { - yield promise.delayed(100).then(function() { - values.push(i++); + enablePromiseManager(() => { + it('is possible to cancel promise generators', function() { + var values = []; + var p = promise.consume(function* () { + var i = 0; + while (i < 3) { + yield promise.delayed(100).then(function() { + values.push(i++); + }); + } + }); + return promise.delayed(75).then(function() { + p.cancel(); + return p.catch(function() { + return promise.delayed(300); }); - } - }, 75).catch(function() { - assert.deepEqual( - [0, 1, 2], values, 'Should complete one loop of wait condition'); + }).then(function() { + assert.deepEqual([0], values); + }); }); - }); - describe('generators in promise callbacks', function() { - it('works with no initial value', function() { + it('executes generator within the control flow', function() { var promises = [ - promise.defer(), - promise.defer() + promise.defer(), + promise.defer() ]; var values = []; setTimeout(function() { + assert.deepEqual([], values); promises[0].fulfill(1); - }, 50); + }, 100); setTimeout(function() { + assert.deepEqual([1], values); promises[1].fulfill(2); - }, 100); + }, 200); - return promise.fulfilled().then(function*() { + return promise.controlFlow().execute(function* () { values.push(yield promises[0].promise); values.push(yield promises[1].promise); values.push('fin'); @@ -258,49 +186,123 @@ describe('promise.consume()', function() { }); }); - it('starts the generator with promised value', function() { - var promises = [ - promise.defer(), - promise.defer() - ]; - var values = []; + it('handles tasks scheduled in generator', function() { + var flow = promise.controlFlow(); + return flow.execute(function* () { + var x = yield flow.execute(function() { + return promise.delayed(10).then(function() { + return 1; + }); + }); - setTimeout(function() { - promises[0].fulfill(1); - }, 50); + var y = yield flow.execute(function() { + return 2; + }); - setTimeout(function() { - promises[1].fulfill(2); - }, 100); + return x + y; + }).then(function(value) { + assert.equal(3, value); + }); + }); - return promise.fulfilled(3).then(function*(value) { - var p1 = yield promises[0].promise; - var p2 = yield promises[1].promise; - values.push(value + p1); - values.push(value + p2); - values.push('fin'); - }).then(function() { - assert.deepEqual([4, 5, 'fin'], values); + it('blocks the control flow while processing generator', function() { + var values = []; + return promise.controlFlow().wait(function* () { + yield values.push(1); + values.push(yield promise.delayed(10).then(function() { + return 2; + })); + yield values.push(3); + return values.length === 6; + }, 250).then(function() { + assert.deepEqual([1, 2, 3, 1, 2, 3], values); }); }); - it('throws yielded rejections within the generator callback', function() { - var d = promise.defer(); - var e = Error('stub'); + it('ControlFlow.wait() will timeout on long generator', function() { + var values = []; + return promise.controlFlow().wait(function* () { + var i = 0; + while (i < 3) { + yield promise.delayed(100).then(function() { + values.push(i++); + }); + } + }, 75).catch(function() { + assert.deepEqual( + [0, 1, 2], values, 'Should complete one loop of wait condition'); + }); + }); - setTimeout(function() { - d.reject(e); - }, 50); + describe('generators in promise callbacks', function() { + it('works with no initial value', function() { + var promises = [ + promise.defer(), + promise.defer() + ]; + var values = []; - return promise.fulfilled().then(function*() { - var threw = false; - try { - yield d.promise; - } catch (ex) { - threw = true; - assert.equal(e, ex); - } - assert.ok(threw); + setTimeout(function() { + promises[0].fulfill(1); + }, 50); + + setTimeout(function() { + promises[1].fulfill(2); + }, 100); + + return promise.fulfilled().then(function*() { + values.push(yield promises[0].promise); + values.push(yield promises[1].promise); + values.push('fin'); + }).then(function() { + assert.deepEqual([1, 2, 'fin'], values); + }); + }); + + it('starts the generator with promised value', function() { + var promises = [ + promise.defer(), + promise.defer() + ]; + var values = []; + + setTimeout(function() { + promises[0].fulfill(1); + }, 50); + + setTimeout(function() { + promises[1].fulfill(2); + }, 100); + + return promise.fulfilled(3).then(function*(value) { + var p1 = yield promises[0].promise; + var p2 = yield promises[1].promise; + values.push(value + p1); + values.push(value + p2); + values.push('fin'); + }).then(function() { + assert.deepEqual([4, 5, 'fin'], values); + }); + }); + + it('throws yielded rejections within the generator callback', function() { + var d = promise.defer(); + var e = Error('stub'); + + setTimeout(function() { + d.reject(e); + }, 50); + + return promise.fulfilled().then(function*() { + var threw = false; + try { + yield d.promise; + } catch (ex) { + threw = true; + assert.equal(e, ex); + } + assert.ok(threw); + }); }); }); }); diff --git a/node_modules/selenium-webdriver/test/lib/promise_test.js b/node_modules/selenium-webdriver/test/lib/promise_test.js index 51554ecd9..96d2ccc22 100644 --- a/node_modules/selenium-webdriver/test/lib/promise_test.js +++ b/node_modules/selenium-webdriver/test/lib/promise_test.js @@ -21,6 +21,7 @@ const assert = require('assert'); const testutil = require('./testutil'); const promise = require('../../lib/promise'); +const {enablePromiseManager, promiseManagerSuite} = require('../../lib/test/promise'); // Aliases for readability. const NativePromise = Promise; @@ -36,1032 +37,1054 @@ describe('promise', function() { var app, uncaughtExceptions; beforeEach(function setUp() { - promise.LONG_STACK_TRACES = false; - uncaughtExceptions = []; + if (promise.USE_PROMISE_MANAGER) { + promise.LONG_STACK_TRACES = false; + uncaughtExceptions = []; - app = promise.controlFlow(); - app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, - (e) => uncaughtExceptions.push(e)); + app = promise.controlFlow(); + app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + (e) => uncaughtExceptions.push(e)); + } }); afterEach(function tearDown() { - app.reset(); - promise.setDefaultFlow(new promise.ControlFlow); - assert.deepEqual([], uncaughtExceptions, - 'Did not expect any uncaught exceptions'); - promise.LONG_STACK_TRACES = false; + if (promise.USE_PROMISE_MANAGER) { + app.reset(); + promise.setDefaultFlow(new promise.ControlFlow); + assert.deepEqual([], uncaughtExceptions, + 'Did not expect any uncaught exceptions'); + promise.LONG_STACK_TRACES = false; + } }); const assertIsPromise = (p) => assert.ok(promise.isPromise(p)); const assertNotPromise = (v) => assert.ok(!promise.isPromise(v)); + function defer() { + let d = {}; + let promise = new Promise((resolve, reject) => { + Object.assign(d, {resolve, reject}); + }); + d.promise = promise; + return d; + } + function createRejectedPromise(reason) { - var p = promise.rejected(reason); - p.catch(function() {}); + var p = Promise.reject(reason); + p.catch(function() {}); // Silence unhandled rejection handlers. return p; } - it('testCanDetectPromiseLikeObjects', function() { - assertIsPromise(new promise.Promise(function(fulfill) { - fulfill(); - })); - assertIsPromise(new promise.Deferred().promise); - assertIsPromise({then:function() {}}); - - assertNotPromise(new promise.Deferred()); - assertNotPromise(undefined); - assertNotPromise(null); - assertNotPromise(''); - assertNotPromise(true); - assertNotPromise(false); - assertNotPromise(1); - assertNotPromise({}); - assertNotPromise({then:1}); - assertNotPromise({then:true}); - assertNotPromise({then:''}); - }); - - describe('then', function() { - it('returnsOwnPromiseIfNoCallbacksWereGiven', function() { - var deferred = new promise.Deferred(); - assert.equal(deferred.promise, deferred.promise.then()); - assert.equal(deferred.promise, deferred.promise.catch()); - assert.equal(deferred.promise, promise.when(deferred.promise)); - }); - - it('stillConsideredUnHandledIfNoCallbacksWereGivenOnCallsToThen', function() { - promise.rejected(new StubError).then(); - var handler = callbackHelper(assertIsStubError); - - // so tearDown() doesn't throw - app.removeAllListeners(); - app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler); - return NativePromise.resolve() - // Macro yield so the uncaught exception has a chance to trigger. - .then(() => new NativePromise(resolve => setTimeout(resolve, 0))) - .then(() => handler.assertCalled()); - }); - }); + enablePromiseManager(() => { + it('testCanDetectPromiseLikeObjects', function() { + assertIsPromise(new promise.Promise(function(fulfill) { + fulfill(); + })); + assertIsPromise(new promise.Deferred().promise); + assertIsPromise(Promise.resolve(123)); + assertIsPromise({then:function() {}}); + + assertNotPromise(new promise.Deferred()); + assertNotPromise(undefined); + assertNotPromise(null); + assertNotPromise(''); + assertNotPromise(true); + assertNotPromise(false); + assertNotPromise(1); + assertNotPromise({}); + assertNotPromise({then:1}); + assertNotPromise({then:true}); + assertNotPromise({then:''}); + }); + + describe('then', function() { + it('returnsOwnPromiseIfNoCallbacksWereGiven', function() { + var deferred = new promise.Deferred(); + assert.equal(deferred.promise, deferred.promise.then()); + assert.equal(deferred.promise, deferred.promise.catch()); + assert.equal(deferred.promise, promise.when(deferred.promise)); + }); - describe('finally', function() { - it('nonFailingCallbackDoesNotSuppressOriginalError', function() { - var done = callbackHelper(assertIsStubError); - return promise.rejected(new StubError). - finally(function() {}). - catch(done). - finally(done.assertCalled); + it('stillConsideredUnHandledIfNoCallbacksWereGivenOnCallsToThen', function() { + promise.rejected(new StubError).then(); + var handler = callbackHelper(assertIsStubError); + + // so tearDown() doesn't throw + app.removeAllListeners(); + app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler); + return NativePromise.resolve() + // Macro yield so the uncaught exception has a chance to trigger. + .then(() => new NativePromise(resolve => setTimeout(resolve, 0))) + .then(() => handler.assertCalled()); + }); }); - it('failingCallbackSuppressesOriginalError', function() { - var done = callbackHelper(assertIsStubError); - return promise.rejected(new Error('original')). - finally(throwStubError). - catch(done). - finally(done.assertCalled); - }); + describe('finally', function() { + it('nonFailingCallbackDoesNotSuppressOriginalError', function() { + var done = callbackHelper(assertIsStubError); + return promise.rejected(new StubError). + finally(function() {}). + catch(done). + finally(done.assertCalled); + }); - it('callbackThrowsAfterFulfilledPromise', function() { - var done = callbackHelper(assertIsStubError); - return promise.fulfilled(). - finally(throwStubError). - catch(done). - finally(done.assertCalled); - }); + it('failingCallbackSuppressesOriginalError', function() { + var done = callbackHelper(assertIsStubError); + return promise.rejected(new Error('original')). + finally(throwStubError). + catch(done). + finally(done.assertCalled); + }); - it('callbackReturnsRejectedPromise', function() { - var done = callbackHelper(assertIsStubError); - return promise.fulfilled(). - finally(function() { - return promise.rejected(new StubError); - }). - catch(done). - finally(done.assertCalled); - }); - }); + it('callbackThrowsAfterFulfilledPromise', function() { + var done = callbackHelper(assertIsStubError); + return promise.fulfilled(). + finally(throwStubError). + catch(done). + finally(done.assertCalled); + }); - describe('cancel', function() { - it('passesTheCancellationReasonToReject', function() { - var d = new promise.Deferred(); - var res = d.promise.then(assert.fail, function(e) { - assert.ok(e instanceof promise.CancellationError); - assert.equal('because i said so', e.message); + it('callbackReturnsRejectedPromise', function() { + var done = callbackHelper(assertIsStubError); + return promise.fulfilled(). + finally(function() { + return promise.rejected(new StubError); + }). + catch(done). + finally(done.assertCalled); }); - d.promise.cancel('because i said so'); - return res; }); - describe('can cancel original promise from its child;', function() { - it('child created by then()', function() { + describe('cancel', function() { + it('passesTheCancellationReasonToReject', function() { var d = new promise.Deferred(); - var p = d.promise.then(assert.fail, function(e) { + var res = d.promise.then(assert.fail, function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('because i said so', e.message); - return 123; }); - - p.cancel('because i said so'); - return p.then(v => assert.equal(123, v)); + d.promise.cancel('because i said so'); + return res; }); - it('child linked by resolving with parent', function() { - let parent = promise.defer(); - let child = new promise.Promise(resolve => resolve(parent.promise)); - child.cancel('all done'); - - return parent.promise.then( - () => assert.fail('expected a cancellation'), - e => { - assert.ok(e instanceof promise.CancellationError); - assert.equal('all done', e.message); - }); - }); - - it('grand child through thenable chain', function() { - let p = new promise.Promise(function() {/* never resolve*/}); - - let noop = function() {}; - let gc = p.then(noop).then(noop).then(noop); - gc.cancel('stop!'); - - return p.then( - () => assert.fail('expected to be cancelled'), - (e) => { - assert.ok(e instanceof promise.CancellationError); - assert.equal('stop!', e.message); - }); - }); - - it('grand child through thenable chain started at resolve', function() { - function noop() {} - - let parent = promise.defer(); - let child = new promise.Promise(resolve => resolve(parent.promise)); - let grandChild = child.then(noop).then(noop).then(noop); - grandChild.cancel('all done'); - - return parent.promise.then( - () => assert.fail('expected a cancellation'), - e => { - assert.ok(e instanceof promise.CancellationError); - assert.equal('all done', e.message); - }); - }); - - it('"parent" is a Thenable', function() { - function noop() {} + describe('can cancel original promise from its child;', function() { + it('child created by then()', function() { + var d = new promise.Deferred(); + var p = d.promise.then(assert.fail, function(e) { + assert.ok(e instanceof promise.CancellationError); + assert.equal('because i said so', e.message); + return 123; + }); - class FakeThenable { - constructor(p) { - this.promise = p; - } + p.cancel('because i said so'); + return p.then(v => assert.equal(123, v)); + }); - cancel(reason) { - this.promise.cancel(reason); - } + it('child linked by resolving with parent', function() { + let parent = promise.defer(); + let child = new promise.Promise(resolve => resolve(parent.promise)); + child.cancel('all done'); - then(cb, eb) { - let result = this.promise.then(cb, eb); - return new FakeThenable(result); - } - } - promise.Thenable.addImplementation(FakeThenable); + return parent.promise.then( + () => assert.fail('expected a cancellation'), + e => { + assert.ok(e instanceof promise.CancellationError); + assert.equal('all done', e.message); + }); + }); - let root = new promise.Promise(noop); - let thenable = new FakeThenable(root); - assert.ok(promise.Thenable.isImplementation(thenable)); + it('grand child through thenable chain', function() { + let p = new promise.Promise(function() {/* never resolve*/}); - let child = new promise.Promise(resolve => resolve(thenable)); - assert.ok(child instanceof promise.Promise); - child.cancel('stop!'); + let noop = function() {}; + let gc = p.then(noop).then(noop).then(noop); + gc.cancel('stop!'); - function assertStopped(p) { return p.then( - () => assert.fail('not stopped!'), + () => assert.fail('expected to be cancelled'), (e) => { assert.ok(e instanceof promise.CancellationError); assert.equal('stop!', e.message); }); - } + }); - return assertStopped(child).then(() => assertStopped(root)); - }); - }); + it('grand child through thenable chain started at resolve', function() { + function noop() {} - it('canCancelATimeout', function() { - var p = promise.delayed(25) - .then(assert.fail, (e) => e instanceof promise.CancellationError); - setTimeout(() => p.cancel(), 20); - p.cancel(); - return p; - }); + let parent = promise.defer(); + let child = new promise.Promise(resolve => resolve(parent.promise)); + let grandChild = child.then(noop).then(noop).then(noop); + grandChild.cancel('all done'); - it('can cancel timeout from grandchild', function() { - }); - - it('cancelIsANoopOnceAPromiseHasBeenFulfilled', function() { - var p = promise.fulfilled(123); - p.cancel(); - return p.then((v) => assert.equal(123, v)); - }); + return parent.promise.then( + () => assert.fail('expected a cancellation'), + e => { + assert.ok(e instanceof promise.CancellationError); + assert.equal('all done', e.message); + }); + }); - it('cancelIsANoopOnceAPromiseHasBeenRejected', function() { - var p = promise.rejected(new StubError); - p.cancel(); + it('"parent" is a CancellableThenable', function() { + function noop() {} - var pair = callbackPair(null, assertIsStubError); - return p.then(assert.fail, assertIsStubError); - }); + class FakeThenable { + constructor(p) { + this.promise = p; + } - it('noopCancelTriggeredOnCallbackOfResolvedPromise', function() { - var d = promise.defer(); - var p = d.promise.then(); + cancel(reason) { + this.promise.cancel(reason); + } - d.fulfill(); - p.cancel(); // This should not throw. - return p; // This should not trigger a failure. - }); - }); + then(cb, eb) { + let result = this.promise.then(cb, eb); + return new FakeThenable(result); + } + } + promise.CancellableThenable.addImplementation(FakeThenable); + + let root = new promise.Promise(noop); + let thenable = new FakeThenable(root); + assert.ok(promise.Thenable.isImplementation(thenable)); + assert.ok(promise.CancellableThenable.isImplementation(thenable)); + + let child = new promise.Promise(resolve => resolve(thenable)); + assert.ok(child instanceof promise.Promise); + child.cancel('stop!'); + + function assertStopped(p) { + return p.then( + () => assert.fail('not stopped!'), + (e) => { + assert.ok(e instanceof promise.CancellationError); + assert.equal('stop!', e.message); + }); + } - describe('when', function() { - it('ReturnsAResolvedPromiseIfGivenANonPromiseValue', function() { - var ret = promise.when('abc'); - assertIsPromise(ret); - return ret.then((value) => assert.equal('abc', value)); - }); + return assertStopped(child).then(() => assertStopped(root)); + }); + }); - it('PassesRawErrorsToCallbacks', function() { - var error = new Error('boo!'); - return promise.when(error, function(value) { - assert.equal(error, value); + it('canCancelATimeout', function() { + var p = promise.delayed(25) + .then(assert.fail, (e) => e instanceof promise.CancellationError); + setTimeout(() => p.cancel(), 20); + p.cancel(); + return p; }); - }); - it('WaitsForValueToBeResolvedBeforeInvokingCallback', function() { - var d = new promise.Deferred(), callback; - let result = promise.when(d.promise, callback = callbackHelper(function(value) { - assert.equal('hi', value); - })); - callback.assertNotCalled(); - d.fulfill('hi'); - return result.then(callback.assertCalled); - }); - }); + it('can cancel timeout from grandchild', function() { + }); - it('firesUncaughtExceptionEventIfRejectionNeverHandled', function() { - promise.rejected(new StubError); - var handler = callbackHelper(assertIsStubError); + it('cancelIsANoopOnceAPromiseHasBeenFulfilled', function() { + var p = promise.fulfilled(123); + p.cancel(); + return p.then((v) => assert.equal(123, v)); + }); - // so tearDown() doesn't throw - app.removeAllListeners(); - app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler); + it('cancelIsANoopOnceAPromiseHasBeenRejected', function() { + var p = promise.rejected(new StubError); + p.cancel(); - return NativePromise.resolve() - // Macro yield so the uncaught exception has a chance to trigger. - .then(() => new NativePromise(resolve => setTimeout(resolve, 0))) - .then(handler.assertCalled); - }); + var pair = callbackPair(null, assertIsStubError); + return p.then(assert.fail, assertIsStubError); + }); - it('cannotResolveADeferredWithItself', function() { - var deferred = new promise.Deferred(); - assert.throws(() => deferred.fulfill(deferred)); - assert.throws(() => deferred.reject(deferred)); - }); + it('noopCancelTriggeredOnCallbackOfResolvedPromise', function() { + var d = promise.defer(); + var p = d.promise.then(); - describe('fullyResolved', function() { - it('primitives', function() { - function runTest(value) { - var callback, errback; - return promise.fullyResolved(value) - .then((resolved) => assert.equal(value, resolved)); - } - return runTest(true) - .then(() => runTest(function() {})) - .then(() => runTest(null)) - .then(() => runTest(123)) - .then(() => runTest('foo bar')) - .then(() => runTest(undefined)); + d.fulfill(); + p.cancel(); // This should not throw. + return p; // This should not trigger a failure. + }); }); + }); - it('arrayOfPrimitives', function() { - var fn = function() {}; - var array = [true, fn, null, 123, '', undefined, 1]; - return promise.fullyResolved(array).then(function(resolved) { - assert.equal(array, resolved); - assert.deepEqual([true, fn, null, 123, '', undefined, 1], - resolved); + promiseManagerSuite(() => { + describe('when', function() { + it('ReturnsAResolvedPromiseIfGivenANonPromiseValue', function() { + var ret = promise.when('abc'); + assertIsPromise(ret); + return ret.then((value) => assert.equal('abc', value)); }); - }); - it('nestedArrayOfPrimitives', function() { - var fn = function() {}; - var array = [true, [fn, null, 123], '', undefined]; - return promise.fullyResolved(array) - .then(function(resolved) { - assert.equal(array, resolved); - assert.deepEqual([true, [fn, null, 123], '', undefined], resolved); - assert.deepEqual([fn, null, 123], resolved[1]); - }); - }); + it('PassesRawErrorsToCallbacks', function() { + var error = new Error('boo!'); + return promise.when(error, function(value) { + assert.equal(error, value); + }); + }); - it('arrayWithPromisedPrimitive', function() { - return promise.fullyResolved([promise.fulfilled(123)]) - .then(function(resolved) { - assert.deepEqual([123], resolved); - }); + it('WaitsForValueToBeResolvedBeforeInvokingCallback', function() { + let d = defer(); + let callback; + let result = promise.when(d.promise, callback = callbackHelper(function(value) { + assert.equal('hi', value); + })); + callback.assertNotCalled(); + d.resolve('hi'); + return result.then(callback.assertCalled); + }); }); - it('promiseResolvesToPrimitive', function() { - return promise.fullyResolved(promise.fulfilled(123)) - .then((resolved) => assert.equal(123, resolved)); - }); + describe('fullyResolved', function() { + it('primitives', function() { + function runTest(value) { + var callback, errback; + return promise.fullyResolved(value) + .then((resolved) => assert.equal(value, resolved)); + } + return runTest(true) + .then(() => runTest(function() {})) + .then(() => runTest(null)) + .then(() => runTest(123)) + .then(() => runTest('foo bar')) + .then(() => runTest(undefined)); + }); - it('promiseResolvesToArray', function() { - var fn = function() {}; - var array = [true, [fn, null, 123], '', undefined]; - var aPromise = promise.fulfilled(array); + it('arrayOfPrimitives', function() { + var fn = function() {}; + var array = [true, fn, null, 123, '', undefined, 1]; + return promise.fullyResolved(array).then(function(resolved) { + assert.equal(array, resolved); + assert.deepEqual([true, fn, null, 123, '', undefined, 1], + resolved); + }); + }); - var result = promise.fullyResolved(aPromise); - return result.then(function(resolved) { - assert.equal(array, resolved); - assert.deepEqual([true, [fn, null, 123], '', undefined], - resolved); - assert.deepEqual([fn, null, 123], resolved[1]); + it('nestedArrayOfPrimitives', function() { + var fn = function() {}; + var array = [true, [fn, null, 123], '', undefined]; + return promise.fullyResolved(array) + .then(function(resolved) { + assert.equal(array, resolved); + assert.deepEqual([true, [fn, null, 123], '', undefined], resolved); + assert.deepEqual([fn, null, 123], resolved[1]); + }); }); - }); - it('promiseResolvesToArrayWithPromises', function() { - var nestedPromise = promise.fulfilled(123); - var aPromise = promise.fulfilled([true, nestedPromise]); - return promise.fullyResolved(aPromise) - .then(function(resolved) { - assert.deepEqual([true, 123], resolved); - }); - }); + it('arrayWithPromisedPrimitive', function() { + return promise.fullyResolved([Promise.resolve(123)]) + .then(function(resolved) { + assert.deepEqual([123], resolved); + }); + }); - it('rejectsIfArrayPromiseRejects', function() { - var nestedPromise = createRejectedPromise(new StubError); - var aPromise = promise.fulfilled([true, nestedPromise]); + it('promiseResolvesToPrimitive', function() { + return promise.fullyResolved(Promise.resolve(123)) + .then((resolved) => assert.equal(123, resolved)); + }); - var pair = callbackPair(null, assertIsStubError); - return promise.fullyResolved(aPromise) - .then(assert.fail, assertIsStubError); - }); + it('promiseResolvesToArray', function() { + var fn = function() {}; + var array = [true, [fn, null, 123], '', undefined]; + var aPromise = Promise.resolve(array); + + var result = promise.fullyResolved(aPromise); + return result.then(function(resolved) { + assert.equal(array, resolved); + assert.deepEqual([true, [fn, null, 123], '', undefined], + resolved); + assert.deepEqual([fn, null, 123], resolved[1]); + }); + }); - it('rejectsOnFirstArrayRejection', function() { - var e1 = new Error('foo'); - var e2 = new Error('bar'); - var aPromise = promise.fulfilled([ - createRejectedPromise(e1), - createRejectedPromise(e2) - ]); - - return promise.fullyResolved(aPromise) - .then(assert.fail, function(error) { - assert.strictEqual(e1, error); - }); - }); + it('promiseResolvesToArrayWithPromises', function() { + var nestedPromise = Promise.resolve(123); + var aPromise = Promise.resolve([true, nestedPromise]); + return promise.fullyResolved(aPromise) + .then(function(resolved) { + assert.deepEqual([true, 123], resolved); + }); + }); - it('rejectsIfNestedArrayPromiseRejects', function() { - var aPromise = promise.fulfilled([ - promise.fulfilled([ - createRejectedPromise(new StubError) - ]) - ]); + it('rejectsIfArrayPromiseRejects', function() { + var nestedPromise = createRejectedPromise(new StubError); + var aPromise = Promise.resolve([true, nestedPromise]); - return promise.fullyResolved(aPromise) - .then(assert.fail, assertIsStubError); - }); + var pair = callbackPair(null, assertIsStubError); + return promise.fullyResolved(aPromise) + .then(assert.fail, assertIsStubError); + }); - it('simpleHash', function() { - var hash = {'a': 123}; - return promise.fullyResolved(hash) - .then(function(resolved) { - assert.strictEqual(hash, resolved); - assert.deepEqual(hash, {'a': 123}); - }); - }); + it('rejectsOnFirstArrayRejection', function() { + var e1 = new Error('foo'); + var e2 = new Error('bar'); + var aPromise = Promise.resolve([ + createRejectedPromise(e1), + createRejectedPromise(e2) + ]); + + return promise.fullyResolved(aPromise) + .then(assert.fail, function(error) { + assert.strictEqual(e1, error); + }); + }); - it('nestedHash', function() { - var nestedHash = {'foo':'bar'}; - var hash = {'a': 123, 'b': nestedHash}; + it('rejectsIfNestedArrayPromiseRejects', function() { + var aPromise = Promise.resolve([ + Promise.resolve([ + createRejectedPromise(new StubError) + ]) + ]); - return promise.fullyResolved(hash) - .then(function(resolved) { - assert.strictEqual(hash, resolved); - assert.deepEqual({'a': 123, 'b': {'foo': 'bar'}}, resolved); - assert.strictEqual(nestedHash, resolved['b']); - }); - }); + return promise.fullyResolved(aPromise) + .then(assert.fail, assertIsStubError); + }); - it('promiseResolvesToSimpleHash', function() { - var hash = {'a': 123}; - var aPromise = promise.fulfilled(hash); + it('simpleHash', function() { + var hash = {'a': 123}; + return promise.fullyResolved(hash) + .then(function(resolved) { + assert.strictEqual(hash, resolved); + assert.deepEqual(hash, {'a': 123}); + }); + }); - return promise.fullyResolved(aPromise) - .then((resolved) => assert.strictEqual(hash, resolved)); - }); + it('nestedHash', function() { + var nestedHash = {'foo':'bar'}; + var hash = {'a': 123, 'b': nestedHash}; - it('promiseResolvesToNestedHash', function() { - var nestedHash = {'foo':'bar'}; - var hash = {'a': 123, 'b': nestedHash}; - var aPromise = promise.fulfilled(hash); + return promise.fullyResolved(hash) + .then(function(resolved) { + assert.strictEqual(hash, resolved); + assert.deepEqual({'a': 123, 'b': {'foo': 'bar'}}, resolved); + assert.strictEqual(nestedHash, resolved['b']); + }); + }); - return promise.fullyResolved(aPromise) - .then(function(resolved) { - assert.strictEqual(hash, resolved); - assert.strictEqual(nestedHash, resolved['b']); - assert.deepEqual(hash, {'a': 123, 'b': {'foo': 'bar'}}); - }); - }); + it('promiseResolvesToSimpleHash', function() { + var hash = {'a': 123}; + var aPromise = Promise.resolve(hash); - it('promiseResolvesToHashWithPromises', function() { - var aPromise = promise.fulfilled({ - 'a': promise.fulfilled(123) + return promise.fullyResolved(aPromise) + .then((resolved) => assert.strictEqual(hash, resolved)); }); - return promise.fullyResolved(aPromise) - .then(function(resolved) { - assert.deepEqual({'a': 123}, resolved); - }); - }); + it('promiseResolvesToNestedHash', function() { + var nestedHash = {'foo':'bar'}; + var hash = {'a': 123, 'b': nestedHash}; + var aPromise = Promise.resolve(hash); - it('rejectsIfHashPromiseRejects', function() { - var aPromise = promise.fulfilled({ - 'a': createRejectedPromise(new StubError) + return promise.fullyResolved(aPromise) + .then(function(resolved) { + assert.strictEqual(hash, resolved); + assert.strictEqual(nestedHash, resolved['b']); + assert.deepEqual(hash, {'a': 123, 'b': {'foo': 'bar'}}); + }); }); - return promise.fullyResolved(aPromise) - .then(assert.fail, assertIsStubError); - }); + it('promiseResolvesToHashWithPromises', function() { + var aPromise = Promise.resolve({ + 'a': Promise.resolve(123) + }); - it('rejectsIfNestedHashPromiseRejects', function() { - var aPromise = promise.fulfilled({ - 'a': {'b': createRejectedPromise(new StubError)} + return promise.fullyResolved(aPromise) + .then(function(resolved) { + assert.deepEqual({'a': 123}, resolved); + }); }); - return promise.fullyResolved(aPromise) - .then(assert.fail, assertIsStubError); - }); - - it('instantiatedObject', function() { - function Foo() { - this.bar = 'baz'; - } - var foo = new Foo; + it('rejectsIfHashPromiseRejects', function() { + var aPromise = Promise.resolve({ + 'a': createRejectedPromise(new StubError) + }); - return promise.fullyResolved(foo).then(function(resolvedFoo) { - assert.equal(foo, resolvedFoo); - assert.ok(resolvedFoo instanceof Foo); - assert.deepEqual(new Foo, resolvedFoo); + return promise.fullyResolved(aPromise) + .then(assert.fail, assertIsStubError); }); - }); - it('withEmptyArray', function() { - return promise.fullyResolved([]).then(function(resolved) { - assert.deepEqual([], resolved); - }); - }); + it('rejectsIfNestedHashPromiseRejects', function() { + var aPromise = Promise.resolve({ + 'a': {'b': createRejectedPromise(new StubError)} + }); - it('withEmptyHash', function() { - return promise.fullyResolved({}).then(function(resolved) { - assert.deepEqual({}, resolved); + return promise.fullyResolved(aPromise) + .then(assert.fail, assertIsStubError); }); - }); - it('arrayWithPromisedHash', function() { - var obj = {'foo': 'bar'}; - var array = [promise.fulfilled(obj)]; + it('instantiatedObject', function() { + function Foo() { + this.bar = 'baz'; + } + var foo = new Foo; - return promise.fullyResolved(array).then(function(resolved) { - assert.deepEqual(resolved, [obj]); + return promise.fullyResolved(foo).then(function(resolvedFoo) { + assert.equal(foo, resolvedFoo); + assert.ok(resolvedFoo instanceof Foo); + assert.deepEqual(new Foo, resolvedFoo); + }); }); - }); - }); - describe('checkedNodeCall', function() { - it('functionThrows', function() { - return promise.checkedNodeCall(throwStubError) - .then(assert.fail, assertIsStubError); - }); + it('withEmptyArray', function() { + return promise.fullyResolved([]).then(function(resolved) { + assert.deepEqual([], resolved); + }); + }); - it('functionReturnsAnError', function() { - return promise.checkedNodeCall(function(callback) { - callback(new StubError); - }).then(assert.fail, assertIsStubError); - }); + it('withEmptyHash', function() { + return promise.fullyResolved({}).then(function(resolved) { + assert.deepEqual({}, resolved); + }); + }); - it('functionReturnsSuccess', function() { - var success = 'success!'; - return promise.checkedNodeCall(function(callback) { - callback(null, success); - }).then((value) => assert.equal(success, value)); - }); + it('arrayWithPromisedHash', function() { + var obj = {'foo': 'bar'}; + var array = [Promise.resolve(obj)]; - it('functionReturnsAndThrows', function() { - var error = new Error('boom'); - var error2 = new Error('boom again'); - return promise.checkedNodeCall(function(callback) { - callback(error); - throw error2; - }).then(assert.fail, (e) => assert.equal(error, e)); - }); - - it('functionThrowsAndReturns', function() { - var error = new Error('boom'); - var error2 = new Error('boom again'); - return promise.checkedNodeCall(function(callback) { - setTimeout(() => callback(error), 10); - throw error2; - }).then(assert.fail, (e) => assert.equal(error2, e)); + return promise.fullyResolved(array).then(function(resolved) { + assert.deepEqual(resolved, [obj]); + }); + }); }); - }); - describe('all', function() { - it('(base case)', function() { - let defer = [promise.defer(), promise.defer()]; - var a = [ - 0, 1, - defer[0].promise, - defer[1].promise, - 4, 5, 6 - ]; - delete a[5]; + describe('checkedNodeCall', function() { + it('functionThrows', function() { + return promise.checkedNodeCall(throwStubError) + .then(assert.fail, assertIsStubError); + }); - var pair = callbackPair(function(value) { - assert.deepEqual([0, 1, 2, 3, 4, undefined, 6], value); + it('functionReturnsAnError', function() { + return promise.checkedNodeCall(function(callback) { + callback(new StubError); + }).then(assert.fail, assertIsStubError); }); - var result = promise.all(a).then(pair.callback, pair.errback); - pair.assertNeither(); + it('functionReturnsSuccess', function() { + var success = 'success!'; + return promise.checkedNodeCall(function(callback) { + callback(null, success); + }).then((value) => assert.equal(success, value)); + }); - defer[0].fulfill(2); - pair.assertNeither(); + it('functionReturnsAndThrows', function() { + var error = new Error('boom'); + var error2 = new Error('boom again'); + return promise.checkedNodeCall(function(callback) { + callback(error); + throw error2; + }).then(assert.fail, (e) => assert.equal(error, e)); + }); - defer[1].fulfill(3); - return result.then(() => pair.assertCallback()); + it('functionThrowsAndReturns', function() { + var error = new Error('boom'); + var error2 = new Error('boom again'); + return promise.checkedNodeCall(function(callback) { + setTimeout(() => callback(error), 10); + throw error2; + }).then(assert.fail, (e) => assert.equal(error2, e)); + }); }); - it('empty array', function() { - return promise.all([]).then((a) => assert.deepEqual([], a)); - }); + describe('all', function() { + it('(base case)', function() { + let deferredObjs = [defer(), defer()]; + var a = [ + 0, 1, + deferredObjs[0].promise, + deferredObjs[1].promise, + 4, 5, 6 + ]; + delete a[5]; - it('usesFirstRejection', function() { - let defer = [promise.defer(), promise.defer()]; - let a = [defer[0].promise, defer[1].promise]; + var pair = callbackPair(function(value) { + assert.deepEqual([0, 1, 2, 3, 4, undefined, 6], value); + }); - var result = promise.all(a).then(assert.fail, assertIsStubError); - defer[1].reject(new StubError); - setTimeout(() => defer[0].reject(Error('ignored')), 0); - return result; - }); - }); + var result = promise.all(a).then(pair.callback, pair.errback); + pair.assertNeither(); + + deferredObjs[0].resolve(2); + pair.assertNeither(); - describe('map', function() { - it('(base case)', function() { - var a = [1, 2, 3]; - return promise.map(a, function(value, index, a2) { - assert.equal(a, a2); - assert.equal('number', typeof index, 'not a number'); - return value + 1; - }).then(function(value) { - assert.deepEqual([2, 3, 4], value); + deferredObjs[1].resolve(3); + return result.then(() => pair.assertCallback()); }); - }); - it('omitsDeleted', function() { - var a = [0, 1, 2, 3, 4, 5, 6]; - delete a[1]; - delete a[3]; - delete a[4]; - delete a[6]; + it('empty array', function() { + return promise.all([]).then((a) => assert.deepEqual([], a)); + }); - var expected = [0, 1, 4, 9, 16, 25, 36]; - delete expected[1]; - delete expected[3]; - delete expected[4]; - delete expected[6]; + it('usesFirstRejection', function() { + let deferredObjs = [defer(), defer()]; + let a = [deferredObjs[0].promise, deferredObjs[1].promise]; - return promise.map(a, function(value) { - return value * value; - }).then(function(value) { - assert.deepEqual(expected, value); + var result = promise.all(a).then(assert.fail, assertIsStubError); + deferredObjs[1].reject(new StubError); + setTimeout(() => deferredObjs[0].reject(Error('ignored')), 0); + return result; }); }); - it('emptyArray', function() { - return promise.map([], function(value) { - return value + 1; - }).then(function(value) { - assert.deepEqual([], value); + describe('map', function() { + it('(base case)', function() { + var a = [1, 2, 3]; + return promise.map(a, function(value, index, a2) { + assert.equal(a, a2); + assert.equal('number', typeof index, 'not a number'); + return value + 1; + }).then(function(value) { + assert.deepEqual([2, 3, 4], value); + }); }); - }); - it('inputIsPromise', function() { - var input = promise.defer(); - var result = promise.map(input.promise, function(value) { - return value + 1; + it('omitsDeleted', function() { + var a = [0, 1, 2, 3, 4, 5, 6]; + delete a[1]; + delete a[3]; + delete a[4]; + delete a[6]; + + var expected = [0, 1, 4, 9, 16, 25, 36]; + delete expected[1]; + delete expected[3]; + delete expected[4]; + delete expected[6]; + + return promise.map(a, function(value) { + return value * value; + }).then(function(value) { + assert.deepEqual(expected, value); + }); }); - var pair = callbackPair(function(value) { - assert.deepEqual([2, 3, 4], value); + it('emptyArray', function() { + return promise.map([], function(value) { + return value + 1; + }).then(function(value) { + assert.deepEqual([], value); + }); }); - result = result.then(pair.callback, pair.errback); - setTimeout(function() { - pair.assertNeither(); - input.fulfill([1, 2, 3]); - }, 10); + it('inputIsPromise', function() { + var input = defer(); + var result = promise.map(input.promise, function(value) { + return value + 1; + }); - return result; - }); + var pair = callbackPair(function(value) { + assert.deepEqual([2, 3, 4], value); + }); + result = result.then(pair.callback, pair.errback); - it('waitsForFunctionResultToResolve', function() { - var innerResults = [ - promise.defer(), - promise.defer() - ]; + setTimeout(function() { + pair.assertNeither(); + input.resolve([1, 2, 3]); + }, 10); - var result = promise.map([1, 2], function(value, index) { - return innerResults[index].promise; + return result; }); - var pair = callbackPair(function(value) { - assert.deepEqual(['a', 'b'], value); - }); - result = result.then(pair.callback, pair.errback); + it('waitsForFunctionResultToResolve', function() { + var innerResults = [ + defer(), + defer() + ]; - return NativePromise.resolve() - .then(function() { - pair.assertNeither(); - innerResults[0].fulfill('a'); - }) - .then(function() { - pair.assertNeither(); - innerResults[1].fulfill('b'); - return result; - }) - .then(pair.assertCallback); - }); - - it('rejectsPromiseIfFunctionThrows', function() { - return promise.map([1], throwStubError) - .then(assert.fail, assertIsStubError); - }); + var result = promise.map([1, 2], function(value, index) { + return innerResults[index].promise; + }); - it('rejectsPromiseIfFunctionReturnsRejectedPromise', function() { - return promise.map([1], function() { - return promise.rejected(new StubError); - }).then(assert.fail, assertIsStubError); - }); + var pair = callbackPair(function(value) { + assert.deepEqual(['a', 'b'], value); + }); + result = result.then(pair.callback, pair.errback); + + return NativePromise.resolve() + .then(function() { + pair.assertNeither(); + innerResults[0].resolve('a'); + }) + .then(function() { + pair.assertNeither(); + innerResults[1].resolve('b'); + return result; + }) + .then(pair.assertCallback); + }); - it('stopsCallingFunctionIfPreviousIterationFailed', function() { - var count = 0; - return promise.map([1, 2, 3, 4], function() { - count++; - if (count == 3) { - throw new StubError; - } - }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.equal(3, count); + it('rejectsPromiseIfFunctionThrows', function() { + return promise.map([1], throwStubError) + .then(assert.fail, assertIsStubError); }); - }); - it('rejectsWithFirstRejectedPromise', function() { - var innerResult = [ - promise.fulfilled(), - createRejectedPromise(new StubError), - createRejectedPromise(Error('should be ignored')) - ]; - var count = 0; - return promise.map([1, 2, 3, 4], function(value, index) { - count += 1; - return innerResult[index]; - }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.equal(2, count); + it('rejectsPromiseIfFunctionReturnsRejectedPromise', function() { + return promise.map([1], function() { + return createRejectedPromise(new StubError); + }).then(assert.fail, assertIsStubError); }); - }); - it('preservesOrderWhenMapReturnsPromise', function() { - var deferreds = [ - promise.defer(), - promise.defer(), - promise.defer(), - promise.defer() - ]; - var result = promise.map(deferreds, function(value) { - return value.promise; + it('stopsCallingFunctionIfPreviousIterationFailed', function() { + var count = 0; + return promise.map([1, 2, 3, 4], function() { + count++; + if (count == 3) { + throw new StubError; + } + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.equal(3, count); + }); }); - var pair = callbackPair(function(value) { - assert.deepEqual([0, 1, 2, 3], value); + it('rejectsWithFirstRejectedPromise', function() { + var innerResult = [ + Promise.resolve(), + createRejectedPromise(new StubError), + createRejectedPromise(Error('should be ignored')) + ]; + var count = 0; + return promise.map([1, 2, 3, 4], function(value, index) { + count += 1; + return innerResult[index]; + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.equal(2, count); + }); }); - result = result.then(pair.callback, pair.errback); - return NativePromise.resolve() - .then(function() { - pair.assertNeither(); - for (let i = deferreds.length; i > 0; i -= 1) { - deferreds[i - 1].fulfill(i - 1); - } - return result; - }).then(pair.assertCallback); - }); - }); + it('preservesOrderWhenMapReturnsPromise', function() { + var deferreds = [ + defer(), + defer(), + defer(), + defer() + ]; + var result = promise.map(deferreds, function(value) { + return value.promise; + }); - describe('filter', function() { - it('basicFiltering', function() { - var a = [0, 1, 2, 3]; - return promise.filter(a, function(val, index, a2) { - assert.equal(a, a2); - assert.equal('number', typeof index, 'not a number'); - return val > 1; - }).then(function(val) { - assert.deepEqual([2, 3], val); + var pair = callbackPair(function(value) { + assert.deepEqual([0, 1, 2, 3], value); + }); + result = result.then(pair.callback, pair.errback); + + return Promise.resolve() + .then(function() { + pair.assertNeither(); + for (let i = deferreds.length; i > 0; i -= 1) { + deferreds[i - 1].resolve(i - 1); + } + return result; + }).then(pair.assertCallback); }); }); - it('omitsDeleted', function() { - var a = [0, 1, 2, 3, 4, 5, 6]; - delete a[3]; - delete a[4]; - - return promise.filter(a, function(value) { - return value > 1 && value < 6; - }).then(function(val) { - assert.deepEqual([2, 5], val); + describe('filter', function() { + it('basicFiltering', function() { + var a = [0, 1, 2, 3]; + return promise.filter(a, function(val, index, a2) { + assert.equal(a, a2); + assert.equal('number', typeof index, 'not a number'); + return val > 1; + }).then(function(val) { + assert.deepEqual([2, 3], val); + }); }); - }); - it('preservesInputs', function() { - var a = [0, 1, 2, 3]; + it('omitsDeleted', function() { + var a = [0, 1, 2, 3, 4, 5, 6]; + delete a[3]; + delete a[4]; - return promise.filter(a, function(value, i, a2) { - assert.equal(a, a2); - // Even if a function modifies the input array, the original value - // should be inserted into the new array. - a2[i] = a2[i] - 1; - return a2[i] >= 1; - }).then(function(val) { - assert.deepEqual([2, 3], val); + return promise.filter(a, function(value) { + return value > 1 && value < 6; + }).then(function(val) { + assert.deepEqual([2, 5], val); + }); }); - }); - it('inputIsPromise', function() { - var input = promise.defer(); - var result = promise.filter(input.promise, function(value) { - return value > 1 && value < 3; + it('preservesInputs', function() { + var a = [0, 1, 2, 3]; + + return promise.filter(a, function(value, i, a2) { + assert.equal(a, a2); + // Even if a function modifies the input array, the original value + // should be inserted into the new array. + a2[i] = a2[i] - 1; + return a2[i] >= 1; + }).then(function(val) { + assert.deepEqual([2, 3], val); + }); }); - var pair = callbackPair(function(value) { - assert.deepEqual([2], value); + it('inputIsPromise', function() { + var input = defer(); + var result = promise.filter(input.promise, function(value) { + return value > 1 && value < 3; + }); + + var pair = callbackPair(function(value) { + assert.deepEqual([2], value); + }); + result = result.then(pair.callback, pair.errback); + return NativePromise.resolve() + .then(function() { + pair.assertNeither(); + input.resolve([1, 2, 3]); + return result; + }) + .then(pair.assertCallback); }); - result = result.then(pair.callback, pair.errback); - return NativePromise.resolve() - .then(function() { - pair.assertNeither(); - input.fulfill([1, 2, 3]); - return result; - }) - .then(pair.assertCallback); - }); - it('waitsForFunctionResultToResolve', function() { - var innerResults = [ - promise.defer(), - promise.defer() - ]; + it('waitsForFunctionResultToResolve', function() { + var innerResults = [ + defer(), + defer() + ]; - var result = promise.filter([1, 2], function(value, index) { - return innerResults[index].promise; - }); + var result = promise.filter([1, 2], function(value, index) { + return innerResults[index].promise; + }); - var pair = callbackPair(function(value) { - assert.deepEqual([2], value); + var pair = callbackPair(function(value) { + assert.deepEqual([2], value); + }); + result = result.then(pair.callback, pair.errback); + return NativePromise.resolve() + .then(function() { + pair.assertNeither(); + innerResults[0].resolve(false); + }) + .then(function() { + pair.assertNeither(); + innerResults[1].resolve(true); + return result; + }) + .then(pair.assertCallback); }); - result = result.then(pair.callback, pair.errback); - return NativePromise.resolve() - .then(function() { - pair.assertNeither(); - innerResults[0].fulfill(false); - }) - .then(function() { - pair.assertNeither(); - innerResults[1].fulfill(true); - return result; - }) - .then(pair.assertCallback); - }); - it('rejectsPromiseIfFunctionReturnsRejectedPromise', function() { - return promise.filter([1], function() { - return promise.rejected(new StubError); - }).then(assert.fail, assertIsStubError); - }); + it('rejectsPromiseIfFunctionReturnsRejectedPromise', function() { + return promise.filter([1], function() { + return createRejectedPromise(new StubError); + }).then(assert.fail, assertIsStubError); + }); - it('stopsCallingFunctionIfPreviousIterationFailed', function() { - var count = 0; - return promise.filter([1, 2, 3, 4], function() { - count++; - if (count == 3) { - throw new StubError; - } - }).then(assert.fail, function(e) { - assertIsStubError(e); - assert.equal(3, count); + it('stopsCallingFunctionIfPreviousIterationFailed', function() { + var count = 0; + return promise.filter([1, 2, 3, 4], function() { + count++; + if (count == 3) { + throw new StubError; + } + }).then(assert.fail, function(e) { + assertIsStubError(e); + assert.equal(3, count); + }); }); - }); - it('rejectsWithFirstRejectedPromise', function() { - var innerResult = [ - promise.fulfilled(), - createRejectedPromise(new StubError), - createRejectedPromise(Error('should be ignored')) - ]; - - return promise.filter([1, 2, 3, 4], function(value, index) { - assert.ok(index < innerResult.length); - return innerResult[index]; - }).then(assert.fail, assertIsStubError); - }); + it('rejectsWithFirstRejectedPromise', function() { + var innerResult = [ + Promise.resolve(), + createRejectedPromise(new StubError), + createRejectedPromise(Error('should be ignored')) + ]; - it('preservesOrderWhenFilterReturnsPromise', function() { - var deferreds = [ - promise.defer(), - promise.defer(), - promise.defer(), - promise.defer() - ]; - var result = promise.filter([0, 1, 2, 3], function(value, index) { - return deferreds[index].promise; + return promise.filter([1, 2, 3, 4], function(value, index) { + assert.ok(index < innerResult.length); + return innerResult[index]; + }).then(assert.fail, assertIsStubError); }); - var pair = callbackPair(function(value) { - assert.deepEqual([1, 2], value); - }); - result = result.then(pair.callback, pair.errback); + it('preservesOrderWhenFilterReturnsPromise', function() { + var deferreds = [ + defer(), + defer(), + defer(), + defer() + ]; + var result = promise.filter([0, 1, 2, 3], function(value, index) { + return deferreds[index].promise; + }); - return NativePromise.resolve() - .then(function() { - pair.assertNeither(); - for (let i = deferreds.length - 1; i >= 0; i -= 1) { - deferreds[i].fulfill(i > 0 && i < 3); - } - return result; - }).then(pair.assertCallback); + var pair = callbackPair(function(value) { + assert.deepEqual([1, 2], value); + }); + result = result.then(pair.callback, pair.errback); + + return NativePromise.resolve() + .then(function() { + pair.assertNeither(); + for (let i = deferreds.length - 1; i >= 0; i -= 1) { + deferreds[i].resolve(i > 0 && i < 3); + } + return result; + }).then(pair.assertCallback); + }); }); }); - it('testAddThenableImplementation', function() { - function tmp() {} - assert.ok(!promise.Thenable.isImplementation(new tmp())); - promise.Thenable.addImplementation(tmp); - assert.ok(promise.Thenable.isImplementation(new tmp())); - - class tmpClass {} - assert.ok(!promise.Thenable.isImplementation(new tmpClass())); - promise.Thenable.addImplementation(tmpClass); - assert.ok(promise.Thenable.isImplementation(new tmpClass())); - }); - - describe('testLongStackTraces', function() { - beforeEach(() => promise.LONG_STACK_TRACES = false); - afterEach(() => promise.LONG_STACK_TRACES = false); + enablePromiseManager(() => { + it('firesUncaughtExceptionEventIfRejectionNeverHandled', function() { + promise.rejected(new StubError); + var handler = callbackHelper(assertIsStubError); - it('doesNotAppendStackIfFeatureDisabled', function() { - promise.LONG_STACK_TRACES = false; + // so tearDown() doesn't throw + app.removeAllListeners(); + app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler); - var error = Error('hello'); - var originalStack = error.stack; - return promise.rejected(error). - then(fail). - then(fail). - then(fail). - then(fail, function(e) { - assert.equal(error, e); - assert.equal(originalStack, e.stack); - }); + return NativePromise.resolve() + // Macro yield so the uncaught exception has a chance to trigger. + .then(() => new NativePromise(resolve => setTimeout(resolve, 0))) + .then(handler.assertCalled); }); - function getStackMessages(error) { - return error.stack.split(/\n/).filter(function(line) { - return /^From: /.test(line); + it('cannotResolveADeferredWithItself', function() { + var deferred = new promise.Deferred(); + assert.throws(() => deferred.fulfill(deferred)); + assert.throws(() => deferred.reject(deferred)); + }); + + describe('testLongStackTraces', function() { + beforeEach(() => promise.LONG_STACK_TRACES = false); + afterEach(() => promise.LONG_STACK_TRACES = false); + + it('doesNotAppendStackIfFeatureDisabled', function() { + promise.LONG_STACK_TRACES = false; + + var error = Error('hello'); + var originalStack = error.stack; + return promise.rejected(error). + then(fail). + then(fail). + then(fail). + then(fail, function(e) { + assert.equal(error, e); + assert.equal(originalStack, e.stack); + }); }); - } - it('appendsInitialPromiseCreation_resolverThrows', function() { - promise.LONG_STACK_TRACES = true; + function getStackMessages(error) { + return error.stack.split(/\n/).filter(function(line) { + return /^From: /.test(line); + }); + } - var error = Error('hello'); - var originalStack = '(placeholder; will be overwritten later)'; + it('appendsInitialPromiseCreation_resolverThrows', function() { + promise.LONG_STACK_TRACES = true; - return new promise.Promise(function() { - try { - throw error; - } catch (e) { - originalStack = e.stack; - throw e; - } - }).then(fail, function(e) { - assert.strictEqual(error, e); - if (typeof originalStack !== 'string') { - return; - } - assert.notEqual(originalStack, e.stack); - assert.equal(e.stack.indexOf(originalStack), 0, - 'should start with original stack'); - assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e)); + var error = Error('hello'); + var originalStack = '(placeholder; will be overwritten later)'; + + return new promise.Promise(function() { + try { + throw error; + } catch (e) { + originalStack = e.stack; + throw e; + } + }).then(fail, function(e) { + assert.strictEqual(error, e); + if (typeof originalStack !== 'string') { + return; + } + assert.notEqual(originalStack, e.stack); + assert.equal(e.stack.indexOf(originalStack), 0, + 'should start with original stack'); + assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e)); + }); }); - }); - it('appendsInitialPromiseCreation_rejectCalled', function() { - promise.LONG_STACK_TRACES = true; + it('appendsInitialPromiseCreation_rejectCalled', function() { + promise.LONG_STACK_TRACES = true; - var error = Error('hello'); - var originalStack = error.stack; + var error = Error('hello'); + var originalStack = error.stack; - return new promise.Promise(function(_, reject) { - reject(error); - }).then(fail, function(e) { - assert.equal(error, e); - if (typeof originalStack !== 'string') { - return; - } - assert.notEqual(originalStack, e.stack); - assert.equal(e.stack.indexOf(originalStack), 0, - 'should start with original stack'); - assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e)); + return new promise.Promise(function(_, reject) { + reject(error); + }).then(fail, function(e) { + assert.equal(error, e); + if (typeof originalStack !== 'string') { + return; + } + assert.notEqual(originalStack, e.stack); + assert.equal(e.stack.indexOf(originalStack), 0, + 'should start with original stack'); + assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e)); + }); }); - }); - it('appendsEachStepToRejectionError', function() { - promise.LONG_STACK_TRACES = true; + it('appendsEachStepToRejectionError', function() { + promise.LONG_STACK_TRACES = true; - var error = Error('hello'); - var originalStack = '(placeholder; will be overwritten later)'; + var error = Error('hello'); + var originalStack = '(placeholder; will be overwritten later)'; - return new promise.Promise(function() { - try { - throw error; - } catch (e) { - originalStack = e.stack; - throw e; - } - }). - then(fail). - catch(function(e) { throw e; }). - then(fail). - catch(function(e) { throw e; }). - then(fail, function(e) { - assert.equal(error, e); - if (typeof originalStack !== 'string') { - return; - } - assert.notEqual(originalStack, e.stack); - assert.equal(e.stack.indexOf(originalStack), 0, - 'should start with original stack'); - assert.deepEqual([ - 'From: ManagedPromise: new', - 'From: Promise: then', - 'From: Promise: catch', - 'From: Promise: then', - 'From: Promise: catch', - ], getStackMessages(e)); + return new promise.Promise(function() { + try { + throw error; + } catch (e) { + originalStack = e.stack; + throw e; + } + }). + then(fail). + catch(function(e) { throw e; }). + then(fail). + catch(function(e) { throw e; }). + then(fail, function(e) { + assert.equal(error, e); + if (typeof originalStack !== 'string') { + return; + } + assert.notEqual(originalStack, e.stack); + assert.equal(e.stack.indexOf(originalStack), 0, + 'should start with original stack'); + assert.deepEqual([ + 'From: ManagedPromise: new', + 'From: Promise: then', + 'From: Promise: catch', + 'From: Promise: then', + 'From: Promise: catch', + ], getStackMessages(e)); + }); }); - }); - it('errorOccursInCallbackChain', function() { - promise.LONG_STACK_TRACES = true; - - var error = Error('hello'); - var originalStack = '(placeholder; will be overwritten later)'; - - return promise.fulfilled(). - then(function() {}). - then(function() {}). - then(function() { - try { - throw error; - } catch (e) { - originalStack = e.stack; - throw e; - } - }). - catch(function(e) { throw e; }). - then(fail, function(e) { - assert.equal(error, e); - if (typeof originalStack !== 'string') { - return; - } - assert.notEqual(originalStack, e.stack); - assert.equal(e.stack.indexOf(originalStack), 0, - 'should start with original stack'); - assert.deepEqual([ - 'From: Promise: then', - 'From: Promise: catch', - ], getStackMessages(e)); - }); + it('errorOccursInCallbackChain', function() { + promise.LONG_STACK_TRACES = true; + + var error = Error('hello'); + var originalStack = '(placeholder; will be overwritten later)'; + + return promise.fulfilled(). + then(function() {}). + then(function() {}). + then(function() { + try { + throw error; + } catch (e) { + originalStack = e.stack; + throw e; + } + }). + catch(function(e) { throw e; }). + then(fail, function(e) { + assert.equal(error, e); + if (typeof originalStack !== 'string') { + return; + } + assert.notEqual(originalStack, e.stack); + assert.equal(e.stack.indexOf(originalStack), 0, + 'should start with original stack'); + assert.deepEqual([ + 'From: Promise: then', + 'From: Promise: catch', + ], getStackMessages(e)); + }); + }); }); }); + + it('testAddThenableImplementation', function() { + function tmp() {} + assert.ok(!promise.Thenable.isImplementation(new tmp())); + promise.Thenable.addImplementation(tmp); + assert.ok(promise.Thenable.isImplementation(new tmp())); + + class tmpClass {} + assert.ok(!promise.Thenable.isImplementation(new tmpClass())); + promise.Thenable.addImplementation(tmpClass); + assert.ok(promise.Thenable.isImplementation(new tmpClass())); + }); }); diff --git a/node_modules/selenium-webdriver/test/lib/until_test.js b/node_modules/selenium-webdriver/test/lib/until_test.js index d3e81ec18..31b2b32ad 100644 --- a/node_modules/selenium-webdriver/test/lib/until_test.js +++ b/node_modules/selenium-webdriver/test/lib/until_test.js @@ -84,7 +84,7 @@ describe('until', function() { it('byWebElementPromise', function() { executor.on(CommandName.SWITCH_TO_FRAME, () => true); var el = new webdriver.WebElementPromise(driver, - promise.fulfilled(new webdriver.WebElement(driver, {ELEMENT: 1234}))); + Promise.resolve(new webdriver.WebElement(driver, {ELEMENT: 1234}))); return driver.wait(until.ableToSwitchToFrame(el), 100); }); diff --git a/node_modules/selenium-webdriver/test/lib/webdriver_test.js b/node_modules/selenium-webdriver/test/lib/webdriver_test.js index d2d87ca3e..0a124530e 100644 --- a/node_modules/selenium-webdriver/test/lib/webdriver_test.js +++ b/node_modules/selenium-webdriver/test/lib/webdriver_test.js @@ -29,6 +29,8 @@ const Key = require('../../lib/input').Key; const logging = require('../../lib/logging'); const Session = require('../../lib/session').Session; const promise = require('../../lib/promise'); +const {enablePromiseManager, promiseManagerSuite} = require('../../lib/test/promise'); +const until = require('../../lib/until'); const Alert = require('../../lib/webdriver').Alert; const AlertPromise = require('../../lib/webdriver').AlertPromise; const UnhandledAlertError = require('../../lib/webdriver').UnhandledAlertError; @@ -73,6 +75,9 @@ describe('WebDriver', function() { }); afterEach(function tearDown() { + if (!promise.USE_PROMISE_MANAGER) { + return; + } return waitForIdle(flow).then(function() { assert.deepEqual([], uncaughtExceptions); flow.reset(); @@ -83,7 +88,19 @@ describe('WebDriver', function() { uncaughtExceptions.push(e); } + function defer() { + let d = {}; + let promise = new Promise((resolve, reject) => { + Object.assign(d, {resolve, reject}); + }); + d.promise = promise; + return d; + } + function waitForIdle(opt_flow) { + if (!promise.USE_PROMISE_MANAGER) { + return Promise.resolve(); + } var theFlow = opt_flow || flow; return new Promise(function(fulfill, reject) { if (theFlow.isIdle()) { @@ -300,19 +317,21 @@ describe('WebDriver', function() { return waitForIdle(driver.controlFlow()); }); - it('canAttachInCustomFlow', function() { - let executor = new FakeExecutor(). - expect(CName.DESCRIBE_SESSION). - withParameters({'sessionId': SESSION_ID}). - andReturnSuccess({}). - end(); + enablePromiseManager(() => { + it('canAttachInCustomFlow', function() { + let executor = new FakeExecutor(). + expect(CName.DESCRIBE_SESSION). + withParameters({'sessionId': SESSION_ID}). + andReturnSuccess({}). + end(); - var otherFlow = new promise.ControlFlow(); - var driver = WebDriver.attachToSession(executor, SESSION_ID, otherFlow); - assert.equal(otherFlow, driver.controlFlow()); - assert.notEqual(otherFlow, promise.controlFlow()); + var otherFlow = new promise.ControlFlow(); + var driver = WebDriver.attachToSession(executor, SESSION_ID, otherFlow); + assert.equal(otherFlow, driver.controlFlow()); + assert.notEqual(otherFlow, promise.controlFlow()); - return waitForIdle(otherFlow); + return waitForIdle(otherFlow); + }); }); }); @@ -345,7 +364,7 @@ describe('WebDriver', function() { return driver.getSession().then(v => assert.strictEqual(v, aSession)); }); - it('handles desired and requried capabilities', function() { + it('handles desired and required capabilities', function() { let aSession = new Session(SESSION_ID, {'browserName': 'firefox'}); let executor = new FakeExecutor(). expect(CName.NEW_SESSION). @@ -374,6 +393,23 @@ describe('WebDriver', function() { return driver.getSession().then(fail, assertIsStubError); }); + it('invokes quit callback if it fails to create a session', function() { + let called = false; + let executor = new FakeExecutor() + .expect(CName.NEW_SESSION) + .withParameters({'desiredCapabilities': {'browserName': 'firefox'}}) + .andReturnError(new StubError()) + .end(); + + var driver = + WebDriver.createSession(executor, {'browserName': 'firefox'}, + null, null, () => called = true); + return driver.getSession().then(fail, err => { + assert.ok(called); + assertIsStubError(err); + }); + }); + it('usesActiveFlowByDefault', function() { let executor = new FakeExecutor(). expect(CName.NEW_SESSION). @@ -387,26 +423,60 @@ describe('WebDriver', function() { return waitForIdle(driver.controlFlow()); }); - it('canCreateInCustomFlow', function() { - let executor = new FakeExecutor(). - expect(CName.NEW_SESSION). - withParameters({'desiredCapabilities': {}}). - andReturnSuccess({}). - end(); + enablePromiseManager(() => { + it('canCreateInCustomFlow', function() { + let executor = new FakeExecutor(). + expect(CName.NEW_SESSION). + withParameters({'desiredCapabilities': {}}). + andReturnSuccess({}). + end(); - var otherFlow = new promise.ControlFlow(); - var driver = WebDriver.createSession(executor, {}, otherFlow); - assert.equal(otherFlow, driver.controlFlow()); - assert.notEqual(otherFlow, promise.controlFlow()); + var otherFlow = new promise.ControlFlow(); + var driver = WebDriver.createSession(executor, {}, otherFlow); + assert.equal(otherFlow, driver.controlFlow()); + assert.notEqual(otherFlow, promise.controlFlow()); + + return waitForIdle(otherFlow); + }); - return waitForIdle(otherFlow); + describe('creation failures bubble up in control flow', function() { + function runTest(...args) { + let executor = new FakeExecutor() + .expect(CName.NEW_SESSION) + .withParameters({'desiredCapabilities': {'browserName': 'firefox'}}) + .andReturnError(new StubError()) + .end(); + + WebDriver.createSession( + executor, {'browserName': 'firefox'}, ...args); + return waitForAbort().then(assertIsStubError); + } + + it('no onQuit callback', () => runTest()); + it('has onQuit callback', () => runTest(null, null, function() {})); + + it('onQuit callback failure suppress creation failure', function() { + let e = new Error('hi!'); + let executor = new FakeExecutor() + .expect(CName.NEW_SESSION) + .withParameters({'desiredCapabilities': {'browserName': 'firefox'}}) + .andReturnError(new StubError()) + .end(); + + WebDriver.createSession( + executor, {'browserName': 'firefox'}, null, null, + () => {throw e}); + return waitForAbort().then(err => assert.strictEqual(err, e)); + }); + }); }); }); it('testDoesNotExecuteCommandIfSessionDoesNotResolve', function() { var session = Promise.reject(new StubError); - new FakeExecutor().createDriver(session).getTitle(); - return waitForAbort().then(assertIsStubError); + return new FakeExecutor().createDriver(session) + .getTitle() + .then(_ => assert.fail('should have failed'), assertIsStubError); }); it('testCommandReturnValuesArePassedToFirstCallback', function() { @@ -415,9 +485,8 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - return driver.getTitle().then(function(title) { - assert.equal('Google Search', title); - }); + return driver.getTitle() + .then(title => assert.equal('Google Search', title)); }); it('testStopsCommandExecutionWhenAnErrorOccurs', function() { @@ -431,11 +500,11 @@ describe('WebDriver', function() { andReturnError(e). end(); - var driver = executor.createDriver(); - driver.switchTo().window('foo'); - driver.getTitle(); // mock should blow if this gets executed - - return waitForAbort().then(v => assert.strictEqual(v, e)); + let driver = executor.createDriver(); + return driver.switchTo().window('foo') + .then( + _ => driver.getTitle(), // mock should blow if this gets executed + v => assert.strictEqual(v, e)); }); it('testCanSuppressCommandFailures', function() { @@ -469,8 +538,9 @@ describe('WebDriver', function() { andReturnError(e). end(); - executor.createDriver().switchTo().window('foo'); - return waitForAbort().then(v => assert.strictEqual(v, e)); + return executor.createDriver() + .switchTo().window('foo') + .then(_ => assert.fail(), v => assert.strictEqual(v, e)); }); it('testErrbacksThatReturnErrorsStillSwitchToCallbackChain', function() { @@ -486,7 +556,7 @@ describe('WebDriver', function() { var driver = executor.createDriver(); return driver.switchTo().window('foo'). catch(function() { return new StubError; }); - then(assertIsStubError); + then(assertIsStubError, () => assert.fail()); }); it('testErrbacksThrownCanOverrideOriginalError', function() { @@ -499,27 +569,9 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - driver.switchTo().window('foo').catch(throwStubError); - - return waitForAbort().then(assertIsStubError); - }); - - it('testCannotScheduleCommandsIfTheSessionIdHasBeenDeleted', function() { - var driver = new FakeExecutor().createDriver(); - delete driver.session_; - assert.throws(() => driver.get('http://www.google.com')); - }); - - it('testDeletesSessionIdAfterQuitting', function() { - var driver; - let executor = new FakeExecutor(). - expect(CName.QUIT). - end(); - - driver = executor.createDriver(); - return driver.quit().then(function() { - assert.equal(void 0, driver.session_); - }); + return driver.switchTo().window('foo') + .catch(throwStubError) + .then(assert.fail, assertIsStubError); }); it('testReportsErrorWhenExecutingCommandsAfterExecutingAQuit', function() { @@ -527,14 +579,15 @@ describe('WebDriver', function() { expect(CName.QUIT). end(); - var driver = executor.createDriver(); - driver.quit(); - driver.get('http://www.google.com'); - return waitForAbort(). - then(expectedError( - error.NoSuchSessionError, - 'This driver instance does not have a valid session ID ' + - '(did you call WebDriver.quit()?) and may no longer be used.')); + let verifyError = expectedError( + error.NoSuchSessionError, + 'This driver instance does not have a valid session ID ' + + '(did you call WebDriver.quit()?) and may no longer be used.'); + + let driver = executor.createDriver(); + return driver.quit() + .then(_ => driver.get('http://www.google.com')) + .then(assert.fail, verifyError); }); it('testCallbackCommandsExecuteBeforeNextCommand', function() { @@ -556,114 +609,29 @@ describe('WebDriver', function() { return waitForIdle(); }); - it('testEachCallbackFrameRunsToCompletionBeforeTheNext', function() { - let executor = new FakeExecutor(). - expect(CName.GET_TITLE). - expect(CName.GET_CURRENT_URL). - expect(CName.GET_CURRENT_WINDOW_HANDLE). - expect(CName.CLOSE). - expect(CName.QUIT). - end(); - - var driver = executor.createDriver(); - driver.getTitle(). - // Everything in this callback... - then(function() { - driver.getCurrentUrl(); - driver.getWindowHandle(); - }). - // ...should execute before everything in this callback. - then(function() { - driver.close(); - }); - // This should execute after everything above - driver.quit(); - - return waitForIdle(); - }); - - describe('nestedCommandFailures', function() { - it('bubbleUpToGlobalHandlerIfUnsuppressed', function() { - let e = new error.NoSuchWindowError('window not found'); - let executor = new FakeExecutor(). - expect(CName.GET_TITLE). - expect(CName.SWITCH_TO_WINDOW, { - 'name': 'foo', - 'handle': 'foo' - }). - andReturnError(e). - end(); - - var driver = executor.createDriver(); - driver.getTitle().then(function() { - driver.switchTo().window('foo'); - }); - - return waitForAbort().then(v => assert.strictEqual(v, e)); - }); - - it('canBeSuppressWhenTheyOccur', function() { + enablePromiseManager(() => { + it('testEachCallbackFrameRunsToCompletionBeforeTheNext', function() { let executor = new FakeExecutor(). expect(CName.GET_TITLE). - expect(CName.SWITCH_TO_WINDOW, { - 'name': 'foo', - 'handle': 'foo' - }). - andReturnError(new error.NoSuchWindowError('window not found')). + expect(CName.GET_CURRENT_URL). + expect(CName.GET_CURRENT_WINDOW_HANDLE). expect(CName.CLOSE). - end(); - - var driver = executor.createDriver(); - driver.getTitle().then(function() { - driver.switchTo().window('foo').catch(function() {}); - }); - driver.close(); - - return waitForIdle(); - }); - - it('bubbleUpThroughTheFrameStack', function() { - let e = new error.NoSuchWindowError('window not found'); - let executor = new FakeExecutor(). - expect(CName.GET_TITLE). - expect(CName.SWITCH_TO_WINDOW, { - 'name': 'foo', - 'handle': 'foo' - }). - andReturnError(e). + expect(CName.QUIT). end(); var driver = executor.createDriver(); driver.getTitle(). + // Everything in this callback... then(function() { - return driver.switchTo().window('foo'); - }). - catch(v => assert.strictEqual(v, e)); - - return waitForIdle(); - }); - - it('canBeCaughtAndSuppressed', function() { - let executor = new FakeExecutor(). - expect(CName.GET_TITLE). - expect(CName.GET_CURRENT_URL). - expect(CName.SWITCH_TO_WINDOW, { - 'name': 'foo', - 'handle': 'foo' + driver.getCurrentUrl(); + driver.getWindowHandle(); }). - andReturnError(new StubError()). - expect(CName.CLOSE). - end(); - - var driver = executor.createDriver(); - driver.getTitle().then(function() { - driver.getCurrentUrl(). - then(function() { - return driver.switchTo().window('foo'); - }). - catch(function() {}); - driver.close(); - }); + // ...should execute before everything in this callback. + then(function() { + driver.close(); + }); + // This should execute after everything above + driver.quit(); return waitForIdle(); }); @@ -678,18 +646,16 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - driver.getTitle(). + return driver.getTitle(). then(function() { return driver.getCurrentUrl(); }). then(function(value) { assert.equal('http://www.google.com', value); }); - return waitForIdle(); }); it('fromAnErrbackSuppressesTheError', function() { - var count = 0; let executor = new FakeExecutor(). expect(CName.SWITCH_TO_WINDOW, { 'name': 'foo', @@ -701,19 +667,12 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - driver.switchTo().window('foo'). + return driver.switchTo().window('foo'). catch(function(e) { assertIsStubError(e); - count += 1; return driver.getCurrentUrl(); }). - then(function(url) { - count += 1; - assert.equal('http://www.google.com', url); - }); - return waitForIdle().then(function() { - assert.equal(2, count); - }); + then(url => assert.equal('http://www.google.com', url)); }); }); @@ -725,23 +684,25 @@ describe('WebDriver', function() { }); }); - it('executionOrderWithCustomFunctions', function() { - var msg = []; - let executor = new FakeExecutor(). - expect(CName.GET_TITLE).andReturnSuccess('cheese '). - expect(CName.GET_CURRENT_URL).andReturnSuccess('tasty'). - end(); + enablePromiseManager(() => { + it('executionOrderWithCustomFunctions', function() { + var msg = []; + let executor = new FakeExecutor(). + expect(CName.GET_TITLE).andReturnSuccess('cheese '). + expect(CName.GET_CURRENT_URL).andReturnSuccess('tasty'). + end(); - var driver = executor.createDriver(); + var driver = executor.createDriver(); - var pushMsg = msg.push.bind(msg); - driver.getTitle().then(pushMsg); - driver.call(() => 'is ').then(pushMsg); - driver.getCurrentUrl().then(pushMsg); - driver.call(() => '!').then(pushMsg); + var pushMsg = msg.push.bind(msg); + driver.getTitle().then(pushMsg); + driver.call(() => 'is ').then(pushMsg); + driver.getCurrentUrl().then(pushMsg); + driver.call(() => '!').then(pushMsg); - return waitForIdle().then(function() { - assert.equal('cheese is tasty!', msg.join('')); + return waitForIdle().then(function() { + assert.equal('cheese is tasty!', msg.join('')); + }); }); }); @@ -756,7 +717,7 @@ describe('WebDriver', function() { }); it('passingPromisedArgumentsToACustomFunction', function() { - var promisedArg = promise.fulfilled(2); + var promisedArg = Promise.resolve(2); var add = function(a, b) { return a + b; }; @@ -834,30 +795,32 @@ describe('WebDriver', function() { }).then(fail, assertIsStubError); }); - it('doesNotCompleteUntilReturnedPromiseIsResolved', function() { - var order = []; - var driver = new FakeExecutor().createDriver(); + enablePromiseManager(() => { + it('doesNotCompleteUntilReturnedPromiseIsResolved', function() { + var order = []; + var driver = new FakeExecutor().createDriver(); - var d = promise.defer(); - d.promise.then(function() { - order.push('b'); - }); + var d = promise.defer(); + d.promise.then(function() { + order.push('b'); + }); - driver.call(function() { - order.push('a'); - return d.promise; - }); - driver.call(function() { - order.push('c'); - }); + driver.call(function() { + order.push('a'); + return d.promise; + }); + driver.call(function() { + order.push('c'); + }); - // timeout to ensure the first function starts its execution before we - // trigger d's callbacks. - return new Promise(f => setTimeout(f, 0)).then(function() { - assert.deepEqual(['a'], order); - d.fulfill(); - return waitForIdle().then(function() { - assert.deepEqual(['a', 'b', 'c'], order); + // timeout to ensure the first function starts its execution before we + // trigger d's callbacks. + return new Promise(f => setTimeout(f, 0)).then(function() { + assert.deepEqual(['a'], order); + d.fulfill(); + return waitForIdle().then(function() { + assert.deepEqual(['a', 'b', 'c'], order); + }); }); }); }); @@ -878,36 +841,58 @@ describe('WebDriver', function() { }); describe('nestedCommands', function() { - it('commandExecutionOrder', function() { - var msg = []; - var driver = new FakeExecutor().createDriver(); - driver.call(msg.push, msg, 'a'); - driver.call(function() { - driver.call(msg.push, msg, 'c'); + enablePromiseManager(() => { + it('commandExecutionOrder', function() { + var msg = []; + var driver = new FakeExecutor().createDriver(); + driver.call(msg.push, msg, 'a'); driver.call(function() { - driver.call(msg.push, msg, 'e'); - driver.call(msg.push, msg, 'f'); + driver.call(msg.push, msg, 'c'); + driver.call(function() { + driver.call(msg.push, msg, 'e'); + driver.call(msg.push, msg, 'f'); + }); + driver.call(msg.push, msg, 'd'); + }); + driver.call(msg.push, msg, 'b'); + return waitForIdle().then(function() { + assert.equal('acefdb', msg.join('')); }); - driver.call(msg.push, msg, 'd'); - }); - driver.call(msg.push, msg, 'b'); - return waitForIdle().then(function() { - assert.equal('acefdb', msg.join('')); }); - }); - it('basicUsage', function() { - var msg = []; - var driver = new FakeExecutor().createDriver(); - var pushMsg = msg.push.bind(msg); - driver.call(() => 'cheese ').then(pushMsg); - driver.call(function() { - driver.call(() => 'is ').then(pushMsg); - driver.call(() => 'tasty').then(pushMsg); + it('basicUsage', function() { + var msg = []; + var driver = new FakeExecutor().createDriver(); + var pushMsg = msg.push.bind(msg); + driver.call(() => 'cheese ').then(pushMsg); + driver.call(function() { + driver.call(() => 'is ').then(pushMsg); + driver.call(() => 'tasty').then(pushMsg); + }); + driver.call(() => '!').then(pushMsg); + return waitForIdle().then(function() { + assert.equal('cheese is tasty!', msg.join('')); + }); }); - driver.call(() => '!').then(pushMsg); - return waitForIdle().then(function() { - assert.equal('cheese is tasty!', msg.join('')); + + it('normalCommandAfterNestedCommandThatReturnsAnAction', function() { + var msg = []; + let executor = new FakeExecutor(). + expect(CName.CLOSE). + end(); + var driver = executor.createDriver(); + driver.call(function() { + return driver.call(function() { + msg.push('a'); + return driver.call(() => 'foobar'); + }); + }); + driver.close().then(function() { + msg.push('b'); + }); + return waitForIdle().then(function() { + assert.equal('ab', msg.join('')); + }); }); }); @@ -922,26 +907,6 @@ describe('WebDriver', function() { }); }); - it('normalCommandAfterNestedCommandThatReturnsAnAction', function() { - var msg = []; - let executor = new FakeExecutor(). - expect(CName.CLOSE). - end(); - var driver = executor.createDriver(); - driver.call(function() { - return driver.call(function() { - msg.push('a'); - return driver.call(() => 'foobar'); - }); - }); - driver.close().then(function() { - msg.push('b'); - }); - return waitForIdle().then(function() { - assert.equal('ab', msg.join('')); - }); - }); - it('errorsBubbleUp_caught', function() { var driver = new FakeExecutor().createDriver(); var result = driver.call(function() { @@ -954,12 +919,12 @@ describe('WebDriver', function() { it('errorsBubbleUp_uncaught', function() { var driver = new FakeExecutor().createDriver(); - driver.call(function() { + return driver.call(function() { return driver.call(function() { return driver.call(throwStubError); }); - }); - return waitForAbort().then(assertIsStubError); + }) + .then(_ => assert.fail('should have failed'), assertIsStubError); }); it('canScheduleCommands', function() { @@ -980,29 +945,27 @@ describe('WebDriver', function() { }); describe('WebElementPromise', function() { + let driver = new FakeExecutor().createDriver(); + it('resolvesWhenUnderlyingElementDoes', function() { - var el = new WebElement({}, {'ELEMENT': 'foo'}); - return new WebElementPromise({}, promise.fulfilled(el)).then(function(e) { - assert.strictEqual(e, el); - }); + let el = new WebElement(driver, {'ELEMENT': 'foo'}); + return new WebElementPromise(driver, Promise.resolve(el)) + .then(e => assert.strictEqual(e, el)); }); it('resolvesBeforeCallbacksOnWireValueTrigger', function() { - var el = new promise.Deferred(); + var el = defer(); - var element = new WebElementPromise({}, el.promise); + var element = new WebElementPromise(driver, el.promise); var messages = []; - element.then(function() { - messages.push('element resolved'); - }); - element.getId().then(function() { - messages.push('wire value resolved'); - }); + let steps = [ + element.then(_ => messages.push('element resolved')), + element.getId().then(_ => messages.push('wire value resolved')) + ]; - assert.deepEqual([], messages); - el.fulfill(new WebElement({}, {'ELEMENT': 'foo'})); - return waitForIdle().then(function() { + el.resolve(new WebElement(driver, {'ELEMENT': 'foo'})); + return Promise.all(steps).then(function() { assert.deepEqual([ 'element resolved', 'wire value resolved' @@ -1011,7 +974,8 @@ describe('WebDriver', function() { }); it('isRejectedIfUnderlyingIdIsRejected', function() { - var element = new WebElementPromise({}, promise.rejected(new StubError)); + let element = + new WebElementPromise(driver, Promise.reject(new StubError)); return element.then(fail, assertIsStubError); }); }); @@ -1207,7 +1171,7 @@ describe('WebDriver', function() { it('failsIfArgumentIsARejectedPromise', function() { let executor = new FakeExecutor(); - var arg = promise.rejected(new StubError); + var arg = Promise.reject(new StubError); arg.catch(function() {}); // Suppress default handler. var driver = executor.createDriver(); @@ -1218,7 +1182,7 @@ describe('WebDriver', function() { describe('executeAsyncScript', function() { it('failsIfArgumentIsARejectedPromise', function() { - var arg = promise.rejected(new StubError); + var arg = Promise.reject(new StubError); arg.catch(function() {}); // Suppress default handler. var driver = new FakeExecutor().createDriver(); @@ -1236,9 +1200,8 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - var element = driver.findElement(By.id('foo')); - element.click(); // This should never execute. - return waitForAbort().then(assertIsStubError); + return driver.findElement(By.id('foo')) + .then(assert.fail, assertIsStubError); }); it('elementNotFoundInACallback', function() { @@ -1249,11 +1212,9 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - promise.fulfilled().then(function() { - var element = driver.findElement(By.id('foo')); - return element.click(); // Should not execute. - }); - return waitForAbort().then(assertIsStubError); + return Promise.resolve() + .then(_ => driver.findElement(By.id('foo'))) + .then(assert.fail, assertIsStubError); }); it('elementFound', function() { @@ -1261,7 +1222,7 @@ describe('WebDriver', function() { expect(CName.FIND_ELEMENT, {using: 'css selector', value: '*[id="foo"]'}). andReturnSuccess(WebElement.buildId('bar')). - expect(CName.CLICK_ELEMENT, {'id': 'bar'}). + expect(CName.CLICK_ELEMENT, {'id': WebElement.buildId('bar')}). andReturnSuccess(). end(); @@ -1276,7 +1237,7 @@ describe('WebDriver', function() { expect(CName.FIND_ELEMENT, {using: 'css selector', value: '*[id="foo"]'}). andReturnSuccess(WebElement.buildId('bar')). - expect(CName.CLICK_ELEMENT, {'id': 'bar'}). + expect(CName.CLICK_ELEMENT, {'id': WebElement.buildId('bar')}). andReturnSuccess(). end(); @@ -1294,7 +1255,7 @@ describe('WebDriver', function() { 'args': [] }). andReturnSuccess(WebElement.buildId('bar')). - expect(CName.CLICK_ELEMENT, {'id': 'bar'}). + expect(CName.CLICK_ELEMENT, {'id': WebElement.buildId('bar')}). end(); var driver = executor.createDriver(); @@ -1310,12 +1271,12 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - var element = driver.findElement(By.js('return 123')); - element.click(); // Should not execute. - return waitForAbort().then(function(e) { - assertIsInstance(TypeError, e); - assert.equal('Custom locator did not return a WebElement', e.message); - }); + return driver.findElement(By.js('return 123')) + .then(assert.fail, function(e) { + assertIsInstance(TypeError, e); + assert.equal( + 'Custom locator did not return a WebElement', e.message); + }); }); it('byJs_canPassArguments', function() { @@ -1325,7 +1286,7 @@ describe('WebDriver', function() { 'script': script, 'args': ['div'] }). - andReturnSuccess({'ELEMENT':'one'}). + andReturnSuccess(WebElement.buildId('one')). end(); var driver = executor.createDriver(); driver.findElement(By.js(script, 'div')); @@ -1338,7 +1299,7 @@ describe('WebDriver', function() { andReturnSuccess([ WebElement.buildId('foo'), WebElement.buildId('bar')]). - expect(CName.CLICK_ELEMENT, {'id': 'foo'}). + expect(CName.CLICK_ELEMENT, {'id': WebElement.buildId('foo')}). andReturnSuccess(). end(); @@ -1347,19 +1308,17 @@ describe('WebDriver', function() { assert.equal(driver, d); return d.findElements(By.tagName('a')); }); - element.click(); - return waitForIdle(); + return element.click(); }); it('customLocatorThrowsIfresultIsNotAWebElement', function() { var driver = new FakeExecutor().createDriver(); - driver.findElement(function() { - return 1; - }); - return waitForAbort().then(function(e) { - assertIsInstance(TypeError, e); - assert.equal('Custom locator did not return a WebElement', e.message); - }); + return driver.findElement(_ => 1) + .then(assert.fail, function(e) { + assertIsInstance(TypeError, e); + assert.equal( + 'Custom locator did not return a WebElement', e.message); + }); }); }); @@ -1486,7 +1445,8 @@ describe('WebDriver', function() { it('convertsVarArgsIntoStrings_simpleArgs', function() { let executor = new FakeExecutor(). expect(CName.SEND_KEYS_TO_ELEMENT, - {'id': 'one', 'value':'12abc3'.split('')}). + {'id': WebElement.buildId('one'), + 'value':'12abc3'.split('')}). andReturnSuccess(). end(); @@ -1502,16 +1462,17 @@ describe('WebDriver', function() { {'using':'css selector', 'value':'*[id="foo"]'}). andReturnSuccess(WebElement.buildId('one')). expect(CName.SEND_KEYS_TO_ELEMENT, - {'id':'one', 'value':'abc123def'.split('')}). + {'id':WebElement.buildId('one'), + 'value':'abc123def'.split('')}). andReturnSuccess(). end(); var driver = executor.createDriver(); var element = driver.findElement(By.id('foo')); - element.sendKeys( - promise.fulfilled('abc'), 123, - promise.fulfilled('def')); - return waitForIdle(); + return element.sendKeys( + Promise.resolve('abc'), + 123, + Promise.resolve('def')); }); it('sendKeysWithAFileDetector', function() { @@ -1520,7 +1481,8 @@ describe('WebDriver', function() { {'using':'css selector', 'value':'*[id="foo"]'}). andReturnSuccess(WebElement.buildId('one')). expect(CName.SEND_KEYS_TO_ELEMENT, - {'id': 'one', 'value':'modified/path'.split('')}). + {'id': WebElement.buildId('one'), + 'value':'modified/path'.split('')}). andReturnSuccess(). end(); @@ -1528,13 +1490,11 @@ describe('WebDriver', function() { let handleFile = function(d, path) { assert.strictEqual(driver, d); assert.equal(path, 'original/path'); - return promise.fulfilled('modified/path'); + return Promise.resolve('modified/path'); }; driver.setFileDetector({handleFile}); - var element = driver.findElement(By.id('foo')); - element.sendKeys('original/', 'path'); - return waitForIdle(); + return driver.findElement(By.id('foo')).sendKeys('original/', 'path'); }); }); @@ -1554,7 +1514,7 @@ describe('WebDriver', function() { return waitForIdle(); }); - it('should propogate exceptions', function() { + it('should propagate exceptions', function() { let e = new error.NoSuchWindowError('window not found'); let executor = new FakeExecutor(). expect(CName.SWITCH_TO_WINDOW). @@ -1565,21 +1525,21 @@ describe('WebDriver', function() { andReturnError(e). end(); - executor.createDriver().switchTo().window('foo'); - return waitForAbort().then(v => assert.strictEqual(v, e)); + return executor.createDriver() + .switchTo().window('foo') + .then(assert.fail, v => assert.strictEqual(v, e)); }); }); }); describe('elementEquality', function() { it('isReflexive', function() { - var a = new WebElement({}, 'foo'); + var a = new WebElement(new FakeExecutor().createDriver(), 'foo'); return WebElement.equals(a, a).then(assert.ok); }); it('failsIfAnInputElementCouldNotBeFound', function() { - var id = promise.rejected(new StubError); - id.catch(function() {}); // Suppress default handler. + let id = Promise.reject(new StubError); var driver = new FakeExecutor().createDriver(); var a = new WebElement(driver, 'foo'); @@ -1590,71 +1550,134 @@ describe('WebDriver', function() { }); describe('waiting', function() { - it('waitSucceeds', function() { - let executor = new FakeExecutor(). - expect(CName.FIND_ELEMENTS, - {using: 'css selector', value: '*[id="foo"]'}). - andReturnSuccess([]). - times(2). - expect(CName.FIND_ELEMENTS, - {using: 'css selector', value: '*[id="foo"]'}). - andReturnSuccess([WebElement.buildId('bar')]). - end(); + describe('supports custom wait functions', function() { + it('waitSucceeds', function() { + let executor = new FakeExecutor(). + expect(CName.FIND_ELEMENTS, + {using: 'css selector', value: '*[id="foo"]'}). + andReturnSuccess([]). + times(2). + expect(CName.FIND_ELEMENTS, + {using: 'css selector', value: '*[id="foo"]'}). + andReturnSuccess([WebElement.buildId('bar')]). + end(); - var driver = executor.createDriver(); - driver.wait(function() { - return driver.findElements(By.id('foo')).then(els => els.length > 0); - }, 200); - return waitForIdle(); + var driver = executor.createDriver(); + driver.wait(function() { + return driver.findElements(By.id('foo')).then(els => els.length > 0); + }, 200); + return waitForIdle(); + }); + + it('waitTimesout_timeoutCaught', function() { + let executor = new FakeExecutor(). + expect(CName.FIND_ELEMENTS, + {using: 'css selector', value: '*[id="foo"]'}). + andReturnSuccess([]). + anyTimes(). + end(); + + var driver = executor.createDriver(); + return driver.wait(function() { + return driver.findElements(By.id('foo')).then(els => els.length > 0); + }, 25).then(fail, function(e) { + assert.equal('Wait timed out after ', + e.message.substring(0, 'Wait timed out after '.length)); + }); + }); + + enablePromiseManager(() => { + it('waitTimesout_timeoutNotCaught', function() { + let executor = new FakeExecutor(). + expect(CName.FIND_ELEMENTS, + {using: 'css selector', value: '*[id="foo"]'}). + andReturnSuccess([]). + anyTimes(). + end(); + + var driver = executor.createDriver(); + driver.wait(function() { + return driver.findElements(By.id('foo')).then(els => els.length > 0); + }, 25); + return waitForAbort().then(function(e) { + assert.equal('Wait timed out after ', + e.message.substring(0, 'Wait timed out after '.length)); + }); + }); + }); }); - it('waitTimesout_timeoutCaught', function() { - let executor = new FakeExecutor(). - expect(CName.FIND_ELEMENTS, - {using: 'css selector', value: '*[id="foo"]'}). - andReturnSuccess([]). - anyTimes(). - end(); + describe('supports condition objects', function() { + it('wait succeeds', function() { + let executor = new FakeExecutor() + .expect(CName.FIND_ELEMENTS, + {using: 'css selector', value: '*[id="foo"]'}) + .andReturnSuccess([]) + .times(2) + .expect(CName.FIND_ELEMENTS, + {using: 'css selector', value: '*[id="foo"]'}) + .andReturnSuccess([WebElement.buildId('bar')]) + .end(); - var driver = executor.createDriver(); - return driver.wait(function() { - return driver.findElements(By.id('foo')).then(els => els.length > 0); - }, 25).then(fail, function(e) { - assert.equal('Wait timed out after ', - e.message.substring(0, 'Wait timed out after '.length)); + let driver = executor.createDriver(); + return driver.wait(until.elementLocated(By.id('foo')), 200); + }); + + it('wait times out', function() { + let executor = new FakeExecutor() + .expect(CName.FIND_ELEMENTS, + {using: 'css selector', value: '*[id="foo"]'}) + .andReturnSuccess([]) + .anyTimes() + .end(); + + let driver = executor.createDriver(); + return driver.wait(until.elementLocated(By.id('foo')), 5) + .then(fail, err => assert.ok(err instanceof error.TimeoutError)); }); }); - it('waitTimesout_timeoutNotCaught', function() { - let executor = new FakeExecutor(). - expect(CName.FIND_ELEMENTS, - {using: 'css selector', value: '*[id="foo"]'}). - andReturnSuccess([]). - anyTimes(). - end(); + describe('supports promise objects', function() { + it('wait succeeds', function() { + let promise = new Promise(resolve => { + setTimeout(() => resolve(1), 10); + }); - var driver = executor.createDriver(); - driver.wait(function() { - return driver.findElements(By.id('foo')).then(els => els.length > 0); - }, 25); - return waitForAbort().then(function(e) { - assert.equal('Wait timed out after ', - e.message.substring(0, 'Wait timed out after '.length)); + let driver = new FakeExecutor().createDriver(); + return driver.wait(promise, 200).then(v => assert.equal(v, 1)); + }); + + it('wait times out', function() { + let promise = new Promise(resolve => {/* never resolves */}); + + let driver = new FakeExecutor().createDriver(); + return driver.wait(promise, 5) + .then(fail, err => assert.ok(err instanceof error.TimeoutError)); + }); + + it('wait fails if promise is rejected', function() { + let err = Error('boom'); + let driver = new FakeExecutor().createDriver(); + return driver.wait(Promise.reject(err), 5) + .then(fail, e => assert.strictEqual(e, err)); }); }); + + it('fails if not supported condition type provided', function() { + let driver = new FakeExecutor().createDriver(); + assert.throws(() => driver.wait({}, 5), TypeError); + }); }); describe('alert handling', function() { it('alertResolvesWhenPromisedTextResolves', function() { - var deferredText = new promise.Deferred(); + let driver = new FakeExecutor().createDriver(); + let deferredText = defer(); - var alert = new AlertPromise({}, deferredText.promise); - assert.ok(alert.isPending()); + let alert = new AlertPromise(driver, deferredText.promise); - deferredText.fulfill(new Alert({}, 'foo')); - return alert.getText().then(function(text) { - assert.equal('foo', text); - }); + deferredText.resolve(new Alert(driver, 'foo')); + return alert.getText().then(text => assert.equal(text, 'foo')); }); it('cannotSwitchToAlertThatIsNotPresent', function() { @@ -1664,32 +1687,33 @@ describe('WebDriver', function() { .andReturnError(e) .end(); - var driver = executor.createDriver(); - var alert = driver.switchTo().alert(); - alert.dismiss(); // Should never execute. - return waitForAbort().then(v => assert.strictEqual(v, e)); + return executor.createDriver() + .switchTo().alert() + .then(assert.fail, v => assert.strictEqual(v, e)); }); - it('alertsBelongToSameFlowAsParentDriver', function() { - let executor = new FakeExecutor() - .expect(CName.GET_ALERT_TEXT).andReturnSuccess('hello') - .end(); + enablePromiseManager(() => { + it('alertsBelongToSameFlowAsParentDriver', function() { + let executor = new FakeExecutor() + .expect(CName.GET_ALERT_TEXT).andReturnSuccess('hello') + .end(); - var driver = executor.createDriver(); - var otherFlow = new promise.ControlFlow(); - otherFlow.execute(function() { - driver.switchTo().alert().then(function() { - assert.strictEqual( - driver.controlFlow(), promise.controlFlow(), - 'Alert should belong to the same flow as its parent driver'); + var driver = executor.createDriver(); + var otherFlow = new promise.ControlFlow(); + otherFlow.execute(function() { + driver.switchTo().alert().then(function() { + assert.strictEqual( + driver.controlFlow(), promise.controlFlow(), + 'Alert should belong to the same flow as its parent driver'); + }); }); - }); - assert.notEqual(otherFlow, driver.controlFlow); - return Promise.all([ - waitForIdle(otherFlow), - waitForIdle(driver.controlFlow()) - ]); + assert.notEqual(otherFlow, driver.controlFlow); + return Promise.all([ + waitForIdle(otherFlow), + waitForIdle(driver.controlFlow()) + ]); + }); }); it('commandsFailIfAlertNotPresent', function() { @@ -1715,26 +1739,28 @@ describe('WebDriver', function() { }); }); - it('testWebElementsBelongToSameFlowAsParentDriver', function() { - let executor = new FakeExecutor() - .expect(CName.FIND_ELEMENT, - {using: 'css selector', value: '*[id="foo"]'}) - .andReturnSuccess(WebElement.buildId('abc123')) - .end(); + enablePromiseManager(() => { + it('testWebElementsBelongToSameFlowAsParentDriver', function() { + let executor = new FakeExecutor() + .expect(CName.FIND_ELEMENT, + {using: 'css selector', value: '*[id="foo"]'}) + .andReturnSuccess(WebElement.buildId('abc123')) + .end(); - var driver = executor.createDriver(); - var otherFlow = new promise.ControlFlow(); - otherFlow.execute(function() { - driver.findElement({id: 'foo'}).then(function() { - assert.equal(driver.controlFlow(), promise.controlFlow()); + var driver = executor.createDriver(); + var otherFlow = new promise.ControlFlow(); + otherFlow.execute(function() { + driver.findElement({id: 'foo'}).then(function() { + assert.equal(driver.controlFlow(), promise.controlFlow()); + }); }); - }); - assert.notEqual(otherFlow, driver.controlFlow); - return Promise.all([ - waitForIdle(otherFlow), - waitForIdle(driver.controlFlow()) - ]); + assert.notEqual(otherFlow, driver.controlFlow); + return Promise.all([ + waitForIdle(otherFlow), + waitForIdle(driver.controlFlow()) + ]); + }); }); it('testFetchingLogs', function() { @@ -1763,7 +1789,7 @@ describe('WebDriver', function() { }); it('testCommandsFailIfInitialSessionCreationFailed', function() { - var session = promise.rejected(new StubError); + var session = Promise.reject(new StubError); var driver = new FakeExecutor().createDriver(session); var navigateResult = driver.get('some-url').then(fail, assertIsStubError); @@ -1811,10 +1837,10 @@ describe('WebDriver', function() { describe('actions()', function() { it('failsIfInitialDriverCreationFailed', function() { - let session = promise.rejected(new StubError); - session.catch(function() {}); - return new FakeExecutor(). - createDriver(session). + let session = Promise.reject(new StubError('no session for you')); + let driver = new FakeExecutor().createDriver(session); + driver.getSession().catch(function() {}); + return driver. actions(). mouseDown(). mouseUp(). @@ -1900,10 +1926,10 @@ describe('WebDriver', function() { describe('touchActions()', function() { it('failsIfInitialDriverCreationFailed', function() { - let session = promise.rejected(new StubError); - session.catch(function() {}); - return new FakeExecutor(). - createDriver(session). + let session = Promise.reject(new StubError); + let driver = new FakeExecutor().createDriver(session); + driver.getSession().catch(function() {}); + return driver. touchActions(). scroll({x: 3, y: 4}). perform(). @@ -1937,8 +1963,8 @@ describe('WebDriver', function() { it('canUseGeneratorsWithWebDriverCall', function() { return driver.call(function* () { - var x = yield promise.fulfilled(1); - var y = yield promise.fulfilled(2); + var x = yield Promise.resolve(1); + var y = yield Promise.resolve(2); return x + y; }).then(function(value) { assert.deepEqual(3, value); @@ -1947,7 +1973,7 @@ describe('WebDriver', function() { it('canDefineScopeOnGeneratorCall', function() { return driver.call(function* () { - var x = yield promise.fulfilled(1); + var x = yield Promise.resolve(1); return this.name + x; }, {name: 'Bob'}).then(function(value) { assert.deepEqual('Bob1', value); @@ -1956,8 +1982,8 @@ describe('WebDriver', function() { it('canSpecifyArgsOnGeneratorCall', function() { return driver.call(function* (a, b) { - var x = yield promise.fulfilled(1); - var y = yield promise.fulfilled(2); + var x = yield Promise.resolve(1); + var y = yield Promise.resolve(2); return [x + y, a, b]; }, null, 'abc', 123).then(function(value) { assert.deepEqual([3, 'abc', 123], value); @@ -1992,6 +2018,8 @@ describe('WebDriver', function() { }); describe('wire format', function() { + const FAKE_DRIVER = new FakeExecutor().createDriver(); + describe('can serialize', function() { function runSerializeTest(input, want) { let executor = new FakeExecutor(). @@ -2035,14 +2063,15 @@ describe('WebDriver', function() { it('WebElement', function() { return runSerializeTest( - new WebElement({}, 'fefifofum'), + new WebElement(FAKE_DRIVER, 'fefifofum'), WebElement.buildId('fefifofum')); }); it('WebElementPromise', function() { return runSerializeTest( new WebElementPromise( - {}, promise.fulfilled(new WebElement({}, 'fefifofum'))), + FAKE_DRIVER, + Promise.resolve(new WebElement(FAKE_DRIVER, 'fefifofum'))), WebElement.buildId('fefifofum')); }); @@ -2053,14 +2082,15 @@ describe('WebDriver', function() { it('with WebElement', function() { return runSerializeTest( - [new WebElement({}, 'fefifofum')], + [new WebElement(FAKE_DRIVER, 'fefifofum')], [WebElement.buildId('fefifofum')]); }); it('with WebElementPromise', function() { return runSerializeTest( [new WebElementPromise( - {}, promise.fulfilled(new WebElement({}, 'fefifofum')))], + FAKE_DRIVER, + Promise.resolve(new WebElement(FAKE_DRIVER, 'fefifofum')))], [WebElement.buildId('fefifofum')]); }); @@ -2070,7 +2100,7 @@ describe('WebDriver', function() { [123, {'foo': 'bar'}] ]; - var element = new WebElement({}, 'fefifofum'); + var element = new WebElement(FAKE_DRIVER, 'fefifofum'); var input = ['abc', 123, true, element, [123, {'foo': 'bar'}]]; return runSerializeTest(input, expected); }); @@ -2114,7 +2144,7 @@ describe('WebDriver', function() { 'sessionId': 'foo' }; - var element = new WebElement({}, 'fefifofum'); + var element = new WebElement(FAKE_DRIVER, 'fefifofum'); var parameters = { 'script': 'return 1', 'args':['abc', 123, true, element, [123, {'foo': 'bar'}]], diff --git a/node_modules/selenium-webdriver/test/logging_test.js b/node_modules/selenium-webdriver/test/logging_test.js index fd88daa72..546879715 100644 --- a/node_modules/selenium-webdriver/test/logging_test.js +++ b/node_modules/selenium-webdriver/test/logging_test.js @@ -40,125 +40,123 @@ test.suite(function(env) { driver = null; }); - test.afterEach(function() { + test.afterEach(function*() { if (driver) { - driver.quit(); + return driver.quit(); } }); - test.it('can be disabled', function() { + test.it('can be disabled', function*() { var prefs = new logging.Preferences(); prefs.setLevel(logging.Type.BROWSER, logging.Level.OFF); - driver = env.builder() + driver = yield env.builder() .setLoggingPrefs(prefs) .build(); - driver.get(dataUrl( + yield driver.get(dataUrl( '<!DOCTYPE html><script>', 'console.info("hello");', 'console.warn("this is a warning");', 'console.error("and this is an error");', '</script>')); - driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { - assert(entries.length).equalTo(0); - }); + return driver.manage().logs().get(logging.Type.BROWSER) + .then(entries => assert(entries.length).equalTo(0)); }); // Firefox does not capture JS error console log messages. test.ignore(env.browsers(Browser.FIREFOX, 'legacy-firefox')). - it('can be turned down', function() { + it('can be turned down', function*() { var prefs = new logging.Preferences(); prefs.setLevel(logging.Type.BROWSER, logging.Level.SEVERE); - driver = env.builder() + driver = yield env.builder() .setLoggingPrefs(prefs) .build(); - driver.get(dataUrl( + yield driver.get(dataUrl( '<!DOCTYPE html><script>', 'console.info("hello");', 'console.warn("this is a warning");', 'console.error("and this is an error");', '</script>')); - driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { - assert(entries.length).equalTo(1); - assert(entries[0].level.name).equalTo('SEVERE'); - assert(entries[0].message).endsWith('and this is an error'); - }); + return driver.manage().logs().get(logging.Type.BROWSER) + .then(function(entries) { + assert(entries.length).equalTo(1); + assert(entries[0].level.name).equalTo('SEVERE'); + assert(entries[0].message).matches(/.*\"?and this is an error\"?/); + }); }); // Firefox does not capture JS error console log messages. test.ignore(env.browsers(Browser.FIREFOX, 'legacy-firefox')). - it('can be made verbose', function() { + it('can be made verbose', function*() { var prefs = new logging.Preferences(); prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG); - driver = env.builder() + driver = yield env.builder() .setLoggingPrefs(prefs) .build(); - driver.get(dataUrl( + yield driver.get(dataUrl( '<!DOCTYPE html><script>', 'console.debug("hello");', 'console.warn("this is a warning");', 'console.error("and this is an error");', '</script>')); - driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { - assert(entries.length).equalTo(3); - assert(entries[0].level.name).equalTo('DEBUG'); - assert(entries[0].message).endsWith('hello'); - - assert(entries[1].level.name).equalTo('WARNING'); - assert(entries[1].message).endsWith('this is a warning'); - - assert(entries[2].level.name).equalTo('SEVERE'); - assert(entries[2].message).endsWith('and this is an error'); - }); + return driver.manage().logs().get(logging.Type.BROWSER) + .then(function(entries) { + assert(entries.length).equalTo(3); + assert(entries[0].level.name).equalTo('DEBUG'); + assert(entries[0].message).matches(/.*\"?hello\"?/); + + assert(entries[1].level.name).equalTo('WARNING'); + assert(entries[1].message).matches(/.*\"?this is a warning\"?/); + + assert(entries[2].level.name).equalTo('SEVERE'); + assert(entries[2].message).matches(/.*\"?and this is an error\"?/); + }); }); // Firefox does not capture JS error console log messages. test.ignore(env.browsers(Browser.FIREFOX, 'legacy-firefox')). - it('clears records after retrieval', function() { + it('clears records after retrieval', function*() { var prefs = new logging.Preferences(); prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG); - driver = env.builder() + driver = yield env.builder() .setLoggingPrefs(prefs) .build(); - driver.get(dataUrl( + yield driver.get(dataUrl( '<!DOCTYPE html><script>', 'console.debug("hello");', 'console.warn("this is a warning");', 'console.error("and this is an error");', '</script>')); - driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { - assert(entries.length).equalTo(3); - }); - driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { - assert(entries.length).equalTo(0); - }); + yield driver.manage().logs().get(logging.Type.BROWSER) + .then(entries => assert(entries.length).equalTo(3)); + return driver.manage().logs().get(logging.Type.BROWSER) + .then(entries => assert(entries.length).equalTo(0)); }); - test.it('does not mix log types', function() { + test.it('does not mix log types', function*() { var prefs = new logging.Preferences(); prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG); prefs.setLevel(logging.Type.DRIVER, logging.Level.SEVERE); - driver = env.builder() + driver = yield env.builder() .setLoggingPrefs(prefs) .build(); - driver.get(dataUrl( + yield driver.get(dataUrl( '<!DOCTYPE html><script>', 'console.debug("hello");', 'console.warn("this is a warning");', 'console.error("and this is an error");', '</script>')); - driver.manage().logs().get(logging.Type.DRIVER).then(function(entries) { - assert(entries.length).equalTo(0); - }); + return driver.manage().logs().get(logging.Type.DRIVER) + .then(entries => assert(entries.length).equalTo(0)); }); }); diff --git a/node_modules/selenium-webdriver/test/net/portprober_test.js b/node_modules/selenium-webdriver/test/net/portprober_test.js index 03a2f7a10..668c4ae0e 100644 --- a/node_modules/selenium-webdriver/test/net/portprober_test.js +++ b/node_modules/selenium-webdriver/test/net/portprober_test.js @@ -17,11 +17,10 @@ 'use strict'; -var assert = require('assert'), - net = require('net'); +const assert = require('assert'); +const net = require('net'); -var promise = require('../..').promise, - portprober = require('../../net/portprober'); +const portprober = require('../../net/portprober'); describe('isFree', function() { @@ -42,12 +41,12 @@ describe('isFree', function() { server.listen(0, function() { var port = server.address().port; assertPortNotfree(port).then(function() { - var done = promise.defer(); - server.close(function() { - server = null; - done.fulfill(assertPortIsFree(port)); + return new Promise(resolve => { + server.close(function() { + server = null; + resolve(assertPortIsFree(port)); + }); }); - return done.promise; }).then(function() { done(); }, done); }); }); @@ -57,12 +56,12 @@ describe('isFree', function() { server.listen(0, host, function() { var port = server.address().port; assertPortNotfree(port, host).then(function() { - var done = promise.defer(); - server.close(function() { - server = null; - done.fulfill(assertPortIsFree(port, host)); + return new Promise(resolve => { + server.close(function() { + server = null; + resolve(assertPortIsFree(port, host)); + }); }); - return done.promise; }).then(function() { done(); }, done); }); }); @@ -86,12 +85,12 @@ describe('findFreePort', function() { portprober.findFreePort().then(function(port) { server.listen(port, function() { assertPortNotfree(port).then(function() { - var done = promise.defer(); - server.close(function() { - server = null; - done.fulfill(assertPortIsFree(port)); + return new Promise(resolve => { + server.close(function() { + server = null; + resolve(assertPortIsFree(port)); + }); }); - return done.promise; }).then(function() { done(); }, done); }); }); @@ -102,12 +101,12 @@ describe('findFreePort', function() { portprober.findFreePort(host).then(function(port) { server.listen(port, host, function() { assertPortNotfree(port, host).then(function() { - var done = promise.defer(); - server.close(function() { - server = null; - done.fulfill(assertPortIsFree(port, host)); + return new Promise(resolve => { + server.close(function() { + server = null; + resolve(assertPortIsFree(port, host)); + }); }); - return done.promise; }).then(function() { done(); }, done); }); }); diff --git a/node_modules/selenium-webdriver/test/page_loading_test.js b/node_modules/selenium-webdriver/test/page_loading_test.js index 098460370..1f09db5b5 100644 --- a/node_modules/selenium-webdriver/test/page_loading_test.js +++ b/node_modules/selenium-webdriver/test/page_loading_test.js @@ -30,122 +30,136 @@ test.suite(function(env) { var browsers = env.browsers; var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().build(); + }); + + test.beforeEach(function*() { + if (!driver) { + driver = yield env.builder().build(); + } }); test.after(function() { - driver.quit(); + if (driver) { + return driver.quit(); + } }); - test.it('should wait for document to be loaded', function() { - driver.get(Pages.simpleTestPage); - assert(driver.getTitle()).equalTo('Hello WebDriver'); + test.it('should wait for document to be loaded', function*() { + yield driver.get(Pages.simpleTestPage); + return assert(driver.getTitle()).equalTo('Hello WebDriver'); }); test.it('should follow redirects sent in the http response headers', - function() { - driver.get(Pages.redirectPage); - assert(driver.getTitle()).equalTo('We Arrive Here'); + function*() { + yield driver.get(Pages.redirectPage); + return assert(driver.getTitle()).equalTo('We Arrive Here'); }); - test.it('should follow meta redirects', function() { - driver.get(Pages.metaRedirectPage); - assert(driver.getTitle()).equalTo('We Arrive Here'); + test.ignore(browsers(Browser.SAFARI)). + it('should follow meta redirects', function*() { + yield driver.get(Pages.metaRedirectPage); + return assert(driver.getTitle()).equalTo('We Arrive Here'); }); // Skip Firefox; see https://bugzilla.mozilla.org/show_bug.cgi?id=1280300 test.ignore(browsers(Browser.FIREFOX)). - it('should be able to get a fragment on the current page', function() { - driver.get(Pages.xhtmlTestPage); - driver.get(Pages.xhtmlTestPage + '#text'); - driver.findElement(By.id('id1')); + it('should be able to get a fragment on the current page', function*() { + yield driver.get(Pages.xhtmlTestPage); + yield driver.get(Pages.xhtmlTestPage + '#text'); + yield driver.findElement(By.id('id1')); }); test.ignore(browsers(Browser.IPAD, Browser.IPHONE)). - it('should wait for all frames to load in a frameset', function() { - driver.get(Pages.framesetPage); - driver.switchTo().frame(0); + it('should wait for all frames to load in a frameset', function*() { + yield driver.get(Pages.framesetPage); + yield driver.switchTo().frame(0); - driver.findElement(By.css('span#pageNumber')).getText().then(function(txt) { - assert(txt.trim()).equalTo('1'); - }); + let txt = yield driver.findElement(By.css('span#pageNumber')).getText(); + assert(txt.trim()).equalTo('1'); - driver.switchTo().defaultContent(); - driver.switchTo().frame(1); - driver.findElement(By.css('span#pageNumber')).getText().then(function(txt) { - assert(txt.trim()).equalTo('2'); - }); + yield driver.switchTo().defaultContent(); + yield driver.switchTo().frame(1); + txt = yield driver.findElement(By.css('span#pageNumber')).getText(); + + assert(txt.trim()).equalTo('2'); }); test.ignore(browsers(Browser.SAFARI)). - it('should be able to navigate back in browser history', function() { - driver.get(Pages.formPage); + it('should be able to navigate back in browser history', function*() { + yield driver.get(Pages.formPage); - driver.findElement(By.id('imageButton')).click(); - driver.wait(until.titleIs('We Arrive Here'), 2500); + yield driver.findElement(By.id('imageButton')).click(); + yield driver.wait(until.titleIs('We Arrive Here'), 2500); - driver.navigate().back(); - driver.wait(until.titleIs('We Leave From Here'), 2500); + yield driver.navigate().back(); + yield driver.wait(until.titleIs('We Leave From Here'), 2500); }); test.ignore(browsers(Browser.SAFARI)). - it('should be able to navigate back in presence of iframes', function() { - driver.get(Pages.xhtmlTestPage); + it('should be able to navigate back in presence of iframes', function*() { + yield driver.get(Pages.xhtmlTestPage); - driver.findElement(By.name('sameWindow')).click(); - driver.wait(until.titleIs('This page has iframes'), 2500); + yield driver.findElement(By.name('sameWindow')).click(); + yield driver.wait(until.titleIs('This page has iframes'), 2500); - driver.navigate().back(); - driver.wait(until.titleIs('XHTML Test Page'), 2500); + yield driver.navigate().back(); + yield driver.wait(until.titleIs('XHTML Test Page'), 2500); }); test.ignore(browsers(Browser.SAFARI)). - it('should be able to navigate forwards in browser history', function() { - driver.get(Pages.formPage); + it('should be able to navigate forwards in browser history', function*() { + yield driver.get(Pages.formPage); - driver.findElement(By.id('imageButton')).click(); - driver.wait(until.titleIs('We Arrive Here'), 5000); + yield driver.findElement(By.id('imageButton')).click(); + yield driver.wait(until.titleIs('We Arrive Here'), 5000); - driver.navigate().back(); - driver.wait(until.titleIs('We Leave From Here'), 5000); + yield driver.navigate().back(); + yield driver.wait(until.titleIs('We Leave From Here'), 5000); - driver.navigate().forward(); - driver.wait(until.titleIs('We Arrive Here'), 5000); + yield driver.navigate().forward(); + yield driver.wait(until.titleIs('We Arrive Here'), 5000); }); // PhantomJS 2.0 does not properly reload pages on refresh. test.ignore(browsers(Browser.PHANTOM_JS)). - it('should be able to refresh a page', function() { - driver.get(Pages.xhtmlTestPage); + it('should be able to refresh a page', function*() { + yield driver.get(Pages.xhtmlTestPage); - driver.navigate().refresh(); + yield driver.navigate().refresh(); - assert(driver.getTitle()).equalTo('XHTML Test Page'); + yield assert(driver.getTitle()).equalTo('XHTML Test Page'); }); - test.it('should return title of page if set', function() { - driver.get(Pages.xhtmlTestPage); - assert(driver.getTitle()).equalTo('XHTML Test Page'); + test.it('should return title of page if set', function*() { + yield driver.get(Pages.xhtmlTestPage); + yield assert(driver.getTitle()).equalTo('XHTML Test Page'); - driver.get(Pages.simpleTestPage); - assert(driver.getTitle()).equalTo('Hello WebDriver'); + yield driver.get(Pages.simpleTestPage); + yield assert(driver.getTitle()).equalTo('Hello WebDriver'); }); - // Only implemented in Firefox. - test.ignore(browsers( - Browser.CHROME, - Browser.IE, - Browser.IPAD, - Browser.IPHONE, - Browser.OPERA, - Browser.PHANTOM_JS, - Browser.SAFARI)). - it('should timeout if page load timeout is set', function() { - driver.call(function() { - driver.manage().timeouts().pageLoadTimeout(1); - driver.get(Pages.sleepingPage + '?time=3'). - then(function() { + describe('timeouts', function() { + test.afterEach(function() { + let nullDriver = () => driver = null; + if (driver) { + return driver.quit().then(nullDriver, nullDriver); + } + }); + + // Only implemented in Firefox. + test.ignore(browsers( + Browser.CHROME, + Browser.IE, + Browser.IPAD, + Browser.IPHONE, + Browser.OPERA, + Browser.PHANTOM_JS)). + it('should timeout if page load timeout is set', function*() { + yield driver.manage().timeouts().pageLoadTimeout(1); + return driver.get(Pages.sleepingPage + '?time=3') + .then(function() { throw Error('Should have timed out on page load'); }, function(e) { if (!(e instanceof error.ScriptTimeoutError) @@ -153,14 +167,6 @@ test.suite(function(env) { throw Error('Unexpected error response: ' + e); } }); - }).then(resetPageLoad, function(err) { - resetPageLoad().finally(function() { - throw err; - }); }); - - function resetPageLoad() { - return driver.manage().timeouts().pageLoadTimeout(-1); - } }); }); diff --git a/node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js b/node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js index 82a814a31..8b7a99f8d 100644 --- a/node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js +++ b/node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js @@ -24,49 +24,35 @@ var test = require('../../lib/test'); test.suite(function(env) { var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().build(); }); test.after(function() { - driver.quit(); + return driver.quit(); }); var testPageUrl = 'data:text/html,<html><h1>' + path.basename(__filename) + '</h1></html>'; test.beforeEach(function() { - driver.get(testPageUrl); + return driver.get(testPageUrl); }); describe('phantomjs.Driver', function() { describe('#executePhantomJS()', function() { - test.it('can execute scripts using PhantomJS API', function() { - return driver.executePhantomJS('return this.url;').then(function(url) { - assert.equal(testPageUrl, decodeURIComponent(url)); - }); + test.it('can execute scripts using PhantomJS API', function*() { + let url = yield driver.executePhantomJS('return this.url;'); + assert.equal(testPageUrl, decodeURIComponent(url)); }); - test.it('can execute scripts as functions', function() { - driver.executePhantomJS(function(a, b) { + test.it('can execute scripts as functions', function*() { + let result = yield driver.executePhantomJS(function(a, b) { return a + b; - }, 1, 2).then(function(result) { - assert.equal(3, result); - }); - }); + }, 1, 2); - test.it('can manipulate the current page', function() { - driver.manage().addCookie({name: 'foo', value: 'bar'}); - driver.manage().getCookie('foo').then(function(cookie) { - assert.equal('bar', cookie.value); - }); - driver.executePhantomJS(function() { - this.clearCookies(); - }); - driver.manage().getCookie('foo').then(function(cookie) { - assert.equal(null, cookie); - }); + assert.equal(3, result); }); }); }); diff --git a/node_modules/selenium-webdriver/test/proxy_test.js b/node_modules/selenium-webdriver/test/proxy_test.js index c25565b48..442ff606e 100644 --- a/node_modules/selenium-webdriver/test/proxy_test.js +++ b/node_modules/selenium-webdriver/test/proxy_test.js @@ -96,7 +96,7 @@ test.suite(function(env) { var driver; test.beforeEach(function() { driver = null; }); - test.afterEach(function() { driver && driver.quit(); }); + test.afterEach(function() { return driver && driver.quit(); }); function createDriver(proxy) { // For Firefox we need to explicitly enable proxies for localhost by @@ -104,7 +104,7 @@ test.suite(function(env) { let profile = new firefox.Profile(); profile.setPreference('network.proxy.no_proxies_on', ''); - driver = env.builder() + return driver = env.builder() .setFirefoxOptions(new firefox.Options().setProfile(profile)) .setProxy(proxy) .build(); @@ -116,14 +116,14 @@ test.suite(function(env) { // phantomjs 1.9.1 in webdriver mode does not appear to respect proxy // settings. test.ignore(env.browsers(Browser.PHANTOM_JS)). - it('can configure HTTP proxy host', function() { - createDriver(proxy.manual({ + it('can configure HTTP proxy host', function*() { + yield createDriver(proxy.manual({ http: proxyServer.host() })); - driver.get(helloServer.url()); - assert(driver.getTitle()).equalTo('Proxy page'); - assert(driver.findElement({tagName: 'h3'}).getText()). + yield driver.get(helloServer.url()); + yield assert(driver.getTitle()).equalTo('Proxy page'); + yield assert(driver.findElement({tagName: 'h3'}).getText()). equalTo('This is the proxy landing page'); }); @@ -134,20 +134,20 @@ test.suite(function(env) { Browser.FIREFOX, 'legacy-' + Browser.FIREFOX, Browser.PHANTOM_JS)). - it('can bypass proxy for specific hosts', function() { - createDriver(proxy.manual({ + it('can bypass proxy for specific hosts', function*() { + yield createDriver(proxy.manual({ http: proxyServer.host(), bypass: helloServer.host() })); - driver.get(helloServer.url()); - assert(driver.getTitle()).equalTo('Hello'); - assert(driver.findElement({tagName: 'h3'}).getText()). + yield driver.get(helloServer.url()); + yield assert(driver.getTitle()).equalTo('Hello'); + yield assert(driver.findElement({tagName: 'h3'}).getText()). equalTo('Hello, world!'); - driver.get(goodbyeServer.url()); - assert(driver.getTitle()).equalTo('Proxy page'); - assert(driver.findElement({tagName: 'h3'}).getText()). + yield driver.get(goodbyeServer.url()); + yield assert(driver.getTitle()).equalTo('Proxy page'); + yield assert(driver.findElement({tagName: 'h3'}).getText()). equalTo('This is the proxy landing page'); }); @@ -159,17 +159,17 @@ test.suite(function(env) { test.ignore(env.browsers( Browser.IE, Browser.OPERA, Browser.PHANTOM_JS, Browser.SAFARI)). describe('pac proxy settings', function() { - test.it('can configure proxy through PAC file', function() { - createDriver(proxy.pac(proxyServer.url('/proxy.pac'))); + test.it('can configure proxy through PAC file', function*() { + yield createDriver(proxy.pac(proxyServer.url('/proxy.pac'))); - driver.get(helloServer.url()); - assert(driver.getTitle()).equalTo('Proxy page'); - assert(driver.findElement({tagName: 'h3'}).getText()). + yield driver.get(helloServer.url()); + yield assert(driver.getTitle()).equalTo('Proxy page'); + yield assert(driver.findElement({tagName: 'h3'}).getText()). equalTo('This is the proxy landing page'); - driver.get(goodbyeServer.url()); - assert(driver.getTitle()).equalTo('Goodbye'); - assert(driver.findElement({tagName: 'h3'}).getText()). + yield driver.get(goodbyeServer.url()); + yield assert(driver.getTitle()).equalTo('Goodbye'); + yield assert(driver.findElement({tagName: 'h3'}).getText()). equalTo('Goodbye, world!'); }); }); diff --git a/node_modules/selenium-webdriver/test/remote_test.js b/node_modules/selenium-webdriver/test/remote_test.js index 5edc448bb..9b2b2eb73 100644 --- a/node_modules/selenium-webdriver/test/remote_test.js +++ b/node_modules/selenium-webdriver/test/remote_test.js @@ -26,6 +26,8 @@ var promise = require('../').promise, cmd = require('../lib/command'), remote = require('../remote'); +const {enablePromiseManager} = require('../lib/test/promise'); + describe('DriverService', function() { describe('start()', function() { var service; @@ -41,36 +43,34 @@ describe('DriverService', function() { return service.kill(); }); - it('fails if child-process dies', function(done) { + it('fails if child-process dies', function() { this.timeout(1000); - service.start(500) - .then(expectFailure.bind(null, done), verifyFailure.bind(null, done)); + return service.start(500).then(expectFailure, verifyFailure); }); - it('failures propagate through control flow if child-process dies', - function(done) { - this.timeout(1000); + enablePromiseManager(function() { + describe( + 'failures propagate through control flow if child-process dies', + function() { + it('', function() { + this.timeout(1000); - promise.controlFlow().execute(function() { - promise.controlFlow().execute(function() { - return service.start(500); + return promise.controlFlow().execute(function() { + promise.controlFlow().execute(function() { + return service.start(500); + }); + }).then(expectFailure, verifyFailure); + }); }); - }) - .then(expectFailure.bind(null, done), verifyFailure.bind(null, done)); - }); + }); - function verifyFailure(done, e) { - try { - assert.ok(!(e instanceof promise.CancellationError)); - assert.equal('Server terminated early with status 1', e.message); - done(); - } catch (ex) { - done(ex); - } + function verifyFailure(e) { + assert.ok(!(e instanceof promise.CancellationError)); + assert.equal('Server terminated early with status 1', e.message); } - function expectFailure(done) { - done(Error('expected to fail')); + function expectFailure() { + throw Error('expected to fail'); } }); }); diff --git a/node_modules/selenium-webdriver/test/safari_test.js b/node_modules/selenium-webdriver/test/safari_test.js index 41525d9cb..6032b865d 100644 --- a/node_modules/selenium-webdriver/test/safari_test.js +++ b/node_modules/selenium-webdriver/test/safari_test.js @@ -43,13 +43,11 @@ describe('safari.Options', function() { logPrefs = {}, caps = webdriver.Capabilities.chrome() .set(webdriver.Capability.PROXY, proxyPrefs) - .set(webdriver.Capability.LOGGING_PREFS, logPrefs) - .set('legacyDriver', true); + .set(webdriver.Capability.LOGGING_PREFS, logPrefs); let options = safari.Options.fromCapabilities(caps); assert(options.proxy_).equalTo(proxyPrefs); assert(options.logPrefs_).equalTo(logPrefs); - assert(options.legacyDriver_).equalTo(true); }); }); @@ -80,13 +78,11 @@ describe('safari.Options', function() { options .setLoggingPrefs(loggingPrefs) - .setProxy(proxyPrefs) - .useLegacyDriver(true); + .setProxy(proxyPrefs); let caps = options.toCapabilities(); assert(caps.get('proxy')).equalTo(proxyPrefs); assert(caps.get('loggingPrefs')).equalTo(loggingPrefs); - assert(caps.get('legacyDriver')).equalTo(true); }); }); }); @@ -95,13 +91,13 @@ test.suite(function(env) { describe('safaridriver', function() { let service; - test.afterEach(function() { + afterEach(function() { if (service) { return service.kill(); } }); - test.it('can start safaridriver', function() { + it('can start safaridriver', function() { service = new safari.ServiceBuilder().build(); return service.start().then(function(url) { diff --git a/node_modules/selenium-webdriver/test/session_test.js b/node_modules/selenium-webdriver/test/session_test.js index 1fb7475b4..546cd7fe9 100644 --- a/node_modules/selenium-webdriver/test/session_test.js +++ b/node_modules/selenium-webdriver/test/session_test.js @@ -27,27 +27,28 @@ test.suite(function(env) { var browsers = env.browsers; var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().build(); }); test.after(function() { - driver.quit(); + return driver.quit(); }); - test.it('can connect to an existing session', function() { - driver.get(Pages.simpleTestPage); - assert(driver.getTitle()).equalTo('Hello WebDriver'); + test.it('can connect to an existing session', function*() { + yield driver.get(Pages.simpleTestPage); + yield assert(driver.getTitle()).equalTo('Hello WebDriver'); return driver.getSession().then(session1 => { let driver2 = WebDriver.attachToSession( driver.getExecutor(), session1.getId()); - assert(driver2.getTitle()).equalTo('Hello WebDriver'); - - let session2Id = driver2.getSession().then(s => s.getId()); - assert(session2Id).equalTo(session1.getId()); + return assert(driver2.getTitle()).equalTo('Hello WebDriver') + .then(_ => { + let session2Id = driver2.getSession().then(s => s.getId()); + return assert(session2Id).equalTo(session1.getId()); + }); }); }); }); diff --git a/node_modules/selenium-webdriver/test/stale_element_test.js b/node_modules/selenium-webdriver/test/stale_element_test.js index 6ab8de7ec..d00b5d440 100644 --- a/node_modules/selenium-webdriver/test/stale_element_test.js +++ b/node_modules/selenium-webdriver/test/stale_element_test.js @@ -30,31 +30,33 @@ var Browser = require('..').Browser, test.suite(function(env) { var driver; - test.before(function() { driver = env.builder().build(); }); - test.after(function() { driver.quit(); }); + test.before(function*() { driver = yield env.builder().build(); }); + test.after(function() { return driver.quit(); }); - test.it( + // Element never goes stale in Safari. + test.ignore(env.browsers(Browser.SAFARI)). + it( 'dynamically removing elements from the DOM trigger a ' + 'StaleElementReferenceError', - function() { - driver.get(Pages.javascriptPage); + function*() { + yield driver.get(Pages.javascriptPage); - var toBeDeleted = driver.findElement(By.id('deleted')); - assert(toBeDeleted.isDisplayed()).isTrue(); + var toBeDeleted = yield driver.findElement(By.id('deleted')); + yield assert(toBeDeleted.getTagName()).isEqualTo('p'); - driver.findElement(By.id('delete')).click(); - driver.wait(until.stalenessOf(toBeDeleted), 5000); + yield driver.findElement(By.id('delete')).click(); + yield driver.wait(until.stalenessOf(toBeDeleted), 5000); }); - test.it('an element found in a different frame is stale', function() { - driver.get(Pages.missedJsReferencePage); + test.it('an element found in a different frame is stale', function*() { + yield driver.get(Pages.missedJsReferencePage); - var frame = driver.findElement(By.css('iframe[name="inner"]')); - driver.switchTo().frame(frame); + var frame = yield driver.findElement(By.css('iframe[name="inner"]')); + yield driver.switchTo().frame(frame); - var el = driver.findElement(By.id('oneline')); - driver.switchTo().defaultContent(); - el.getText().then(fail, function(e) { + var el = yield driver.findElement(By.id('oneline')); + yield driver.switchTo().defaultContent(); + return el.getText().then(fail, function(e) { assert(e).instanceOf(error.StaleElementReferenceError); }); }); diff --git a/node_modules/selenium-webdriver/test/tag_name_test.js b/node_modules/selenium-webdriver/test/tag_name_test.js index d5e18a9a2..b934e6bb3 100644 --- a/node_modules/selenium-webdriver/test/tag_name_test.js +++ b/node_modules/selenium-webdriver/test/tag_name_test.js @@ -24,11 +24,13 @@ var By = require('..').By, test.suite(function(env) { var driver; - test.after(function() { driver.quit(); }); + test.after(function() { return driver.quit(); }); - test.it('should return lower case tag name', function() { - driver = env.builder().build(); - driver.get(test.Pages.formPage); - assert(driver.findElement(By.id('cheese')).getTagName()).equalTo('input'); + test.it('should return lower case tag name', function*() { + driver = yield env.builder().build(); + yield driver.get(test.Pages.formPage); + + let el = yield driver.findElement(By.id('cheese')); + return assert(el.getTagName()).equalTo('input'); }); }); diff --git a/node_modules/selenium-webdriver/test/testing/assert_test.js b/node_modules/selenium-webdriver/test/testing/assert_test.js index 8c8848254..eaced8bf1 100644 --- a/node_modules/selenium-webdriver/test/testing/assert_test.js +++ b/node_modules/selenium-webdriver/test/testing/assert_test.js @@ -178,9 +178,8 @@ describe('assert', function() { }); it('waits for promised values', function() { - let d = Promise.defer(); - setTimeout(() => d.resolve(123), 10); - return assert(d.promise).closeTo(124, 1); + let p = new Promise(resolve => setTimeout(() => resolve(123), 10)); + return assert(p).closeTo(124, 1); }); }); diff --git a/node_modules/selenium-webdriver/test/testing/index_test.js b/node_modules/selenium-webdriver/test/testing/index_test.js index 31acff238..edc841bbe 100644 --- a/node_modules/selenium-webdriver/test/testing/index_test.js +++ b/node_modules/selenium-webdriver/test/testing/index_test.js @@ -17,8 +17,10 @@ 'use strict'; -var assert = require('assert'); -var promise = require('../..').promise; +const assert = require('assert'); +const promise = require('../..').promise; +const {enablePromiseManager} = require('../../lib/test/promise'); + var test = require('../../testing'); @@ -42,137 +44,181 @@ describe('Mocha Integration', function() { afterEach(function() { assert.equal(this.x, 2); }); }); - describe('timeout handling', function() { - describe('it does not reset the control flow on a non-timeout', function() { - var flowReset = false; + enablePromiseManager(function() { + describe('timeout handling', function() { + describe('it does not reset the control flow on a non-timeout', function() { + var flowReset = false; - beforeEach(function() { - flowReset = false; - test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset); - }); + beforeEach(function() { + flowReset = false; + test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset); + }); - test.it('', function() { - this.timeout(100); - return promise.delayed(50); - }); + test.it('', function() { + this.timeout(100); + return promise.delayed(50); + }); - afterEach(function() { - assert.ok(!flowReset); - test.controlFlow().removeListener( - promise.ControlFlow.EventType.RESET, onreset); - }); + afterEach(function() { + assert.ok(!flowReset); + test.controlFlow().removeListener( + promise.ControlFlow.EventType.RESET, onreset); + }); - function onreset() { - flowReset = true; - } - }); + function onreset() { + flowReset = true; + } + }); - describe('it resets the control flow after a timeout' ,function() { - var timeoutErr, flowReset; + describe('it resets the control flow after a timeout' ,function() { + var timeoutErr, flowReset; - beforeEach(function() { - flowReset = false; - test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset); - }); + beforeEach(function() { + flowReset = false; + test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset); + }); - test.it('', function() { - var callback = this.runnable().callback; - var test = this; - this.runnable().callback = function(err) { - timeoutErr = err; - // Reset our timeout to 0 so Mocha does not fail the test. - test.timeout(0); - // When we invoke the real callback, do not pass along the error so - // Mocha does not fail the test. - return callback.call(this); - }; - - test.timeout(50); - return promise.defer().promise; - }); + test.it('', function() { + var callback = this.runnable().callback; + var test = this; + this.runnable().callback = function(err) { + timeoutErr = err; + // Reset our timeout to 0 so Mocha does not fail the test. + test.timeout(0); + // When we invoke the real callback, do not pass along the error so + // Mocha does not fail the test. + return callback.call(this); + }; + + test.timeout(50); + return promise.defer().promise; + }); - afterEach(function() { - return Promise.resolve().then(function() { - test.controlFlow().removeListener( - promise.ControlFlow.EventType.RESET, onreset); - assert.ok(flowReset, 'control flow was not reset after a timeout'); + afterEach(function() { + return Promise.resolve().then(function() { + test.controlFlow().removeListener( + promise.ControlFlow.EventType.RESET, onreset); + assert.ok(flowReset, 'control flow was not reset after a timeout'); + }); }); + + function onreset() { + flowReset = true; + } }); + }); + + describe('async "done" support', function() { + this.timeout(2*1000); + + var waited = false; + var DELAY = 100; // ms enough to notice + + // Each test asynchronously sets waited to true, so clear/check waited + // before/after: + beforeEach(function() { + waited = false; + }); + + afterEach(function() { + assert.strictEqual(waited, true); + }); + + // --- First, vanilla mocha "it" should support the "done" callback correctly. + + // This 'it' should block until 'done' is invoked + it('vanilla delayed', function(done) { + setTimeout(function delayedVanillaTimeout() { + waited = true; + done(); + }, DELAY); + }); + + // --- Now with the webdriver wrappers for 'it' should support the "done" callback: + + test.it('delayed', function(done) { + assert(done); + assert.strictEqual(typeof done, 'function'); + setTimeout(function delayedTimeoutCallback() { + waited = true; + done(); + }, DELAY); + }); + + // --- And test that the webdriver wrapper for 'it' works with a returned promise, too: + + test.it('delayed by promise', function() { + var defer = promise.defer(); + setTimeout(function delayedPromiseCallback() { + waited = true; + defer.fulfill('ignored'); + }); + return defer.promise; + }); + }); - function onreset() { - flowReset = true; - } + describe('ControlFlow and "done" work together', function() { + var flow, order; + before(function() { + order = []; + flow = test.controlFlow(); + flow.execute(function() { order.push(1); }); + }); + + test.it('control flow updates and async done', function(done) { + flow.execute(function() { order.push(2); }); + flow.execute(function() { order.push(3); }).then(function() { + order.push(4); + }); + done(); + }); + + after(function() { + assert.deepEqual([1, 2, 3, 4], order); + }); }); }); -}); -describe('Mocha async "done" support', function() { - this.timeout(2*1000); - - var waited = false; - var DELAY = 100; // ms enough to notice - - // Each test asynchronously sets waited to true, so clear/check waited - // before/after: - beforeEach(function() { - waited = false; - }); - - afterEach(function() { - assert.strictEqual(waited, true); - }); - - // --- First, vanilla mocha "it" should support the "done" callback correctly. - - // This 'it' should block until 'done' is invoked - it('vanilla delayed', function(done) { - setTimeout(function delayedVanillaTimeout() { - waited = true; - done(); - }, DELAY); - }); - - // --- Now with the webdriver wrappers for 'it' should support the "done" callback: - - test.it('delayed', function(done) { - assert(done); - assert.strictEqual(typeof done, 'function'); - setTimeout(function delayedTimeoutCallback() { - waited = true; - done(); - }, DELAY); - }); - - // --- And test that the webdriver wrapper for 'it' works with a returned promise, too: - - test.it('delayed by promise', function() { - var defer = promise.defer(); - setTimeout(function delayedPromiseCallback() { - waited = true; - defer.fulfill('ignored'); - }); - return defer.promise; - }); + describe('generator support', function() { + let arr; -}); + beforeEach(() => arr = []); + afterEach(() => assert.deepEqual(arr, [0, 1, 2, 3])); + + test.it('sync generator', function* () { + arr.push(yield arr.length); + arr.push(yield arr.length); + arr.push(yield arr.length); + arr.push(yield arr.length); + }); + + test.it('async generator', function* () { + arr.push(yield Promise.resolve(arr.length)); + arr.push(yield Promise.resolve(arr.length)); + arr.push(yield Promise.resolve(arr.length)); + arr.push(yield Promise.resolve(arr.length)); + }); + + test.it('generator returns promise', function*() { + arr.push(yield Promise.resolve(arr.length)); + arr.push(yield Promise.resolve(arr.length)); + arr.push(yield Promise.resolve(arr.length)); + setTimeout(_ => arr.push(arr.length), 10); + return new Promise((resolve) => setTimeout(_ => resolve(), 25)); + }); -describe('ControlFlow and "done" work together', function() { - var flow, order; - before(function() { - order = []; - flow = test.controlFlow(); - flow.execute(function() { order.push(1); }); - }); - - test.it('control flow updates and async done', function(done) { - flow.execute(function() { order.push(2); }); - flow.execute(function() { order.push(3); }).then(function() { - order.push(4); + describe('generator runs with proper "this" context', () => { + before(function() { this.values = [0, 1, 2, 3]; }); + test.it('', function*() { + arr = this.values; }); - done(); - }) + }); - after(function() { - assert.deepEqual([1, 2, 3, 4], order); - }) + it('generator function must not take a callback', function() { + arr = [0, 1, 2, 3]; // For teardown hook. + assert.throws(_ => { + test.it('', function*(done){}); + }, TypeError); + }); + }); }); diff --git a/node_modules/selenium-webdriver/test/upload_test.js b/node_modules/selenium-webdriver/test/upload_test.js index 3329f7ca7..c677550fc 100644 --- a/node_modules/selenium-webdriver/test/upload_test.js +++ b/node_modules/selenium-webdriver/test/upload_test.js @@ -41,13 +41,13 @@ test.suite(function(env) { }) var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().build(); }); test.after(function() { if (driver) { - driver.quit(); + return driver.quit(); } }); @@ -58,29 +58,29 @@ test.suite(function(env) { // See https://github.com/ariya/phantomjs/issues/12506 Browser.PHANTOM_JS, Browser.SAFARI)). - it('can upload files', function() { + it('can upload files', function*() { driver.setFileDetector(new remote.FileDetector); - driver.get(Pages.uploadPage); + yield driver.get(Pages.uploadPage); - var fp = driver.call(function() { + var fp = yield driver.call(function() { return io.tmpFile().then(function(fp) { fs.writeFileSync(fp, FILE_HTML); return fp; }); }); - driver.findElement(By.id('upload')).sendKeys(fp); - driver.findElement(By.id('go')).click(); + yield driver.findElement(By.id('upload')).sendKeys(fp); + yield driver.findElement(By.id('go')).click(); // Uploading files across a network may take a while, even if they're small. - var label = driver.findElement(By.id('upload_label')); - driver.wait(until.elementIsNotVisible(label), + var label = yield driver.findElement(By.id('upload_label')); + yield driver.wait(until.elementIsNotVisible(label), 10 * 1000, 'File took longer than 10 seconds to upload!'); - var frame = driver.findElement(By.id('upload_target')); - driver.switchTo().frame(frame); - assert(driver.findElement(By.css('body')).getText()) + var frame = yield driver.findElement(By.id('upload_target')); + yield driver.switchTo().frame(frame); + yield assert(driver.findElement(By.css('body')).getText()) .equalTo(LOREM_IPSUM_TEXT); }); }); diff --git a/node_modules/selenium-webdriver/test/window_test.js b/node_modules/selenium-webdriver/test/window_test.js index 6213d19ac..73cdd802d 100644 --- a/node_modules/selenium-webdriver/test/window_test.js +++ b/node_modules/selenium-webdriver/test/window_test.js @@ -26,113 +26,99 @@ var Browser = require('..').Browser, test.suite(function(env) { var driver; - test.before(function() { driver = env.builder().build(); }); - test.after(function() { driver.quit(); }); + test.before(function*() { driver = yield env.builder().build(); }); + test.after(function() { return driver.quit(); }); test.beforeEach(function() { - driver.switchTo().defaultContent(); + return driver.switchTo().defaultContent(); }); - test.it('can set size of the current window', function() { - driver.get(test.Pages.echoPage); - changeSizeBy(-20, -20); + test.it('can set size of the current window', function*() { + yield driver.get(test.Pages.echoPage); + yield changeSizeBy(-20, -20); }); - test.it('can set size of the current window from frame', function() { - driver.get(test.Pages.framesetPage); + test.it('can set size of the current window from frame', function*() { + yield driver.get(test.Pages.framesetPage); - var frame = driver.findElement({css: 'frame[name="fourth"]'}); - driver.switchTo().frame(frame); - changeSizeBy(-20, -20); + var frame = yield driver.findElement({css: 'frame[name="fourth"]'}); + yield driver.switchTo().frame(frame); + yield changeSizeBy(-20, -20); }); - test.it('can set size of the current window from iframe', function() { - driver.get(test.Pages.iframePage); + test.it('can set size of the current window from iframe', function*() { + yield driver.get(test.Pages.iframePage); - var frame = driver.findElement({css: 'iframe[name="iframe1-name"]'}); - driver.switchTo().frame(frame); - changeSizeBy(-20, -20); + var frame = yield driver.findElement({css: 'iframe[name="iframe1-name"]'}); + yield driver.switchTo().frame(frame); + yield changeSizeBy(-20, -20); }); - test.it('can switch to a new window', function() { - driver.get(test.Pages.xhtmlTestPage); + test.it('can switch to a new window', function*() { + yield driver.get(test.Pages.xhtmlTestPage); - driver.getWindowHandle().then(function(handle) { - driver.getAllWindowHandles().then(function(originalHandles) { - driver.findElement(By.linkText("Open new window")).click(); + let handle = yield driver.getWindowHandle(); + let originalHandles = yield driver.getAllWindowHandles(); - driver.wait(forNewWindowToBeOpened(originalHandles), 2000); + yield driver.findElement(By.linkText("Open new window")).click(); + yield driver.wait(forNewWindowToBeOpened(originalHandles), 2000); + yield assert(driver.getTitle()).equalTo("XHTML Test Page"); - assert(driver.getTitle()).equalTo("XHTML Test Page"); + let newHandle = yield getNewWindowHandle(originalHandles); - getNewWindowHandle(originalHandles).then(function(newHandle) { - driver.switchTo().window(newHandle); - - assert(driver.getTitle()).equalTo("We Arrive Here") - }); - }); - }); + yield driver.switchTo().window(newHandle); + yield assert(driver.getTitle()).equalTo("We Arrive Here"); }); - // See https://github.com/mozilla/geckodriver/issues/113 - test.ignore(env.browsers(Browser.FIREFOX)). - it('can set the window position of the current window', function() { - driver.manage().window().getPosition().then(function(position) { - driver.manage().window().setSize(640, 480); - driver.manage().window().setPosition(position.x + 10, position.y + 10); - - // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0) - if (env.currentBrowser() === Browser.PHANTOM_JS) { - driver.manage().window().getPosition().then(function(position) { - assert(position.x).equalTo(0); - assert(position.y).equalTo(0); - }); - } else { - var dx = position.x + 10; - var dy = position.y + 10; - // On OSX, Safari position's the window relative to below the menubar - // at the top of the screen, which is 23 pixels tall. - if (env.currentBrowser() === Browser.SAFARI && - process.platform === 'darwin') { - dy += 23; - } - } - }); + test.it('can set the window position of the current window', function*() { + let position = yield driver.manage().window().getPosition(); + + yield driver.manage().window().setSize(640, 480); + yield driver.manage().window().setPosition(position.x + 10, position.y + 10); + + // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0) + if (env.currentBrowser() === Browser.PHANTOM_JS) { + position = yield driver.manage().window().getPosition(); + assert(position.x).equalTo(0); + assert(position.y).equalTo(0); + } else { + var dx = position.x + 10; + var dy = position.y + 10; + return driver.wait(forPositionToBe(dx, dy), 1000); + } }); - // See https://github.com/mozilla/geckodriver/issues/113 - test.ignore(env.browsers(Browser.FIREFOX)). - it('can set the window position from a frame', function() { - driver.get(test.Pages.iframePage); - driver.switchTo().frame('iframe1-name'); - driver.manage().window().getPosition().then(function(position) { - driver.manage().window().setSize(640, 480); - driver.manage().window().setPosition(position.x + 10, position.y + 10); - - // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0) - if (env.currentBrowser() === Browser.PHANTOM_JS) { - driver.manage().window().getPosition().then(function(position) { - assert(position.x).equalTo(0); - assert(position.y).equalTo(0); - }); - } else { - var dx = position.x + 10; - var dy = position.y + 10; - // On OSX, Safari position's the window relative to below the menubar - // at the top of the screen, which is 23 pixels tall. - if (env.currentBrowser() === Browser.SAFARI && - process.platform === 'darwin') { - dy += 23; - } - driver.wait(forPositionToBe(dx, dy), 1000); - } - }); + test.it('can set the window position from a frame', function*() { + yield driver.get(test.Pages.iframePage); + + let frame = yield driver.findElement(By.name('iframe1-name')); + yield driver.switchTo().frame(frame); + + let position = yield driver.manage().window().getPosition(); + yield driver.manage().window().setSize(640, 480); + yield driver.manage().window().setPosition(position.x + 10, position.y + 10); + + // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0) + if (env.currentBrowser() === Browser.PHANTOM_JS) { + return driver.manage().window().getPosition().then(function(position) { + assert(position.x).equalTo(0); + assert(position.y).equalTo(0); + }); + } else { + var dx = position.x + 10; + var dy = position.y + 10; + return driver.wait(forPositionToBe(dx, dy), 1000); + } }); function changeSizeBy(dx, dy) { - driver.manage().window().getSize().then(function(size) { - driver.manage().window().setSize(size.width + dx, size.height + dy); - driver.wait(forSizeToBe(size.width + dx, size.height + dy), 1000); + return driver.manage().window().getSize().then(function(size) { + return driver.manage().window() + .setSize(size.width + dx, size.height + dy) + .then(_ => { + return driver.wait( + forSizeToBe(size.width + dx, size.height + dy), 1000); + }); }); } diff --git a/node_modules/selenium-webdriver/testing/index.js b/node_modules/selenium-webdriver/testing/index.js index 0e9d06232..5bb82d15f 100644 --- a/node_modules/selenium-webdriver/testing/index.js +++ b/node_modules/selenium-webdriver/testing/index.js @@ -28,6 +28,16 @@ * - it.skip * - xit * + * Each of the wrapped functions support generator functions. If the generator + * {@linkplain ../lib/promise.consume yields a promise}, the test will wait + * for that promise to resolve before invoking the next iteration of the + * generator: + * + * test.it('generators', function*() { + * let x = yield Promise.resolve(1); + * assert.equal(x, 1); + * }); + * * The provided wrappers leverage the {@link webdriver.promise.ControlFlow} * to simplify writing asynchronous tests: * @@ -66,8 +76,22 @@ * function maybe() { return Math.random() < 0.5; } */ -var promise = require('..').promise; -var flow = promise.controlFlow(); +'use strict'; + +const promise = require('..').promise; +const flow = (function() { + const initial = process.env['SELENIUM_PROMISE_MANAGER']; + try { + process.env['SELENIUM_PROMISE_MANAGER'] = '1'; + return promise.controlFlow(); + } finally { + if (initial === undefined) { + delete process.env['SELENIUM_PROMISE_MANAGER']; + } else { + process.env['SELENIUM_PROMISE_MANAGER'] = initial; + } + } +})(); /** @@ -119,13 +143,38 @@ function wrapArgument(value) { * Should preserve the semantics of Mocha's Runnable.prototype.run (See * https://github.com/mochajs/mocha/blob/master/lib/runnable.js#L192) * - * @param {Function} fn - * @return {Function} + * @param {!Function} fn + * @return {!Function} */ function makeAsyncTestFn(fn) { - var async = fn.length > 0; // if test function expects a callback, its "async" + const isAsync = fn.length > 0; + const isGenerator = promise.isGenerator(fn); + if (isAsync && isGenerator) { + throw TypeError( + 'generator-based tests must not take a callback; for async testing,' + + ' return a promise (or yield on a promise)'); + } var ret = /** @type {function(this: mocha.Context)}*/ (function(done) { + const runTest = (resolve, reject) => { + try { + if (isAsync) { + fn.call(this, err => err ? reject(err) : resolve()); + } else if (isGenerator) { + resolve(promise.consume(fn, this)); + } else { + resolve(fn.call(this)); + } + } catch (ex) { + reject(ex); + } + }; + + if (!promise.USE_PROMISE_MANAGER) { + new Promise(runTest).then(seal(done), done); + return; + } + var runnable = this.runnable(); var mochaCallback = runnable.callback; runnable.callback = function() { @@ -133,25 +182,9 @@ function makeAsyncTestFn(fn) { return mochaCallback.apply(this, arguments); }; - var testFn = fn.bind(this); flow.execute(function controlFlowExecute() { return new promise.Promise(function(fulfill, reject) { - if (async) { - // If testFn is async (it expects a done callback), resolve the promise of this - // test whenever that callback says to. Any promises returned from testFn are - // ignored. - testFn(function testFnDoneCallback(err) { - if (err) { - reject(err); - } else { - fulfill(); - } - }); - } else { - // Without a callback, testFn can return a promise, or it will - // be assumed to have completed synchronously - fulfill(testFn()); - } + return runTest(fulfill, reject); }, flow); }, runnable.fullTitle()).then(seal(done), done); }); @@ -196,6 +229,45 @@ function ignore(predicateFn) { } +/** + * @param {string} name + * @return {!Function} + * @throws {TypeError} + */ +function getMochaGlobal(name) { + let fn = global[name]; + let type = typeof fn; + if (type !== 'function') { + throw TypeError( + `Expected global.${name} to be a function, but is ${type}. ` + + 'This can happen if you try using this module when running ' + + 'with node directly instead of using the mocha executable'); + } + return fn; +} + + +const WRAPPED = { + after: null, + afterEach: null, + before: null, + beforeEach: null, + it: null, + itOnly: null, + xit: null +}; + + +function wrapIt() { + if (!WRAPPED.it) { + let it = getMochaGlobal('it'); + WRAPPED.it = wrapped(it); + WRAPPED.itOnly = wrapped(it.only); + } +} + + + // PUBLIC API @@ -211,67 +283,134 @@ exports.controlFlow = function(){ /** * Registers a new test suite. * @param {string} name The suite name. - * @param {function()=} fn The suite function, or {@code undefined} to define + * @param {function()=} opt_fn The suite function, or `undefined` to define * a pending test suite. */ -exports.describe = global.describe; +exports.describe = function(name, opt_fn) { + let fn = getMochaGlobal('describe'); + return opt_fn ? fn(name, opt_fn) : fn(name); +}; + /** * Defines a suppressed test suite. * @param {string} name The suite name. - * @param {function()=} fn The suite function, or {@code undefined} to define + * @param {function()=} opt_fn The suite function, or `undefined` to define * a pending test suite. */ -exports.xdescribe = global.xdescribe; -exports.describe.skip = global.describe.skip; +exports.describe.skip = function(name, opt_fn) { + let fn = getMochaGlobal('describe'); + return opt_fn ? fn.skip(name, opt_fn) : fn.skip(name); +}; + + +/** + * Defines a suppressed test suite. + * @param {string} name The suite name. + * @param {function()=} opt_fn The suite function, or `undefined` to define + * a pending test suite. + */ +exports.xdescribe = function(name, opt_fn) { + let fn = getMochaGlobal('xdescribe'); + return opt_fn ? fn(name, opt_fn) : fn(name); +}; + /** * Register a function to call after the current suite finishes. * @param {function()} fn . */ -exports.after = wrapped(global.after); +exports.after = function(fn) { + if (!WRAPPED.after) { + WRAPPED.after = wrapped(getMochaGlobal('after')); + } + WRAPPED.after(fn); +}; + /** * Register a function to call after each test in a suite. * @param {function()} fn . */ -exports.afterEach = wrapped(global.afterEach); +exports.afterEach = function(fn) { + if (!WRAPPED.afterEach) { + WRAPPED.afterEach = wrapped(getMochaGlobal('afterEach')); + } + WRAPPED.afterEach(fn); +}; + /** * Register a function to call before the current suite starts. * @param {function()} fn . */ -exports.before = wrapped(global.before); +exports.before = function(fn) { + if (!WRAPPED.before) { + WRAPPED.before = wrapped(getMochaGlobal('before')); + } + WRAPPED.before(fn); +}; /** * Register a function to call before each test in a suite. * @param {function()} fn . */ -exports.beforeEach = wrapped(global.beforeEach); +exports.beforeEach = function(fn) { + if (!WRAPPED.beforeEach) { + WRAPPED.beforeEach = wrapped(getMochaGlobal('beforeEach')); + } + WRAPPED.beforeEach(fn); +}; /** * Add a test to the current suite. * @param {string} name The test name. - * @param {function()=} fn The test function, or {@code undefined} to define + * @param {function()=} opt_fn The test function, or `undefined` to define * a pending test case. */ -exports.it = wrapped(global.it); +exports.it = function(name, opt_fn) { + wrapIt(); + if (opt_fn) { + WRAPPED.it(name, opt_fn); + } else { + WRAPPED.it(name); + } +}; /** * An alias for {@link #it()} that flags the test as the only one that should * be run within the current suite. * @param {string} name The test name. - * @param {function()=} fn The test function, or {@code undefined} to define + * @param {function()=} opt_fn The test function, or `undefined` to define * a pending test case. */ -exports.iit = exports.it.only = wrapped(global.it.only); +exports.it.only = function(name, opt_fn) { + wrapIt(); + if (opt_fn) { + WRAPPED.itOnly(name, opt_fn); + } else { + WRAPPED.itOnly(name); + } +}; + /** * Adds a test to the current suite while suppressing it so it is not run. * @param {string} name The test name. - * @param {function()=} fn The test function, or {@code undefined} to define + * @param {function()=} opt_fn The test function, or `undefined` to define * a pending test case. */ -exports.xit = exports.it.skip = wrapped(global.xit); +exports.xit = function(name, opt_fn) { + if (!WRAPPED.xit) { + WRAPPED.xit = wrapped(getMochaGlobal('xit')); + } + if (opt_fn) { + WRAPPED.xit(name, opt_fn); + } else { + WRAPPED.xit(name); + } +}; + +exports.it.skip = exports.xit; exports.ignore = ignore; |