diff --git a/javascript/node/selenium-webdriver/CHANGES.md b/javascript/node/selenium-webdriver/CHANGES.md index 17bbfc4058ee1..09cf6d0a76f66 100644 --- a/javascript/node/selenium-webdriver/CHANGES.md +++ b/javascript/node/selenium-webdriver/CHANGES.md @@ -16,6 +16,10 @@ "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 + ### API Changes diff --git a/javascript/node/selenium-webdriver/example/google_search.js b/javascript/node/selenium-webdriver/example/google_search.js index 22d0d21ce0d18..fc95acea7322e 100644 --- a/javascript/node/selenium-webdriver/example/google_search.js +++ b/javascript/node/selenium-webdriver/example/google_search.js @@ -35,16 +35,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/javascript/node/selenium-webdriver/index.js b/javascript/node/selenium-webdriver/index.js index dae91e01b346c..d619dea84e680 100644 --- a/javascript/node/selenium-webdriver/index.js +++ b/javascript/node/selenium-webdriver/index.js @@ -594,7 +594,7 @@ class Builder { * __Note:__ this method is purely a convenience wrapper around * {@link #build()}. * - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * fulfilled with the newly created WebDriver instance once the browser * has been fully initialized. * @see #build() diff --git a/javascript/node/selenium-webdriver/lib/promise.js b/javascript/node/selenium-webdriver/lib/promise.js index af0b907109885..e68076bbf7adb 100644 --- a/javascript/node/selenium-webdriver/lib/promise.js +++ b/javascript/node/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 @@ -643,7 +679,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. @@ -836,7 +871,7 @@ function hasMarkerSymbol(object, symbol) { * Thenable is a promise-like object with a {@code then} method which may be * used to schedule callbacks on a promised value. * - * @interface + * @record * @extends {IThenable} * @template T */ @@ -871,8 +906,8 @@ class Thenable { * @param {?(function(*): (R|IThenable))=} opt_errback * The function to call if this promise is rejected. The function should * expect a single argument: the rejection reason. - * @return {!ManagedPromise} A new promise which will be - * resolved with the result of the invoked callback. + * @return {!Thenable} A new promise which will be resolved with the result + * of the invoked callback. * @template R */ then(opt_callback, opt_errback) {} @@ -896,8 +931,8 @@ class Thenable { * @param {function(*): (R|IThenable)} errback The * function to call if this promise is rejected. The function should * expect a single argument: the rejection reason. - * @return {!ManagedPromise} A new promise which will be - * resolved with the result of the invoked callback. + * @return {!Thenable} A new promise which will be resolved with the result + * of the invoked callback. * @template R */ catch(errback) {} @@ -991,6 +1026,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} */ @@ -1411,19 +1452,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); }); } @@ -1480,12 +1513,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); @@ -1560,16 +1593,15 @@ function thenFinally(promise, callback) { * 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); } @@ -1601,14 +1633,14 @@ function asap(value, callback, opt_errback) { * * @param {!Array<(T|!ManagedPromise)>} arr An array of * promises to wait on. - * @return {!ManagedPromise>} A promise that is + * @return {!Thenable>} A promise that is * fulfilled with an array containing the fulfilled values of the * input array, or rejected with the same reason as the first * rejected value. * @template T */ function all(arr) { - return new ManagedPromise(function(fulfill, reject) { + return createPromise(function(fulfill, reject) { var n = arr.length; var values = []; @@ -1662,12 +1694,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) { @@ -1720,12 +1752,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; @@ -1773,7 +1805,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) { @@ -1787,7 +1819,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) { @@ -1814,13 +1846,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) { @@ -1832,8 +1864,9 @@ function fullyResolveKeys(obj) { } return n; })(); + if (!numKeys) { - return fulfilled(obj); + return createPromise(resolve => resolve(obj)); } function forEachProperty(obj, fn) { @@ -1847,7 +1880,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) { @@ -1881,6 +1914,228 @@ function fullyResolveKeys(obj) { ////////////////////////////////////////////////////////////////////////////// +/** + * Defines methods for coordinating the execution of asynchronous tasks. + * @record + */ +class Scheduler { + /** + * Schedules a task for execution. If the task function is a generator, the + * task will be executed using {@link ./promise.consume consume()}. + * + * @param {function(): (T|IThenable)} fn The function to call to start the + * task. + * @param {string=} opt_description A description of the task for debugging + * purposes. + * @return {!Thenable} A promise that will be resolved with the task + * result. + * @template T + */ + execute(fn, opt_description) {} + + /** + * Creates a new promise using the given resolver function. + * + * @param {function( + * function((T|IThenable|Thenable|null)=), + * function(*=))} resolver + * @return {!Thenable} + * @template T + */ + promise(resolver) {} + + /** + * Schedules a `setTimeout` call. + * + * @param {number} ms The timeout delay, in milliseconds. + * @param {string=} opt_description A description to accompany the timeout. + * @return {!Thenable} A promise that will be resolved when the timeout + * fires. + */ + timeout(ms, opt_description) {} + + /** + * Schedules a task to wait for a condition to hold. + * + * If the condition is defined as a function, it may return any value. Promies + * will be resolved before testing if the condition holds (resolution time + * counts towards the timeout). Once resolved, values are always evaluated as + * booleans. + * + * If the condition function throws, or returns a rejected promise, the + * wait task will fail. + * + * If the condition is defined as a promise, the scheduler will wait for it to + * settle. If the timeout expires before the promise settles, the promise + * returned by this function will be rejected. + * + * If this function is invoked with `timeout === 0`, or the timeout is + * omitted, this scheduler will wait indefinitely for the condition to be + * satisfied. + * + * @param {(!IThenable|function())} condition The condition to poll, + * or a promise to wait on. + * @param {number=} opt_timeout How long to wait, in milliseconds, for the + * condition to hold before timing out. If omitted, the flow will wait + * indefinitely. + * @param {string=} opt_message An optional error message to include if the + * wait times out; defaults to the empty string. + * @return {!Thenable} A promise that will be fulfilled + * when the condition has been satisified. The promise shall be rejected + * if the wait times out waiting for the condition. + * @throws {TypeError} If condition is not a function or promise or if timeout + * is not a number >= 0. + * @template T + */ + wait(condition, opt_timeout, opt_message) {} +} + + +function usePromiseManager() { + return process.env['SELENIUM_PROMISE_MANAGER'] === undefined + || !/^0|false$/i.test(process.env['SELENIUM_PROMISE_MANAGER']); +} + + +/** + * @param {function( + * function((T|IThenable|Thenable|null)=), + * function(*=))} resolver + * @return {!Thenable} + * @template T + */ +function createPromise(resolver) { + let ctor = usePromiseManager() ? ManagedPromise : NativePromise; + return new ctor(resolver); +} + + +/** + * @param {!Scheduler} scheduler The scheduler to use. + * @param {(!IThenable|function())} condition The condition to poll, + * or a promise to wait on. + * @param {number=} opt_timeout How long to wait, in milliseconds, for the + * condition to hold before timing out. If omitted, the flow will wait + * indefinitely. + * @param {string=} opt_message An optional error message to include if the + * wait times out; defaults to the empty string. + * @return {!Thenable} A promise that will be fulfilled + * when the condition has been satisified. The promise shall be rejected + * if the wait times out waiting for the condition. + * @throws {TypeError} If condition is not a function or promise or if timeout + * is not a number >= 0. + * @template T + */ +function scheduleWait(scheduler, condition, opt_timeout, opt_message) { + let timeout = opt_timeout || 0; + if (typeof timeout !== 'number' || timeout < 0) { + throw TypeError('timeout must be a number >= 0: ' + timeout); + } + + if (isPromise(condition)) { + return scheduler.execute(function() { + if (!timeout) { + return condition; + } + return scheduler.promise(function(fulfill, reject) { + let start = Date.now(); + let timer = setTimeout(function() { + timer = null; + reject(Error((opt_message ? opt_message + '\n' : '') + + 'Timed out waiting for promise to resolve after ' + + (Date.now() - start) + 'ms')); + }, timeout); + + /** @type {Thenable} */(condition).then( + function(value) { + timer && clearTimeout(timer); + fulfill(value); + }, + function(error) { + timer && clearTimeout(timer); + reject(error); + }); + }); + }, opt_message || ''); + } + + if (typeof condition !== 'function') { + throw TypeError('Invalid condition; must be a function or promise: ' + + typeof condition); + } + + if (isGenerator(condition)) { + let original = condition; + condition = () => consume(original); + } + + 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((opt_message ? opt_message + '\n' : '') + + 'Wait timed out after ' + elapsed + 'ms')); + } else { + // Do not use asyncRun here because we need a non-micro yield + // here so the UI thread is given a chance when running in a + // browser. + setTimeout(pollCondition, 0); + } + }, reject); + } + }); + }, opt_message || ''); +} + + +/** + * A scheduler that executes all tasks immediately, with no coordination. This + * class is an event emitter for API compatibility with the {@link ControlFlow}, + * however, it emits no events. + * + * @implements {Scheduler} + */ +class SimpleScheduler extends events.EventEmitter { + /** @override */ + execute(fn) { + return this.promise((resolve, reject) => { + try { + if (isGenerator(fn)) { + consume(fn).then(resolve, reject); + } else { + resolve(fn.call(undefined)); + } + } catch (ex) { + reject(ex); + } + }); + } + + /** @override */ + promise(resolver) { + return new NativePromise(resolver); + } + + /** @override */ + timeout(ms) { + return this.promise(resolve => setTimeout(_ => resolve(), ms)); + } + + /** @override */ + wait(condition, opt_timeout, opt_message) { + return scheduleWait(this, condition, opt_timeout, opt_message); + } +} +const SIMPLE_SCHEDULER = new SimpleScheduler; + /** * Handles the execution of scheduled tasks, each of which may be an @@ -1910,10 +2165,17 @@ function fullyResolveKeys(obj) { * 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} */ @@ -2083,21 +2345,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|IThenable)} fn The function to - * call to start the task. If the function returns a promise, - * this instance will wait for it to be resolved before starting the - * next task. - * @param {string=} opt_description A description of the task. - * @return {!Thenable} 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; @@ -2119,126 +2367,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 {!Thenable} 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 {(!IThenable|function())} condition The condition to poll, - * or a promise to wait on. - * @param {number=} opt_timeout How long to wait, in milliseconds, for the - * condition to hold before timing out. If omitted, the flow will wait - * indefinitely. - * @param {string=} opt_message An optional error message to include if the - * wait times out; defaults to the empty string. - * @return {!Thenable} A promise that will be fulfilled - * when the condition has been satisified. The promise shall be rejected - * if the wait times out waiting for the condition. - * @throws {TypeError} If condition is not a function or promise or if timeout - * is not a number >= 0. - * @template T - */ + /** @override */ wait(condition, opt_timeout, opt_message) { - var timeout = opt_timeout || 0; - if (typeof timeout !== 'number' || timeout < 0) { - throw TypeError('timeout must be a number >= 0: ' + timeout); - } - - if (isPromise(condition)) { - return this.execute(function() { - if (!timeout) { - return condition; - } - return new ManagedPromise(function(fulfill, reject) { - var start = Date.now(); - var timer = setTimeout(function() { - timer = null; - reject(Error((opt_message ? opt_message + '\n' : '') + - 'Timed out waiting for promise to resolve after ' + - (Date.now() - start) + 'ms')); - }, timeout); - - /** @type {Thenable} */(condition).then( - function(value) { - timer && clearTimeout(timer); - fulfill(value); - }, - function(error) { - timer && clearTimeout(timer); - reject(error); - }); - }); - }, opt_message || ''); - } - - if (typeof condition !== 'function') { - throw TypeError('Invalid condition; must be a function or promise: ' + - typeof condition); - } - - if (isGenerator(condition)) { - let original = condition; - condition = () => consume(original); - } - - var self = this; - return this.execute(function() { - var startTime = Date.now(); - return new ManagedPromise(function(fulfill, reject) { - pollCondition(); - - function pollCondition() { - var conditionFn = /** @type {function()} */(condition); - self.execute(conditionFn).then(function(value) { - var elapsed = Date.now() - startTime; - if (!!value) { - fulfill(value); - } else if (timeout && elapsed >= timeout) { - reject(new Error((opt_message ? opt_message + '\n' : '') + - 'Wait timed out after ' + elapsed + 'ms')); - } else { - // Do not use asyncRun here because we need a non-micro yield - // here so the UI thread is given a chance when running in a - // browser. - setTimeout(pollCondition, 0); - } - }, reject); - } - }); - }, opt_message || ''); + return scheduleWait(this, condition, opt_timeout, opt_message); } /** @@ -2907,9 +3050,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; /** @@ -2928,6 +3071,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'); } @@ -2937,10 +3085,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; } @@ -3004,53 +3163,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 (!isPending(deferred.promise)) { - 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); + } + }); } @@ -3065,6 +3222,7 @@ module.exports = { MultipleUnhandledRejectionError: MultipleUnhandledRejectionError, Thenable: Thenable, Promise: ManagedPromise, + Scheduler: Scheduler, all: all, asap: asap, captureStackTrace: captureStackTrace, @@ -3085,6 +3243,9 @@ module.exports = { setDefaultFlow: setDefaultFlow, when: when, + /** @return {boolean} Whether the promise manager is enabled. */ + get USE_PROMISE_MANAGER() { return usePromiseManager(); }, + get LONG_STACK_TRACES() { return LONG_STACK_TRACES; }, set LONG_STACK_TRACES(v) { LONG_STACK_TRACES = v; }, }; diff --git a/javascript/node/selenium-webdriver/lib/test/build.js b/javascript/node/selenium-webdriver/lib/test/build.js index 59f0b1357c08e..3e21e886361b7 100644 --- a/javascript/node/selenium-webdriver/lib/test/build.js +++ b/javascript/node/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,7 +99,7 @@ Build.prototype.go = function() { cmd = path.join(projectRoot, 'go'); } - var result = promise.defer(); + var result = Promise.defer(); spawn(cmd, args, { cwd: projectRoot, env: process.env, @@ -110,7 +109,7 @@ Build.prototype.go = function() { targets.forEach(function(target) { builtTargets[target] = 1; }); - return result.fulfill(); + return result.resolve(); } var msg = 'Unable to build artifacts'; diff --git a/javascript/node/selenium-webdriver/lib/test/fileserver.js b/javascript/node/selenium-webdriver/lib/test/fileserver.js index 448b0c9a2df88..7c6ef0cc947b0 100644 --- a/javascript/node/selenium-webdriver/lib/test/fileserver.js +++ b/javascript/node/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'; @@ -269,7 +268,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.} A promise that will resolve + * @return {!Promise} A promise that will resolve * with the server host when it has fully started. */ exports.start = server.start.bind(server); @@ -277,7 +276,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/javascript/node/selenium-webdriver/lib/test/httpserver.js b/javascript/node/selenium-webdriver/lib/test/httpserver.js index 55b12551fb66b..a99b6bf106706 100644 --- a/javascript/node/selenium-webdriver/lib/test/httpserver.js +++ b/javascript/node/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.} A promise that will resolve + * @return {!Promise} 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,12 +67,12 @@ 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); + var d = Promise.defer(); + server.close(d.resolve); return d.promise; }; diff --git a/javascript/node/selenium-webdriver/lib/test/index.js b/javascript/node/selenium-webdriver/lib/test/index.js index 6df456306b826..e3e266ef925dd 100644 --- a/javascript/node/selenium-webdriver/lib/test/index.js +++ b/javascript/node/selenium-webdriver/lib/test/index.js @@ -217,18 +217,18 @@ function suite(fn, opt_options) { try { // 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(); }); @@ -243,7 +243,7 @@ function suite(fn, opt_options) { serverJar, {loopback: useLoopback}); } - testing.before(function() { + before(function() { this.timeout(0); return seleniumServer.start(60 * 1000); }); @@ -259,14 +259,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/javascript/node/selenium-webdriver/lib/test/promise.js b/javascript/node/selenium-webdriver/lib/test/promise.js new file mode 100644 index 0000000000000..073e21d6b6d85 --- /dev/null +++ b/javascript/node/selenium-webdriver/lib/test/promise.js @@ -0,0 +1,79 @@ +// 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'; + + +/** + * Configures a block of mocha tests, ensuring the promise manager is either + * enabled or disabled for the tests' execution. + * for their execution. + */ +function withPromiseManager(enabled, fn) { + describe(`SELENIUM_PROMISE_MANAGER=${enabled}`, function() { + let saved; + + function changeState() { + saved = process.env.SELENIUM_PROMISE_MANAGER; + process.env.SELENIUM_PROMISE_MANAGER = enabled; + } + + function restoreState() { + if (saved === undefined) { + delete process.env.SELENIUM_PROMISE_MANAGER; + } else { + process.env.SELENIUM_PROMISE_MANAGER = saved; + } + } + + before(changeState); + after(restoreState); + + try { + changeState(); + fn(); + } finally { + restoreState(); + } + }); +}; + + +/** + * Defines a set of tests to run both with and without the promise manager + * enabled. + */ +exports.promiseManagerSuite = function(fn) { + withPromiseManager(true, fn); + withPromiseManager(false, fn); +}; + + +/** + * Ensures the promise manager is enabled when the provided tests run. + */ +exports.enablePromiseManager = function(fn) { + withPromiseManager(true, fn); +}; + + +/** + * Ensures the promise manager is disabled when the provided tests run. + */ +exports.disablePromiseManager = function(fn) { + withPromiseManager(false, fn); +}; diff --git a/javascript/node/selenium-webdriver/lib/webdriver.js b/javascript/node/selenium-webdriver/lib/webdriver.js index a9855a387fc67..243f94e1e48ae 100644 --- a/javascript/node/selenium-webdriver/lib/webdriver.js +++ b/javascript/node/selenium-webdriver/lib/webdriver.js @@ -265,15 +265,15 @@ class WebDriver { * schedule commands through. Defaults to the active flow object. */ constructor(session, executor, opt_flow) { + /** @private {!promise.ControlFlow} */ + this.flow_ = opt_flow || promise.controlFlow(); + /** @private {!promise.Thenable} */ - this.session_ = promise.fulfilled(session); + this.session_ = this.flow_.promise(resolve => resolve(session)); /** @private {!command.Executor} */ this.executor_ = executor; - /** @private {!promise.ControlFlow} */ - this.flow_ = opt_flow || promise.controlFlow(); - /** @private {input.FileDetector} */ this.fileDetector_ = null; } @@ -1717,7 +1717,7 @@ class WebElement { this.driver_ = driver; /** @private {!promise.Thenable} */ - this.id_ = promise.fulfilled(id); + this.id_ = driver.controlFlow().promise(resolve => resolve(id)); } /** @@ -1769,7 +1769,7 @@ class WebElement { */ 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) { @@ -2278,7 +2278,7 @@ class Alert { this.driver_ = driver; /** @private {!promise.Thenable} */ - this.text_ = promise.fulfilled(text); + this.text_ = driver.controlFlow().promise(resolve => resolve(text)); } /** diff --git a/javascript/node/selenium-webdriver/remote/index.js b/javascript/node/selenium-webdriver/remote/index.js index 22ffb4b5579e8..87dee7e8d23db 100644 --- a/javascript/node/selenium-webdriver/remote/index.js +++ b/javascript/node/selenium-webdriver/remote/index.js @@ -250,8 +250,15 @@ class DriverService { return new Promise((fulfill, reject) => { let cancelToken = earlyTermination.catch(e => reject(Error(e.message))); - return httpUtil.waitForServer(serverUrl, timeout, cancelToken) - .then(_ => fulfill(serverUrl)); + + httpUtil.waitForServer(serverUrl, timeout, cancelToken) + .then(_ => fulfill(serverUrl), err => { + if (err instanceof promise.CancellationError) { + fulfill(serverUrl); + } else { + reject(err); + } + }); }); }); })); diff --git a/javascript/node/selenium-webdriver/test/actions_test.js b/javascript/node/selenium-webdriver/test/actions_test.js index 7ea0047ade312..3ac0849d82c99 100644 --- a/javascript/node/selenium-webdriver/test/actions_test.js +++ b/javascript/node/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().buildAsync(); }); + 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/javascript/node/selenium-webdriver/test/chrome/options_test.js b/javascript/node/selenium-webdriver/test/chrome/options_test.js index 28c4faa914ba3..5a89fd309a4c6 100644 --- a/javascript/node/selenium-webdriver/test/chrome/options_test.js +++ b/javascript/node/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) + .buildAsync(); - 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/javascript/node/selenium-webdriver/test/cookie_test.js b/javascript/node/selenium-webdriver/test/cookie_test.js index 3912fdbeea877..a9b13eb1b0996 100644 --- a/javascript/node/selenium-webdriver/test/cookie_test.js +++ b/javascript/node/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().buildAsync(); }); 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.PHANTOMJS, 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/javascript/node/selenium-webdriver/test/element_finding_test.js b/javascript/node/selenium-webdriver/test/element_finding_test.js index c5632f37f2dc4..6bf3b0659cec5 100644 --- a/javascript/node/selenium-webdriver/test/element_finding_test.js +++ b/javascript/node/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().buildAsync(); }); - 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()).matches(/Update\s+a\s+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(); @@ -408,14 +407,17 @@ test.suite(function(env) { }); describe('switchTo().activeElement()', function() { - test.it('returns document.activeElement', function() { - driver.get(Pages.formPage); + // 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 = driver.findElement(By.css('#email')); - driver.executeScript('arguments[0].focus()', email); + let email = yield driver.findElement(By.css('#email')); + yield driver.executeScript('arguments[0].focus()', email); - let ae = driver.switchTo().activeElement(); - let equal = driver.executeScript( + let ae = yield driver.switchTo().activeElement(); + let equal = yield driver.executeScript( 'return arguments[0] === arguments[1]', email, ae); assert(equal).isTrue(); }); diff --git a/javascript/node/selenium-webdriver/test/execute_script_test.js b/javascript/node/selenium-webdriver/test/execute_script_test.js index 4188724f59b5e..73fabc5b4c5c2 100644 --- a/javascript/node/selenium-webdriver/test/execute_script_test.js +++ b/javascript/node/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().buildAsync(); }); 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)). 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, Browser.SAFARI)). - 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); - }); + 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/javascript/node/selenium-webdriver/test/fingerprint_test.js b/javascript/node/selenium-webdriver/test/fingerprint_test.js index ca36ce142a187..0a13dd7486903 100644 --- a/javascript/node/selenium-webdriver/test/fingerprint_test.js +++ b/javascript/node/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/javascript/node/selenium-webdriver/test/firefox/firefox_test.js b/javascript/node/selenium-webdriver/test/firefox/firefox_test.js index e61a65660667c..ebd9e27f0b251 100644 --- a/javascript/node/selenium-webdriver/test/firefox/firefox_test.js +++ b/javascript/node/selenium-webdriver/test/firefox/firefox_test.js @@ -44,7 +44,7 @@ test.suite(function(env) { test.afterEach(function() { if (driver) { - driver.quit(); + return driver.quit(); } }); @@ -67,7 +67,7 @@ test.suite(function(env) { }); } - test.it('can start Firefox with custom preferences', function() { + test.it('can start Firefox with custom preferences', function*() { var profile = new firefox.Profile(); profile.setPreference('general.useragent.override', 'foo;bar'); @@ -77,9 +77,9 @@ test.suite(function(env) { setFirefoxOptions(options). build(); - driver.get('data:text/html,
content
'); + yield driver.get('data:text/html,
content
'); - var userAgent = driver.executeScript( + var userAgent = yield driver.executeScript( 'return window.navigator.userAgent'); assert(userAgent).equalTo('foo;bar'); }); @@ -90,11 +90,13 @@ test.suite(function(env) { let options = new firefox.Options().setProfile(profile); - return runWithFirefoxDev(options, function() { - loadJetpackPage(driver, + return runWithFirefoxDev(options, function*() { + yield loadJetpackPage(driver, 'data:text/html;charset=UTF-8,
content
'); - 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!'); }); }); @@ -104,10 +106,13 @@ test.suite(function(env) { let options = new firefox.Options().setProfile(profile); - return runWithFirefoxDev(options, function() { - driver.get('data:text/html,
content
'); - assert(driver.findElement({id: 'sample-extension-footer'}).getText()) - .equalTo('Goodbye'); + return runWithFirefoxDev(options, function*() { + yield driver.get('data:text/html,
content
'); + + let footer = + yield driver.findElement({id: 'sample-extension-footer'}); + let text = yield footer.getText(); + assert(text).equalTo('Goodbye'); }); }); @@ -118,13 +123,18 @@ test.suite(function(env) { let options = new firefox.Options().setProfile(profile); - return runWithFirefoxDev(options, function() { - loadJetpackPage(driver, + return runWithFirefoxDev(options, function*() { + yield loadJetpackPage(driver, 'data:text/html;charset=UTF-8,
content
'); - assert(driver.findElement({id: 'jetpack-sample-banner'}).getText()) - .equalTo('Hello, world!'); - assert(driver.findElement({id: 'sample-extension-footer'}).getText()) - .equalTo('Goodbye'); + + let banner = + yield driver.findElement({id: 'jetpack-sample-banner'}).getText(); + assert(banner).equalTo('Hello, world!'); + + let footer = + yield driver.findElement({id: 'sample-extension-footer'}) + .getText(); + assert(footer).equalTo('Goodbye'); }); }); @@ -132,7 +142,7 @@ test.suite(function(env) { // 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); @@ -144,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().buildAsync(); + driver2 = yield env.builder().buildAsync(); // 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(); } }); }); @@ -166,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().buildAsync(); }); 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/javascript/node/selenium-webdriver/test/lib/promise_aplus_test.js b/javascript/node/selenium-webdriver/test/lib/promise_aplus_test.js index a8939159014af..207f490a1bbaa 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_aplus_test.js +++ b/javascript/node/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/javascript/node/selenium-webdriver/test/lib/promise_error_test.js b/javascript/node/selenium-webdriver/test/lib/promise_error_test.js index 6bc3910f09861..b89a2f8751eec 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_error_test.js +++ b/javascript/node/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,595 +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); - }) - .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); - 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/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js b/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js index f9a40be045ca8..2a012cdef02a6 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js +++ b/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js @@ -23,6 +23,7 @@ const sinon = require('sinon'); const testutil = require('./testutil'); const promise = require('../../lib/promise'); +const {enablePromiseManager} = require('../../lib/test/promise'); const NativePromise = Promise; @@ -33,2240 +34,2243 @@ 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); - }); + enablePromiseManager(() => { + let flow, flowHistory, messages, uncaughtExceptions; - afterEach(function tearDown() { - flow.removeAllListeners( - promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); - assert.deepEqual([], uncaughtExceptions, - 'There were uncaught exceptions'); - flow.reset(); - promise.LONG_STACK_TRACES = false; - }); + beforeEach(function setUp() { + promise.LONG_STACK_TRACES = false; + flow = new promise.ControlFlow(); + promise.setDefaultFlow(flow); + messages = []; + flowHistory = []; - 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); + uncaughtExceptions = []; + flow.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + onUncaughtException); }); - } - - 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); + afterEach(function tearDown() { + flow.removeAllListeners( + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + assert.deepEqual([], uncaughtExceptions, + 'There were uncaught exceptions'); + flow.reset(); + promise.LONG_STACK_TRACES = false; }); - } + function onUncaughtException(e) { + uncaughtExceptions.push(e); + } - 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'); + 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('aSimpleFunctionWithANonPromiseReturnValue', function() { - schedule('go', 123).then(function(value) { - assert.equal(123, value); + 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; + 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'); + }); }); - 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'); + }); }); - }); - it('aSimpleSequence', function() { - schedule('a'); - schedule('b'); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + it('aSimpleSequence', function() { + schedule('a'); + schedule('b'); + schedule('c'); + 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); + 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'); + }); }); - return timeout(5).then(function() { - assert.ok(!called); - d.fulfill(123); - return done; - }). - then(function() { - assertFlowHistory('a'); + + 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')); }); - }); - 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')); - }); + it('waitsForReturnedPromisesToResolve', function() { + var d1 = new promise.Deferred(); + var d2 = new promise.Deferred(); - it('waitsForReturnedPromisesToResolve', function() { - var d1 = new promise.Deferred(); - var d2 = new promise.Deferred(); + var callback = sinon.spy(); + schedule('a', d1.promise).then(callback); - 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'); + }); + }); - 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'); + it('executesTasksInAFutureTurnAfterTheyAreScheduled', function() { + var count = 0; + function incr() { count++; } + + scheduleAction('', incr); + assert.equal(0, count); + return waitForIdle().then(function() { + assert.equal(1, count); + }); }); - }); - it('executesTasksInAFutureTurnAfterTheyAreScheduled', function() { - var count = 0; - function incr() { count++; } + it('executesOneTaskPerTurnOfTheEventLoop', function() { + var order = []; + function go() { + order.push(order.length / 2); + asyncRun(function() { + order.push('-'); + }); + } - scheduleAction('', incr); - assert.equal(0, count); - return waitForIdle().then(function() { - assert.equal(1, count); + scheduleAction('', go); + scheduleAction('', go); + return waitForIdle().then(function() { + assert.deepEqual([0, '-', 1, '-'], order); + }) }); - }); - it('executesOneTaskPerTurnOfTheEventLoop', function() { - var order = []; - function go() { - order.push(order.length / 2); - asyncRun(function() { - order.push('-'); + it('firstScheduledTaskIsWithinACallback', function() { + promise.fulfilled().then(function() { + schedule('a'); + schedule('b'); + schedule('c'); + }).then(function() { + assertFlowHistory('a', 'b', 'c'); }); - } - - scheduleAction('', go); - scheduleAction('', go); - return waitForIdle().then(function() { - assert.deepEqual([0, '-', 1, '-'], order); - }) - }); + return waitForIdle(); + }); - it('firstScheduledTaskIsWithinACallback', function() { - promise.fulfilled().then(function() { - schedule('a'); + it('newTasksAddedWhileWaitingOnTaskReturnedPromise1', function() { + scheduleAction('a', function() { + var d = promise.defer(); + setTimeout(function() { + schedule('c'); + d.fulfill(); + }, 10); + return d.promise; + }); 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('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('newTasksAddedWhileWaitingOnTaskReturnedPromise2', function() { - scheduleAction('a', function() { - var d = promise.defer(); - setTimeout(function() { + describe('testFraming', function() { + it('callbacksRunInANewFrame', function() { + schedule('a').then(function() { schedule('c'); - asyncRun(d.fulfill); - }, 10); - return d.promise; - }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'b'); + }); + schedule('b'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); }); - }); - }); - describe('testFraming', function() { - it('callbacksRunInANewFrame', function() { - schedule('a').then(function() { - schedule('c'); - }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'b'); + it('lotsOfNesting', function() { + schedule('a').then(function() { + schedule('c').then(function() { + schedule('e').then(function() { + schedule('g'); + }); + schedule('f'); + }); + schedule('d'); + }); + schedule('b'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'e', 'g', 'f', 'd', 'b'); + }); }); - }); - it('lotsOfNesting', function() { - schedule('a').then(function() { - schedule('c').then(function() { - schedule('e').then(function() { - schedule('g'); + it('callbackReturnsPromiseThatDependsOnATask_1', function() { + schedule('a').then(function() { + schedule('b'); + return promise.delayed(5).then(function() { + return schedule('c'); }); - schedule('f'); }); schedule('d'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'e', 'g', 'f', 'd', 'b'); + 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('a', 'b', 'c', 'd'); + }); }); - }); - it('callbackReturnsPromiseThatDependsOnATask_1', function() { - schedule('a').then(function() { - schedule('b'); - return promise.delayed(5).then(function() { - return schedule('c'); + it('eachCallbackWaitsForAllScheduledTasksToComplete', function() { + schedule('a'). + then(function() { + schedule('b'); + schedule('c'); + }). + then(function() { + schedule('d'); + }); + schedule('e'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); }); }); - schedule('d'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd'); + 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'); + }); }); - }); - it('callbackReturnsPromiseThatDependsOnATask_2', function() { - schedule('a').then(function() { + it('callbacksOnAResolvedPromiseInsertIntoTheCurrentFlow', function() { + promise.fulfilled().then(function() { + schedule('b'); + }); + schedule('a'); + + return waitForIdle().then(function() { + assertFlowHistory('b', 'a'); + }); + }); + + it('callbacksInterruptTheFlowWhenPromiseIsResolved', 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'); }); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); }); - schedule('d'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd'); + it('allCallbacksInAFrameAreScheduledWhenPromiseIsResolved', function() { + var a = schedule('a'); + a.then(function() { schedule('b'); }); + schedule('c'); + a.then(function() { schedule('d'); }); + schedule('e'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); }); - }); - it('eachCallbackWaitsForAllScheduledTasksToComplete', function() { - schedule('a'). - then(function() { - schedule('b'); - schedule('c'); - }). - then(function() { - schedule('d'); - }); - schedule('e'); + it('tasksScheduledInInActiveFrameDoNotGetPrecedence', function() { + var d = promise.fulfilled(); + schedule('a'); + schedule('b'); + d.then(function() { schedule('c'); }); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - }); - it('eachCallbackWaitsForReturnTasksToComplete', function() { - schedule('a'). - then(function() { - schedule('b'); - return schedule('c'); - }). - then(function() { + it('tasksScheduledInAFrameGetPrecedence_1', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { + schedule('c'); schedule('d'); }); - schedule('e'); + var e = schedule('e'); + a.then(function() { + schedule('f'); + e.then(function() { + schedule('g'); + }); + schedule('h'); + }); + schedule('i'); + }); + schedule('j'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'); + }); }); }); - it('callbacksOnAResolvedPromiseInsertIntoTheCurrentFlow', function() { - promise.fulfilled().then(function() { - schedule('b'); + describe('testErrorHandling', function() { + it('thrownErrorsArePassedToTaskErrback', function() { + scheduleAction('function that throws', throwStubError). + then(fail, assertIsStubError); + return waitForIdle(); }); - schedule('a'); - return waitForIdle().then(function() { - assertFlowHistory('b', 'a'); + it('thrownErrorsPropagateThroughPromiseChain', function() { + scheduleAction('function that throws', throwStubError). + then(fail). + then(fail, assertIsStubError); + return waitForIdle(); }); - }); - it('callbacksInterruptTheFlowWhenPromiseIsResolved', function() { - schedule('a').then(function() { - schedule('c'); + it('catchesErrorsFromFailedTasksInAFrame', function() { + schedule('a').then(function() { + schedule('b'); + scheduleAction('function that throws', throwStubError); + }). + then(fail, assertIsStubError); + return waitForIdle(); }); - schedule('b'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'c', 'b'); + it('abortsIfOnlyTaskReturnsAnUnhandledRejection', function() { + scheduleAction('function that returns rejected promise', function() { + return promise.rejected(new StubError); + }); + return waitForAbort().then(assertIsStubError); }); - }); - - it('allCallbacksInAFrameAreScheduledWhenPromiseIsResolved', function() { - var a = schedule('a'); - a.then(function() { schedule('b'); }); - schedule('c'); - a.then(function() { schedule('d'); }); - schedule('e'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); + it('abortsIfThereIsAnUnhandledRejection', function() { + promise.rejected(new StubError); + schedule('this should not run'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory(/* none */); + }); }); - }); - it('tasksScheduledInInActiveFrameDoNotGetPrecedence', function() { - var d = promise.fulfilled(); - schedule('a'); - schedule('b'); - d.then(function() { schedule('c'); }); + it('abortsSequenceIfATaskFails', function() { + schedule('a'); + schedule('b'); + scheduleAction('c', throwStubError); + schedule('d'); // Should never execute. - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); + 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); }); - var e = schedule('e'); - a.then(function() { - schedule('f'); - e.then(function() { - schedule('g'); + schedule('this should not run'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('outer task', 'inner task'); + }); + }); + + it('abortsFromUnhandledFramedTaskFailures_2', function() { + schedule('a').then(function() { + schedule('b').then(function() { + scheduleAction('c', throwStubError); + // This should not execute. + schedule('d'); }); - schedule('h'); }); - schedule('i'); - }); - schedule('j'); - 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'); + }); }); - }); - }); - - describe('testErrorHandling', function() { - it('thrownErrorsArePassedToTaskErrback', function() { - scheduleAction('function that throws', throwStubError). - then(fail, assertIsStubError); - return waitForIdle(); - }); - it('thrownErrorsPropagateThroughPromiseChain', function() { - scheduleAction('function that throws', throwStubError). - then(fail). - then(fail, assertIsStubError); - return waitForIdle(); - }); + it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject', function() { + var callback = sinon.spy(); - it('catchesErrorsFromFailedTasksInAFrame', function() { - schedule('a').then(function() { - schedule('b'); - 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('abortsIfOnlyTaskReturnsAnUnhandledRejection', function() { - scheduleAction('function that returns rejected promise', function() { - return promise.rejected(new StubError); + return waitForAbort(). + then(assertIsStubError). + then(() => assert(!callback.called)); }); - 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. + it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject_withCallback', function() { + var callback1 = sinon.spy(); + var callback2 = sinon.spy(); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); + scheduleAction('', function() { + var obj = {'foo': promise.rejected(new StubError)}; + return promise.fullyResolved(obj).then(callback1); + }).then(callback2); - it('abortsFromUnhandledFramedTaskFailures_1', function() { - schedule('outer task').then(function() { - scheduleAction('inner task', throwStubError); + return waitForAbort(). + then(assertIsStubError). + then(() => assert(!callback1.called)). + then(() => assert(!callback2.called)); }); - schedule('this should not run'); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('outer task', 'inner task'); - }); - }); - it('abortsFromUnhandledFramedTaskFailures_2', function() { - schedule('a').then(function() { - schedule('b').then(function() { - scheduleAction('c', throwStubError); - // This should not execute. - schedule('d'); + 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('abortsWhenErrorBubblesUpFromFullyResolvingAnObject', function() { - var callback = sinon.spy(); - - scheduleAction('', function() { - var obj = {'foo': promise.rejected(new StubError)}; - return promise.fullyResolved(obj).then(callback); + }).catch(errback); + schedule('d'); + return waitForIdle(). + then(function() { + assert(errback.called); + assertIsStubError(errback.getCall(0).args[0]); + assertFlowHistory('a', 'b', 'c', 'd'); + }); }); - return waitForAbort(). - then(assertIsStubError). - then(() => assert(!callback.called)); - }); - - it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject_withCallback', function() { - var callback1 = sinon.spy(); - var callback2 = sinon.spy(); - - scheduleAction('', function() { - var obj = {'foo': promise.rejected(new StubError)}; - return promise.fullyResolved(obj).then(callback1); - }).then(callback2); - - return waitForAbort(). - then(assertIsStubError). - then(() => assert(!callback1.called)). - then(() => assert(!callback2.called)); - }); - - 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('nestedCommandFailuresCanBeCaughtAndSuppressed', function() { - var errback = sinon.spy(); - schedule('a').then(function() { - return schedule('b').then(function() { - return schedule('c').then(function() { - throw new StubError; - }); + it('aTaskWithAnUnhandledPromiseRejection', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + promise.rejected(new StubError); }); - }).catch(errback); - schedule('d'); - return waitForIdle(). - then(function() { - assert(errback.called); - assertIsStubError(errback.getCall(0).args[0]); - assertFlowHistory('a', 'b', 'c', 'd'); - }); - }); + schedule('should never run'); - it('aTaskWithAnUnhandledPromiseRejection', function() { - schedule('a'); - scheduleAction('sub-tasks', function() { - promise.rejected(new StubError); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks'); + }); }); - schedule('should never run'); - return waitForAbort(). - then(assertIsStubError). - then(function() { - assertFlowHistory('a', 'sub-tasks'); - }); - }); + it('aTaskThatReutrnsARejectedPromise', function() { + schedule('a'); + scheduleAction('sub-tasks', function() { + return promise.rejected(new StubError); + }); + schedule('should never run'); - it('aTaskThatReutrnsARejectedPromise', 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 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'); - - return waitForIdle(). - then(pair.assertErrback). - then(function() { - assertFlowHistory('a', 'd'); - }); - }); - - it('discardsRemainingSubtasksIfASubtaskFails', function() { - var pair = callbackPair(null, assertIsStubError); - scheduleAction('a', function() { - schedule('b'); - scheduleAction('c', throwStubError); + it('discardsSubtasksIfTaskThrows', function() { + var pair = callbackPair(null, assertIsStubError); + scheduleAction('a', function() { + schedule('b'); + schedule('c'); + throwStubError(); + }).then(pair.callback, pair.errback); schedule('d'); - }).then(pair.callback, pair.errback); - schedule('e'); - - return waitForIdle(). - then(pair.assertErrback). - then(function() { - assertFlowHistory('a', 'b', 'c', '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; + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory('a', 'd'); }); - }). - 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'); - }); - }); - it('taskDependsOnImmediatelyFulfilledPromise', function() { - scheduleAction('a', function() { - return promise.fulfilled(); - }).then(function() { - schedule('b'); - }); - schedule('c'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c'); - }); - }); + 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('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 waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory('a', 'b', 'c', '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'); + 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('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'); + describe('testTaskCallbacksInterruptFlow', function() { + it('(base case)', function() { + schedule('a').then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); }); - }); - it('nestedTaskCallbacksInterruptFlowWhenResolved', function() { - schedule('a').then(function() { - schedule('b').then(function() { - schedule('c'); + it('taskDependsOnImmediatelyFulfilledPromise', function() { + scheduleAction('a', function() { + return promise.fulfilled(); + }).then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); }); }); - schedule('d'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd'); - }); - }); - }); - - describe('testDelayedNesting', function() { - it('1', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }); - 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('2', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }); - schedule('d'); - a.then(function() { schedule('e'); }); + 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'); + }); }); - 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('3', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }); - a.then(function() { schedule('d'); }); - }); - schedule('e'); - - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e'); - }); - }); + describe('testDelayedNesting', function() { - it('4', function() { - var a = schedule('a'); - schedule('b').then(function() { - a.then(function() { schedule('c'); }).then(function() { + it('1', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); schedule('d'); }); - a.then(function() { schedule('e'); }); - }); - schedule('f'); + schedule('e'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); }); - }); - it('5', function() { - var a = schedule('a'); - schedule('b').then(function() { - var c; - a.then(function() { c = schedule('c'); }).then(function() { + it('2', 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'); + schedule('f'); - return waitForIdle().then(function() { - assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'); - }); - }); - }); - - 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', 'f'); + }); }); - }); - it('aSimpleCountingCondition', function() { - var count = 0; - scheduleWait(function() { - return ++count == 3; - }, 100, 'counting to 3'); + it('3', function() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); + a.then(function() { schedule('d'); }); + }); + schedule('e'); - return waitForIdle().then(function() { - assert.equal(3, count); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); }); - }); - - it('aConditionThatReturnsAPromise', function() { - var d = new promise.Deferred(); - var count = 0; - scheduleWait(function() { - count += 1; - return d.promise; - }, 0, '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 timeout(50).then(function() { - assert.equal(1, count); - d.fulfill(123); - return waitForIdle(); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + }); }); - }); - it('aConditionThatReturnsAPromise_2', function() { - var count = 0; - scheduleWait(function() { - return promise.fulfilled(++count == 3); - }, 100, 'waiting for promise'); + 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'); }); + }); + schedule('i'); - return waitForIdle().then(function() { - assert.equal(3, count); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'); + }); }); }); - it('aConditionThatReturnsATaskResult', function() { - var count = 0; - scheduleWait(function() { - return scheduleAction('increment count', function() { - return ++count == 3; + describe('testWaiting', function() { + it('onAConditionThatIsAlwaysTrue', function() { + scheduleWait(function() { return true;}, 0, 'waiting on true'); + return waitForIdle().then(function() { + assertFlowHistory('0: waiting on true'); }); - }, 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('conditionContainsASubtask', function() { - var count = 0; - scheduleWait(function() { - schedule('sub task'); - return ++count == 3; - }, 100, 'counting to 3'); - schedule('post wait'); + it('aSimpleCountingCondition', function() { + var count = 0; + scheduleWait(function() { + return ++count == 3; + }, 100, 'counting to 3'); - 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'); + return waitForIdle().then(function() { + assert.equal(3, count); + }); }); - }); - 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('aConditionThatReturnsAPromise', function() { + var d = new promise.Deferred(); + var count = 0; - return waitForIdle(). - then(pair.assertErrback). - then(function() { - assertFlowHistory( - '0: waiting to go boom', 'boom', - '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(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( + /^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'); + }); + }); + + 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'); + }); }); - 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); + 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); - 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( + /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'); - }); - }); - }); + 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 = NativePromise.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 = NativePromise.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 = NativePromise.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 = NativePromise.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 = NativePromise.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() { - 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('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() { - 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); + 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); - 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); + }).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/javascript/node/selenium-webdriver/test/lib/promise_generator_test.js b/javascript/node/selenium-webdriver/test/lib/promise_generator_test.js index 5fdff9f80f51d..b3388da78f937 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_generator_test.js +++ b/javascript/node/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/javascript/node/selenium-webdriver/test/lib/promise_test.js b/javascript/node/selenium-webdriver/test/lib/promise_test.js index 08f310ab445e1..2d3632a455345 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_test.js +++ b/javascript/node/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,1033 +37,1046 @@ 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 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(Promise.defer().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)); + }); - 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)); - }); - - 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); - }); + d.promise.cancel('because i said so'); + return res; }); - 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); - }); - }); + 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; + }); - it('"parent" is a CancellableThenable', function() { - function noop() {} + p.cancel('because i said so'); + return p.then(v => assert.equal(123, v)); + }); - class FakeThenable { - constructor(p) { - this.promise = p; - } + 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'); - cancel(reason) { - this.promise.cancel(reason); - } - - then(cb, eb) { - let result = this.promise.then(cb, eb); - return new FakeThenable(result); - } - } - promise.CancellableThenable.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)); - assert.ok(promise.CancellableThenable.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('canCancelATimeout', function() { - var p = promise.delayed(25) - .then(assert.fail, (e) => e instanceof promise.CancellationError); - setTimeout(() => p.cancel(), 20); - p.cancel(); - return p; - }); + it('grand child through thenable chain started at resolve', function() { + function noop() {} - it('can cancel timeout from grandchild', function() { - }); + 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('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 = Promise.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('functionReturnsAnError', function() { - return promise.checkedNodeCall(function(callback) { - callback(new StubError); - }).then(assert.fail, assertIsStubError); - }); + it('withEmptyArray', 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('withEmptyHash', function() { + return promise.fullyResolved({}).then(function(resolved) { + assert.deepEqual({}, resolved); + }); + }); - 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('arrayWithPromisedHash', function() { + var obj = {'foo': 'bar'}; + var array = [Promise.resolve(obj)]; - 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 defer = [Promise.defer(), Promise.defer()]; + var a = [ + 0, 1, + defer[0].promise, + defer[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(); - 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); + defer[0].resolve(2); + pair.assertNeither(); + + defer[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 defer = [Promise.defer(), Promise.defer()]; + let a = [defer[0].promise, defer[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); + defer[1].reject(new StubError); + setTimeout(() => defer[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); - return result; - }); + it('inputIsPromise', function() { + var input = Promise.defer(); + var result = promise.map(input.promise, function(value) { + return value + 1; + }); - it('waitsForFunctionResultToResolve', function() { - var innerResults = [ - promise.defer(), - promise.defer() - ]; + var pair = callbackPair(function(value) { + assert.deepEqual([2, 3, 4], value); + }); + result = result.then(pair.callback, pair.errback); - var result = promise.map([1, 2], function(value, index) { - return innerResults[index].promise; - }); + setTimeout(function() { + pair.assertNeither(); + input.resolve([1, 2, 3]); + }, 10); - var pair = callbackPair(function(value) { - assert.deepEqual(['a', 'b'], value); + return result; }); - result = result.then(pair.callback, pair.errback); - 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('waitsForFunctionResultToResolve', function() { + var innerResults = [ + Promise.defer(), + Promise.defer() + ]; - 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 = [ + Promise.defer(), + Promise.defer(), + Promise.defer(), + Promise.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 = Promise.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 = [ + Promise.defer(), + Promise.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 = [ + Promise.defer(), + Promise.defer(), + Promise.defer(), + Promise.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/javascript/node/selenium-webdriver/test/lib/until_test.js b/javascript/node/selenium-webdriver/test/lib/until_test.js index d3e81ec18fcba..31b2b32adcaff 100644 --- a/javascript/node/selenium-webdriver/test/lib/until_test.js +++ b/javascript/node/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/javascript/node/selenium-webdriver/test/lib/webdriver_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js index e0b7fc744d809..ca78a1b0fe828 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js @@ -29,6 +29,7 @@ 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 Alert = require('../../lib/webdriver').Alert; const AlertPromise = require('../../lib/webdriver').AlertPromise; const UnhandledAlertError = require('../../lib/webdriver').UnhandledAlertError; @@ -73,6 +74,9 @@ describe('WebDriver', function() { }); afterEach(function tearDown() { + if (!promise.USE_PROMISE_MANAGER) { + return; + } return waitForIdle(flow).then(function() { assert.deepEqual([], uncaughtExceptions); flow.reset(); @@ -84,6 +88,9 @@ describe('WebDriver', function() { } 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 +307,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); + }); }); }); @@ -387,26 +396,29 @@ 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); + }); }); }); 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 +427,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 +442,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 +480,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 +498,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,9 +511,9 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - driver.switchTo().window('foo').catch(throwStubError); - - return waitForAbort().then(assertIsStubError); + return driver.switchTo().window('foo') + .catch(throwStubError) + .then(assert.fail, assertIsStubError); }); it('testCannotScheduleCommandsIfTheSessionIdHasBeenDeleted', function() { @@ -527,14 +539,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 +569,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'); + driver.getCurrentUrl(); + driver.getWindowHandle(); }). - 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' - }). - 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 +606,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 +627,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 +644,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 +677,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 +755,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 +801,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 +867,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 +879,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 +905,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 = Promise.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 +934,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 +1131,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 +1142,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 +1160,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 +1172,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() { @@ -1310,12 +1231,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() { @@ -1347,19 +1268,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); + }); }); }); @@ -1508,10 +1427,10 @@ describe('WebDriver', function() { 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() { @@ -1528,13 +1447,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 +1471,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 +1482,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'); @@ -1625,35 +1542,36 @@ describe('WebDriver', function() { }); }); - it('waitTimesout_timeoutNotCaught', function() { - let executor = new FakeExecutor(). - expect(CName.FIND_ELEMENTS, - {using: 'css selector', value: '*[id="foo"]'}). - andReturnSuccess([]). - anyTimes(). - end(); + 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)); + 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)); + }); }); }); }); describe('alert handling', function() { it('alertResolvesWhenPromisedTextResolves', function() { - var deferredText = new promise.Deferred(); + let driver = new FakeExecutor().createDriver(); + let deferredText = Promise.defer(); - var alert = new AlertPromise({}, deferredText.promise); + 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() { @@ -1663,32 +1581,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() { @@ -1714,26 +1633,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() { @@ -1762,7 +1683,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); @@ -1810,10 +1731,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); + let driver = new FakeExecutor().createDriver(session); + driver.getSession().catch(function() {}); + return driver. actions(). mouseDown(). mouseUp(). @@ -1899,10 +1820,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(). @@ -1936,8 +1857,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); @@ -1946,7 +1867,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); @@ -1955,8 +1876,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); @@ -1991,6 +1912,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(). @@ -2034,14 +1957,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')); }); @@ -2052,14 +1976,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')]); }); @@ -2069,7 +1994,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); }); @@ -2113,7 +2038,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/javascript/node/selenium-webdriver/test/logging_test.js b/javascript/node/selenium-webdriver/test/logging_test.js index fd88daa72e4a0..87d794704d881 100644 --- a/javascript/node/selenium-webdriver/test/logging_test.js +++ b/javascript/node/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(); + .buildAsync(); - driver.get(dataUrl( + yield driver.get(dataUrl( '')); - 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(); + .buildAsync(); - driver.get(dataUrl( + yield driver.get(dataUrl( '')); - 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).endsWith('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(); + .buildAsync(); - driver.get(dataUrl( + yield driver.get(dataUrl( '')); - 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).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'); + }); }); // 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(); + .buildAsync(); - driver.get(dataUrl( + yield driver.get(dataUrl( '')); - 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(); + .buildAsync(); - driver.get(dataUrl( + yield driver.get(dataUrl( '')); - 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/javascript/node/selenium-webdriver/test/net/portprober_test.js b/javascript/node/selenium-webdriver/test/net/portprober_test.js index 03a2f7a105965..2a6f272d74c18 100644 --- a/javascript/node/selenium-webdriver/test/net/portprober_test.js +++ b/javascript/node/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,10 +41,10 @@ describe('isFree', function() { server.listen(0, function() { var port = server.address().port; assertPortNotfree(port).then(function() { - var done = promise.defer(); + var done = Promise.defer(); server.close(function() { server = null; - done.fulfill(assertPortIsFree(port)); + done.resolve(assertPortIsFree(port)); }); return done.promise; }).then(function() { done(); }, done); @@ -57,10 +56,10 @@ describe('isFree', function() { server.listen(0, host, function() { var port = server.address().port; assertPortNotfree(port, host).then(function() { - var done = promise.defer(); + var done = Promise.defer(); server.close(function() { server = null; - done.fulfill(assertPortIsFree(port, host)); + done.resolve(assertPortIsFree(port, host)); }); return done.promise; }).then(function() { done(); }, done); @@ -86,10 +85,10 @@ describe('findFreePort', function() { portprober.findFreePort().then(function(port) { server.listen(port, function() { assertPortNotfree(port).then(function() { - var done = promise.defer(); + var done = Promise.defer(); server.close(function() { server = null; - done.fulfill(assertPortIsFree(port)); + done.resolve(assertPortIsFree(port)); }); return done.promise; }).then(function() { done(); }, done); @@ -102,10 +101,10 @@ describe('findFreePort', function() { portprober.findFreePort(host).then(function(port) { server.listen(port, host, function() { assertPortNotfree(port, host).then(function() { - var done = promise.defer(); + var done = Promise.defer(); server.close(function() { server = null; - done.fulfill(assertPortIsFree(port, host)); + done.resolve(assertPortIsFree(port, host)); }); return done.promise; }).then(function() { done(); }, done); diff --git a/javascript/node/selenium-webdriver/test/page_loading_test.js b/javascript/node/selenium-webdriver/test/page_loading_test.js index 27b9cd5a4c2d4..52efa2a428cb0 100644 --- a/javascript/node/selenium-webdriver/test/page_loading_test.js +++ b/javascript/node/selenium-webdriver/test/page_loading_test.js @@ -30,121 +30,122 @@ test.suite(function(env) { var browsers = env.browsers; var driver; - test.before(function() { - driver = env.builder().build(); + test.before(function*() { + driver = yield env.builder().buildAsync(); }); - test.beforeEach(function() { + test.beforeEach(function*() { if (!driver) { - driver = env.builder().build(); + driver = yield env.builder().buildAsync(); } }); test.after(function() { if (driver) { - driver.quit(); + 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.ignore(browsers(Browser.SAFARI)). - it('should follow meta redirects', function() { - driver.get(Pages.metaRedirectPage); - assert(driver.getTitle()).equalTo('We Arrive Here'); + 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'); }); describe('timeouts', function() { test.afterEach(function() { let nullDriver = () => driver = null; - return driver.quit().then(nullDriver, nullDriver); + if (driver) { + return driver.quit().then(nullDriver, nullDriver); + } }); // Only implemented in Firefox. @@ -155,19 +156,17 @@ test.suite(function(env) { Browser.IPHONE, Browser.OPERA, Browser.PHANTOM_JS)). - 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() { - throw Error('Should have timed out on page load'); - }, function(e) { - if (!(e instanceof error.ScriptTimeoutError) - && !(e instanceof error.TimeoutError)) { - throw Error('Unexpected error response: ' + e); - } - }); - }); + 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) + && !(e instanceof error.TimeoutError)) { + throw Error('Unexpected error response: ' + e); + } + }); }); }); }); diff --git a/javascript/node/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js b/javascript/node/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js index 82a814a31ddcc..5afe028b60727 100644 --- a/javascript/node/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js +++ b/javascript/node/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().buildAsync(); }); test.after(function() { - driver.quit(); + return driver.quit(); }); var testPageUrl = 'data:text/html,

' + path.basename(__filename) + '

'; 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/javascript/node/selenium-webdriver/test/proxy_test.js b/javascript/node/selenium-webdriver/test/proxy_test.js index c25565b48072a..8af57dd317f08 100644 --- a/javascript/node/selenium-webdriver/test/proxy_test.js +++ b/javascript/node/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,10 +104,11 @@ test.suite(function(env) { let profile = new firefox.Profile(); profile.setPreference('network.proxy.no_proxies_on', ''); - driver = env.builder() + return env.builder() .setFirefoxOptions(new firefox.Options().setProfile(profile)) .setProxy(proxy) - .build(); + .buildAsync() + .then(d => driver = d); } // Proxy support not implemented. @@ -116,14 +117,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 +135,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 +160,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/javascript/node/selenium-webdriver/test/remote_test.js b/javascript/node/selenium-webdriver/test/remote_test.js index 5edc448bb0c34..9b2b2eb739c26 100644 --- a/javascript/node/selenium-webdriver/test/remote_test.js +++ b/javascript/node/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/javascript/node/selenium-webdriver/test/safari_test.js b/javascript/node/selenium-webdriver/test/safari_test.js index 0e818ac454699..6032b865dee25 100644 --- a/javascript/node/selenium-webdriver/test/safari_test.js +++ b/javascript/node/selenium-webdriver/test/safari_test.js @@ -91,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/javascript/node/selenium-webdriver/test/session_test.js b/javascript/node/selenium-webdriver/test/session_test.js index 1fb7475b40922..fa5db87c4121c 100644 --- a/javascript/node/selenium-webdriver/test/session_test.js +++ b/javascript/node/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().buildAsync(); }); 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/javascript/node/selenium-webdriver/test/stale_element_test.js b/javascript/node/selenium-webdriver/test/stale_element_test.js index 6ab8de7ec6638..135123d895b68 100644 --- a/javascript/node/selenium-webdriver/test/stale_element_test.js +++ b/javascript/node/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().buildAsync(); }); + 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/javascript/node/selenium-webdriver/test/tag_name_test.js b/javascript/node/selenium-webdriver/test/tag_name_test.js index d5e18a9a2e8b5..1eb2c937a29b8 100644 --- a/javascript/node/selenium-webdriver/test/tag_name_test.js +++ b/javascript/node/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().buildAsync(); + yield driver.get(test.Pages.formPage); + + let el = yield driver.findElement(By.id('cheese')); + return assert(el.getTagName()).equalTo('input'); }); }); diff --git a/javascript/node/selenium-webdriver/test/testing/index_test.js b/javascript/node/selenium-webdriver/test/testing/index_test.js index 8ec96d363fff8..edc841bbed56e 100644 --- a/javascript/node/selenium-webdriver/test/testing/index_test.js +++ b/javascript/node/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,180 +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; - - beforeEach(function() { - flowReset = false; - test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset); - }); - - test.it('', function() { - this.timeout(100); - return promise.delayed(50); - }); - - afterEach(function() { - assert.ok(!flowReset); - test.controlFlow().removeListener( - promise.ControlFlow.EventType.RESET, onreset); - }); - - function onreset() { - flowReset = true; - } - }); - - describe('it resets the control flow after a timeout' ,function() { - var timeoutErr, flowReset; + 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() { - 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() { + this.timeout(100); + return promise.delayed(50); + }); - afterEach(function() { - return Promise.resolve().then(function() { + afterEach(function() { + assert.ok(!flowReset); test.controlFlow().removeListener( promise.ControlFlow.EventType.RESET, onreset); - assert.ok(flowReset, 'control flow was not reset after a timeout'); }); + + function onreset() { + flowReset = true; + } }); - function onreset() { - flowReset = true; - } - }); - }); -}); + describe('it resets the control flow after a timeout' ,function() { + var timeoutErr, flowReset; -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; - }); + 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; + }); -describe('generator support', function() { - let arr; + 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; + } + }); + }); - beforeEach(() => arr = []); - afterEach(() => assert.deepEqual(arr, [0, 1, 2, 3])); + 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; + }); + }); - 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); + 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); + }); + }); }); - 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)); - }); + describe('generator support', function() { + let arr; - 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)); - }); + beforeEach(() => arr = []); + afterEach(() => assert.deepEqual(arr, [0, 1, 2, 3])); - describe('generator runs with proper "this" context', () => { - before(function() { this.values = [0, 1, 2, 3]; }); - test.it('', function*() { - arr = this.values; + 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); }); - }); - it('generator function must not take a callback', function() { - arr = [0, 1, 2, 3]; // For teardown hook. - assert.throws(_ => { - test.it('', function*(done){}); - }, TypeError); - }); -}); + 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)); + }); -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); + 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('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/javascript/node/selenium-webdriver/test/upload_test.js b/javascript/node/selenium-webdriver/test/upload_test.js index 3329f7ca708ca..01fdba99a6b63 100644 --- a/javascript/node/selenium-webdriver/test/upload_test.js +++ b/javascript/node/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().buildAsync(); }); 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/javascript/node/selenium-webdriver/test/window_test.js b/javascript/node/selenium-webdriver/test/window_test.js index 54c25c0884914..76dc3aad1b835 100644 --- a/javascript/node/selenium-webdriver/test/window_test.js +++ b/javascript/node/selenium-webdriver/test/window_test.js @@ -26,102 +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().buildAsync(); }); + 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; - return driver.wait(forPositionToBe(dx, dy), 1000); - } - }); + 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) { - 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); - } - }); + 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/javascript/node/selenium-webdriver/testing/index.js b/javascript/node/selenium-webdriver/testing/index.js index e688c9165f806..5bb82d15ff40a 100644 --- a/javascript/node/selenium-webdriver/testing/index.js +++ b/javascript/node/selenium-webdriver/testing/index.js @@ -78,8 +78,20 @@ 'use strict'; -var promise = require('..').promise; -var flow = promise.controlFlow(); +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; + } + } +})(); /** @@ -147,20 +159,22 @@ function makeAsyncTestFn(fn) { const runTest = (resolve, reject) => { try { if (isAsync) { - const callback = err => err ? reject(err) : resolve(); - if (isGenerator) { - promise.consume(fn, this, callback); - } else { - fn.call(this, callback); - } + fn.call(this, err => err ? reject(err) : resolve()); + } else if (isGenerator) { + resolve(promise.consume(fn, this)); } else { - resolve(isGenerator ? promise.consume(fn, this) : fn.call(this)); + 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() {