From bd65bb67e25a79b019d745b7262b2008ce2adb15 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 16 Nov 2016 01:59:39 +0100 Subject: incrementally verify denoms The denominations are not stored in a separate object store. --- node_modules/selenium-webdriver/lib/webdriver.js | 1059 ++++++++++++---------- 1 file changed, 582 insertions(+), 477 deletions(-) (limited to 'node_modules/selenium-webdriver/lib/webdriver.js') diff --git a/node_modules/selenium-webdriver/lib/webdriver.js b/node_modules/selenium-webdriver/lib/webdriver.js index 13077b54e..081d77bda 100644 --- a/node_modules/selenium-webdriver/lib/webdriver.js +++ b/node_modules/selenium-webdriver/lib/webdriver.js @@ -28,7 +28,7 @@ const command = require('./command'); const error = require('./error'); const input = require('./input'); const logging = require('./logging'); -const Session = require('./session').Session; +const {Session} = require('./session'); const Symbols = require('./symbols'); const promise = require('./promise'); @@ -64,13 +64,13 @@ class Condition { /** * Defines a condition that will result in a {@link WebElement}. * - * @extends {Condition)>} + * @extends {Condition)>} */ class WebElementCondition extends Condition { /** * @param {string} message A descriptive error message. Should complete the * sentence "Waiting [...]" - * @param {function(!WebDriver): !(WebElement|promise.Promise)} + * @param {function(!WebDriver): !(WebElement|IThenable)} * fn The condition function to evaluate on each iteration of the wait * loop. */ @@ -237,45 +237,445 @@ function fromWireValue(driver, value) { /** - * Creates a new WebDriver client, which provides control over a browser. + * Structural interface for a WebDriver client. * - * Every command.Command returns a {@link promise.Promise} that - * represents the result of that command. Callbacks may be registered on this - * object to manipulate the command result or catch an expected error. Any - * commands scheduled with a callback are considered sub-commands and will - * execute before the next command in the current frame. For example: - * - * var message = []; - * driver.call(message.push, message, 'a').then(function() { - * driver.call(message.push, message, 'b'); - * }); - * driver.call(message.push, message, 'c'); - * driver.call(function() { - * alert('message is abc? ' + (message.join('') == 'abc')); - * }); + * @record + */ +class IWebDriver { + + /** @return {!promise.ControlFlow} The control flow used by this instance. */ + controlFlow() {} + + /** + * Schedules a {@link command.Command} to be executed by this driver's + * {@link command.Executor}. + * + * @param {!command.Command} command The command to schedule. + * @param {string} description A description of the command for debugging. + * @return {!promise.Thenable} A promise that will be resolved + * with the command result. + * @template T + */ + schedule(command, description) {} + + /** + * Sets the {@linkplain input.FileDetector file detector} that should be + * used with this instance. + * @param {input.FileDetector} detector The detector to use or {@code null}. + */ + setFileDetector(detector) {} + + /** + * @return {!command.Executor} The command executor used by this instance. + */ + getExecutor() {} + + /** + * @return {!promise.Thenable} A promise for this client's session. + */ + getSession() {} + + /** + * @return {!promise.Thenable} A promise that will resolve with + * the this instance's capabilities. + */ + getCapabilities() {} + + /** + * Terminates the browser session. After calling quit, this instance will be + * invalidated and may no longer be used to issue commands against the + * browser. + * + * @return {!promise.Thenable} A promise that will be resolved when the + * command has completed. + */ + quit() {} + + /** + * Creates a new action sequence using this driver. The sequence will not be + * scheduled for execution until {@link actions.ActionSequence#perform} is + * called. Example: + * + * driver.actions(). + * mouseDown(element1). + * mouseMove(element2). + * mouseUp(). + * perform(); + * + * @return {!actions.ActionSequence} A new action sequence for this instance. + */ + actions() {} + + /** + * Creates a new touch sequence using this driver. The sequence will not be + * scheduled for execution until {@link actions.TouchSequence#perform} is + * called. Example: + * + * driver.touchActions(). + * tap(element1). + * doubleTap(element2). + * perform(); + * + * @return {!actions.TouchSequence} A new touch sequence for this instance. + */ + touchActions() {} + + /** + * Schedules a command to execute JavaScript in the context of the currently + * selected frame or window. The script fragment will be executed as the body + * of an anonymous function. If the script is provided as a function object, + * that function will be converted to a string for injection into the target + * window. + * + * Any arguments provided in addition to the script will be included as script + * arguments and may be referenced using the {@code arguments} object. + * Arguments may be a boolean, number, string, or {@linkplain WebElement}. + * Arrays and objects may also be used as script arguments as long as each item + * adheres to the types previously mentioned. + * + * The script may refer to any variables accessible from the current window. + * Furthermore, the script will execute in the window's context, thus + * {@code document} may be used to refer to the current document. Any local + * variables will not be available once the script has finished executing, + * though global variables will persist. + * + * If the script has a return value (i.e. if the script contains a return + * statement), then the following steps will be taken for resolving this + * functions return value: + * + * - For a HTML element, the value will resolve to a {@linkplain WebElement} + * - Null and undefined return values will resolve to null + * - Booleans, numbers, and strings will resolve as is + * - Functions will resolve to their string representation + * - For arrays and objects, each member item will be converted according to + * the rules above + * + * @param {!(string|Function)} script The script to execute. + * @param {...*} var_args The arguments to pass to the script. + * @return {!promise.Thenable} A promise that will resolve to the + * scripts return value. + * @template T + */ + executeScript(script, var_args) {} + + /** + * Schedules a command to execute asynchronous JavaScript in the context of the + * currently selected frame or window. The script fragment will be executed as + * the body of an anonymous function. If the script is provided as a function + * object, that function will be converted to a string for injection into the + * target window. + * + * Any arguments provided in addition to the script will be included as script + * arguments and may be referenced using the {@code arguments} object. + * Arguments may be a boolean, number, string, or {@code WebElement}. + * Arrays and objects may also be used as script arguments as long as each item + * adheres to the types previously mentioned. + * + * Unlike executing synchronous JavaScript with {@link #executeScript}, + * scripts executed with this function must explicitly signal they are finished + * by invoking the provided callback. This callback will always be injected + * into the executed function as the last argument, and thus may be referenced + * with {@code arguments[arguments.length - 1]}. The following steps will be + * taken for resolving this functions return value against the first argument + * to the script's callback function: + * + * - For a HTML element, the value will resolve to a + * {@link WebElement} + * - Null and undefined return values will resolve to null + * - Booleans, numbers, and strings will resolve as is + * - Functions will resolve to their string representation + * - For arrays and objects, each member item will be converted according to + * the rules above + * + * __Example #1:__ Performing a sleep that is synchronized with the currently + * selected window: + * + * var start = new Date().getTime(); + * driver.executeAsyncScript( + * 'window.setTimeout(arguments[arguments.length - 1], 500);'). + * then(function() { + * console.log( + * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms'); + * }); + * + * __Example #2:__ Synchronizing a test with an AJAX application: + * + * var button = driver.findElement(By.id('compose-button')); + * button.click(); + * driver.executeAsyncScript( + * 'var callback = arguments[arguments.length - 1];' + + * 'mailClient.getComposeWindowWidget().onload(callback);'); + * driver.switchTo().frame('composeWidget'); + * driver.findElement(By.id('to')).sendKeys('dog@example.com'); + * + * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In + * this example, the inject script is specified with a function literal. When + * using this format, the function is converted to a string for injection, so it + * should not reference any symbols not defined in the scope of the page under + * test. + * + * driver.executeAsyncScript(function() { + * var callback = arguments[arguments.length - 1]; + * var xhr = new XMLHttpRequest(); + * xhr.open("GET", "/resource/data.json", true); + * xhr.onreadystatechange = function() { + * if (xhr.readyState == 4) { + * callback(xhr.responseText); + * } + * }; + * xhr.send(''); + * }).then(function(str) { + * console.log(JSON.parse(str)['food']); + * }); + * + * @param {!(string|Function)} script The script to execute. + * @param {...*} var_args The arguments to pass to the script. + * @return {!promise.Thenable} A promise that will resolve to the + * scripts return value. + * @template T + */ + executeAsyncScript(script, var_args) {} + + /** + * Schedules a command to execute a custom function. + * @param {function(...): (T|IThenable)} fn The function to execute. + * @param {Object=} opt_scope The object in whose scope to execute the function. + * @param {...*} var_args Any arguments to pass to the function. + * @return {!promise.Thenable} A promise that will be resolved' + * with the function's result. + * @template T + */ + call(fn, opt_scope, var_args) {} + + /** + * Schedules a command to wait for a condition to hold. The condition may be + * specified by a {@link Condition}, as a custom function, or as any + * promise-like thenable. + * + * For a {@link Condition} or function, the wait will repeatedly + * evaluate the condition until it returns a truthy value. If any errors occur + * while evaluating the condition, they will be allowed to propagate. In the + * event a condition returns a {@link promise.Promise promise}, the polling + * loop will wait for it to be resolved and use the resolved value for whether + * the condition has been satisified. Note the resolution time for a promise + * is factored into whether a wait has timed out. + * + * Note, if the provided condition is a {@link WebElementCondition}, then + * the wait will return a {@link WebElementPromise} that will resolve to the + * element that satisified the condition. + * + * _Example:_ waiting up to 10 seconds for an element to be present on the + * page. + * + * var button = driver.wait(until.elementLocated(By.id('foo')), 10000); + * button.click(); + * + * This function may also be used to block the command flow on the resolution + * of any thenable promise object. When given a promise, the command will + * simply wait for its resolution before completing. A timeout may be provided + * to fail the command if the promise does not resolve before the timeout + * expires. + * + * _Example:_ Suppose you have a function, `startTestServer`, that returns a + * promise for when a server is ready for requests. You can block a WebDriver + * client on this promise with: + * + * var started = startTestServer(); + * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds'); + * driver.get(getServerUrl()); + * + * @param {!(IThenable| + * Condition| + * function(!WebDriver): T)} condition The condition to + * wait on, defined as a promise, condition object, or a function to + * evaluate as a condition. + * @param {number=} opt_timeout How long to wait for the condition to be true. + * @param {string=} opt_message An optional message to use if the wait times + * out. + * @return {!(promise.Thenable|WebElementPromise)} A promise that will be + * resolved with the first truthy value returned by the condition + * function, or rejected if the condition times out. If the input + * input condition is an instance of a {@link WebElementCondition}, + * the returned value will be a {@link WebElementPromise}. + * @throws {TypeError} if the provided `condition` is not a valid type. + * @template T + */ + wait(condition, opt_timeout, opt_message) {} + + /** + * Schedules a command to make the driver sleep for the given amount of time. + * @param {number} ms The amount of time, in milliseconds, to sleep. + * @return {!promise.Thenable} A promise that will be resolved + * when the sleep has finished. + */ + sleep(ms) {} + + /** + * Schedules a command to retrieve the current window handle. + * @return {!promise.Thenable} A promise that will be + * resolved with the current window handle. + */ + getWindowHandle() {} + + /** + * Schedules a command to retrieve the current list of available window handles. + * @return {!promise.Thenable>} A promise that will + * be resolved with an array of window handles. + */ + getAllWindowHandles() {} + + /** + * Schedules a command to retrieve the current page's source. The page source + * returned is a representation of the underlying DOM: do not expect it to be + * formatted or escaped in the same way as the response sent from the web + * server. + * @return {!promise.Thenable} A promise that will be + * resolved with the current page source. + */ + getPageSource() {} + + /** + * Schedules a command to close the current window. + * @return {!promise.Thenable} A promise that will be resolved + * when this command has completed. + */ + close() {} + + /** + * Schedules a command to navigate to the given URL. + * @param {string} url The fully qualified URL to open. + * @return {!promise.Thenable} A promise that will be resolved + * when the document has finished loading. + */ + get(url) {} + + /** + * Schedules a command to retrieve the URL of the current page. + * @return {!promise.Thenable} A promise that will be + * resolved with the current URL. + */ + getCurrentUrl() {} + + /** + * Schedules a command to retrieve the current page's title. + * @return {!promise.Thenable} A promise that will be + * resolved with the current page's title. + */ + getTitle() {} + + /** + * Schedule a command to find an element on the page. If the element cannot be + * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned + * by the driver. Unlike other commands, this error cannot be suppressed. In + * other words, scheduling a command to find an element doubles as an assert + * that the element is present on the page. To test whether an element is + * present on the page, use {@link #findElements}: + * + * driver.findElements(By.id('foo')) + * .then(found => console.log('Element found? %s', !!found.length)); + * + * The search criteria for an element may be defined using one of the + * factories in the {@link webdriver.By} namespace, or as a short-hand + * {@link webdriver.By.Hash} object. For example, the following two statements + * are equivalent: + * + * var e1 = driver.findElement(By.id('foo')); + * var e2 = driver.findElement({id:'foo'}); + * + * You may also provide a custom locator function, which takes as input this + * instance and returns a {@link WebElement}, or a promise that will resolve + * to a WebElement. If the returned promise resolves to an array of + * WebElements, WebDriver will use the first element. For example, to find the + * first visible link on a page, you could write: + * + * var link = driver.findElement(firstVisibleLink); + * + * function firstVisibleLink(driver) { + * var links = driver.findElements(By.tagName('a')); + * return promise.filter(links, function(link) { + * return link.isDisplayed(); + * }); + * } + * + * @param {!(by.By|Function)} locator The locator to use. + * @return {!WebElementPromise} A WebElement that can be used to issue + * commands against the located element. If the element is not found, the + * element will be invalidated and all scheduled commands aborted. + */ + findElement(locator) {} + + /** + * Schedule a command to search for multiple elements on the page. + * + * @param {!(by.By|Function)} locator The locator to use. + * @return {!promise.Thenable>} A + * promise that will resolve to an array of WebElements. + */ + findElements(locator) {} + + /** + * Schedule a command to take a screenshot. The driver makes a best effort to + * return a screenshot of the following, in order of preference: + * + * 1. Entire page + * 2. Current window + * 3. Visible portion of the current frame + * 4. The entire display containing the browser + * + * @return {!promise.Thenable} A promise that will be + * resolved to the screenshot as a base-64 encoded PNG. + */ + takeScreenshot() {} + + /** + * @return {!Options} The options interface for this instance. + */ + manage() {} + + /** + * @return {!Navigation} The navigation interface for this instance. + */ + navigate() {} + + /** + * @return {!TargetLocator} The target locator interface for this + * instance. + */ + switchTo() {} +} + + +/** + * Each WebDriver instance provides automated control over a browser session. * + * @implements {IWebDriver} */ class WebDriver { /** - * @param {!(Session|promise.Promise)} session Either a - * known session or a promise that will be resolved to a session. + * @param {!(Session|IThenable)} session Either a known session or a + * promise that will be resolved to a session. * @param {!command.Executor} executor The executor to use when sending * commands to the browser. * @param {promise.ControlFlow=} opt_flow The flow to * schedule commands through. Defaults to the active flow object. + * @param {(function(this: void): ?)=} opt_onQuit A function to call, if any, + * when the session is terminated. */ - constructor(session, executor, opt_flow) { - /** @private {!promise.Promise} */ - this.session_ = promise.fulfilled(session); + constructor(session, executor, opt_flow, opt_onQuit) { + /** @private {!promise.ControlFlow} */ + this.flow_ = opt_flow || promise.controlFlow(); + + /** @private {!promise.Thenable} */ + this.session_ = this.flow_.promise(resolve => resolve(session)); /** @private {!command.Executor} */ this.executor_ = executor; - /** @private {!promise.ControlFlow} */ - this.flow_ = opt_flow || promise.controlFlow(); - /** @private {input.FileDetector} */ this.fileDetector_ = null; + + /** @private @const {(function(this: void): ?|undefined)} */ + this.onQuit_ = opt_onQuit; } /** @@ -350,9 +750,20 @@ class WebDriver { * commands should execute under, including the initial session creation. * Defaults to the {@link promise.controlFlow() currently active} * control flow. + * @param {(function(new: WebDriver, + * !IThenable, + * !command.Executor, + * promise.ControlFlow=))=} opt_ctor + * A reference to the constructor of the specific type of WebDriver client + * to instantiate. Will create a vanilla {@linkplain WebDriver} instance + * if a constructor is not provided. + * @param {(function(this: void): ?)=} opt_onQuit A callback to invoke when + * the newly created session is terminated. This should be used to clean + * up any resources associated with the session. * @return {!WebDriver} The driver for the newly created session. */ - static createSession(executor, capabilities, opt_flow) { + static createSession( + executor, capabilities, opt_flow, opt_ctor, opt_onQuit) { let flow = opt_flow || promise.controlFlow(); let cmd = new command.Command(command.Name.NEW_SESSION); @@ -366,31 +777,22 @@ class WebDriver { let session = flow.execute( () => executeCommand(executor, cmd), 'WebDriver.createSession()'); - return new WebDriver(session, executor, flow); + if (typeof opt_onQuit === 'function') { + session = session.catch(err => { + return Promise.resolve(opt_onQuit.call(void 0)).then(_ => {throw err}); + }); + } + const ctor = opt_ctor || WebDriver; + return new ctor(session, executor, flow, opt_onQuit); } - /** - * @return {!promise.ControlFlow} The control flow used by this - * instance. - */ + /** @override */ controlFlow() { return this.flow_; } - /** - * Schedules a {@link command.Command} to be executed by this driver's - * {@link command.Executor}. - * - * @param {!command.Command} command The command to schedule. - * @param {string} description A description of the command for debugging. - * @return {!promise.Promise} A promise that will be resolved - * with the command result. - * @template T - */ + /** @override */ schedule(command, description) { - var self = this; - - checkHasNotQuit(); command.setParameter('sessionId', this.session_); // If any of the command parameters are rejected promises, those @@ -411,149 +813,71 @@ class WebDriver { var flow = this.flow_; var executor = this.executor_; - return flow.execute(function() { - // A call to WebDriver.quit() may have been scheduled in the same event - // loop as this |command|, which would prevent us from detecting that the - // driver has quit above. Therefore, we need to make another quick check. - // We still check above so we can fail as early as possible. - checkHasNotQuit(); - + return flow.execute(() => { // Retrieve resolved command parameters; any previously suppressed errors // will now propagate up through the control flow as part of the command // execution. return prepCommand.then(function(parameters) { command.setParameters(parameters); return executor.execute(command); - }).then(value => fromWireValue(self, value)); + }).then(value => fromWireValue(this, value)); }, description); - - function checkHasNotQuit() { - if (!self.session_) { - throw new error.NoSuchSessionError( - 'This driver instance does not have a valid session ID ' + - '(did you call WebDriver.quit()?) and may no longer be ' + - 'used.'); - } - } } - /** - * Sets the {@linkplain input.FileDetector file detector} that should be - * used with this instance. - * @param {input.FileDetector} detector The detector to use or {@code null}. - */ + /** @override */ setFileDetector(detector) { this.fileDetector_ = detector; } - /** - * @return {!command.Executor} The command executor used by this instance. - */ + /** @override */ getExecutor() { return this.executor_; } - /** - * @return {!promise.Promise} A promise for this client's - * session. - */ + /** @override */ getSession() { return this.session_; } - /** - * @return {!promise.Promise} A promise - * that will resolve with the this instance's capabilities. - */ + /** @override */ getCapabilities() { - return this.session_.then(session => session.getCapabilities()); + return this.session_.then(s => s.getCapabilities()); } - /** - * Schedules a command to quit the current session. After calling quit, this - * instance will be invalidated and may no longer be used to issue commands - * against the browser. - * @return {!promise.Promise} A promise that will be resolved - * when the command has completed. - */ + /** @override */ quit() { var result = this.schedule( new command.Command(command.Name.QUIT), 'WebDriver.quit()'); // Delete our session ID when the quit command finishes; this will allow us // to throw an error when attemnpting to use a driver post-quit. - return result.finally(() => delete this.session_); + return /** @type {!promise.Thenable} */(promise.finally(result, () => { + this.session_ = this.flow_.promise((_, reject) => { + reject(new error.NoSuchSessionError( + 'This driver instance does not have a valid session ID ' + + '(did you call WebDriver.quit()?) and may no longer be used.')); + }); + + // Only want the session rejection to bubble if accessed. + this.session_.catch(function() {}); + + if (this.onQuit_) { + return this.onQuit_.call(void 0); + } + })); } - /** - * Creates a new action sequence using this driver. The sequence will not be - * scheduled for execution until {@link actions.ActionSequence#perform} is - * called. Example: - * - * driver.actions(). - * mouseDown(element1). - * mouseMove(element2). - * mouseUp(). - * perform(); - * - * @return {!actions.ActionSequence} A new action sequence for this instance. - */ + /** @override */ actions() { return new actions.ActionSequence(this); } - /** - * Creates a new touch sequence using this driver. The sequence will not be - * scheduled for execution until {@link actions.TouchSequence#perform} is - * called. Example: - * - * driver.touchActions(). - * tap(element1). - * doubleTap(element2). - * perform(); - * - * @return {!actions.TouchSequence} A new touch sequence for this instance. - */ + /** @override */ touchActions() { return new actions.TouchSequence(this); } - - /** - * Schedules a command to execute JavaScript in the context of the currently - * selected frame or window. The script fragment will be executed as the body - * of an anonymous function. If the script is provided as a function object, - * that function will be converted to a string for injection into the target - * window. - * - * Any arguments provided in addition to the script will be included as script - * arguments and may be referenced using the {@code arguments} object. - * Arguments may be a boolean, number, string, or {@linkplain WebElement}. - * Arrays and objects may also be used as script arguments as long as each item - * adheres to the types previously mentioned. - * - * The script may refer to any variables accessible from the current window. - * Furthermore, the script will execute in the window's context, thus - * {@code document} may be used to refer to the current document. Any local - * variables will not be available once the script has finished executing, - * though global variables will persist. - * - * If the script has a return value (i.e. if the script contains a return - * statement), then the following steps will be taken for resolving this - * functions return value: - * - * - For a HTML element, the value will resolve to a {@linkplain WebElement} - * - Null and undefined return values will resolve to null - * - Booleans, numbers, and strings will resolve as is - * - Functions will resolve to their string representation - * - For arrays and objects, each member item will be converted according to - * the rules above - * - * @param {!(string|Function)} script The script to execute. - * @param {...*} var_args The arguments to pass to the script. - * @return {!promise.Promise} A promise that will resolve to the - * scripts return value. - * @template T - */ + + /** @override */ executeScript(script, var_args) { if (typeof script === 'function') { script = 'return (' + script + ').apply(null, arguments);'; @@ -567,82 +891,7 @@ class WebDriver { 'WebDriver.executeScript()'); } - /** - * Schedules a command to execute asynchronous JavaScript in the context of the - * currently selected frame or window. The script fragment will be executed as - * the body of an anonymous function. If the script is provided as a function - * object, that function will be converted to a string for injection into the - * target window. - * - * Any arguments provided in addition to the script will be included as script - * arguments and may be referenced using the {@code arguments} object. - * Arguments may be a boolean, number, string, or {@code WebElement}. - * Arrays and objects may also be used as script arguments as long as each item - * adheres to the types previously mentioned. - * - * Unlike executing synchronous JavaScript with {@link #executeScript}, - * scripts executed with this function must explicitly signal they are finished - * by invoking the provided callback. This callback will always be injected - * into the executed function as the last argument, and thus may be referenced - * with {@code arguments[arguments.length - 1]}. The following steps will be - * taken for resolving this functions return value against the first argument - * to the script's callback function: - * - * - For a HTML element, the value will resolve to a - * {@link WebElement} - * - Null and undefined return values will resolve to null - * - Booleans, numbers, and strings will resolve as is - * - Functions will resolve to their string representation - * - For arrays and objects, each member item will be converted according to - * the rules above - * - * __Example #1:__ Performing a sleep that is synchronized with the currently - * selected window: - * - * var start = new Date().getTime(); - * driver.executeAsyncScript( - * 'window.setTimeout(arguments[arguments.length - 1], 500);'). - * then(function() { - * console.log( - * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms'); - * }); - * - * __Example #2:__ Synchronizing a test with an AJAX application: - * - * var button = driver.findElement(By.id('compose-button')); - * button.click(); - * driver.executeAsyncScript( - * 'var callback = arguments[arguments.length - 1];' + - * 'mailClient.getComposeWindowWidget().onload(callback);'); - * driver.switchTo().frame('composeWidget'); - * driver.findElement(By.id('to')).sendKeys('dog@example.com'); - * - * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In - * this example, the inject script is specified with a function literal. When - * using this format, the function is converted to a string for injection, so it - * should not reference any symbols not defined in the scope of the page under - * test. - * - * driver.executeAsyncScript(function() { - * var callback = arguments[arguments.length - 1]; - * var xhr = new XMLHttpRequest(); - * xhr.open("GET", "/resource/data.json", true); - * xhr.onreadystatechange = function() { - * if (xhr.readyState == 4) { - * callback(xhr.responseText); - * } - * }; - * xhr.send(''); - * }).then(function(str) { - * console.log(JSON.parse(str)['food']); - * }); - * - * @param {!(string|Function)} script The script to execute. - * @param {...*} var_args The arguments to pass to the script. - * @return {!promise.Promise} A promise that will resolve to the - * scripts return value. - * @template T - */ + /** @override */ executeAsyncScript(script, var_args) { if (typeof script === 'function') { script = 'return (' + script + ').apply(null, arguments);'; @@ -655,20 +904,10 @@ class WebDriver { 'WebDriver.executeScript()'); } - /** - * Schedules a command to execute a custom function. - * @param {function(...): (T|promise.Promise)} fn The function to - * execute. - * @param {Object=} opt_scope The object in whose scope to execute the function. - * @param {...*} var_args Any arguments to pass to the function. - * @return {!promise.Promise} A promise that will be resolved' - * with the function's result. - * @template T - */ + /** @override */ call(fn, opt_scope, var_args) { let args = Array.prototype.slice.call(arguments, 2); - let flow = this.flow_; - return flow.execute(function() { + return this.flow_.execute(function() { return promise.fullyResolved(args).then(function(args) { if (promise.isGenerator(fn)) { args.unshift(fn, opt_scope); @@ -679,62 +918,11 @@ class WebDriver { }, 'WebDriver.call(' + (fn.name || 'function') + ')'); } - /** - * Schedules a command to wait for a condition to hold. The condition may be - * specified by a {@link Condition}, as a custom function, or as any - * promise-like thenable. - * - * For a {@link Condition} or function, the wait will repeatedly - * evaluate the condition until it returns a truthy value. If any errors occur - * while evaluating the condition, they will be allowed to propagate. In the - * event a condition returns a {@link promise.Promise promise}, the polling - * loop will wait for it to be resolved and use the resolved value for whether - * the condition has been satisified. Note the resolution time for a promise - * is factored into whether a wait has timed out. - * - * Note, if the provided condition is a {@link WebElementCondition}, then - * the wait will return a {@link WebElementPromise} that will resolve to the - * element that satisified the condition. - * - * _Example:_ waiting up to 10 seconds for an element to be present on the - * page. - * - * var button = driver.wait(until.elementLocated(By.id('foo')), 10000); - * button.click(); - * - * This function may also be used to block the command flow on the resolution - * of any thenable promise object. When given a promise, the command will - * simply wait for its resolution before completing. A timeout may be provided - * to fail the command if the promise does not resolve before the timeout - * expires. - * - * _Example:_ Suppose you have a function, `startTestServer`, that returns a - * promise for when a server is ready for requests. You can block a WebDriver - * client on this promise with: - * - * var started = startTestServer(); - * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds'); - * driver.get(getServerUrl()); - * - * @param {!(promise.Promise| - * Condition| - * function(!WebDriver): T)} condition The condition to - * wait on, defined as a promise, condition object, or a function to - * evaluate as a condition. - * @param {number=} opt_timeout How long to wait for the condition to be true. - * @param {string=} opt_message An optional message to use if the wait times - * out. - * @return {!(promise.Promise|WebElementPromise)} A promise that will be - * resolved with the first truthy value returned by the condition - * function, or rejected if the condition times out. If the input - * input condition is an instance of a {@link WebElementCondition}, - * the returned value will be a {@link WebElementPromise}. - * @template T - */ + /** @override */ wait(condition, opt_timeout, opt_message) { if (promise.isPromise(condition)) { return this.flow_.wait( - /** @type {!promise.Promise} */(condition), + /** @type {!IThenable} */(condition), opt_timeout, opt_message); } @@ -745,6 +933,12 @@ class WebDriver { fn = condition.fn; } + if (typeof fn !== 'function') { + throw TypeError( + 'Wait condition must be a promise-like object, function, or a ' + + 'Condition object'); + } + var driver = this; var result = this.flow_.wait(function() { if (promise.isGenerator(fn)) { @@ -766,129 +960,57 @@ class WebDriver { return result; } - /** - * Schedules a command to make the driver sleep for the given amount of time. - * @param {number} ms The amount of time, in milliseconds, to sleep. - * @return {!promise.Promise} A promise that will be resolved - * when the sleep has finished. - */ + /** @override */ sleep(ms) { return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')'); } - /** - * Schedules a command to retrieve the current window handle. - * @return {!promise.Promise} A promise that will be - * resolved with the current window handle. - */ + /** @override */ getWindowHandle() { return this.schedule( new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE), 'WebDriver.getWindowHandle()'); } - /** - * Schedules a command to retrieve the current list of available window handles. - * @return {!promise.Promise.>} A promise that will - * be resolved with an array of window handles. - */ + /** @override */ getAllWindowHandles() { return this.schedule( new command.Command(command.Name.GET_WINDOW_HANDLES), 'WebDriver.getAllWindowHandles()'); } - /** - * Schedules a command to retrieve the current page's source. The page source - * returned is a representation of the underlying DOM: do not expect it to be - * formatted or escaped in the same way as the response sent from the web - * server. - * @return {!promise.Promise} A promise that will be - * resolved with the current page source. - */ + /** @override */ getPageSource() { return this.schedule( new command.Command(command.Name.GET_PAGE_SOURCE), 'WebDriver.getPageSource()'); } - /** - * Schedules a command to close the current window. - * @return {!promise.Promise} A promise that will be resolved - * when this command has completed. - */ + /** @override */ close() { return this.schedule(new command.Command(command.Name.CLOSE), 'WebDriver.close()'); } - /** - * Schedules a command to navigate to the given URL. - * @param {string} url The fully qualified URL to open. - * @return {!promise.Promise} A promise that will be resolved - * when the document has finished loading. - */ + /** @override */ get(url) { return this.navigate().to(url); } - /** - * Schedules a command to retrieve the URL of the current page. - * @return {!promise.Promise} A promise that will be - * resolved with the current URL. - */ + /** @override */ getCurrentUrl() { return this.schedule( new command.Command(command.Name.GET_CURRENT_URL), 'WebDriver.getCurrentUrl()'); } - /** - * Schedules a command to retrieve the current page's title. - * @return {!promise.Promise} A promise that will be - * resolved with the current page's title. - */ + /** @override */ getTitle() { return this.schedule(new command.Command(command.Name.GET_TITLE), 'WebDriver.getTitle()'); } - /** - * Schedule a command to find an element on the page. If the element cannot be - * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned - * by the driver. Unlike other commands, this error cannot be suppressed. In - * other words, scheduling a command to find an element doubles as an assert - * that the element is present on the page. To test whether an element is - * present on the page, use {@link #isElementPresent} instead. - * - * The search criteria for an element may be defined using one of the - * factories in the {@link webdriver.By} namespace, or as a short-hand - * {@link webdriver.By.Hash} object. For example, the following two statements - * are equivalent: - * - * var e1 = driver.findElement(By.id('foo')); - * var e2 = driver.findElement({id:'foo'}); - * - * You may also provide a custom locator function, which takes as input this - * instance and returns a {@link WebElement}, or a promise that will resolve - * to a WebElement. If the returned promise resolves to an array of - * WebElements, WebDriver will use the first element. For example, to find the - * first visible link on a page, you could write: - * - * var link = driver.findElement(firstVisibleLink); - * - * function firstVisibleLink(driver) { - * var links = driver.findElements(By.tagName('a')); - * return promise.filter(links, function(link) { - * return link.isDisplayed(); - * }); - * } - * - * @param {!(by.By|Function)} locator The locator to use. - * @return {!WebElementPromise} A WebElement that can be used to issue - * commands against the located element. If the element is not found, the - * element will be invalidated and all scheduled commands aborted. - */ + /** @override */ findElement(locator) { let id; locator = by.checkedLocator(locator); @@ -907,7 +1029,7 @@ class WebDriver { * @param {!Function} locatorFn The locator function to use. * @param {!(WebDriver|WebElement)} context The search * context. - * @return {!promise.Promise.} A + * @return {!promise.Thenable} A * promise that will resolve to a list of WebElements. * @private */ @@ -923,13 +1045,7 @@ class WebDriver { }); } - /** - * Schedule a command to search for multiple elements on the page. - * - * @param {!(by.By|Function)} locator The locator to use. - * @return {!promise.Promise.>} A - * promise that will resolve to an array of WebElements. - */ + /** @override */ findElements(locator) { locator = by.checkedLocator(locator); if (typeof locator === 'function') { @@ -951,7 +1067,7 @@ class WebDriver { /** * @param {!Function} locatorFn The locator function to use. * @param {!(WebDriver|WebElement)} context The search context. - * @return {!promise.Promise>} A promise that + * @return {!promise.Thenable>} A promise that * will resolve to an array of WebElements. * @private */ @@ -971,41 +1087,23 @@ class WebDriver { }); } - /** - * Schedule a command to take a screenshot. The driver makes a best effort to - * return a screenshot of the following, in order of preference: - * - * 1. Entire page - * 2. Current window - * 3. Visible portion of the current frame - * 4. The entire display containing the browser - * - * @return {!promise.Promise} A promise that will be - * resolved to the screenshot as a base-64 encoded PNG. - */ + /** @override */ takeScreenshot() { return this.schedule(new command.Command(command.Name.SCREENSHOT), 'WebDriver.takeScreenshot()'); } - /** - * @return {!Options} The options interface for this instance. - */ + /** @override */ manage() { return new Options(this); } - /** - * @return {!Navigation} The navigation interface for this instance. - */ + /** @override */ navigate() { return new Navigation(this); } - /** - * @return {!TargetLocator} The target locator interface for this - * instance. - */ + /** @override */ switchTo() { return new TargetLocator(this); } @@ -1015,7 +1113,7 @@ class WebDriver { /** * Interface for navigating back and forth in the browser history. * - * This class should never be instantiated directly. Insead, obtain an instance + * This class should never be instantiated directly. Instead, obtain an instance * with * * webdriver.navigate() @@ -1035,7 +1133,7 @@ class Navigation { /** * Schedules a command to navigate to a new URL. * @param {string} url The URL to navigate to. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the URL has been loaded. */ to(url) { @@ -1047,7 +1145,7 @@ class Navigation { /** * Schedules a command to move backwards in the browser history. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the navigation event has completed. */ back() { @@ -1058,7 +1156,7 @@ class Navigation { /** * Schedules a command to move forwards in the browser history. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the navigation event has completed. */ forward() { @@ -1069,7 +1167,7 @@ class Navigation { /** * Schedules a command to refresh the current page. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the navigation event has completed. */ refresh() { @@ -1116,7 +1214,7 @@ class Options { * }); * * @param {!Options.Cookie} spec Defines the cookie to add. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the cookie has been added to the page. * @throws {error.InvalidArgumentError} if any of the cookie parameters are * invalid. @@ -1171,7 +1269,7 @@ class Options { /** * Schedules a command to delete all cookies visible to the current page. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when all cookies have been deleted. */ deleteAllCookies() { @@ -1185,7 +1283,7 @@ class Options { * is a no-op if there is no cookie with the given name visible to the current * page. * @param {string} name The name of the cookie to delete. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the cookie has been deleted. */ deleteCookie(name) { @@ -1199,7 +1297,7 @@ class Options { * Schedules a command to retrieve all cookies visible to the current page. * Each cookie will be returned as a JSON object as described by the WebDriver * wire protocol. - * @return {!promise.Promise>} A promise that will be + * @return {!promise.Thenable>} A promise that will be * resolved with the cookies visible to the current browsing context. */ getCookies() { @@ -1214,7 +1312,7 @@ class Options { * described by the WebDriver wire protocol. * * @param {string} name The name of the cookie to retrieve. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * with the named cookie, or `null` if there is no such cookie. */ getCookie(name) { @@ -1365,7 +1463,7 @@ class Timeouts { * slower location strategies like XPath. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the implicit wait timeout has been set. */ implicitlyWait(ms) { @@ -1378,7 +1476,7 @@ class Timeouts { * less than or equal to 0, the script will be allowed to run indefinitely. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the script timeout has been set. */ setScriptTimeout(ms) { @@ -1391,7 +1489,7 @@ class Timeouts { * indefinite. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the timeout has been set. */ pageLoadTimeout(ms) { @@ -1411,7 +1509,7 @@ class Timeouts { /** * An interface for managing the current window. * - * This class should never be instantiated directly. Insead, obtain an instance + * This class should never be instantiated directly. Instead, obtain an instance * with * * webdriver.manage().window() @@ -1432,7 +1530,7 @@ class Window { /** * Retrieves the window's current position, relative to the top left corner of * the screen. - * @return {!promise.Promise.<{x: number, y: number}>} A promise + * @return {!promise.Thenable<{x: number, y: number}>} A promise * that will be resolved with the window's position in the form of a * {x:number, y:number} object literal. */ @@ -1449,7 +1547,7 @@ class Window { * side of the screen. * @param {number} y The desired vertical position, relative to the top of the * of the screen. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the command has completed. */ setPosition(x, y) { @@ -1463,7 +1561,7 @@ class Window { /** * Retrieves the window's current size. - * @return {!promise.Promise<{width: number, height: number}>} A + * @return {!promise.Thenable<{width: number, height: number}>} A * promise that will be resolved with the window's size in the form of a * {width:number, height:number} object literal. */ @@ -1478,7 +1576,7 @@ class Window { * Resizes the current window. * @param {number} width The desired window width. * @param {number} height The desired window height. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the command has completed. */ setSize(width, height) { @@ -1492,7 +1590,7 @@ class Window { /** * Maximizes the current window. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the command has completed. */ maximize() { @@ -1534,7 +1632,7 @@ class Logs { * entries since the last call, or from the start of the session. * * @param {!logging.Type} type The desired log type. - * @return {!promise.Promise.>} A + * @return {!promise.Thenable>} A * promise that will resolve to a list of log entries for the specified * type. */ @@ -1557,7 +1655,7 @@ class Logs { /** * Retrieves the log types available to this driver. - * @return {!promise.Promise>} A + * @return {!promise.Thenable>} A * promise that will resolve to a list of available log types. */ getAvailableLogTypes() { @@ -1604,7 +1702,7 @@ class TargetLocator { /** * Schedules a command to switch focus of all future commands to the topmost * frame on the page. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the driver has changed focus to the default content. */ defaultContent() { @@ -1630,7 +1728,7 @@ class TargetLocator { * rejected with a {@linkplain error.NoSuchFrameError}. * * @param {(number|WebElement|null)} id The frame locator. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the driver has changed focus to the specified frame. */ frame(id) { @@ -1650,7 +1748,7 @@ class TargetLocator { * * @param {string} nameOrHandle The name or window handle of the window to * switch focus to. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the driver has changed focus to the specified window. */ window(nameOrHandle) { @@ -1714,8 +1812,8 @@ class WebElement { /** @private {!WebDriver} */ this.driver_ = driver; - /** @private {!promise.Promise} */ - this.id_ = promise.fulfilled(id); + /** @private {!promise.Thenable} */ + this.id_ = driver.controlFlow().promise(resolve => resolve(id)); } /** @@ -1762,12 +1860,12 @@ class WebElement { * * @param {!WebElement} a A WebElement. * @param {!WebElement} b A WebElement. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved to whether the two WebElements are equal. */ static equals(a, b) { if (a === b) { - return promise.fulfilled(true); + return a.driver_.controlFlow().promise(resolve => resolve(true)); } let ids = [a.getId(), b.getId()]; return promise.all(ids).then(function(ids) { @@ -1791,7 +1889,7 @@ class WebElement { } /** - * @return {!promise.Promise} A promise that resolves to + * @return {!promise.Thenable} A promise that resolves to * the server-assigned opaque ID assigned to this element. */ getId() { @@ -1812,14 +1910,14 @@ class WebElement { * * @param {!command.Command} command The command to schedule. * @param {string} description A description of the command for debugging. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * with the command result. * @template T * @see WebDriver#schedule * @private */ schedule_(command, description) { - command.setParameter('id', this.getId()); + command.setParameter('id', this); return this.driver_.schedule(command, description); } @@ -1878,7 +1976,7 @@ class WebElement { * * @param {!(by.By|Function)} locator The locator strategy to use when * searching for the element. - * @return {!promise.Promise>} A + * @return {!promise.Thenable>} A * promise that will resolve to an array of WebElements. */ findElements(locator) { @@ -1897,7 +1995,7 @@ class WebElement { /** * Schedules a command to click on this element. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the click command has completed. */ click() { @@ -1959,7 +2057,7 @@ class WebElement { * sequence of keys to type. Number keys may be referenced numerically or * by string (1 or '1'). All arguments will be joined into a single * sequence. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when all keys have been typed. */ sendKeys(var_args) { @@ -1993,7 +2091,7 @@ class WebElement { keys.catch(function() {}); var element = this; - return this.driver_.flow_.execute(function() { + return this.getDriver().controlFlow().execute(function() { return keys.then(function(keys) { return element.driver_.fileDetector_ .handleFile(element.driver_, keys.join('')); @@ -2008,7 +2106,7 @@ class WebElement { /** * Schedules a command to query for the tag/node name of this element. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved with the element's tag name. */ getTagName() { @@ -2029,7 +2127,7 @@ class WebElement { * * @param {string} cssStyleProperty The name of the CSS style property to look * up. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved with the requested CSS value. */ getCssValue(cssStyleProperty) { @@ -2065,7 +2163,7 @@ class WebElement { * - "readonly" * * @param {string} attributeName The name of the attribute to query. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved with the attribute's value. The returned value will always be * either a string or null. */ @@ -2080,7 +2178,7 @@ class WebElement { * Get the visible (i.e. not hidden by CSS) innerText of this element, * including sub-elements, without any leading or trailing whitespace. * - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved with the element's visible text. */ getText() { @@ -2092,7 +2190,7 @@ class WebElement { /** * Schedules a command to compute the size of this element's bounding box, in * pixels. - * @return {!promise.Promise.<{width: number, height: number}>} A + * @return {!promise.Thenable<{width: number, height: number}>} A * promise that will be resolved with the element's size as a * {@code {width:number, height:number}} object. */ @@ -2104,7 +2202,7 @@ class WebElement { /** * Schedules a command to compute the location of this element in page space. - * @return {!promise.Promise.<{x: number, y: number}>} A promise that + * @return {!promise.Thenable<{x: number, y: number}>} A promise that * will be resolved to the element's location as a * {@code {x:number, y:number}} object. */ @@ -2117,7 +2215,7 @@ class WebElement { /** * Schedules a command to query whether the DOM element represented by this * instance is enabled, as dicted by the {@code disabled} attribute. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved with whether this element is currently enabled. */ isEnabled() { @@ -2128,7 +2226,7 @@ class WebElement { /** * Schedules a command to query whether this element is selected. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved with whether this element is currently selected. */ isSelected() { @@ -2141,7 +2239,7 @@ class WebElement { * Schedules a command to submit the form containing this element (or this * element if it is a FORM element). This command is a no-op if the element is * not contained in a form. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the form has been submitted. */ submit() { @@ -2154,7 +2252,7 @@ class WebElement { * Schedules a command to clear the `value` of this element. This command has * no effect if the underlying DOM element is neither a text INPUT element * nor a TEXTAREA element. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when the element has been cleared. */ clear() { @@ -2165,7 +2263,7 @@ class WebElement { /** * Schedules a command to test whether this element is currently displayed. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved with whether this element is currently visible on the page. */ isDisplayed() { @@ -2181,7 +2279,7 @@ class WebElement { * @param {boolean=} opt_scroll Optional argument that indicates whether the * element should be scrolled into view before taking a screenshot. * Defaults to false. - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved to the screenshot as a base-64 encoded PNG. */ takeScreenshot(opt_scroll) { @@ -2206,24 +2304,30 @@ class WebElement { * return el.click(); * }); * - * @implements {promise.Thenable} + * @implements {promise.CancellableThenable} * @final */ class WebElementPromise extends WebElement { /** * @param {!WebDriver} driver The parent WebDriver instance for this * element. - * @param {!promise.Promise} el A promise + * @param {!promise.Thenable} el A promise * that will resolve to the promised element. */ constructor(driver, el) { super(driver, 'unused'); - /** @override */ - this.cancel = el.cancel.bind(el); - - /** @override */ - this.isPending = el.isPending.bind(el); + /** + * Cancel operation is only supported if the wrapped thenable is also + * cancellable. + * @param {(string|Error)=} opt_reason + * @override + */ + this.cancel = function(opt_reason) { + if (promise.CancellableThenable.isImplementation(el)) { + /** @type {!promise.CancellableThenable} */(el).cancel(opt_reason); + } + } /** @override */ this.then = el.then.bind(el); @@ -2231,9 +2335,6 @@ class WebElementPromise extends WebElement { /** @override */ this.catch = el.catch.bind(el); - /** @override */ - this.finally = el.finally.bind(el); - /** * Defers returning the element ID until the wrapped WebElement has been * resolved. @@ -2246,7 +2347,7 @@ class WebElementPromise extends WebElement { }; } } -promise.Thenable.addImplementation(WebElementPromise); +promise.CancellableThenable.addImplementation(WebElementPromise); ////////////////////////////////////////////////////////////////////////////// @@ -2272,15 +2373,15 @@ class Alert { /** @private {!WebDriver} */ this.driver_ = driver; - /** @private {!promise.Promise} */ - this.text_ = promise.fulfilled(text); + /** @private {!promise.Thenable} */ + this.text_ = driver.controlFlow().promise(resolve => resolve(text)); } /** * Retrieves the message text displayed with this alert. For instance, if the * alert were opened with alert("hello"), then this would return "hello". * - * @return {!promise.Promise} A promise that will be + * @return {!promise.Thenable} A promise that will be * resolved to the text displayed with this alert. */ getText() { @@ -2294,7 +2395,7 @@ class Alert { * * @param {string} username The username to send. * @param {string} password The password to send. - * @return {!promise.Promise} A promise that will be resolved when this + * @return {!promise.Thenable} A promise that will be resolved when this * command has completed. */ authenticateAs(username, password) { @@ -2307,7 +2408,7 @@ class Alert { /** * Accepts this alert. * - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when this command has completed. */ accept() { @@ -2319,7 +2420,7 @@ class Alert { /** * Dismisses this alert. * - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when this command has completed. */ dismiss() { @@ -2334,7 +2435,7 @@ class Alert { * window.confirm). * * @param {string} text The text to set. - * @return {!promise.Promise} A promise that will be resolved + * @return {!promise.Thenable} A promise that will be resolved * when this command has completed. */ sendKeys(text) { @@ -2357,7 +2458,7 @@ class Alert { * return alert.dismiss(); * }); * - * @implements {promise.Thenable.} + * @implements {promise.CancellableThenable} * @final */ class AlertPromise extends Alert { @@ -2370,11 +2471,17 @@ class AlertPromise extends Alert { constructor(driver, alert) { super(driver, 'unused'); - /** @override */ - this.cancel = alert.cancel.bind(alert); - - /** @override */ - this.isPending = alert.isPending.bind(alert); + /** + * Cancel operation is only supported if the wrapped thenable is also + * cancellable. + * @param {(string|Error)=} opt_reason + * @override + */ + this.cancel = function(opt_reason) { + if (promise.CancellableThenable.isImplementation(alert)) { + /** @type {!promise.CancellableThenable} */(alert).cancel(opt_reason); + } + }; /** @override */ this.then = alert.then.bind(alert); @@ -2382,9 +2489,6 @@ class AlertPromise extends Alert { /** @override */ this.catch = alert.catch.bind(alert); - /** @override */ - this.finally = alert.finally.bind(alert); - /** * Defer returning text until the promised alert has been resolved. * @override @@ -2436,7 +2540,7 @@ class AlertPromise extends Alert { }; } } -promise.Thenable.addImplementation(AlertPromise); +promise.CancellableThenable.addImplementation(AlertPromise); // PUBLIC API @@ -2451,6 +2555,7 @@ module.exports = { Options: Options, TargetLocator: TargetLocator, Timeouts: Timeouts, + IWebDriver: IWebDriver, WebDriver: WebDriver, WebElement: WebElement, WebElementCondition: WebElementCondition, -- cgit v1.2.3