selenium-webdriver 3.0.0-beta-1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGES.md +109 -0
  2. package/LICENSE +1 -1
  3. package/README.md +7 -6
  4. package/chrome.js +31 -128
  5. package/edge.js +13 -91
  6. package/example/chrome_android.js +18 -13
  7. package/example/chrome_mobile_emulation.js +19 -15
  8. package/example/google_search.js +11 -11
  9. package/example/google_search_generator.js +23 -18
  10. package/example/google_search_test.js +31 -17
  11. package/example/logging.js +9 -17
  12. package/example/parallel_flows.js +3 -0
  13. package/firefox/binary.js +17 -0
  14. package/firefox/index.js +198 -101
  15. package/firefox/profile.js +24 -3
  16. package/http/util.js +95 -59
  17. package/ie.js +6 -10
  18. package/index.js +636 -2
  19. package/lib/actions.js +18 -10
  20. package/lib/atoms/getAttribute.js +11 -0
  21. package/lib/atoms/isDisplayed.js +106 -0
  22. package/lib/firefox/amd64/libnoblur64.so +0 -0
  23. package/lib/firefox/i386/libnoblur.so +0 -0
  24. package/lib/firefox/webdriver.xpi +0 -0
  25. package/lib/http.js +159 -46
  26. package/lib/logging.js +7 -1
  27. package/lib/promise.js +602 -358
  28. package/lib/proxy.js +28 -0
  29. package/lib/session.js +1 -1
  30. package/lib/test/build.js +26 -28
  31. package/lib/test/data/formPage.html +1 -0
  32. package/lib/test/fileserver.js +12 -10
  33. package/lib/test/httpserver.js +4 -6
  34. package/lib/test/index.js +18 -11
  35. package/lib/test/promise.js +79 -0
  36. package/lib/webdriver.js +587 -479
  37. package/opera.js +12 -88
  38. package/package.json +13 -14
  39. package/phantomjs.js +7 -14
  40. package/remote/index.js +201 -30
  41. package/safari.js +62 -416
  42. package/test/actions_test.js +9 -11
  43. package/test/chrome/options_test.js +8 -8
  44. package/test/chrome/service_test.js +2 -2
  45. package/test/cookie_test.js +57 -57
  46. package/test/element_finding_test.js +201 -185
  47. package/test/execute_script_test.js +108 -111
  48. package/test/fingerprint_test.js +16 -11
  49. package/test/firefox/firefox_test.js +75 -49
  50. package/test/firefox/profile_test.js +1 -3
  51. package/test/http/util_test.js +21 -27
  52. package/test/io_test.js +2 -5
  53. package/test/lib/http_test.js +8 -6
  54. package/test/lib/promise_aplus_test.js +48 -44
  55. package/test/lib/promise_error_test.js +687 -611
  56. package/test/lib/promise_flow_test.js +1810 -1806
  57. package/test/lib/promise_generator_test.js +227 -225
  58. package/test/lib/promise_test.js +864 -835
  59. package/test/lib/until_test.js +1 -1
  60. package/test/lib/webdriver_test.js +520 -424
  61. package/test/logging_test.js +43 -45
  62. package/test/net/portprober_test.js +23 -24
  63. package/test/page_loading_test.js +84 -78
  64. package/test/phantomjs/execute_phantomjs_test.js +11 -25
  65. package/test/proxy_test.js +23 -23
  66. package/test/remote_test.js +22 -22
  67. package/test/safari_test.js +108 -0
  68. package/test/session_test.js +11 -10
  69. package/test/stale_element_test.js +18 -16
  70. package/test/tag_name_test.js +7 -5
  71. package/test/testing/assert_test.js +2 -3
  72. package/test/testing/index_test.js +163 -117
  73. package/test/upload_test.js +13 -13
  74. package/test/window_test.js +94 -70
  75. package/testing/index.js +176 -37
  76. package/builder.js +0 -553
  77. package/lib/safari/client.js +0 -56
package/lib/webdriver.js CHANGED
@@ -28,7 +28,7 @@ const command = require('./command');
28
28
  const error = require('./error');
29
29
  const input = require('./input');
30
30
  const logging = require('./logging');
31
- const Session = require('./session').Session;
31
+ const {Session} = require('./session');
32
32
  const Symbols = require('./symbols');
33
33
  const promise = require('./promise');
34
34
 
@@ -64,13 +64,13 @@ class Condition {
64
64
  /**
65
65
  * Defines a condition that will result in a {@link WebElement}.
66
66
  *
67
- * @extends {Condition<!(WebElement|promise.Promise<!WebElement>)>}
67
+ * @extends {Condition<!(WebElement|IThenable<!WebElement>)>}
68
68
  */
69
69
  class WebElementCondition extends Condition {
70
70
  /**
71
71
  * @param {string} message A descriptive error message. Should complete the
72
72
  * sentence "Waiting [...]"
73
- * @param {function(!WebDriver): !(WebElement|promise.Promise<!WebElement>)}
73
+ * @param {function(!WebDriver): !(WebElement|IThenable<!WebElement>)}
74
74
  * fn The condition function to evaluate on each iteration of the wait
75
75
  * loop.
76
76
  */
@@ -237,45 +237,445 @@ function fromWireValue(driver, value) {
237
237
 
238
238
 
239
239
  /**
240
- * Creates a new WebDriver client, which provides control over a browser.
240
+ * Structural interface for a WebDriver client.
241
241
  *
242
- * Every command.Command returns a {@link promise.Promise} that
243
- * represents the result of that command. Callbacks may be registered on this
244
- * object to manipulate the command result or catch an expected error. Any
245
- * commands scheduled with a callback are considered sub-commands and will
246
- * execute before the next command in the current frame. For example:
247
- *
248
- * var message = [];
249
- * driver.call(message.push, message, 'a').then(function() {
250
- * driver.call(message.push, message, 'b');
251
- * });
252
- * driver.call(message.push, message, 'c');
253
- * driver.call(function() {
254
- * alert('message is abc? ' + (message.join('') == 'abc'));
255
- * });
242
+ * @record
243
+ */
244
+ class IWebDriver {
245
+
246
+ /** @return {!promise.ControlFlow} The control flow used by this instance. */
247
+ controlFlow() {}
248
+
249
+ /**
250
+ * Schedules a {@link command.Command} to be executed by this driver's
251
+ * {@link command.Executor}.
252
+ *
253
+ * @param {!command.Command} command The command to schedule.
254
+ * @param {string} description A description of the command for debugging.
255
+ * @return {!promise.Thenable<T>} A promise that will be resolved
256
+ * with the command result.
257
+ * @template T
258
+ */
259
+ schedule(command, description) {}
260
+
261
+ /**
262
+ * Sets the {@linkplain input.FileDetector file detector} that should be
263
+ * used with this instance.
264
+ * @param {input.FileDetector} detector The detector to use or {@code null}.
265
+ */
266
+ setFileDetector(detector) {}
267
+
268
+ /**
269
+ * @return {!command.Executor} The command executor used by this instance.
270
+ */
271
+ getExecutor() {}
272
+
273
+ /**
274
+ * @return {!promise.Thenable<!Session>} A promise for this client's session.
275
+ */
276
+ getSession() {}
277
+
278
+ /**
279
+ * @return {!promise.Thenable<!Capabilities>} A promise that will resolve with
280
+ * the this instance's capabilities.
281
+ */
282
+ getCapabilities() {}
283
+
284
+ /**
285
+ * Terminates the browser session. After calling quit, this instance will be
286
+ * invalidated and may no longer be used to issue commands against the
287
+ * browser.
288
+ *
289
+ * @return {!promise.Thenable<void>} A promise that will be resolved when the
290
+ * command has completed.
291
+ */
292
+ quit() {}
293
+
294
+ /**
295
+ * Creates a new action sequence using this driver. The sequence will not be
296
+ * scheduled for execution until {@link actions.ActionSequence#perform} is
297
+ * called. Example:
298
+ *
299
+ * driver.actions().
300
+ * mouseDown(element1).
301
+ * mouseMove(element2).
302
+ * mouseUp().
303
+ * perform();
304
+ *
305
+ * @return {!actions.ActionSequence} A new action sequence for this instance.
306
+ */
307
+ actions() {}
308
+
309
+ /**
310
+ * Creates a new touch sequence using this driver. The sequence will not be
311
+ * scheduled for execution until {@link actions.TouchSequence#perform} is
312
+ * called. Example:
313
+ *
314
+ * driver.touchActions().
315
+ * tap(element1).
316
+ * doubleTap(element2).
317
+ * perform();
318
+ *
319
+ * @return {!actions.TouchSequence} A new touch sequence for this instance.
320
+ */
321
+ touchActions() {}
322
+
323
+ /**
324
+ * Schedules a command to execute JavaScript in the context of the currently
325
+ * selected frame or window. The script fragment will be executed as the body
326
+ * of an anonymous function. If the script is provided as a function object,
327
+ * that function will be converted to a string for injection into the target
328
+ * window.
329
+ *
330
+ * Any arguments provided in addition to the script will be included as script
331
+ * arguments and may be referenced using the {@code arguments} object.
332
+ * Arguments may be a boolean, number, string, or {@linkplain WebElement}.
333
+ * Arrays and objects may also be used as script arguments as long as each item
334
+ * adheres to the types previously mentioned.
335
+ *
336
+ * The script may refer to any variables accessible from the current window.
337
+ * Furthermore, the script will execute in the window's context, thus
338
+ * {@code document} may be used to refer to the current document. Any local
339
+ * variables will not be available once the script has finished executing,
340
+ * though global variables will persist.
341
+ *
342
+ * If the script has a return value (i.e. if the script contains a return
343
+ * statement), then the following steps will be taken for resolving this
344
+ * functions return value:
345
+ *
346
+ * - For a HTML element, the value will resolve to a {@linkplain WebElement}
347
+ * - Null and undefined return values will resolve to null</li>
348
+ * - Booleans, numbers, and strings will resolve as is</li>
349
+ * - Functions will resolve to their string representation</li>
350
+ * - For arrays and objects, each member item will be converted according to
351
+ * the rules above
352
+ *
353
+ * @param {!(string|Function)} script The script to execute.
354
+ * @param {...*} var_args The arguments to pass to the script.
355
+ * @return {!promise.Thenable<T>} A promise that will resolve to the
356
+ * scripts return value.
357
+ * @template T
358
+ */
359
+ executeScript(script, var_args) {}
360
+
361
+ /**
362
+ * Schedules a command to execute asynchronous JavaScript in the context of the
363
+ * currently selected frame or window. The script fragment will be executed as
364
+ * the body of an anonymous function. If the script is provided as a function
365
+ * object, that function will be converted to a string for injection into the
366
+ * target window.
367
+ *
368
+ * Any arguments provided in addition to the script will be included as script
369
+ * arguments and may be referenced using the {@code arguments} object.
370
+ * Arguments may be a boolean, number, string, or {@code WebElement}.
371
+ * Arrays and objects may also be used as script arguments as long as each item
372
+ * adheres to the types previously mentioned.
373
+ *
374
+ * Unlike executing synchronous JavaScript with {@link #executeScript},
375
+ * scripts executed with this function must explicitly signal they are finished
376
+ * by invoking the provided callback. This callback will always be injected
377
+ * into the executed function as the last argument, and thus may be referenced
378
+ * with {@code arguments[arguments.length - 1]}. The following steps will be
379
+ * taken for resolving this functions return value against the first argument
380
+ * to the script's callback function:
381
+ *
382
+ * - For a HTML element, the value will resolve to a
383
+ * {@link WebElement}
384
+ * - Null and undefined return values will resolve to null
385
+ * - Booleans, numbers, and strings will resolve as is
386
+ * - Functions will resolve to their string representation
387
+ * - For arrays and objects, each member item will be converted according to
388
+ * the rules above
389
+ *
390
+ * __Example #1:__ Performing a sleep that is synchronized with the currently
391
+ * selected window:
392
+ *
393
+ * var start = new Date().getTime();
394
+ * driver.executeAsyncScript(
395
+ * 'window.setTimeout(arguments[arguments.length - 1], 500);').
396
+ * then(function() {
397
+ * console.log(
398
+ * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
399
+ * });
400
+ *
401
+ * __Example #2:__ Synchronizing a test with an AJAX application:
402
+ *
403
+ * var button = driver.findElement(By.id('compose-button'));
404
+ * button.click();
405
+ * driver.executeAsyncScript(
406
+ * 'var callback = arguments[arguments.length - 1];' +
407
+ * 'mailClient.getComposeWindowWidget().onload(callback);');
408
+ * driver.switchTo().frame('composeWidget');
409
+ * driver.findElement(By.id('to')).sendKeys('dog@example.com');
410
+ *
411
+ * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In
412
+ * this example, the inject script is specified with a function literal. When
413
+ * using this format, the function is converted to a string for injection, so it
414
+ * should not reference any symbols not defined in the scope of the page under
415
+ * test.
416
+ *
417
+ * driver.executeAsyncScript(function() {
418
+ * var callback = arguments[arguments.length - 1];
419
+ * var xhr = new XMLHttpRequest();
420
+ * xhr.open("GET", "/resource/data.json", true);
421
+ * xhr.onreadystatechange = function() {
422
+ * if (xhr.readyState == 4) {
423
+ * callback(xhr.responseText);
424
+ * }
425
+ * };
426
+ * xhr.send('');
427
+ * }).then(function(str) {
428
+ * console.log(JSON.parse(str)['food']);
429
+ * });
430
+ *
431
+ * @param {!(string|Function)} script The script to execute.
432
+ * @param {...*} var_args The arguments to pass to the script.
433
+ * @return {!promise.Thenable<T>} A promise that will resolve to the
434
+ * scripts return value.
435
+ * @template T
436
+ */
437
+ executeAsyncScript(script, var_args) {}
438
+
439
+ /**
440
+ * Schedules a command to execute a custom function.
441
+ * @param {function(...): (T|IThenable<T>)} fn The function to execute.
442
+ * @param {Object=} opt_scope The object in whose scope to execute the function.
443
+ * @param {...*} var_args Any arguments to pass to the function.
444
+ * @return {!promise.Thenable<T>} A promise that will be resolved'
445
+ * with the function's result.
446
+ * @template T
447
+ */
448
+ call(fn, opt_scope, var_args) {}
449
+
450
+ /**
451
+ * Schedules a command to wait for a condition to hold. The condition may be
452
+ * specified by a {@link Condition}, as a custom function, or as any
453
+ * promise-like thenable.
454
+ *
455
+ * For a {@link Condition} or function, the wait will repeatedly
456
+ * evaluate the condition until it returns a truthy value. If any errors occur
457
+ * while evaluating the condition, they will be allowed to propagate. In the
458
+ * event a condition returns a {@link promise.Promise promise}, the polling
459
+ * loop will wait for it to be resolved and use the resolved value for whether
460
+ * the condition has been satisified. Note the resolution time for a promise
461
+ * is factored into whether a wait has timed out.
462
+ *
463
+ * Note, if the provided condition is a {@link WebElementCondition}, then
464
+ * the wait will return a {@link WebElementPromise} that will resolve to the
465
+ * element that satisified the condition.
466
+ *
467
+ * _Example:_ waiting up to 10 seconds for an element to be present on the
468
+ * page.
469
+ *
470
+ * var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
471
+ * button.click();
472
+ *
473
+ * This function may also be used to block the command flow on the resolution
474
+ * of any thenable promise object. When given a promise, the command will
475
+ * simply wait for its resolution before completing. A timeout may be provided
476
+ * to fail the command if the promise does not resolve before the timeout
477
+ * expires.
478
+ *
479
+ * _Example:_ Suppose you have a function, `startTestServer`, that returns a
480
+ * promise for when a server is ready for requests. You can block a WebDriver
481
+ * client on this promise with:
482
+ *
483
+ * var started = startTestServer();
484
+ * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
485
+ * driver.get(getServerUrl());
486
+ *
487
+ * @param {!(IThenable<T>|
488
+ * Condition<T>|
489
+ * function(!WebDriver): T)} condition The condition to
490
+ * wait on, defined as a promise, condition object, or a function to
491
+ * evaluate as a condition.
492
+ * @param {number=} opt_timeout How long to wait for the condition to be true.
493
+ * @param {string=} opt_message An optional message to use if the wait times
494
+ * out.
495
+ * @return {!(promise.Thenable<T>|WebElementPromise)} A promise that will be
496
+ * resolved with the first truthy value returned by the condition
497
+ * function, or rejected if the condition times out. If the input
498
+ * input condition is an instance of a {@link WebElementCondition},
499
+ * the returned value will be a {@link WebElementPromise}.
500
+ * @throws {TypeError} if the provided `condition` is not a valid type.
501
+ * @template T
502
+ */
503
+ wait(condition, opt_timeout, opt_message) {}
504
+
505
+ /**
506
+ * Schedules a command to make the driver sleep for the given amount of time.
507
+ * @param {number} ms The amount of time, in milliseconds, to sleep.
508
+ * @return {!promise.Thenable<void>} A promise that will be resolved
509
+ * when the sleep has finished.
510
+ */
511
+ sleep(ms) {}
512
+
513
+ /**
514
+ * Schedules a command to retrieve the current window handle.
515
+ * @return {!promise.Thenable<string>} A promise that will be
516
+ * resolved with the current window handle.
517
+ */
518
+ getWindowHandle() {}
519
+
520
+ /**
521
+ * Schedules a command to retrieve the current list of available window handles.
522
+ * @return {!promise.Thenable<!Array<string>>} A promise that will
523
+ * be resolved with an array of window handles.
524
+ */
525
+ getAllWindowHandles() {}
526
+
527
+ /**
528
+ * Schedules a command to retrieve the current page's source. The page source
529
+ * returned is a representation of the underlying DOM: do not expect it to be
530
+ * formatted or escaped in the same way as the response sent from the web
531
+ * server.
532
+ * @return {!promise.Thenable<string>} A promise that will be
533
+ * resolved with the current page source.
534
+ */
535
+ getPageSource() {}
536
+
537
+ /**
538
+ * Schedules a command to close the current window.
539
+ * @return {!promise.Thenable<void>} A promise that will be resolved
540
+ * when this command has completed.
541
+ */
542
+ close() {}
543
+
544
+ /**
545
+ * Schedules a command to navigate to the given URL.
546
+ * @param {string} url The fully qualified URL to open.
547
+ * @return {!promise.Thenable<void>} A promise that will be resolved
548
+ * when the document has finished loading.
549
+ */
550
+ get(url) {}
551
+
552
+ /**
553
+ * Schedules a command to retrieve the URL of the current page.
554
+ * @return {!promise.Thenable<string>} A promise that will be
555
+ * resolved with the current URL.
556
+ */
557
+ getCurrentUrl() {}
558
+
559
+ /**
560
+ * Schedules a command to retrieve the current page's title.
561
+ * @return {!promise.Thenable<string>} A promise that will be
562
+ * resolved with the current page's title.
563
+ */
564
+ getTitle() {}
565
+
566
+ /**
567
+ * Schedule a command to find an element on the page. If the element cannot be
568
+ * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned
569
+ * by the driver. Unlike other commands, this error cannot be suppressed. In
570
+ * other words, scheduling a command to find an element doubles as an assert
571
+ * that the element is present on the page. To test whether an element is
572
+ * present on the page, use {@link #findElements}:
573
+ *
574
+ * driver.findElements(By.id('foo'))
575
+ * .then(found => console.log('Element found? %s', !!found.length));
576
+ *
577
+ * The search criteria for an element may be defined using one of the
578
+ * factories in the {@link webdriver.By} namespace, or as a short-hand
579
+ * {@link webdriver.By.Hash} object. For example, the following two statements
580
+ * are equivalent:
581
+ *
582
+ * var e1 = driver.findElement(By.id('foo'));
583
+ * var e2 = driver.findElement({id:'foo'});
584
+ *
585
+ * You may also provide a custom locator function, which takes as input this
586
+ * instance and returns a {@link WebElement}, or a promise that will resolve
587
+ * to a WebElement. If the returned promise resolves to an array of
588
+ * WebElements, WebDriver will use the first element. For example, to find the
589
+ * first visible link on a page, you could write:
590
+ *
591
+ * var link = driver.findElement(firstVisibleLink);
592
+ *
593
+ * function firstVisibleLink(driver) {
594
+ * var links = driver.findElements(By.tagName('a'));
595
+ * return promise.filter(links, function(link) {
596
+ * return link.isDisplayed();
597
+ * });
598
+ * }
599
+ *
600
+ * @param {!(by.By|Function)} locator The locator to use.
601
+ * @return {!WebElementPromise} A WebElement that can be used to issue
602
+ * commands against the located element. If the element is not found, the
603
+ * element will be invalidated and all scheduled commands aborted.
604
+ */
605
+ findElement(locator) {}
606
+
607
+ /**
608
+ * Schedule a command to search for multiple elements on the page.
609
+ *
610
+ * @param {!(by.By|Function)} locator The locator to use.
611
+ * @return {!promise.Thenable<!Array<!WebElement>>} A
612
+ * promise that will resolve to an array of WebElements.
613
+ */
614
+ findElements(locator) {}
615
+
616
+ /**
617
+ * Schedule a command to take a screenshot. The driver makes a best effort to
618
+ * return a screenshot of the following, in order of preference:
619
+ *
620
+ * 1. Entire page
621
+ * 2. Current window
622
+ * 3. Visible portion of the current frame
623
+ * 4. The entire display containing the browser
624
+ *
625
+ * @return {!promise.Thenable<string>} A promise that will be
626
+ * resolved to the screenshot as a base-64 encoded PNG.
627
+ */
628
+ takeScreenshot() {}
629
+
630
+ /**
631
+ * @return {!Options} The options interface for this instance.
632
+ */
633
+ manage() {}
634
+
635
+ /**
636
+ * @return {!Navigation} The navigation interface for this instance.
637
+ */
638
+ navigate() {}
639
+
640
+ /**
641
+ * @return {!TargetLocator} The target locator interface for this
642
+ * instance.
643
+ */
644
+ switchTo() {}
645
+ }
646
+
647
+
648
+ /**
649
+ * Each WebDriver instance provides automated control over a browser session.
256
650
  *
651
+ * @implements {IWebDriver}
257
652
  */
258
653
  class WebDriver {
259
654
  /**
260
- * @param {!(Session|promise.Promise<!Session>)} session Either a
261
- * known session or a promise that will be resolved to a session.
655
+ * @param {!(Session|IThenable<!Session>)} session Either a known session or a
656
+ * promise that will be resolved to a session.
262
657
  * @param {!command.Executor} executor The executor to use when sending
263
658
  * commands to the browser.
264
659
  * @param {promise.ControlFlow=} opt_flow The flow to
265
660
  * schedule commands through. Defaults to the active flow object.
661
+ * @param {(function(this: void): ?)=} opt_onQuit A function to call, if any,
662
+ * when the session is terminated.
266
663
  */
267
- constructor(session, executor, opt_flow) {
268
- /** @private {!promise.Promise<!Session>} */
269
- this.session_ = promise.fulfilled(session);
664
+ constructor(session, executor, opt_flow, opt_onQuit) {
665
+ /** @private {!promise.ControlFlow} */
666
+ this.flow_ = opt_flow || promise.controlFlow();
667
+
668
+ /** @private {!promise.Thenable<!Session>} */
669
+ this.session_ = this.flow_.promise(resolve => resolve(session));
270
670
 
271
671
  /** @private {!command.Executor} */
272
672
  this.executor_ = executor;
273
673
 
274
- /** @private {!promise.ControlFlow} */
275
- this.flow_ = opt_flow || promise.controlFlow();
276
-
277
674
  /** @private {input.FileDetector} */
278
675
  this.fileDetector_ = null;
676
+
677
+ /** @private @const {(function(this: void): ?|undefined)} */
678
+ this.onQuit_ = opt_onQuit;
279
679
  }
280
680
 
281
681
  /**
@@ -350,9 +750,20 @@ class WebDriver {
350
750
  * commands should execute under, including the initial session creation.
351
751
  * Defaults to the {@link promise.controlFlow() currently active}
352
752
  * control flow.
753
+ * @param {(function(new: WebDriver,
754
+ * !IThenable<!Session>,
755
+ * !command.Executor,
756
+ * promise.ControlFlow=))=} opt_ctor
757
+ * A reference to the constructor of the specific type of WebDriver client
758
+ * to instantiate. Will create a vanilla {@linkplain WebDriver} instance
759
+ * if a constructor is not provided.
760
+ * @param {(function(this: void): ?)=} opt_onQuit A callback to invoke when
761
+ * the newly created session is terminated. This should be used to clean
762
+ * up any resources associated with the session.
353
763
  * @return {!WebDriver} The driver for the newly created session.
354
764
  */
355
- static createSession(executor, capabilities, opt_flow) {
765
+ static createSession(
766
+ executor, capabilities, opt_flow, opt_ctor, opt_onQuit) {
356
767
  let flow = opt_flow || promise.controlFlow();
357
768
  let cmd = new command.Command(command.Name.NEW_SESSION);
358
769
 
@@ -366,31 +777,22 @@ class WebDriver {
366
777
  let session = flow.execute(
367
778
  () => executeCommand(executor, cmd),
368
779
  'WebDriver.createSession()');
369
- return new WebDriver(session, executor, flow);
780
+ if (typeof opt_onQuit === 'function') {
781
+ session = session.catch(err => {
782
+ return Promise.resolve(opt_onQuit.call(void 0)).then(_ => {throw err});
783
+ });
784
+ }
785
+ const ctor = opt_ctor || WebDriver;
786
+ return new ctor(session, executor, flow, opt_onQuit);
370
787
  }
371
788
 
372
- /**
373
- * @return {!promise.ControlFlow} The control flow used by this
374
- * instance.
375
- */
789
+ /** @override */
376
790
  controlFlow() {
377
791
  return this.flow_;
378
792
  }
379
793
 
380
- /**
381
- * Schedules a {@link command.Command} to be executed by this driver's
382
- * {@link command.Executor}.
383
- *
384
- * @param {!command.Command} command The command to schedule.
385
- * @param {string} description A description of the command for debugging.
386
- * @return {!promise.Promise<T>} A promise that will be resolved
387
- * with the command result.
388
- * @template T
389
- */
794
+ /** @override */
390
795
  schedule(command, description) {
391
- var self = this;
392
-
393
- checkHasNotQuit();
394
796
  command.setParameter('sessionId', this.session_);
395
797
 
396
798
  // If any of the command parameters are rejected promises, those
@@ -411,149 +813,71 @@ class WebDriver {
411
813
 
412
814
  var flow = this.flow_;
413
815
  var executor = this.executor_;
414
- return flow.execute(function() {
415
- // A call to WebDriver.quit() may have been scheduled in the same event
416
- // loop as this |command|, which would prevent us from detecting that the
417
- // driver has quit above. Therefore, we need to make another quick check.
418
- // We still check above so we can fail as early as possible.
419
- checkHasNotQuit();
420
-
816
+ return flow.execute(() => {
421
817
  // Retrieve resolved command parameters; any previously suppressed errors
422
818
  // will now propagate up through the control flow as part of the command
423
819
  // execution.
424
820
  return prepCommand.then(function(parameters) {
425
821
  command.setParameters(parameters);
426
822
  return executor.execute(command);
427
- }).then(value => fromWireValue(self, value));
823
+ }).then(value => fromWireValue(this, value));
428
824
  }, description);
429
-
430
- function checkHasNotQuit() {
431
- if (!self.session_) {
432
- throw new error.NoSuchSessionError(
433
- 'This driver instance does not have a valid session ID ' +
434
- '(did you call WebDriver.quit()?) and may no longer be ' +
435
- 'used.');
436
- }
437
- }
438
825
  }
439
826
 
440
- /**
441
- * Sets the {@linkplain input.FileDetector file detector} that should be
442
- * used with this instance.
443
- * @param {input.FileDetector} detector The detector to use or {@code null}.
444
- */
827
+ /** @override */
445
828
  setFileDetector(detector) {
446
829
  this.fileDetector_ = detector;
447
830
  }
448
831
 
449
- /**
450
- * @return {!command.Executor} The command executor used by this instance.
451
- */
832
+ /** @override */
452
833
  getExecutor() {
453
834
  return this.executor_;
454
835
  }
455
836
 
456
- /**
457
- * @return {!promise.Promise<!Session>} A promise for this client's
458
- * session.
459
- */
837
+ /** @override */
460
838
  getSession() {
461
839
  return this.session_;
462
840
  }
463
841
 
464
- /**
465
- * @return {!promise.Promise<!Capabilities>} A promise
466
- * that will resolve with the this instance's capabilities.
467
- */
842
+ /** @override */
468
843
  getCapabilities() {
469
- return this.session_.then(session => session.getCapabilities());
844
+ return this.session_.then(s => s.getCapabilities());
470
845
  }
471
846
 
472
- /**
473
- * Schedules a command to quit the current session. After calling quit, this
474
- * instance will be invalidated and may no longer be used to issue commands
475
- * against the browser.
476
- * @return {!promise.Promise<void>} A promise that will be resolved
477
- * when the command has completed.
478
- */
847
+ /** @override */
479
848
  quit() {
480
849
  var result = this.schedule(
481
850
  new command.Command(command.Name.QUIT),
482
851
  'WebDriver.quit()');
483
852
  // Delete our session ID when the quit command finishes; this will allow us
484
853
  // to throw an error when attemnpting to use a driver post-quit.
485
- return result.finally(() => delete this.session_);
854
+ return /** @type {!promise.Thenable} */(promise.finally(result, () => {
855
+ this.session_ = this.flow_.promise((_, reject) => {
856
+ reject(new error.NoSuchSessionError(
857
+ 'This driver instance does not have a valid session ID ' +
858
+ '(did you call WebDriver.quit()?) and may no longer be used.'));
859
+ });
860
+
861
+ // Only want the session rejection to bubble if accessed.
862
+ this.session_.catch(function() {});
863
+
864
+ if (this.onQuit_) {
865
+ return this.onQuit_.call(void 0);
866
+ }
867
+ }));
486
868
  }
487
869
 
488
- /**
489
- * Creates a new action sequence using this driver. The sequence will not be
490
- * scheduled for execution until {@link actions.ActionSequence#perform} is
491
- * called. Example:
492
- *
493
- * driver.actions().
494
- * mouseDown(element1).
495
- * mouseMove(element2).
496
- * mouseUp().
497
- * perform();
498
- *
499
- * @return {!actions.ActionSequence} A new action sequence for this instance.
500
- */
870
+ /** @override */
501
871
  actions() {
502
872
  return new actions.ActionSequence(this);
503
873
  }
504
874
 
505
- /**
506
- * Creates a new touch sequence using this driver. The sequence will not be
507
- * scheduled for execution until {@link actions.TouchSequence#perform} is
508
- * called. Example:
509
- *
510
- * driver.touchActions().
511
- * tap(element1).
512
- * doubleTap(element2).
513
- * perform();
514
- *
515
- * @return {!actions.TouchSequence} A new touch sequence for this instance.
516
- */
875
+ /** @override */
517
876
  touchActions() {
518
877
  return new actions.TouchSequence(this);
519
878
  }
520
-
521
- /**
522
- * Schedules a command to execute JavaScript in the context of the currently
523
- * selected frame or window. The script fragment will be executed as the body
524
- * of an anonymous function. If the script is provided as a function object,
525
- * that function will be converted to a string for injection into the target
526
- * window.
527
- *
528
- * Any arguments provided in addition to the script will be included as script
529
- * arguments and may be referenced using the {@code arguments} object.
530
- * Arguments may be a boolean, number, string, or {@linkplain WebElement}.
531
- * Arrays and objects may also be used as script arguments as long as each item
532
- * adheres to the types previously mentioned.
533
- *
534
- * The script may refer to any variables accessible from the current window.
535
- * Furthermore, the script will execute in the window's context, thus
536
- * {@code document} may be used to refer to the current document. Any local
537
- * variables will not be available once the script has finished executing,
538
- * though global variables will persist.
539
- *
540
- * If the script has a return value (i.e. if the script contains a return
541
- * statement), then the following steps will be taken for resolving this
542
- * functions return value:
543
- *
544
- * - For a HTML element, the value will resolve to a {@linkplain WebElement}
545
- * - Null and undefined return values will resolve to null</li>
546
- * - Booleans, numbers, and strings will resolve as is</li>
547
- * - Functions will resolve to their string representation</li>
548
- * - For arrays and objects, each member item will be converted according to
549
- * the rules above
550
- *
551
- * @param {!(string|Function)} script The script to execute.
552
- * @param {...*} var_args The arguments to pass to the script.
553
- * @return {!promise.Promise<T>} A promise that will resolve to the
554
- * scripts return value.
555
- * @template T
556
- */
879
+
880
+ /** @override */
557
881
  executeScript(script, var_args) {
558
882
  if (typeof script === 'function') {
559
883
  script = 'return (' + script + ').apply(null, arguments);';
@@ -567,82 +891,7 @@ class WebDriver {
567
891
  'WebDriver.executeScript()');
568
892
  }
569
893
 
570
- /**
571
- * Schedules a command to execute asynchronous JavaScript in the context of the
572
- * currently selected frame or window. The script fragment will be executed as
573
- * the body of an anonymous function. If the script is provided as a function
574
- * object, that function will be converted to a string for injection into the
575
- * target window.
576
- *
577
- * Any arguments provided in addition to the script will be included as script
578
- * arguments and may be referenced using the {@code arguments} object.
579
- * Arguments may be a boolean, number, string, or {@code WebElement}.
580
- * Arrays and objects may also be used as script arguments as long as each item
581
- * adheres to the types previously mentioned.
582
- *
583
- * Unlike executing synchronous JavaScript with {@link #executeScript},
584
- * scripts executed with this function must explicitly signal they are finished
585
- * by invoking the provided callback. This callback will always be injected
586
- * into the executed function as the last argument, and thus may be referenced
587
- * with {@code arguments[arguments.length - 1]}. The following steps will be
588
- * taken for resolving this functions return value against the first argument
589
- * to the script's callback function:
590
- *
591
- * - For a HTML element, the value will resolve to a
592
- * {@link WebElement}
593
- * - Null and undefined return values will resolve to null
594
- * - Booleans, numbers, and strings will resolve as is
595
- * - Functions will resolve to their string representation
596
- * - For arrays and objects, each member item will be converted according to
597
- * the rules above
598
- *
599
- * __Example #1:__ Performing a sleep that is synchronized with the currently
600
- * selected window:
601
- *
602
- * var start = new Date().getTime();
603
- * driver.executeAsyncScript(
604
- * 'window.setTimeout(arguments[arguments.length - 1], 500);').
605
- * then(function() {
606
- * console.log(
607
- * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
608
- * });
609
- *
610
- * __Example #2:__ Synchronizing a test with an AJAX application:
611
- *
612
- * var button = driver.findElement(By.id('compose-button'));
613
- * button.click();
614
- * driver.executeAsyncScript(
615
- * 'var callback = arguments[arguments.length - 1];' +
616
- * 'mailClient.getComposeWindowWidget().onload(callback);');
617
- * driver.switchTo().frame('composeWidget');
618
- * driver.findElement(By.id('to')).sendKeys('dog@example.com');
619
- *
620
- * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In
621
- * this example, the inject script is specified with a function literal. When
622
- * using this format, the function is converted to a string for injection, so it
623
- * should not reference any symbols not defined in the scope of the page under
624
- * test.
625
- *
626
- * driver.executeAsyncScript(function() {
627
- * var callback = arguments[arguments.length - 1];
628
- * var xhr = new XMLHttpRequest();
629
- * xhr.open("GET", "/resource/data.json", true);
630
- * xhr.onreadystatechange = function() {
631
- * if (xhr.readyState == 4) {
632
- * callback(xhr.responseText);
633
- * }
634
- * };
635
- * xhr.send('');
636
- * }).then(function(str) {
637
- * console.log(JSON.parse(str)['food']);
638
- * });
639
- *
640
- * @param {!(string|Function)} script The script to execute.
641
- * @param {...*} var_args The arguments to pass to the script.
642
- * @return {!promise.Promise<T>} A promise that will resolve to the
643
- * scripts return value.
644
- * @template T
645
- */
894
+ /** @override */
646
895
  executeAsyncScript(script, var_args) {
647
896
  if (typeof script === 'function') {
648
897
  script = 'return (' + script + ').apply(null, arguments);';
@@ -655,20 +904,10 @@ class WebDriver {
655
904
  'WebDriver.executeScript()');
656
905
  }
657
906
 
658
- /**
659
- * Schedules a command to execute a custom function.
660
- * @param {function(...): (T|promise.Promise<T>)} fn The function to
661
- * execute.
662
- * @param {Object=} opt_scope The object in whose scope to execute the function.
663
- * @param {...*} var_args Any arguments to pass to the function.
664
- * @return {!promise.Promise<T>} A promise that will be resolved'
665
- * with the function's result.
666
- * @template T
667
- */
907
+ /** @override */
668
908
  call(fn, opt_scope, var_args) {
669
909
  let args = Array.prototype.slice.call(arguments, 2);
670
- let flow = this.flow_;
671
- return flow.execute(function() {
910
+ return this.flow_.execute(function() {
672
911
  return promise.fullyResolved(args).then(function(args) {
673
912
  if (promise.isGenerator(fn)) {
674
913
  args.unshift(fn, opt_scope);
@@ -679,62 +918,11 @@ class WebDriver {
679
918
  }, 'WebDriver.call(' + (fn.name || 'function') + ')');
680
919
  }
681
920
 
682
- /**
683
- * Schedules a command to wait for a condition to hold. The condition may be
684
- * specified by a {@link Condition}, as a custom function, or as any
685
- * promise-like thenable.
686
- *
687
- * For a {@link Condition} or function, the wait will repeatedly
688
- * evaluate the condition until it returns a truthy value. If any errors occur
689
- * while evaluating the condition, they will be allowed to propagate. In the
690
- * event a condition returns a {@link promise.Promise promise}, the polling
691
- * loop will wait for it to be resolved and use the resolved value for whether
692
- * the condition has been satisified. Note the resolution time for a promise
693
- * is factored into whether a wait has timed out.
694
- *
695
- * Note, if the provided condition is a {@link WebElementCondition}, then
696
- * the wait will return a {@link WebElementPromise} that will resolve to the
697
- * element that satisified the condition.
698
- *
699
- * _Example:_ waiting up to 10 seconds for an element to be present on the
700
- * page.
701
- *
702
- * var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
703
- * button.click();
704
- *
705
- * This function may also be used to block the command flow on the resolution
706
- * of any thenable promise object. When given a promise, the command will
707
- * simply wait for its resolution before completing. A timeout may be provided
708
- * to fail the command if the promise does not resolve before the timeout
709
- * expires.
710
- *
711
- * _Example:_ Suppose you have a function, `startTestServer`, that returns a
712
- * promise for when a server is ready for requests. You can block a WebDriver
713
- * client on this promise with:
714
- *
715
- * var started = startTestServer();
716
- * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
717
- * driver.get(getServerUrl());
718
- *
719
- * @param {!(promise.Promise<T>|
720
- * Condition<T>|
721
- * function(!WebDriver): T)} condition The condition to
722
- * wait on, defined as a promise, condition object, or a function to
723
- * evaluate as a condition.
724
- * @param {number=} opt_timeout How long to wait for the condition to be true.
725
- * @param {string=} opt_message An optional message to use if the wait times
726
- * out.
727
- * @return {!(promise.Promise<T>|WebElementPromise)} A promise that will be
728
- * resolved with the first truthy value returned by the condition
729
- * function, or rejected if the condition times out. If the input
730
- * input condition is an instance of a {@link WebElementCondition},
731
- * the returned value will be a {@link WebElementPromise}.
732
- * @template T
733
- */
921
+ /** @override */
734
922
  wait(condition, opt_timeout, opt_message) {
735
923
  if (promise.isPromise(condition)) {
736
924
  return this.flow_.wait(
737
- /** @type {!promise.Promise} */(condition),
925
+ /** @type {!IThenable} */(condition),
738
926
  opt_timeout, opt_message);
739
927
  }
740
928
 
@@ -745,6 +933,12 @@ class WebDriver {
745
933
  fn = condition.fn;
746
934
  }
747
935
 
936
+ if (typeof fn !== 'function') {
937
+ throw TypeError(
938
+ 'Wait condition must be a promise-like object, function, or a '
939
+ + 'Condition object');
940
+ }
941
+
748
942
  var driver = this;
749
943
  var result = this.flow_.wait(function() {
750
944
  if (promise.isGenerator(fn)) {
@@ -766,129 +960,57 @@ class WebDriver {
766
960
  return result;
767
961
  }
768
962
 
769
- /**
770
- * Schedules a command to make the driver sleep for the given amount of time.
771
- * @param {number} ms The amount of time, in milliseconds, to sleep.
772
- * @return {!promise.Promise<void>} A promise that will be resolved
773
- * when the sleep has finished.
774
- */
963
+ /** @override */
775
964
  sleep(ms) {
776
965
  return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');
777
966
  }
778
967
 
779
- /**
780
- * Schedules a command to retrieve the current window handle.
781
- * @return {!promise.Promise<string>} A promise that will be
782
- * resolved with the current window handle.
783
- */
968
+ /** @override */
784
969
  getWindowHandle() {
785
970
  return this.schedule(
786
971
  new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE),
787
972
  'WebDriver.getWindowHandle()');
788
973
  }
789
974
 
790
- /**
791
- * Schedules a command to retrieve the current list of available window handles.
792
- * @return {!promise.Promise.<!Array<string>>} A promise that will
793
- * be resolved with an array of window handles.
794
- */
975
+ /** @override */
795
976
  getAllWindowHandles() {
796
977
  return this.schedule(
797
978
  new command.Command(command.Name.GET_WINDOW_HANDLES),
798
979
  'WebDriver.getAllWindowHandles()');
799
980
  }
800
981
 
801
- /**
802
- * Schedules a command to retrieve the current page's source. The page source
803
- * returned is a representation of the underlying DOM: do not expect it to be
804
- * formatted or escaped in the same way as the response sent from the web
805
- * server.
806
- * @return {!promise.Promise<string>} A promise that will be
807
- * resolved with the current page source.
808
- */
982
+ /** @override */
809
983
  getPageSource() {
810
984
  return this.schedule(
811
985
  new command.Command(command.Name.GET_PAGE_SOURCE),
812
986
  'WebDriver.getPageSource()');
813
987
  }
814
988
 
815
- /**
816
- * Schedules a command to close the current window.
817
- * @return {!promise.Promise<void>} A promise that will be resolved
818
- * when this command has completed.
819
- */
989
+ /** @override */
820
990
  close() {
821
991
  return this.schedule(new command.Command(command.Name.CLOSE),
822
992
  'WebDriver.close()');
823
993
  }
824
994
 
825
- /**
826
- * Schedules a command to navigate to the given URL.
827
- * @param {string} url The fully qualified URL to open.
828
- * @return {!promise.Promise<void>} A promise that will be resolved
829
- * when the document has finished loading.
830
- */
995
+ /** @override */
831
996
  get(url) {
832
997
  return this.navigate().to(url);
833
998
  }
834
999
 
835
- /**
836
- * Schedules a command to retrieve the URL of the current page.
837
- * @return {!promise.Promise<string>} A promise that will be
838
- * resolved with the current URL.
839
- */
1000
+ /** @override */
840
1001
  getCurrentUrl() {
841
1002
  return this.schedule(
842
1003
  new command.Command(command.Name.GET_CURRENT_URL),
843
1004
  'WebDriver.getCurrentUrl()');
844
1005
  }
845
1006
 
846
- /**
847
- * Schedules a command to retrieve the current page's title.
848
- * @return {!promise.Promise<string>} A promise that will be
849
- * resolved with the current page's title.
850
- */
1007
+ /** @override */
851
1008
  getTitle() {
852
1009
  return this.schedule(new command.Command(command.Name.GET_TITLE),
853
1010
  'WebDriver.getTitle()');
854
1011
  }
855
1012
 
856
- /**
857
- * Schedule a command to find an element on the page. If the element cannot be
858
- * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned
859
- * by the driver. Unlike other commands, this error cannot be suppressed. In
860
- * other words, scheduling a command to find an element doubles as an assert
861
- * that the element is present on the page. To test whether an element is
862
- * present on the page, use {@link #isElementPresent} instead.
863
- *
864
- * The search criteria for an element may be defined using one of the
865
- * factories in the {@link webdriver.By} namespace, or as a short-hand
866
- * {@link webdriver.By.Hash} object. For example, the following two statements
867
- * are equivalent:
868
- *
869
- * var e1 = driver.findElement(By.id('foo'));
870
- * var e2 = driver.findElement({id:'foo'});
871
- *
872
- * You may also provide a custom locator function, which takes as input this
873
- * instance and returns a {@link WebElement}, or a promise that will resolve
874
- * to a WebElement. If the returned promise resolves to an array of
875
- * WebElements, WebDriver will use the first element. For example, to find the
876
- * first visible link on a page, you could write:
877
- *
878
- * var link = driver.findElement(firstVisibleLink);
879
- *
880
- * function firstVisibleLink(driver) {
881
- * var links = driver.findElements(By.tagName('a'));
882
- * return promise.filter(links, function(link) {
883
- * return link.isDisplayed();
884
- * });
885
- * }
886
- *
887
- * @param {!(by.By|Function)} locator The locator to use.
888
- * @return {!WebElementPromise} A WebElement that can be used to issue
889
- * commands against the located element. If the element is not found, the
890
- * element will be invalidated and all scheduled commands aborted.
891
- */
1013
+ /** @override */
892
1014
  findElement(locator) {
893
1015
  let id;
894
1016
  locator = by.checkedLocator(locator);
@@ -907,7 +1029,7 @@ class WebDriver {
907
1029
  * @param {!Function} locatorFn The locator function to use.
908
1030
  * @param {!(WebDriver|WebElement)} context The search
909
1031
  * context.
910
- * @return {!promise.Promise.<!WebElement>} A
1032
+ * @return {!promise.Thenable<!WebElement>} A
911
1033
  * promise that will resolve to a list of WebElements.
912
1034
  * @private
913
1035
  */
@@ -923,13 +1045,7 @@ class WebDriver {
923
1045
  });
924
1046
  }
925
1047
 
926
- /**
927
- * Schedule a command to search for multiple elements on the page.
928
- *
929
- * @param {!(by.By|Function)} locator The locator to use.
930
- * @return {!promise.Promise.<!Array.<!WebElement>>} A
931
- * promise that will resolve to an array of WebElements.
932
- */
1048
+ /** @override */
933
1049
  findElements(locator) {
934
1050
  locator = by.checkedLocator(locator);
935
1051
  if (typeof locator === 'function') {
@@ -951,7 +1067,7 @@ class WebDriver {
951
1067
  /**
952
1068
  * @param {!Function} locatorFn The locator function to use.
953
1069
  * @param {!(WebDriver|WebElement)} context The search context.
954
- * @return {!promise.Promise<!Array<!WebElement>>} A promise that
1070
+ * @return {!promise.Thenable<!Array<!WebElement>>} A promise that
955
1071
  * will resolve to an array of WebElements.
956
1072
  * @private
957
1073
  */
@@ -971,41 +1087,23 @@ class WebDriver {
971
1087
  });
972
1088
  }
973
1089
 
974
- /**
975
- * Schedule a command to take a screenshot. The driver makes a best effort to
976
- * return a screenshot of the following, in order of preference:
977
- *
978
- * 1. Entire page
979
- * 2. Current window
980
- * 3. Visible portion of the current frame
981
- * 4. The entire display containing the browser
982
- *
983
- * @return {!promise.Promise<string>} A promise that will be
984
- * resolved to the screenshot as a base-64 encoded PNG.
985
- */
1090
+ /** @override */
986
1091
  takeScreenshot() {
987
1092
  return this.schedule(new command.Command(command.Name.SCREENSHOT),
988
1093
  'WebDriver.takeScreenshot()');
989
1094
  }
990
1095
 
991
- /**
992
- * @return {!Options} The options interface for this instance.
993
- */
1096
+ /** @override */
994
1097
  manage() {
995
1098
  return new Options(this);
996
1099
  }
997
1100
 
998
- /**
999
- * @return {!Navigation} The navigation interface for this instance.
1000
- */
1101
+ /** @override */
1001
1102
  navigate() {
1002
1103
  return new Navigation(this);
1003
1104
  }
1004
1105
 
1005
- /**
1006
- * @return {!TargetLocator} The target locator interface for this
1007
- * instance.
1008
- */
1106
+ /** @override */
1009
1107
  switchTo() {
1010
1108
  return new TargetLocator(this);
1011
1109
  }
@@ -1015,7 +1113,7 @@ class WebDriver {
1015
1113
  /**
1016
1114
  * Interface for navigating back and forth in the browser history.
1017
1115
  *
1018
- * This class should never be instantiated directly. Insead, obtain an instance
1116
+ * This class should never be instantiated directly. Instead, obtain an instance
1019
1117
  * with
1020
1118
  *
1021
1119
  * webdriver.navigate()
@@ -1035,7 +1133,7 @@ class Navigation {
1035
1133
  /**
1036
1134
  * Schedules a command to navigate to a new URL.
1037
1135
  * @param {string} url The URL to navigate to.
1038
- * @return {!promise.Promise<void>} A promise that will be resolved
1136
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1039
1137
  * when the URL has been loaded.
1040
1138
  */
1041
1139
  to(url) {
@@ -1047,7 +1145,7 @@ class Navigation {
1047
1145
 
1048
1146
  /**
1049
1147
  * Schedules a command to move backwards in the browser history.
1050
- * @return {!promise.Promise<void>} A promise that will be resolved
1148
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1051
1149
  * when the navigation event has completed.
1052
1150
  */
1053
1151
  back() {
@@ -1058,7 +1156,7 @@ class Navigation {
1058
1156
 
1059
1157
  /**
1060
1158
  * Schedules a command to move forwards in the browser history.
1061
- * @return {!promise.Promise<void>} A promise that will be resolved
1159
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1062
1160
  * when the navigation event has completed.
1063
1161
  */
1064
1162
  forward() {
@@ -1069,7 +1167,7 @@ class Navigation {
1069
1167
 
1070
1168
  /**
1071
1169
  * Schedules a command to refresh the current page.
1072
- * @return {!promise.Promise<void>} A promise that will be resolved
1170
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1073
1171
  * when the navigation event has completed.
1074
1172
  */
1075
1173
  refresh() {
@@ -1116,7 +1214,7 @@ class Options {
1116
1214
  * });
1117
1215
  *
1118
1216
  * @param {!Options.Cookie} spec Defines the cookie to add.
1119
- * @return {!promise.Promise<void>} A promise that will be resolved
1217
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1120
1218
  * when the cookie has been added to the page.
1121
1219
  * @throws {error.InvalidArgumentError} if any of the cookie parameters are
1122
1220
  * invalid.
@@ -1171,7 +1269,7 @@ class Options {
1171
1269
 
1172
1270
  /**
1173
1271
  * Schedules a command to delete all cookies visible to the current page.
1174
- * @return {!promise.Promise<void>} A promise that will be resolved
1272
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1175
1273
  * when all cookies have been deleted.
1176
1274
  */
1177
1275
  deleteAllCookies() {
@@ -1185,7 +1283,7 @@ class Options {
1185
1283
  * is a no-op if there is no cookie with the given name visible to the current
1186
1284
  * page.
1187
1285
  * @param {string} name The name of the cookie to delete.
1188
- * @return {!promise.Promise<void>} A promise that will be resolved
1286
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1189
1287
  * when the cookie has been deleted.
1190
1288
  */
1191
1289
  deleteCookie(name) {
@@ -1199,7 +1297,7 @@ class Options {
1199
1297
  * Schedules a command to retrieve all cookies visible to the current page.
1200
1298
  * Each cookie will be returned as a JSON object as described by the WebDriver
1201
1299
  * wire protocol.
1202
- * @return {!promise.Promise<!Array<!Options.Cookie>>} A promise that will be
1300
+ * @return {!promise.Thenable<!Array<!Options.Cookie>>} A promise that will be
1203
1301
  * resolved with the cookies visible to the current browsing context.
1204
1302
  */
1205
1303
  getCookies() {
@@ -1214,7 +1312,7 @@ class Options {
1214
1312
  * described by the WebDriver wire protocol.
1215
1313
  *
1216
1314
  * @param {string} name The name of the cookie to retrieve.
1217
- * @return {!promise.Promise<?Options.Cookie>} A promise that will be resolved
1315
+ * @return {!promise.Thenable<?Options.Cookie>} A promise that will be resolved
1218
1316
  * with the named cookie, or `null` if there is no such cookie.
1219
1317
  */
1220
1318
  getCookie(name) {
@@ -1365,7 +1463,7 @@ class Timeouts {
1365
1463
  * slower location strategies like XPath.
1366
1464
  *
1367
1465
  * @param {number} ms The amount of time to wait, in milliseconds.
1368
- * @return {!promise.Promise<void>} A promise that will be resolved
1466
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1369
1467
  * when the implicit wait timeout has been set.
1370
1468
  */
1371
1469
  implicitlyWait(ms) {
@@ -1378,7 +1476,7 @@ class Timeouts {
1378
1476
  * less than or equal to 0, the script will be allowed to run indefinitely.
1379
1477
  *
1380
1478
  * @param {number} ms The amount of time to wait, in milliseconds.
1381
- * @return {!promise.Promise<void>} A promise that will be resolved
1479
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1382
1480
  * when the script timeout has been set.
1383
1481
  */
1384
1482
  setScriptTimeout(ms) {
@@ -1391,7 +1489,7 @@ class Timeouts {
1391
1489
  * indefinite.
1392
1490
  *
1393
1491
  * @param {number} ms The amount of time to wait, in milliseconds.
1394
- * @return {!promise.Promise<void>} A promise that will be resolved
1492
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1395
1493
  * when the timeout has been set.
1396
1494
  */
1397
1495
  pageLoadTimeout(ms) {
@@ -1411,7 +1509,7 @@ class Timeouts {
1411
1509
  /**
1412
1510
  * An interface for managing the current window.
1413
1511
  *
1414
- * This class should never be instantiated directly. Insead, obtain an instance
1512
+ * This class should never be instantiated directly. Instead, obtain an instance
1415
1513
  * with
1416
1514
  *
1417
1515
  * webdriver.manage().window()
@@ -1432,7 +1530,7 @@ class Window {
1432
1530
  /**
1433
1531
  * Retrieves the window's current position, relative to the top left corner of
1434
1532
  * the screen.
1435
- * @return {!promise.Promise.<{x: number, y: number}>} A promise
1533
+ * @return {!promise.Thenable<{x: number, y: number}>} A promise
1436
1534
  * that will be resolved with the window's position in the form of a
1437
1535
  * {x:number, y:number} object literal.
1438
1536
  */
@@ -1449,7 +1547,7 @@ class Window {
1449
1547
  * side of the screen.
1450
1548
  * @param {number} y The desired vertical position, relative to the top of the
1451
1549
  * of the screen.
1452
- * @return {!promise.Promise<void>} A promise that will be resolved
1550
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1453
1551
  * when the command has completed.
1454
1552
  */
1455
1553
  setPosition(x, y) {
@@ -1463,7 +1561,7 @@ class Window {
1463
1561
 
1464
1562
  /**
1465
1563
  * Retrieves the window's current size.
1466
- * @return {!promise.Promise<{width: number, height: number}>} A
1564
+ * @return {!promise.Thenable<{width: number, height: number}>} A
1467
1565
  * promise that will be resolved with the window's size in the form of a
1468
1566
  * {width:number, height:number} object literal.
1469
1567
  */
@@ -1478,7 +1576,7 @@ class Window {
1478
1576
  * Resizes the current window.
1479
1577
  * @param {number} width The desired window width.
1480
1578
  * @param {number} height The desired window height.
1481
- * @return {!promise.Promise<void>} A promise that will be resolved
1579
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1482
1580
  * when the command has completed.
1483
1581
  */
1484
1582
  setSize(width, height) {
@@ -1492,7 +1590,7 @@ class Window {
1492
1590
 
1493
1591
  /**
1494
1592
  * Maximizes the current window.
1495
- * @return {!promise.Promise<void>} A promise that will be resolved
1593
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1496
1594
  * when the command has completed.
1497
1595
  */
1498
1596
  maximize() {
@@ -1534,7 +1632,7 @@ class Logs {
1534
1632
  * entries since the last call, or from the start of the session.
1535
1633
  *
1536
1634
  * @param {!logging.Type} type The desired log type.
1537
- * @return {!promise.Promise.<!Array.<!logging.Entry>>} A
1635
+ * @return {!promise.Thenable<!Array.<!logging.Entry>>} A
1538
1636
  * promise that will resolve to a list of log entries for the specified
1539
1637
  * type.
1540
1638
  */
@@ -1557,7 +1655,7 @@ class Logs {
1557
1655
 
1558
1656
  /**
1559
1657
  * Retrieves the log types available to this driver.
1560
- * @return {!promise.Promise<!Array<!logging.Type>>} A
1658
+ * @return {!promise.Thenable<!Array<!logging.Type>>} A
1561
1659
  * promise that will resolve to a list of available log types.
1562
1660
  */
1563
1661
  getAvailableLogTypes() {
@@ -1604,7 +1702,7 @@ class TargetLocator {
1604
1702
  /**
1605
1703
  * Schedules a command to switch focus of all future commands to the topmost
1606
1704
  * frame on the page.
1607
- * @return {!promise.Promise<void>} A promise that will be resolved
1705
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1608
1706
  * when the driver has changed focus to the default content.
1609
1707
  */
1610
1708
  defaultContent() {
@@ -1630,7 +1728,7 @@ class TargetLocator {
1630
1728
  * rejected with a {@linkplain error.NoSuchFrameError}.
1631
1729
  *
1632
1730
  * @param {(number|WebElement|null)} id The frame locator.
1633
- * @return {!promise.Promise<void>} A promise that will be resolved
1731
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1634
1732
  * when the driver has changed focus to the specified frame.
1635
1733
  */
1636
1734
  frame(id) {
@@ -1650,13 +1748,16 @@ class TargetLocator {
1650
1748
  *
1651
1749
  * @param {string} nameOrHandle The name or window handle of the window to
1652
1750
  * switch focus to.
1653
- * @return {!promise.Promise<void>} A promise that will be resolved
1751
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1654
1752
  * when the driver has changed focus to the specified window.
1655
1753
  */
1656
1754
  window(nameOrHandle) {
1657
1755
  return this.driver_.schedule(
1658
1756
  new command.Command(command.Name.SWITCH_TO_WINDOW).
1659
- setParameter('name', nameOrHandle),
1757
+ // "name" supports the legacy drivers. "handle" is the W3C
1758
+ // compliant parameter.
1759
+ setParameter('name', nameOrHandle).
1760
+ setParameter('handle', nameOrHandle),
1660
1761
  'WebDriver.switchTo().window(' + nameOrHandle + ')');
1661
1762
  }
1662
1763
 
@@ -1711,8 +1812,8 @@ class WebElement {
1711
1812
  /** @private {!WebDriver} */
1712
1813
  this.driver_ = driver;
1713
1814
 
1714
- /** @private {!promise.Promise<string>} */
1715
- this.id_ = promise.fulfilled(id);
1815
+ /** @private {!promise.Thenable<string>} */
1816
+ this.id_ = driver.controlFlow().promise(resolve => resolve(id));
1716
1817
  }
1717
1818
 
1718
1819
  /**
@@ -1759,12 +1860,12 @@ class WebElement {
1759
1860
  *
1760
1861
  * @param {!WebElement} a A WebElement.
1761
1862
  * @param {!WebElement} b A WebElement.
1762
- * @return {!promise.Promise<boolean>} A promise that will be
1863
+ * @return {!promise.Thenable<boolean>} A promise that will be
1763
1864
  * resolved to whether the two WebElements are equal.
1764
1865
  */
1765
1866
  static equals(a, b) {
1766
1867
  if (a === b) {
1767
- return promise.fulfilled(true);
1868
+ return a.driver_.controlFlow().promise(resolve => resolve(true));
1768
1869
  }
1769
1870
  let ids = [a.getId(), b.getId()];
1770
1871
  return promise.all(ids).then(function(ids) {
@@ -1788,7 +1889,7 @@ class WebElement {
1788
1889
  }
1789
1890
 
1790
1891
  /**
1791
- * @return {!promise.Promise<string>} A promise that resolves to
1892
+ * @return {!promise.Thenable<string>} A promise that resolves to
1792
1893
  * the server-assigned opaque ID assigned to this element.
1793
1894
  */
1794
1895
  getId() {
@@ -1809,14 +1910,14 @@ class WebElement {
1809
1910
  *
1810
1911
  * @param {!command.Command} command The command to schedule.
1811
1912
  * @param {string} description A description of the command for debugging.
1812
- * @return {!promise.Promise<T>} A promise that will be resolved
1913
+ * @return {!promise.Thenable<T>} A promise that will be resolved
1813
1914
  * with the command result.
1814
1915
  * @template T
1815
1916
  * @see WebDriver#schedule
1816
1917
  * @private
1817
1918
  */
1818
1919
  schedule_(command, description) {
1819
- command.setParameter('id', this.getId());
1920
+ command.setParameter('id', this);
1820
1921
  return this.driver_.schedule(command, description);
1821
1922
  }
1822
1923
 
@@ -1875,7 +1976,7 @@ class WebElement {
1875
1976
  *
1876
1977
  * @param {!(by.By|Function)} locator The locator strategy to use when
1877
1978
  * searching for the element.
1878
- * @return {!promise.Promise<!Array<!WebElement>>} A
1979
+ * @return {!promise.Thenable<!Array<!WebElement>>} A
1879
1980
  * promise that will resolve to an array of WebElements.
1880
1981
  */
1881
1982
  findElements(locator) {
@@ -1894,7 +1995,7 @@ class WebElement {
1894
1995
 
1895
1996
  /**
1896
1997
  * Schedules a command to click on this element.
1897
- * @return {!promise.Promise<void>} A promise that will be resolved
1998
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1898
1999
  * when the click command has completed.
1899
2000
  */
1900
2001
  click() {
@@ -1956,7 +2057,7 @@ class WebElement {
1956
2057
  * sequence of keys to type. Number keys may be referenced numerically or
1957
2058
  * by string (1 or '1'). All arguments will be joined into a single
1958
2059
  * sequence.
1959
- * @return {!promise.Promise<void>} A promise that will be resolved
2060
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1960
2061
  * when all keys have been typed.
1961
2062
  */
1962
2063
  sendKeys(var_args) {
@@ -1990,14 +2091,14 @@ class WebElement {
1990
2091
  keys.catch(function() {});
1991
2092
 
1992
2093
  var element = this;
1993
- return this.driver_.flow_.execute(function() {
2094
+ return this.getDriver().controlFlow().execute(function() {
1994
2095
  return keys.then(function(keys) {
1995
2096
  return element.driver_.fileDetector_
1996
2097
  .handleFile(element.driver_, keys.join(''));
1997
2098
  }).then(function(keys) {
1998
2099
  return element.schedule_(
1999
2100
  new command.Command(command.Name.SEND_KEYS_TO_ELEMENT).
2000
- setParameter('value', [keys]),
2101
+ setParameter('value', keys.split('')),
2001
2102
  'WebElement.sendKeys()');
2002
2103
  });
2003
2104
  }, 'WebElement.sendKeys()');
@@ -2005,7 +2106,7 @@ class WebElement {
2005
2106
 
2006
2107
  /**
2007
2108
  * Schedules a command to query for the tag/node name of this element.
2008
- * @return {!promise.Promise<string>} A promise that will be
2109
+ * @return {!promise.Thenable<string>} A promise that will be
2009
2110
  * resolved with the element's tag name.
2010
2111
  */
2011
2112
  getTagName() {
@@ -2026,7 +2127,7 @@ class WebElement {
2026
2127
  *
2027
2128
  * @param {string} cssStyleProperty The name of the CSS style property to look
2028
2129
  * up.
2029
- * @return {!promise.Promise<string>} A promise that will be
2130
+ * @return {!promise.Thenable<string>} A promise that will be
2030
2131
  * resolved with the requested CSS value.
2031
2132
  */
2032
2133
  getCssValue(cssStyleProperty) {
@@ -2062,7 +2163,7 @@ class WebElement {
2062
2163
  * - "readonly"
2063
2164
  *
2064
2165
  * @param {string} attributeName The name of the attribute to query.
2065
- * @return {!promise.Promise<?string>} A promise that will be
2166
+ * @return {!promise.Thenable<?string>} A promise that will be
2066
2167
  * resolved with the attribute's value. The returned value will always be
2067
2168
  * either a string or null.
2068
2169
  */
@@ -2077,7 +2178,7 @@ class WebElement {
2077
2178
  * Get the visible (i.e. not hidden by CSS) innerText of this element,
2078
2179
  * including sub-elements, without any leading or trailing whitespace.
2079
2180
  *
2080
- * @return {!promise.Promise<string>} A promise that will be
2181
+ * @return {!promise.Thenable<string>} A promise that will be
2081
2182
  * resolved with the element's visible text.
2082
2183
  */
2083
2184
  getText() {
@@ -2089,7 +2190,7 @@ class WebElement {
2089
2190
  /**
2090
2191
  * Schedules a command to compute the size of this element's bounding box, in
2091
2192
  * pixels.
2092
- * @return {!promise.Promise.<{width: number, height: number}>} A
2193
+ * @return {!promise.Thenable<{width: number, height: number}>} A
2093
2194
  * promise that will be resolved with the element's size as a
2094
2195
  * {@code {width:number, height:number}} object.
2095
2196
  */
@@ -2101,7 +2202,7 @@ class WebElement {
2101
2202
 
2102
2203
  /**
2103
2204
  * Schedules a command to compute the location of this element in page space.
2104
- * @return {!promise.Promise.<{x: number, y: number}>} A promise that
2205
+ * @return {!promise.Thenable<{x: number, y: number}>} A promise that
2105
2206
  * will be resolved to the element's location as a
2106
2207
  * {@code {x:number, y:number}} object.
2107
2208
  */
@@ -2114,7 +2215,7 @@ class WebElement {
2114
2215
  /**
2115
2216
  * Schedules a command to query whether the DOM element represented by this
2116
2217
  * instance is enabled, as dicted by the {@code disabled} attribute.
2117
- * @return {!promise.Promise<boolean>} A promise that will be
2218
+ * @return {!promise.Thenable<boolean>} A promise that will be
2118
2219
  * resolved with whether this element is currently enabled.
2119
2220
  */
2120
2221
  isEnabled() {
@@ -2125,7 +2226,7 @@ class WebElement {
2125
2226
 
2126
2227
  /**
2127
2228
  * Schedules a command to query whether this element is selected.
2128
- * @return {!promise.Promise<boolean>} A promise that will be
2229
+ * @return {!promise.Thenable<boolean>} A promise that will be
2129
2230
  * resolved with whether this element is currently selected.
2130
2231
  */
2131
2232
  isSelected() {
@@ -2138,7 +2239,7 @@ class WebElement {
2138
2239
  * Schedules a command to submit the form containing this element (or this
2139
2240
  * element if it is a FORM element). This command is a no-op if the element is
2140
2241
  * not contained in a form.
2141
- * @return {!promise.Promise<void>} A promise that will be resolved
2242
+ * @return {!promise.Thenable<void>} A promise that will be resolved
2142
2243
  * when the form has been submitted.
2143
2244
  */
2144
2245
  submit() {
@@ -2151,7 +2252,7 @@ class WebElement {
2151
2252
  * Schedules a command to clear the `value` of this element. This command has
2152
2253
  * no effect if the underlying DOM element is neither a text INPUT element
2153
2254
  * nor a TEXTAREA element.
2154
- * @return {!promise.Promise<void>} A promise that will be resolved
2255
+ * @return {!promise.Thenable<void>} A promise that will be resolved
2155
2256
  * when the element has been cleared.
2156
2257
  */
2157
2258
  clear() {
@@ -2162,7 +2263,7 @@ class WebElement {
2162
2263
 
2163
2264
  /**
2164
2265
  * Schedules a command to test whether this element is currently displayed.
2165
- * @return {!promise.Promise<boolean>} A promise that will be
2266
+ * @return {!promise.Thenable<boolean>} A promise that will be
2166
2267
  * resolved with whether this element is currently visible on the page.
2167
2268
  */
2168
2269
  isDisplayed() {
@@ -2178,7 +2279,7 @@ class WebElement {
2178
2279
  * @param {boolean=} opt_scroll Optional argument that indicates whether the
2179
2280
  * element should be scrolled into view before taking a screenshot.
2180
2281
  * Defaults to false.
2181
- * @return {!promise.Promise<string>} A promise that will be
2282
+ * @return {!promise.Thenable<string>} A promise that will be
2182
2283
  * resolved to the screenshot as a base-64 encoded PNG.
2183
2284
  */
2184
2285
  takeScreenshot(opt_scroll) {
@@ -2203,24 +2304,30 @@ class WebElement {
2203
2304
  * return el.click();
2204
2305
  * });
2205
2306
  *
2206
- * @implements {promise.Thenable<!WebElement>}
2307
+ * @implements {promise.CancellableThenable<!WebElement>}
2207
2308
  * @final
2208
2309
  */
2209
2310
  class WebElementPromise extends WebElement {
2210
2311
  /**
2211
2312
  * @param {!WebDriver} driver The parent WebDriver instance for this
2212
2313
  * element.
2213
- * @param {!promise.Promise<!WebElement>} el A promise
2314
+ * @param {!promise.Thenable<!WebElement>} el A promise
2214
2315
  * that will resolve to the promised element.
2215
2316
  */
2216
2317
  constructor(driver, el) {
2217
2318
  super(driver, 'unused');
2218
2319
 
2219
- /** @override */
2220
- this.cancel = el.cancel.bind(el);
2221
-
2222
- /** @override */
2223
- this.isPending = el.isPending.bind(el);
2320
+ /**
2321
+ * Cancel operation is only supported if the wrapped thenable is also
2322
+ * cancellable.
2323
+ * @param {(string|Error)=} opt_reason
2324
+ * @override
2325
+ */
2326
+ this.cancel = function(opt_reason) {
2327
+ if (promise.CancellableThenable.isImplementation(el)) {
2328
+ /** @type {!promise.CancellableThenable} */(el).cancel(opt_reason);
2329
+ }
2330
+ }
2224
2331
 
2225
2332
  /** @override */
2226
2333
  this.then = el.then.bind(el);
@@ -2228,9 +2335,6 @@ class WebElementPromise extends WebElement {
2228
2335
  /** @override */
2229
2336
  this.catch = el.catch.bind(el);
2230
2337
 
2231
- /** @override */
2232
- this.finally = el.finally.bind(el);
2233
-
2234
2338
  /**
2235
2339
  * Defers returning the element ID until the wrapped WebElement has been
2236
2340
  * resolved.
@@ -2243,7 +2347,7 @@ class WebElementPromise extends WebElement {
2243
2347
  };
2244
2348
  }
2245
2349
  }
2246
- promise.Thenable.addImplementation(WebElementPromise);
2350
+ promise.CancellableThenable.addImplementation(WebElementPromise);
2247
2351
 
2248
2352
 
2249
2353
  //////////////////////////////////////////////////////////////////////////////
@@ -2269,15 +2373,15 @@ class Alert {
2269
2373
  /** @private {!WebDriver} */
2270
2374
  this.driver_ = driver;
2271
2375
 
2272
- /** @private {!promise.Promise<string>} */
2273
- this.text_ = promise.fulfilled(text);
2376
+ /** @private {!promise.Thenable<string>} */
2377
+ this.text_ = driver.controlFlow().promise(resolve => resolve(text));
2274
2378
  }
2275
2379
 
2276
2380
  /**
2277
2381
  * Retrieves the message text displayed with this alert. For instance, if the
2278
2382
  * alert were opened with alert("hello"), then this would return "hello".
2279
2383
  *
2280
- * @return {!promise.Promise<string>} A promise that will be
2384
+ * @return {!promise.Thenable<string>} A promise that will be
2281
2385
  * resolved to the text displayed with this alert.
2282
2386
  */
2283
2387
  getText() {
@@ -2291,7 +2395,7 @@ class Alert {
2291
2395
  *
2292
2396
  * @param {string} username The username to send.
2293
2397
  * @param {string} password The password to send.
2294
- * @return {!promise.Promise<void>} A promise that will be resolved when this
2398
+ * @return {!promise.Thenable<void>} A promise that will be resolved when this
2295
2399
  * command has completed.
2296
2400
  */
2297
2401
  authenticateAs(username, password) {
@@ -2304,7 +2408,7 @@ class Alert {
2304
2408
  /**
2305
2409
  * Accepts this alert.
2306
2410
  *
2307
- * @return {!promise.Promise<void>} A promise that will be resolved
2411
+ * @return {!promise.Thenable<void>} A promise that will be resolved
2308
2412
  * when this command has completed.
2309
2413
  */
2310
2414
  accept() {
@@ -2316,7 +2420,7 @@ class Alert {
2316
2420
  /**
2317
2421
  * Dismisses this alert.
2318
2422
  *
2319
- * @return {!promise.Promise<void>} A promise that will be resolved
2423
+ * @return {!promise.Thenable<void>} A promise that will be resolved
2320
2424
  * when this command has completed.
2321
2425
  */
2322
2426
  dismiss() {
@@ -2331,7 +2435,7 @@ class Alert {
2331
2435
  * window.confirm).
2332
2436
  *
2333
2437
  * @param {string} text The text to set.
2334
- * @return {!promise.Promise<void>} A promise that will be resolved
2438
+ * @return {!promise.Thenable<void>} A promise that will be resolved
2335
2439
  * when this command has completed.
2336
2440
  */
2337
2441
  sendKeys(text) {
@@ -2354,7 +2458,7 @@ class Alert {
2354
2458
  * return alert.dismiss();
2355
2459
  * });
2356
2460
  *
2357
- * @implements {promise.Thenable.<!webdriver.Alert>}
2461
+ * @implements {promise.CancellableThenable<!webdriver.Alert>}
2358
2462
  * @final
2359
2463
  */
2360
2464
  class AlertPromise extends Alert {
@@ -2367,11 +2471,17 @@ class AlertPromise extends Alert {
2367
2471
  constructor(driver, alert) {
2368
2472
  super(driver, 'unused');
2369
2473
 
2370
- /** @override */
2371
- this.cancel = alert.cancel.bind(alert);
2372
-
2373
- /** @override */
2374
- this.isPending = alert.isPending.bind(alert);
2474
+ /**
2475
+ * Cancel operation is only supported if the wrapped thenable is also
2476
+ * cancellable.
2477
+ * @param {(string|Error)=} opt_reason
2478
+ * @override
2479
+ */
2480
+ this.cancel = function(opt_reason) {
2481
+ if (promise.CancellableThenable.isImplementation(alert)) {
2482
+ /** @type {!promise.CancellableThenable} */(alert).cancel(opt_reason);
2483
+ }
2484
+ };
2375
2485
 
2376
2486
  /** @override */
2377
2487
  this.then = alert.then.bind(alert);
@@ -2379,9 +2489,6 @@ class AlertPromise extends Alert {
2379
2489
  /** @override */
2380
2490
  this.catch = alert.catch.bind(alert);
2381
2491
 
2382
- /** @override */
2383
- this.finally = alert.finally.bind(alert);
2384
-
2385
2492
  /**
2386
2493
  * Defer returning text until the promised alert has been resolved.
2387
2494
  * @override
@@ -2433,7 +2540,7 @@ class AlertPromise extends Alert {
2433
2540
  };
2434
2541
  }
2435
2542
  }
2436
- promise.Thenable.addImplementation(AlertPromise);
2543
+ promise.CancellableThenable.addImplementation(AlertPromise);
2437
2544
 
2438
2545
 
2439
2546
  // PUBLIC API
@@ -2448,6 +2555,7 @@ module.exports = {
2448
2555
  Options: Options,
2449
2556
  TargetLocator: TargetLocator,
2450
2557
  Timeouts: Timeouts,
2558
+ IWebDriver: IWebDriver,
2451
2559
  WebDriver: WebDriver,
2452
2560
  WebElement: WebElement,
2453
2561
  WebElementCondition: WebElementCondition,