selenium-webdriver 3.3.0 → 4.0.0-alpha.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 (117) hide show
  1. package/CHANGES.md +273 -0
  2. package/NOTICE +1 -1
  3. package/README.md +28 -36
  4. package/chrome.js +221 -146
  5. package/edge.js +43 -118
  6. package/example/chrome_android.js +8 -9
  7. package/example/chrome_mobile_emulation.js +9 -10
  8. package/example/firefox_channels.js +23 -19
  9. package/example/google_search.js +3 -3
  10. package/example/google_search_test.js +39 -27
  11. package/example/headless.js +55 -0
  12. package/example/logging.js +40 -7
  13. package/firefox.js +769 -0
  14. package/http/index.js +41 -4
  15. package/http/util.js +11 -6
  16. package/ie.js +57 -98
  17. package/index.js +182 -115
  18. package/io/index.js +59 -0
  19. package/io/zip.js +214 -0
  20. package/jasmine.json +11 -0
  21. package/lib/actions.js +68 -63
  22. package/lib/atoms/get-attribute.js +9 -0
  23. package/lib/atoms/is-displayed.js +73 -78
  24. package/lib/by.js +9 -1
  25. package/lib/capabilities.js +303 -201
  26. package/lib/command.js +28 -50
  27. package/lib/error.js +113 -42
  28. package/lib/http.js +167 -135
  29. package/lib/input.js +1044 -8
  30. package/lib/promise.js +222 -3331
  31. package/lib/proxy.js +134 -39
  32. package/lib/test/bootstrap_jasmine.js +28 -0
  33. package/lib/test/data/actions/click.html +24 -0
  34. package/lib/test/data/actions/drag.html +77 -0
  35. package/lib/test/data/actions/record_click.html +21 -0
  36. package/lib/test/data/chrome/download.html +2 -0
  37. package/lib/test/data/click_tests/disabled_element.html +12 -0
  38. package/lib/test/data/content-editable.html +10 -0
  39. package/lib/test/data/firefox/webextension.xpi +0 -0
  40. package/lib/test/data/inputs.html +42 -0
  41. package/lib/test/data/key_logger.html +34 -0
  42. package/lib/test/data/nestedElements.html +10 -1
  43. package/lib/test/data/scrolling_tests/page_with_partially_hidden_element.html +14 -0
  44. package/lib/test/data/selectPage.html +12 -0
  45. package/lib/test/data/simpleTest.html +5 -0
  46. package/lib/test/data/single_text_input.html +12 -0
  47. package/lib/test/data/upload_invisible.html +45 -0
  48. package/lib/test/fileserver.js +23 -19
  49. package/lib/test/index.js +30 -244
  50. package/lib/test/resources.js +0 -1
  51. package/lib/webdriver.js +770 -939
  52. package/net/index.js +21 -25
  53. package/net/portprober.js +51 -68
  54. package/package.json +6 -4
  55. package/remote/index.js +18 -30
  56. package/safari.js +42 -129
  57. package/test/actions_test.js +173 -18
  58. package/test/builder_test.js +106 -0
  59. package/test/chrome/devtools_test.js +93 -0
  60. package/test/chrome/options_test.js +48 -154
  61. package/test/chrome/service_test.js +7 -7
  62. package/test/cookie_test.js +77 -63
  63. package/test/element_finding_test.js +206 -201
  64. package/test/execute_script_test.js +115 -114
  65. package/test/fingerprint_test.js +16 -15
  66. package/test/firefox_test.js +251 -0
  67. package/test/http/http_test.js +18 -1
  68. package/test/http/util_test.js +2 -2
  69. package/test/{io_test.js → io/io_test.js} +40 -1
  70. package/test/io/zip_test.js +127 -0
  71. package/test/lib/by_test.js +21 -0
  72. package/test/lib/error_test.js +55 -8
  73. package/test/lib/http_test.js +18 -130
  74. package/test/lib/input_test.js +1379 -0
  75. package/test/lib/promise_test.js +473 -908
  76. package/test/lib/webdriver_test.js +357 -825
  77. package/test/logging_test.js +37 -43
  78. package/test/page_loading_test.js +70 -86
  79. package/test/proxy_test.js +64 -72
  80. package/test/rect_test.js +14 -25
  81. package/test/remote_test.js +13 -32
  82. package/test/safari_test.js +7 -71
  83. package/test/stale_element_test.js +20 -23
  84. package/test/tag_name_test.js +10 -9
  85. package/test/upload_test.js +27 -35
  86. package/test/window_test.js +72 -85
  87. package/testing/index.js +386 -315
  88. package/.npmignore +0 -2
  89. package/example/async_await_test.js +0 -68
  90. package/example/google_search_generator.js +0 -50
  91. package/example/parallel_flows.js +0 -54
  92. package/firefox/binary.js +0 -344
  93. package/firefox/extension.js +0 -187
  94. package/firefox/index.js +0 -722
  95. package/firefox/profile.js +0 -430
  96. package/lib/atoms/getAttribute.js +0 -12
  97. package/lib/events.js +0 -210
  98. package/lib/firefox/webdriver.json +0 -70
  99. package/lib/firefox/webdriver.xpi +0 -0
  100. package/lib/test/data/firefox/jetpack-sample.xpi +0 -0
  101. package/lib/test/data/firefox/sample.xpi +0 -0
  102. package/lib/test/promise.js +0 -79
  103. package/opera.js +0 -405
  104. package/phantomjs.js +0 -282
  105. package/test/firefox/extension_test.js +0 -96
  106. package/test/firefox/firefox_test.js +0 -226
  107. package/test/firefox/profile_test.js +0 -185
  108. package/test/lib/events_test.js +0 -177
  109. package/test/lib/promise_aplus_test.js +0 -78
  110. package/test/lib/promise_error_test.js +0 -884
  111. package/test/lib/promise_flow_test.js +0 -2288
  112. package/test/lib/promise_generator_test.js +0 -310
  113. package/test/phantomjs/execute_phantomjs_test.js +0 -59
  114. package/test/session_test.js +0 -54
  115. package/test/testing/assert_test.js +0 -373
  116. package/test/testing/index_test.js +0 -224
  117. package/testing/assert.js +0 -378
package/lib/webdriver.js CHANGED
@@ -21,16 +21,29 @@
21
21
 
22
22
  'use strict';
23
23
 
24
- const actions = require('./actions');
25
24
  const by = require('./by');
26
- const Capabilities = require('./capabilities').Capabilities;
27
25
  const command = require('./command');
28
26
  const error = require('./error');
29
27
  const input = require('./input');
30
28
  const logging = require('./logging');
31
- const {Session} = require('./session');
32
- const Symbols = require('./symbols');
33
29
  const promise = require('./promise');
30
+ const Symbols = require('./symbols');
31
+ const {Capabilities} = require('./capabilities');
32
+ const {Session} = require('./session');
33
+
34
+
35
+ // Capability names that are defined in the W3C spec.
36
+ const W3C_CAPABILITY_NAMES = new Set([
37
+ 'acceptInsecureCerts',
38
+ 'browserName',
39
+ 'browserVersion',
40
+ 'platformName',
41
+ 'pageLoadStrategy',
42
+ 'proxy',
43
+ 'setWindowRect',
44
+ 'timeouts',
45
+ 'unhandledPromptBehavior',
46
+ ]);
34
47
 
35
48
 
36
49
  /**
@@ -121,15 +134,8 @@ function executeCommand(executor, command) {
121
134
  * @return {!Promise<?>} A promise that will resolve to the input value's JSON
122
135
  * representation.
123
136
  */
124
- function toWireValue(obj) {
125
- if (promise.isPromise(obj)) {
126
- return Promise.resolve(obj).then(toWireValue);
127
- }
128
- return Promise.resolve(convertValue(obj));
129
- }
130
-
131
-
132
- function convertValue(value) {
137
+ async function toWireValue(obj) {
138
+ let value = await Promise.resolve(obj);
133
139
  if (value === void 0 || value === null) {
134
140
  return value;
135
141
  }
@@ -157,53 +163,33 @@ function convertValue(value) {
157
163
  }
158
164
 
159
165
 
160
- function convertKeys(obj) {
166
+ async function convertKeys(obj) {
161
167
  const isArray = Array.isArray(obj);
162
168
  const numKeys = isArray ? obj.length : Object.keys(obj).length;
163
169
  const ret = isArray ? new Array(numKeys) : {};
164
170
  if (!numKeys) {
165
- return Promise.resolve(ret);
171
+ return ret;
166
172
  }
167
173
 
168
174
  let numResolved = 0;
169
175
 
170
- function forEachKey(obj, fn) {
176
+ async function forEachKey(obj, fn) {
171
177
  if (Array.isArray(obj)) {
172
178
  for (let i = 0, n = obj.length; i < n; i++) {
173
- fn(obj[i], i);
179
+ await fn(obj[i], i);
174
180
  }
175
181
  } else {
176
182
  for (let key in obj) {
177
- fn(obj[key], key);
183
+ await fn(obj[key], key);
178
184
  }
179
185
  }
180
186
  }
181
187
 
182
- return new Promise(function(done, reject) {
183
- forEachKey(obj, function(value, key) {
184
- if (promise.isPromise(value)) {
185
- value.then(toWireValue).then(setValue, reject);
186
- } else {
187
- value = convertValue(value);
188
- if (promise.isPromise(value)) {
189
- value.then(toWireValue).then(setValue, reject);
190
- } else {
191
- setValue(value);
192
- }
193
- }
194
-
195
- function setValue(value) {
196
- ret[key] = value;
197
- maybeFulfill();
198
- }
199
- });
200
-
201
- function maybeFulfill() {
202
- if (++numResolved === numKeys) {
203
- done(ret);
204
- }
205
- }
188
+ await forEachKey(obj, async function(value, key) {
189
+ ret[key] = await toWireValue(value);
206
190
  });
191
+
192
+ return ret;
207
193
  }
208
194
 
209
195
 
@@ -243,25 +229,21 @@ function fromWireValue(driver, value) {
243
229
  */
244
230
  class IWebDriver {
245
231
 
246
- /** @return {!promise.ControlFlow} The control flow used by this instance. */
247
- controlFlow() {}
248
-
249
232
  /**
250
- * Schedules a {@link command.Command} to be executed by this driver's
233
+ * Executes the provided {@link command.Command} using this driver's
251
234
  * {@link command.Executor}.
252
235
  *
253
236
  * @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.
237
+ * @return {!Promise<T>} A promise that will be resolved with the command
238
+ * result.
257
239
  * @template T
258
240
  */
259
- schedule(command, description) {}
241
+ execute(command) {}
260
242
 
261
243
  /**
262
244
  * Sets the {@linkplain input.FileDetector file detector} that should be
263
245
  * used with this instance.
264
- * @param {input.FileDetector} detector The detector to use or {@code null}.
246
+ * @param {input.FileDetector} detector The detector to use or `null`.
265
247
  */
266
248
  setFileDetector(detector) {}
267
249
 
@@ -271,12 +253,12 @@ class IWebDriver {
271
253
  getExecutor() {}
272
254
 
273
255
  /**
274
- * @return {!promise.Thenable<!Session>} A promise for this client's session.
256
+ * @return {!Promise<!Session>} A promise for this client's session.
275
257
  */
276
258
  getSession() {}
277
259
 
278
260
  /**
279
- * @return {!promise.Thenable<!Capabilities>} A promise that will resolve with
261
+ * @return {!Promise<!Capabilities>} A promise that will resolve with
280
262
  * the this instance's capabilities.
281
263
  */
282
264
  getCapabilities() {}
@@ -286,56 +268,40 @@ class IWebDriver {
286
268
  * invalidated and may no longer be used to issue commands against the
287
269
  * browser.
288
270
  *
289
- * @return {!promise.Thenable<void>} A promise that will be resolved when the
271
+ * @return {!Promise<void>} A promise that will be resolved when the
290
272
  * command has completed.
291
273
  */
292
274
  quit() {}
293
275
 
294
276
  /**
295
277
  * 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:
278
+ * submitted for execution until
279
+ * {@link ./input.Actions#perform Actions.perform()} is called.
313
280
  *
314
- * driver.touchActions().
315
- * tap(element1).
316
- * doubleTap(element2).
317
- * perform();
318
- *
319
- * @return {!actions.TouchSequence} A new touch sequence for this instance.
281
+ * @param {{async: (boolean|undefined),
282
+ * bridge: (boolean|undefined)}=} options Configuration options for
283
+ * the action sequence (see {@link ./input.Actions Actions} documentation
284
+ * for details).
285
+ * @return {!input.Actions} A new action sequence for this instance.
320
286
  */
321
- touchActions() {}
287
+ actions(options) {}
322
288
 
323
289
  /**
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
290
+ * Executes a snippet of JavaScript in the context of the currently selected
291
+ * frame or window. The script fragment will be executed as the body of an
292
+ * anonymous function. If the script is provided as a function object, that
293
+ * function will be converted to a string for injection into the target
328
294
  * window.
329
295
  *
330
296
  * 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.
297
+ * arguments and may be referenced using the `arguments` object. Arguments may
298
+ * be a boolean, number, string, or {@linkplain WebElement}. Arrays and
299
+ * objects may also be used as script arguments as long as each item adheres
300
+ * to the types previously mentioned.
335
301
  *
336
302
  * The script may refer to any variables accessible from the current window.
337
303
  * 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
304
+ * `document` may be used to refer to the current document. Any local
339
305
  * variables will not be available once the script has finished executing,
340
306
  * though global variables will persist.
341
307
  *
@@ -351,36 +317,35 @@ class IWebDriver {
351
317
  * the rules above
352
318
  *
353
319
  * @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
320
+ * @param {...*} args The arguments to pass to the script.
321
+ * @return {!IThenable<T>} A promise that will resolve to the
356
322
  * scripts return value.
357
323
  * @template T
358
324
  */
359
- executeScript(script, var_args) {}
325
+ executeScript(script, ...args) {}
360
326
 
361
327
  /**
362
- * Schedules a command to execute asynchronous JavaScript in the context of the
328
+ * Executes a snippet of asynchronous JavaScript in the context of the
363
329
  * currently selected frame or window. The script fragment will be executed as
364
330
  * the body of an anonymous function. If the script is provided as a function
365
331
  * object, that function will be converted to a string for injection into the
366
332
  * target window.
367
333
  *
368
334
  * 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.
335
+ * arguments and may be referenced using the `arguments` object. Arguments may
336
+ * be a boolean, number, string, or {@linkplain WebElement}. Arrays and
337
+ * objects may also be used as script arguments as long as each item adheres
338
+ * to the types previously mentioned.
373
339
  *
374
340
  * 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}
341
+ * scripts executed with this function must explicitly signal they are
342
+ * finished by invoking the provided callback. This callback will always be
343
+ * injected into the executed function as the last argument, and thus may be
344
+ * referenced with `arguments[arguments.length - 1]`. The following steps
345
+ * will be taken for resolving this functions return value against the first
346
+ * argument to the script's callback function:
347
+ *
348
+ * - For a HTML element, the value will resolve to a {@link WebElement}
384
349
  * - Null and undefined return values will resolve to null
385
350
  * - Booleans, numbers, and strings will resolve as is
386
351
  * - Functions will resolve to their string representation
@@ -410,9 +375,9 @@ class IWebDriver {
410
375
  *
411
376
  * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In
412
377
  * 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.
378
+ * using this format, the function is converted to a string for injection, so
379
+ * it should not reference any symbols not defined in the scope of the page
380
+ * under test.
416
381
  *
417
382
  * driver.executeAsyncScript(function() {
418
383
  * var callback = arguments[arguments.length - 1];
@@ -429,70 +394,47 @@ class IWebDriver {
429
394
  * });
430
395
  *
431
396
  * @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.
397
+ * @param {...*} args The arguments to pass to the script.
398
+ * @return {!IThenable<T>} A promise that will resolve to the scripts return
399
+ * value.
446
400
  * @template T
447
401
  */
448
- call(fn, opt_scope, var_args) {}
402
+ executeAsyncScript(script, ...args) {}
449
403
 
450
404
  /**
451
- * Schedules a command to wait for a condition to hold. The condition may be
405
+ * Waits for a condition to evaluate to a "truthy" value. The condition may be
452
406
  * specified by a {@link Condition}, as a custom function, or as any
453
407
  * promise-like thenable.
454
408
  *
455
409
  * For a {@link Condition} or function, the wait will repeatedly
456
410
  * evaluate the condition until it returns a truthy value. If any errors occur
457
411
  * 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 satisfied. Note the resolution time for a promise
461
- * is factored into whether a wait has timed out.
412
+ * event a condition returns a {@linkplain Promise}, the polling loop will
413
+ * wait for it to be resolved and use the resolved value for whether the
414
+ * condition has been satisfied. The resolution time for a promise is always
415
+ * factored into whether a wait has timed out.
462
416
  *
463
- * Note, if the provided condition is a {@link WebElementCondition}, then
417
+ * If the provided condition is a {@link WebElementCondition}, then
464
418
  * the wait will return a {@link WebElementPromise} that will resolve to the
465
419
  * element that satisfied the condition.
466
420
  *
467
421
  * _Example:_ waiting up to 10 seconds for an element to be present on the
468
422
  * page.
469
423
  *
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());
424
+ * async function example() {
425
+ * let button =
426
+ * await driver.wait(until.elementLocated(By.id('foo')), 10000);
427
+ * await button.click();
428
+ * }
486
429
  *
487
430
  * @param {!(IThenable<T>|
488
431
  * Condition<T>|
489
432
  * function(!WebDriver): T)} condition The condition to
490
433
  * wait on, defined as a promise, condition object, or a function to
491
434
  * 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
435
+ * @param {number=} timeout How long to wait for the condition to be true.
436
+ * @param {string=} message An optional message to use if the wait times out.
437
+ * @return {!(IThenable<T>|WebElementPromise)} A promise that will be
496
438
  * resolved with the first truthy value returned by the condition
497
439
  * function, or rejected if the condition times out. If the input
498
440
  * input condition is an instance of a {@link WebElementCondition},
@@ -500,76 +442,82 @@ class IWebDriver {
500
442
  * @throws {TypeError} if the provided `condition` is not a valid type.
501
443
  * @template T
502
444
  */
503
- wait(condition, opt_timeout, opt_message) {}
445
+ wait(condition, timeout = undefined, message = undefined) {}
504
446
 
505
447
  /**
506
- * Schedules a command to make the driver sleep for the given amount of time.
448
+ * Makes the driver sleep for the given amount of time.
449
+ *
507
450
  * @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.
451
+ * @return {!Promise<void>} A promise that will be resolved when the sleep has
452
+ * finished.
510
453
  */
511
454
  sleep(ms) {}
512
455
 
513
456
  /**
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.
457
+ * Retrieves the current window handle.
458
+ *
459
+ * @return {!Promise<string>} A promise that will be resolved with the current
460
+ * window handle.
517
461
  */
518
462
  getWindowHandle() {}
519
463
 
520
464
  /**
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.
465
+ * Retrieves a list of all available window handles.
466
+ *
467
+ * @return {!Promise<!Array<string>>} A promise that will be resolved with an
468
+ * array of window handles.
524
469
  */
525
470
  getAllWindowHandles() {}
526
471
 
527
472
  /**
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.
473
+ * Retrieves the current page's source. The returned souce is a representation
474
+ * of the underlying DOM: do not expect it to be formatted or escaped in the
475
+ * same way as the raw response sent from the web server.
476
+ *
477
+ * @return {!Promise<string>} A promise that will be resolved with the current
478
+ * page source.
534
479
  */
535
480
  getPageSource() {}
536
481
 
537
482
  /**
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.
483
+ * Closes the current window.
484
+ *
485
+ * @return {!Promise<void>} A promise that will be resolved when this command
486
+ * has completed.
541
487
  */
542
488
  close() {}
543
489
 
544
490
  /**
545
- * Schedules a command to navigate to the given URL.
491
+ * Navigates to the given URL.
492
+ *
546
493
  * @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.
494
+ * @return {!Promise<void>} A promise that will be resolved when the document
495
+ * has finished loading.
549
496
  */
550
497
  get(url) {}
551
498
 
552
499
  /**
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.
500
+ * Retrieves the URL for the current page.
501
+ *
502
+ * @return {!Promise<string>} A promise that will be resolved with the
503
+ * current URL.
556
504
  */
557
505
  getCurrentUrl() {}
558
506
 
559
507
  /**
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.
508
+ * Retrieves the current page title.
509
+ *
510
+ * @return {!Promise<string>} A promise that will be resolved with the current
511
+ * page's title.
563
512
  */
564
513
  getTitle() {}
565
514
 
566
515
  /**
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}:
516
+ * Locates an element on the page. If the element cannot be found, a
517
+ * {@link error.NoSuchEementError} will be returned by the driver.
518
+ *
519
+ * This function should not be used to test whether an element is present on
520
+ * the page. Rather, you should use {@link #findElements}:
573
521
  *
574
522
  * driver.findElements(By.id('foo'))
575
523
  * .then(found => console.log('Element found? %s', !!found.length));
@@ -605,16 +553,17 @@ class IWebDriver {
605
553
  findElement(locator) {}
606
554
 
607
555
  /**
608
- * Schedule a command to search for multiple elements on the page.
556
+ * Search for multiple elements on the page. Refer to the documentation on
557
+ * {@link #findElement(by)} for information on element locator strategies.
609
558
  *
610
559
  * @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.
560
+ * @return {!Promise<!Array<!WebElement>>} A promise that will resolve to an
561
+ * array of WebElements.
613
562
  */
614
563
  findElements(locator) {}
615
564
 
616
565
  /**
617
- * Schedule a command to take a screenshot. The driver makes a best effort to
566
+ * Takes a screenshot of the current page. The driver makes a best effort to
618
567
  * return a screenshot of the following, in order of preference:
619
568
  *
620
569
  * 1. Entire page
@@ -622,8 +571,8 @@ class IWebDriver {
622
571
  * 3. Visible portion of the current frame
623
572
  * 4. The entire display containing the browser
624
573
  *
625
- * @return {!promise.Thenable<string>} A promise that will be
626
- * resolved to the screenshot as a base-64 encoded PNG.
574
+ * @return {!Promise<string>} A promise that will be resolved to the
575
+ * screenshot as a base-64 encoded PNG.
627
576
  */
628
577
  takeScreenshot() {}
629
578
 
@@ -645,6 +594,23 @@ class IWebDriver {
645
594
  }
646
595
 
647
596
 
597
+ /**
598
+ * @param {!Capabilities} capabilities A capabilities object.
599
+ * @return {!Capabilities} A copy of the parameter capabilities, omitting
600
+ * capability names that are not valid W3C names.
601
+ */
602
+ function filterNonW3CCaps(capabilities) {
603
+ let newCaps = new Capabilities(capabilities);
604
+ for (let k of newCaps.keys()) {
605
+ // Any key containing a colon is a vendor-prefixed capability.
606
+ if (!(W3C_CAPABILITY_NAMES.has(k) || k.indexOf(':') >= 0)) {
607
+ newCaps.delete(k);
608
+ }
609
+ }
610
+ return newCaps;
611
+ }
612
+
613
+
648
614
  /**
649
615
  * Each WebDriver instance provides automated control over a browser session.
650
616
  *
@@ -656,17 +622,17 @@ class WebDriver {
656
622
  * promise that will be resolved to a session.
657
623
  * @param {!command.Executor} executor The executor to use when sending
658
624
  * commands to the browser.
659
- * @param {promise.ControlFlow=} opt_flow The flow to
660
- * schedule commands through. Defaults to the active flow object.
661
- * @param {(function(this: void): ?)=} opt_onQuit A function to call, if any,
625
+ * @param {(function(this: void): ?)=} onQuit A function to call, if any,
662
626
  * when the session is terminated.
663
627
  */
664
- constructor(session, executor, opt_flow, opt_onQuit) {
665
- /** @private {!promise.ControlFlow} */
666
- this.flow_ = opt_flow || promise.controlFlow();
628
+ constructor(session, executor, onQuit = undefined) {
629
+ /** @private {!Promise<!Session>} */
630
+ this.session_ = Promise.resolve(session);
667
631
 
668
- /** @private {!promise.Thenable<!Session>} */
669
- this.session_ = this.flow_.promise(resolve => resolve(session));
632
+ // If session is a rejected promise, add a no-op rejection handler.
633
+ // This effectively hides setup errors until users attempt to interact
634
+ // with the session.
635
+ this.session_.catch(function() {});
670
636
 
671
637
  /** @private {!command.Executor} */
672
638
  this.executor_ = executor;
@@ -675,63 +641,17 @@ class WebDriver {
675
641
  this.fileDetector_ = null;
676
642
 
677
643
  /** @private @const {(function(this: void): ?|undefined)} */
678
- this.onQuit_ = opt_onQuit;
679
- }
680
-
681
- /**
682
- * Creates a new WebDriver client for an existing session.
683
- * @param {!command.Executor} executor Command executor to use when querying
684
- * for session details.
685
- * @param {string} sessionId ID of the session to attach to.
686
- * @param {promise.ControlFlow=} opt_flow The control flow all
687
- * driver commands should execute under. Defaults to the
688
- * {@link promise.controlFlow() currently active} control flow.
689
- * @return {!WebDriver} A new client for the specified session.
690
- */
691
- static attachToSession(executor, sessionId, opt_flow) {
692
- let flow = opt_flow || promise.controlFlow();
693
- let cmd = new command.Command(command.Name.DESCRIBE_SESSION)
694
- .setParameter('sessionId', sessionId);
695
- let session = flow.execute(
696
- () => executeCommand(executor, cmd).catch(err => {
697
- // The DESCRIBE_SESSION command is not supported by the W3C spec, so
698
- // if we get back an unknown command, just return a session with
699
- // unknown capabilities.
700
- if (err instanceof error.UnknownCommandError) {
701
- return new Session(sessionId, new Capabilities);
702
- }
703
- throw err;
704
- }),
705
- 'WebDriver.attachToSession()');
706
- return new WebDriver(session, executor, flow);
644
+ this.onQuit_ = onQuit;
707
645
  }
708
646
 
709
647
  /**
710
648
  * Creates a new WebDriver session.
711
649
  *
712
- * By default, the requested session `capabilities` are merely "desired" and
713
- * the remote end will still create a new session even if it cannot satisfy
714
- * all of the requested capabilities. You can query which capabilities a
715
- * session actually has using the
716
- * {@linkplain #getCapabilities() getCapabilities()} method on the returned
717
- * WebDriver instance.
718
- *
719
- * To define _required capabilities_, provide the `capabilities` as an object
720
- * literal with `required` and `desired` keys. The `desired` key may be
721
- * omitted if all capabilities are required, and vice versa. If the server
722
- * cannot create a session with all of the required capabilities, it will
723
- * return an {@linkplain error.SessionNotCreatedError}.
724
- *
725
- * let required = new Capabilities().set('browserName', 'firefox');
726
- * let desired = new Capabilities().set('version', '45');
727
- * let driver = WebDriver.createSession(executor, {required, desired});
728
- *
729
650
  * This function will always return a WebDriver instance. If there is an error
730
651
  * creating the session, such as the aforementioned SessionNotCreatedError,
731
652
  * the driver will have a rejected {@linkplain #getSession session} promise.
732
- * It is recommended that this promise is left _unhandled_ so it will
733
- * propagate through the {@linkplain promise.ControlFlow control flow} and
734
- * cause subsequent commands to fail.
653
+ * This rejection will propagate through any subsequent commands scheduled
654
+ * on the returned WebDriver instance.
735
655
  *
736
656
  * let required = Capabilities.firefox();
737
657
  * let driver = WebDriver.createSession(executor, {required});
@@ -742,86 +662,39 @@ class WebDriver {
742
662
  *
743
663
  * @param {!command.Executor} executor The executor to create the new session
744
664
  * with.
745
- * @param {(!Capabilities|
746
- * {desired: (Capabilities|undefined),
747
- * required: (Capabilities|undefined)})} capabilities The desired
748
- * capabilities for the new session.
749
- * @param {promise.ControlFlow=} opt_flow The control flow all driver
750
- * commands should execute under, including the initial session creation.
751
- * Defaults to the {@link promise.controlFlow() currently active}
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
665
+ * @param {!Capabilities} capabilities The desired capabilities for the new
666
+ * session.
667
+ * @param {(function(this: void): ?)=} onQuit A callback to invoke when
761
668
  * the newly created session is terminated. This should be used to clean
762
669
  * up any resources associated with the session.
763
670
  * @return {!WebDriver} The driver for the newly created session.
764
671
  */
765
- static createSession(
766
- executor, capabilities, opt_flow, opt_ctor, opt_onQuit) {
767
- let flow = opt_flow || promise.controlFlow();
672
+ static createSession(executor, capabilities, onQuit = undefined) {
768
673
  let cmd = new command.Command(command.Name.NEW_SESSION);
769
674
 
770
- if (capabilities && (capabilities.desired || capabilities.required)) {
771
- cmd.setParameter('desiredCapabilities', capabilities.desired);
772
- cmd.setParameter('requiredCapabilities', capabilities.required);
773
- } else {
774
- cmd.setParameter('desiredCapabilities', capabilities);
775
- }
675
+ // For OSS remote ends.
676
+ cmd.setParameter('desiredCapabilities', capabilities);
677
+ // For W3C remote ends.
678
+ cmd.setParameter('capabilities', {
679
+ alwaysMatch: filterNonW3CCaps(capabilities),
680
+ });
776
681
 
777
- let session = flow.execute(
778
- () => executeCommand(executor, cmd),
779
- 'WebDriver.createSession()');
780
- if (typeof opt_onQuit === 'function') {
682
+ let session = executeCommand(executor, cmd);
683
+ if (typeof onQuit === 'function') {
781
684
  session = session.catch(err => {
782
- return Promise.resolve(opt_onQuit.call(void 0)).then(_ => {throw err;});
685
+ return Promise.resolve(onQuit.call(void 0)).then(_ => {throw err;});
783
686
  });
784
687
  }
785
- const ctor = opt_ctor || WebDriver;
786
- return new ctor(session, executor, flow, opt_onQuit);
688
+ return new this(session, executor, onQuit);
787
689
  }
788
690
 
789
691
  /** @override */
790
- controlFlow() {
791
- return this.flow_;
792
- }
793
-
794
- /** @override */
795
- schedule(command, description) {
692
+ async execute(command) {
796
693
  command.setParameter('sessionId', this.session_);
797
-
798
- // If any of the command parameters are rejected promises, those
799
- // rejections may be reported as unhandled before the control flow
800
- // attempts to execute the command. To ensure parameters errors
801
- // propagate through the command itself, we resolve all of the
802
- // command parameters now, but suppress any errors until the ControlFlow
803
- // actually executes the command. This addresses scenarios like catching
804
- // an element not found error in:
805
- //
806
- // driver.findElement(By.id('foo')).click().catch(function(e) {
807
- // if (e instanceof NoSuchElementError) {
808
- // // Do something.
809
- // }
810
- // });
811
- var prepCommand = toWireValue(command.getParameters());
812
- prepCommand.catch(function() {});
813
-
814
- var flow = this.flow_;
815
- var executor = this.executor_;
816
- return flow.execute(() => {
817
- // Retrieve resolved command parameters; any previously suppressed errors
818
- // will now propagate up through the control flow as part of the command
819
- // execution.
820
- return prepCommand.then(function(parameters) {
821
- command.setParameters(parameters);
822
- return executor.execute(command);
823
- }).then(value => fromWireValue(this, value));
824
- }, description);
694
+ let parameters = await toWireValue(command.getParameters());
695
+ command.setParameters(parameters);
696
+ let value = await this.executor_.execute(command);
697
+ return fromWireValue(this, value);
825
698
  }
826
699
 
827
700
  /** @override */
@@ -846,17 +719,13 @@ class WebDriver {
846
719
 
847
720
  /** @override */
848
721
  quit() {
849
- var result = this.schedule(
850
- new command.Command(command.Name.QUIT),
851
- 'WebDriver.quit()');
722
+ let result = this.execute(new command.Command(command.Name.QUIT));
852
723
  // Delete our session ID when the quit command finishes; this will allow us
853
724
  // to throw an error when attempting to use a driver post-quit.
854
- return /** @type {!promise.Thenable} */(promise.finally(result, () => {
855
- this.session_ = this.flow_.promise((_, reject) => {
856
- reject(new error.NoSuchSessionError(
725
+ return promise.finally(result, () => {
726
+ this.session_ = Promise.reject(new error.NoSuchSessionError(
857
727
  'This driver instance does not have a valid session ID ' +
858
728
  '(did you call WebDriver.quit()?) and may no longer be used.'));
859
- });
860
729
 
861
730
  // Only want the session rejection to bubble if accessed.
862
731
  this.session_.catch(function() {});
@@ -864,70 +733,73 @@ class WebDriver {
864
733
  if (this.onQuit_) {
865
734
  return this.onQuit_.call(void 0);
866
735
  }
867
- }));
868
- }
869
-
870
- /** @override */
871
- actions() {
872
- return new actions.ActionSequence(this);
736
+ });
873
737
  }
874
738
 
875
739
  /** @override */
876
- touchActions() {
877
- return new actions.TouchSequence(this);
740
+ actions(options) {
741
+ return new input.Actions(this, options || undefined);
878
742
  }
879
743
 
880
744
  /** @override */
881
- executeScript(script, var_args) {
745
+ executeScript(script, ...args) {
882
746
  if (typeof script === 'function') {
883
747
  script = 'return (' + script + ').apply(null, arguments);';
884
748
  }
885
- let args =
886
- arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : [];
887
- return this.schedule(
749
+ return this.execute(
888
750
  new command.Command(command.Name.EXECUTE_SCRIPT).
889
751
  setParameter('script', script).
890
- setParameter('args', args),
891
- 'WebDriver.executeScript()');
752
+ setParameter('args', args));
892
753
  }
893
754
 
894
755
  /** @override */
895
- executeAsyncScript(script, var_args) {
756
+ executeAsyncScript(script, ...args) {
896
757
  if (typeof script === 'function') {
897
758
  script = 'return (' + script + ').apply(null, arguments);';
898
759
  }
899
- let args = Array.prototype.slice.call(arguments, 1);
900
- return this.schedule(
760
+ return this.execute(
901
761
  new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT).
902
762
  setParameter('script', script).
903
- setParameter('args', args),
904
- 'WebDriver.executeScript()');
763
+ setParameter('args', args));
905
764
  }
906
765
 
907
766
  /** @override */
908
- call(fn, opt_scope, var_args) {
909
- let args = Array.prototype.slice.call(arguments, 2);
910
- return this.flow_.execute(function() {
911
- return promise.fullyResolved(args).then(function(args) {
912
- if (promise.isGenerator(fn)) {
913
- args.unshift(fn, opt_scope);
914
- return promise.consume.apply(null, args);
915
- }
916
- return fn.apply(opt_scope, args);
917
- });
918
- }, 'WebDriver.call(' + (fn.name || 'function') + ')');
919
- }
767
+ wait(condition, timeout = 0, message = undefined) {
768
+ if (typeof timeout !== 'number' || timeout < 0) {
769
+ throw TypeError('timeout must be a number >= 0: ' + timeout);
770
+ }
920
771
 
921
- /** @override */
922
- wait(condition, opt_timeout, opt_message) {
923
772
  if (promise.isPromise(condition)) {
924
- return this.flow_.wait(
925
- /** @type {!IThenable} */(condition),
926
- opt_timeout, opt_message);
773
+ return new Promise((resolve, reject) => {
774
+ if (!timeout) {
775
+ resolve(condition);
776
+ return;
777
+ }
778
+
779
+ let start = Date.now();
780
+ let timer = setTimeout(function() {
781
+ timer = null;
782
+ reject(
783
+ new error.TimeoutError(
784
+ (message ? `${message}\n` : '')
785
+ + 'Timed out waiting for promise to resolve after '
786
+ + (Date.now() - start) + 'ms'));
787
+ }, timeout);
788
+ const clearTimer = () => timer && clearTimeout(timer);
789
+
790
+ /** @type {!IThenable} */(condition).then(
791
+ function(value) {
792
+ clearTimer();
793
+ resolve(value);
794
+ },
795
+ function(error) {
796
+ clearTimer();
797
+ reject(error);
798
+ });
799
+ });
927
800
  }
928
801
 
929
- var message = opt_message;
930
- var fn = /** @type {!Function} */(condition);
802
+ let fn = /** @type {!Function} */(condition);
931
803
  if (condition instanceof Condition) {
932
804
  message = message || condition.description();
933
805
  fn = condition.fn;
@@ -939,13 +811,36 @@ class WebDriver {
939
811
  + 'Condition object');
940
812
  }
941
813
 
942
- var driver = this;
943
- var result = this.flow_.wait(function() {
944
- if (promise.isGenerator(fn)) {
945
- return promise.consume(fn, null, [driver]);
946
- }
947
- return fn(driver);
948
- }, opt_timeout, message);
814
+ const driver = this;
815
+ function evaluateCondition() {
816
+ return new Promise((resolve, reject) => {
817
+ try {
818
+ resolve(fn(driver));
819
+ } catch (ex) {
820
+ reject(ex);
821
+ }
822
+ });
823
+ }
824
+
825
+ let result = new Promise((resolve, reject) => {
826
+ const startTime = Date.now();
827
+ const pollCondition = async () => {
828
+ evaluateCondition().then(function(value) {
829
+ const elapsed = Date.now() - startTime;
830
+ if (!!value) {
831
+ resolve(value);
832
+ } else if (timeout && elapsed >= timeout) {
833
+ reject(
834
+ new error.TimeoutError(
835
+ (message ? `${message}\n` : '')
836
+ + `Wait timed out after ${elapsed}ms`));
837
+ } else {
838
+ setTimeout(pollCondition, 0);
839
+ }
840
+ }, reject);
841
+ };
842
+ pollCondition();
843
+ });
949
844
 
950
845
  if (condition instanceof WebElementCondition) {
951
846
  result = new WebElementPromise(this, result.then(function(value) {
@@ -962,34 +857,30 @@ class WebDriver {
962
857
 
963
858
  /** @override */
964
859
  sleep(ms) {
965
- return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');
860
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
966
861
  }
967
862
 
968
863
  /** @override */
969
864
  getWindowHandle() {
970
- return this.schedule(
971
- new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE),
972
- 'WebDriver.getWindowHandle()');
865
+ return this.execute(
866
+ new command.Command(command.Name.GET_CURRENT_WINDOW_HANDLE));
973
867
  }
974
868
 
975
869
  /** @override */
976
870
  getAllWindowHandles() {
977
- return this.schedule(
978
- new command.Command(command.Name.GET_WINDOW_HANDLES),
979
- 'WebDriver.getAllWindowHandles()');
871
+ return this.execute(
872
+ new command.Command(command.Name.GET_WINDOW_HANDLES));
980
873
  }
981
874
 
982
875
  /** @override */
983
876
  getPageSource() {
984
- return this.schedule(
985
- new command.Command(command.Name.GET_PAGE_SOURCE),
986
- 'WebDriver.getPageSource()');
877
+ return this.execute(
878
+ new command.Command(command.Name.GET_PAGE_SOURCE));
987
879
  }
988
880
 
989
881
  /** @override */
990
882
  close() {
991
- return this.schedule(new command.Command(command.Name.CLOSE),
992
- 'WebDriver.close()');
883
+ return this.execute(new command.Command(command.Name.CLOSE));
993
884
  }
994
885
 
995
886
  /** @override */
@@ -999,15 +890,12 @@ class WebDriver {
999
890
 
1000
891
  /** @override */
1001
892
  getCurrentUrl() {
1002
- return this.schedule(
1003
- new command.Command(command.Name.GET_CURRENT_URL),
1004
- 'WebDriver.getCurrentUrl()');
893
+ return this.execute(new command.Command(command.Name.GET_CURRENT_URL));
1005
894
  }
1006
895
 
1007
896
  /** @override */
1008
897
  getTitle() {
1009
- return this.schedule(new command.Command(command.Name.GET_TITLE),
1010
- 'WebDriver.getTitle()');
898
+ return this.execute(new command.Command(command.Name.GET_TITLE));
1011
899
  }
1012
900
 
1013
901
  /** @override */
@@ -1020,33 +908,31 @@ class WebDriver {
1020
908
  let cmd = new command.Command(command.Name.FIND_ELEMENT).
1021
909
  setParameter('using', locator.using).
1022
910
  setParameter('value', locator.value);
1023
- id = this.schedule(cmd, 'WebDriver.findElement(' + locator + ')');
911
+ id = this.execute(cmd);
1024
912
  }
1025
913
  return new WebElementPromise(this, id);
1026
914
  }
1027
915
 
1028
916
  /**
1029
917
  * @param {!Function} locatorFn The locator function to use.
1030
- * @param {!(WebDriver|WebElement)} context The search
1031
- * context.
1032
- * @return {!promise.Thenable<!WebElement>} A
1033
- * promise that will resolve to a list of WebElements.
918
+ * @param {!(WebDriver|WebElement)} context The search context.
919
+ * @return {!Promise<!WebElement>} A promise that will resolve to a list of
920
+ * WebElements.
1034
921
  * @private
1035
922
  */
1036
- findElementInternal_(locatorFn, context) {
1037
- return this.call(() => locatorFn(context)).then(function(result) {
1038
- if (Array.isArray(result)) {
1039
- result = result[0];
1040
- }
1041
- if (!(result instanceof WebElement)) {
1042
- throw new TypeError('Custom locator did not return a WebElement');
1043
- }
1044
- return result;
1045
- });
923
+ async findElementInternal_(locatorFn, context) {
924
+ let result = await locatorFn(context);
925
+ if (Array.isArray(result)) {
926
+ result = result[0];
927
+ }
928
+ if (!(result instanceof WebElement)) {
929
+ throw new TypeError('Custom locator did not return a WebElement');
930
+ }
931
+ return result;
1046
932
  }
1047
933
 
1048
934
  /** @override */
1049
- findElements(locator) {
935
+ async findElements(locator) {
1050
936
  locator = by.checkedLocator(locator);
1051
937
  if (typeof locator === 'function') {
1052
938
  return this.findElementsInternal_(locator, this);
@@ -1054,43 +940,43 @@ class WebDriver {
1054
940
  let cmd = new command.Command(command.Name.FIND_ELEMENTS).
1055
941
  setParameter('using', locator.using).
1056
942
  setParameter('value', locator.value);
1057
- let res = this.schedule(cmd, 'WebDriver.findElements(' + locator + ')');
1058
- return res.catch(function(e) {
1059
- if (e instanceof error.NoSuchElementError) {
943
+ try {
944
+ let res = await this.execute(cmd);
945
+ return Array.isArray(res) ? res : [];
946
+ } catch (ex) {
947
+ if (ex instanceof error.NoSuchElementError) {
1060
948
  return [];
1061
949
  }
1062
- throw e;
1063
- });
950
+ throw ex;
951
+ }
1064
952
  }
1065
953
  }
1066
954
 
1067
955
  /**
1068
956
  * @param {!Function} locatorFn The locator function to use.
1069
957
  * @param {!(WebDriver|WebElement)} context The search context.
1070
- * @return {!promise.Thenable<!Array<!WebElement>>} A promise that
1071
- * will resolve to an array of WebElements.
958
+ * @return {!Promise<!Array<!WebElement>>} A promise that will resolve to an
959
+ * array of WebElements.
1072
960
  * @private
1073
961
  */
1074
- findElementsInternal_(locatorFn, context) {
1075
- return this.call(() => locatorFn(context)).then(function(result) {
1076
- if (result instanceof WebElement) {
1077
- return [result];
1078
- }
962
+ async findElementsInternal_(locatorFn, context) {
963
+ const result = await locatorFn(context);
964
+ if (result instanceof WebElement) {
965
+ return [result];
966
+ }
1079
967
 
1080
- if (!Array.isArray(result)) {
1081
- return [];
1082
- }
968
+ if (!Array.isArray(result)) {
969
+ return [];
970
+ }
1083
971
 
1084
- return result.filter(function(item) {
1085
- return item instanceof WebElement;
1086
- });
972
+ return result.filter(function(item) {
973
+ return item instanceof WebElement;
1087
974
  });
1088
975
  }
1089
976
 
1090
977
  /** @override */
1091
978
  takeScreenshot() {
1092
- return this.schedule(new command.Command(command.Name.SCREENSHOT),
1093
- 'WebDriver.takeScreenshot()');
979
+ return this.execute(new command.Command(command.Name.SCREENSHOT));
1094
980
  }
1095
981
 
1096
982
  /** @override */
@@ -1131,49 +1017,46 @@ class Navigation {
1131
1017
  }
1132
1018
 
1133
1019
  /**
1134
- * Schedules a command to navigate to a new URL.
1020
+ * Navigates to a new URL.
1021
+ *
1135
1022
  * @param {string} url The URL to navigate to.
1136
- * @return {!promise.Thenable<void>} A promise that will be resolved
1137
- * when the URL has been loaded.
1023
+ * @return {!Promise<void>} A promise that will be resolved when the URL
1024
+ * has been loaded.
1138
1025
  */
1139
1026
  to(url) {
1140
- return this.driver_.schedule(
1027
+ return this.driver_.execute(
1141
1028
  new command.Command(command.Name.GET).
1142
- setParameter('url', url),
1143
- 'WebDriver.navigate().to(' + url + ')');
1029
+ setParameter('url', url));
1144
1030
  }
1145
1031
 
1146
1032
  /**
1147
- * Schedules a command to move backwards in the browser history.
1148
- * @return {!promise.Thenable<void>} A promise that will be resolved
1149
- * when the navigation event has completed.
1033
+ * Moves backwards in the browser history.
1034
+ *
1035
+ * @return {!Promise<void>} A promise that will be resolved when the
1036
+ * navigation event has completed.
1150
1037
  */
1151
1038
  back() {
1152
- return this.driver_.schedule(
1153
- new command.Command(command.Name.GO_BACK),
1154
- 'WebDriver.navigate().back()');
1039
+ return this.driver_.execute(new command.Command(command.Name.GO_BACK));
1155
1040
  }
1156
1041
 
1157
1042
  /**
1158
- * Schedules a command to move forwards in the browser history.
1159
- * @return {!promise.Thenable<void>} A promise that will be resolved
1160
- * when the navigation event has completed.
1043
+ * Moves forwards in the browser history.
1044
+ *
1045
+ * @return {!Promise<void>} A promise that will be resolved when the
1046
+ * navigation event has completed.
1161
1047
  */
1162
1048
  forward() {
1163
- return this.driver_.schedule(
1164
- new command.Command(command.Name.GO_FORWARD),
1165
- 'WebDriver.navigate().forward()');
1049
+ return this.driver_.execute(new command.Command(command.Name.GO_FORWARD));
1166
1050
  }
1167
1051
 
1168
1052
  /**
1169
- * Schedules a command to refresh the current page.
1170
- * @return {!promise.Thenable<void>} A promise that will be resolved
1171
- * when the navigation event has completed.
1053
+ * Refreshes the current page.
1054
+ *
1055
+ * @return {!Promise<void>} A promise that will be resolved when the
1056
+ * navigation event has completed.
1172
1057
  */
1173
1058
  refresh() {
1174
- return this.driver_.schedule(
1175
- new command.Command(command.Name.REFRESH),
1176
- 'WebDriver.navigate().refresh()');
1059
+ return this.driver_.execute(new command.Command(command.Name.REFRESH));
1177
1060
  }
1178
1061
  }
1179
1062
 
@@ -1195,150 +1078,219 @@ class Options {
1195
1078
  }
1196
1079
 
1197
1080
  /**
1198
- * Schedules a command to add a cookie.
1081
+ * Adds a cookie.
1199
1082
  *
1200
1083
  * __Sample Usage:__
1201
1084
  *
1202
1085
  * // Set a basic cookie.
1203
- * driver.options().addCookie({name: 'foo', value: 'bar'});
1086
+ * driver.manage().addCookie({name: 'foo', value: 'bar'});
1204
1087
  *
1205
1088
  * // Set a cookie that expires in 10 minutes.
1206
1089
  * let expiry = new Date(Date.now() + (10 * 60 * 1000));
1207
- * driver.options().addCookie({name: 'foo', value: 'bar', expiry});
1090
+ * driver.manage().addCookie({name: 'foo', value: 'bar', expiry});
1208
1091
  *
1209
1092
  * // The cookie expiration may also be specified in seconds since epoch.
1210
- * driver.options().addCookie({
1093
+ * driver.manage().addCookie({
1211
1094
  * name: 'foo',
1212
1095
  * value: 'bar',
1213
1096
  * expiry: Math.floor(Date.now() / 1000)
1214
1097
  * });
1215
1098
  *
1216
1099
  * @param {!Options.Cookie} spec Defines the cookie to add.
1217
- * @return {!promise.Thenable<void>} A promise that will be resolved
1100
+ * @return {!Promise<void>} A promise that will be resolved
1218
1101
  * when the cookie has been added to the page.
1219
1102
  * @throws {error.InvalidArgumentError} if any of the cookie parameters are
1220
1103
  * invalid.
1221
1104
  * @throws {TypeError} if `spec` is not a cookie object.
1222
1105
  */
1223
- addCookie(spec) {
1224
- if (!spec || typeof spec !== 'object') {
1225
- throw TypeError('addCookie called with non-cookie parameter');
1226
- }
1227
-
1106
+ addCookie({name, value, path, domain, secure, httpOnly, expiry}) {
1228
1107
  // We do not allow '=' or ';' in the name.
1229
- let name = spec.name;
1230
1108
  if (/[;=]/.test(name)) {
1231
1109
  throw new error.InvalidArgumentError(
1232
1110
  'Invalid cookie name "' + name + '"');
1233
1111
  }
1234
1112
 
1235
1113
  // We do not allow ';' in value.
1236
- let value = spec.value;
1237
1114
  if (/;/.test(value)) {
1238
1115
  throw new error.InvalidArgumentError(
1239
1116
  'Invalid cookie value "' + value + '"');
1240
1117
  }
1241
1118
 
1242
- let cookieString = name + '=' + value +
1243
- (spec.domain ? ';domain=' + spec.domain : '') +
1244
- (spec.path ? ';path=' + spec.path : '') +
1245
- (spec.secure ? ';secure' : '');
1246
-
1247
- let expiry;
1248
- if (typeof spec.expiry === 'number') {
1249
- expiry = Math.floor(spec.expiry);
1250
- cookieString += ';expires=' + new Date(spec.expiry * 1000).toUTCString();
1251
- } else if (spec.expiry instanceof Date) {
1252
- let date = /** @type {!Date} */(spec.expiry);
1119
+ if (typeof expiry === 'number') {
1120
+ expiry = Math.floor(expiry);
1121
+ } else if (expiry instanceof Date) {
1122
+ let date = /** @type {!Date} */(expiry);
1253
1123
  expiry = Math.floor(date.getTime() / 1000);
1254
- cookieString += ';expires=' + date.toUTCString();
1255
1124
  }
1256
1125
 
1257
- return this.driver_.schedule(
1126
+ return this.driver_.execute(
1258
1127
  new command.Command(command.Name.ADD_COOKIE).
1259
1128
  setParameter('cookie', {
1260
1129
  'name': name,
1261
1130
  'value': value,
1262
- 'path': spec.path,
1263
- 'domain': spec.domain,
1264
- 'secure': !!spec.secure,
1131
+ 'path': path,
1132
+ 'domain': domain,
1133
+ 'secure': !!secure,
1134
+ 'httpOnly': !!httpOnly,
1265
1135
  'expiry': expiry
1266
- }),
1267
- 'WebDriver.manage().addCookie(' + cookieString + ')');
1136
+ }));
1268
1137
  }
1269
1138
 
1270
1139
  /**
1271
- * Schedules a command to delete all cookies visible to the current page.
1272
- * @return {!promise.Thenable<void>} A promise that will be resolved
1140
+ * Deletes all cookies visible to the current page.
1141
+ *
1142
+ * @return {!Promise<void>} A promise that will be resolved
1273
1143
  * when all cookies have been deleted.
1274
1144
  */
1275
1145
  deleteAllCookies() {
1276
- return this.driver_.schedule(
1277
- new command.Command(command.Name.DELETE_ALL_COOKIES),
1278
- 'WebDriver.manage().deleteAllCookies()');
1146
+ return this.driver_.execute(
1147
+ new command.Command(command.Name.DELETE_ALL_COOKIES));
1279
1148
  }
1280
1149
 
1281
1150
  /**
1282
- * Schedules a command to delete the cookie with the given name. This command
1283
- * is a no-op if there is no cookie with the given name visible to the current
1284
- * page.
1151
+ * Deletes the cookie with the given name. This command is a no-op if there is
1152
+ * no cookie with the given name visible to the current page.
1153
+ *
1285
1154
  * @param {string} name The name of the cookie to delete.
1286
- * @return {!promise.Thenable<void>} A promise that will be resolved
1155
+ * @return {!Promise<void>} A promise that will be resolved
1287
1156
  * when the cookie has been deleted.
1288
1157
  */
1289
1158
  deleteCookie(name) {
1290
- return this.driver_.schedule(
1159
+ return this.driver_.execute(
1291
1160
  new command.Command(command.Name.DELETE_COOKIE).
1292
- setParameter('name', name),
1293
- 'WebDriver.manage().deleteCookie(' + name + ')');
1161
+ setParameter('name', name));
1294
1162
  }
1295
1163
 
1296
1164
  /**
1297
- * Schedules a command to retrieve all cookies visible to the current page.
1298
- * Each cookie will be returned as a JSON object as described by the WebDriver
1299
- * wire protocol.
1300
- * @return {!promise.Thenable<!Array<!Options.Cookie>>} A promise that will be
1165
+ * Retrieves all cookies visible to the current page. Each cookie will be
1166
+ * returned as a JSON object as described by the WebDriver wire protocol.
1167
+ *
1168
+ * @return {!Promise<!Array<!Options.Cookie>>} A promise that will be
1301
1169
  * resolved with the cookies visible to the current browsing context.
1302
1170
  */
1303
1171
  getCookies() {
1304
- return this.driver_.schedule(
1305
- new command.Command(command.Name.GET_ALL_COOKIES),
1306
- 'WebDriver.manage().getCookies()');
1172
+ return this.driver_.execute(
1173
+ new command.Command(command.Name.GET_ALL_COOKIES));
1307
1174
  }
1308
1175
 
1309
1176
  /**
1310
- * Schedules a command to retrieve the cookie with the given name. Returns null
1311
- * if there is no such cookie. The cookie will be returned as a JSON object as
1312
- * described by the WebDriver wire protocol.
1177
+ * Retrieves the cookie with the given name. Returns null if there is no such
1178
+ * cookie. The cookie will be returned as a JSON object as described by the
1179
+ * WebDriver wire protocol.
1313
1180
  *
1314
1181
  * @param {string} name The name of the cookie to retrieve.
1315
- * @return {!promise.Thenable<?Options.Cookie>} A promise that will be resolved
1182
+ * @return {!Promise<?Options.Cookie>} A promise that will be resolved
1316
1183
  * with the named cookie, or `null` if there is no such cookie.
1317
1184
  */
1318
- getCookie(name) {
1319
- return this.getCookies().then(function(cookies) {
1185
+ async getCookie(name) {
1186
+ try {
1187
+ const cookie =
1188
+ await this.driver_.execute(
1189
+ new command.Command(command.Name.GET_COOKIE)
1190
+ .setParameter('name', name));
1191
+ return cookie;
1192
+ } catch (err) {
1193
+ if (!(err instanceof error.UnknownCommandError)
1194
+ && !(err instanceof error.UnsupportedOperationError)) {
1195
+ throw err;
1196
+ }
1197
+
1198
+ const cookies = await this.getCookies();
1320
1199
  for (let cookie of cookies) {
1321
1200
  if (cookie && cookie['name'] === name) {
1322
1201
  return cookie;
1323
1202
  }
1324
1203
  }
1325
1204
  return null;
1326
- });
1205
+ }
1327
1206
  }
1328
1207
 
1329
1208
  /**
1330
- * @return {!Logs} The interface for managing driver
1331
- * logs.
1209
+ * Fetches the timeouts currently configured for the current session.
1210
+ *
1211
+ * @return {!Promise<{script: number,
1212
+ * pageLoad: number,
1213
+ * implicit: number}>} A promise that will be
1214
+ * resolved with the timeouts currently configured for the current
1215
+ * session.
1216
+ * @see #setTimeouts()
1332
1217
  */
1333
- logs() {
1334
- return new Logs(this.driver_);
1218
+ getTimeouts() {
1219
+ return this.driver_.execute(new command.Command(command.Name.GET_TIMEOUT));
1335
1220
  }
1336
1221
 
1337
1222
  /**
1338
- * @return {!Timeouts} The interface for managing driver timeouts.
1223
+ * Sets the timeout durations associated with the current session.
1224
+ *
1225
+ * The following timeouts are supported (all timeouts are specified in
1226
+ * milliseconds):
1227
+ *
1228
+ * - `implicit` specifies the maximum amount of time to wait for an element
1229
+ * locator to succeed when {@linkplain WebDriver#findElement locating}
1230
+ * {@linkplain WebDriver#findElements elements} on the page.
1231
+ * Defaults to 0 milliseconds.
1232
+ *
1233
+ * - `pageLoad` specifies the maximum amount of time to wait for a page to
1234
+ * finishing loading. Defaults to 300000 milliseconds.
1235
+ *
1236
+ * - `script` specifies the maximum amount of time to wait for an
1237
+ * {@linkplain WebDriver#executeScript evaluated script} to run. If set to
1238
+ * `null`, the script timeout will be indefinite.
1239
+ * Defaults to 30000 milliseconds.
1240
+ *
1241
+ * @param {{script: (number|null|undefined),
1242
+ * pageLoad: (number|null|undefined),
1243
+ * implicit: (number|null|undefined)}} conf
1244
+ * The desired timeout configuration.
1245
+ * @return {!Promise<void>} A promise that will be resolved when the timeouts
1246
+ * have been set.
1247
+ * @throws {!TypeError} if an invalid options object is provided.
1248
+ * @see #getTimeouts()
1249
+ * @see <https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-timeouts>
1339
1250
  */
1340
- timeouts() {
1341
- return new Timeouts(this.driver_);
1251
+ setTimeouts({script, pageLoad, implicit} = {}) {
1252
+ let cmd = new command.Command(command.Name.SET_TIMEOUT);
1253
+
1254
+ let valid = false;
1255
+ function setParam(key, value) {
1256
+ if (value === null || typeof value === 'number') {
1257
+ valid = true;
1258
+ cmd.setParameter(key, value);
1259
+ } else if (typeof value !== 'undefined') {
1260
+ throw TypeError(
1261
+ 'invalid timeouts configuration:'
1262
+ + ` expected "${key}" to be a number, got ${typeof value}`);
1263
+ }
1264
+ }
1265
+ setParam('implicit', implicit);
1266
+ setParam('pageLoad', pageLoad);
1267
+ setParam('script', script);
1268
+
1269
+ if (valid) {
1270
+ return this.driver_.execute(cmd)
1271
+ .catch(() => {
1272
+ // Fallback to the legacy method.
1273
+ let cmds = [];
1274
+ if (typeof script === 'number') {
1275
+ cmds.push(legacyTimeout(this.driver_, 'script', script));
1276
+ }
1277
+ if (typeof implicit === 'number') {
1278
+ cmds.push(legacyTimeout(this.driver_, 'implicit', implicit));
1279
+ }
1280
+ if (typeof pageLoad === 'number') {
1281
+ cmds.push(legacyTimeout(this.driver_, 'page load', pageLoad));
1282
+ }
1283
+ return Promise.all(cmds);
1284
+ });
1285
+ }
1286
+ throw TypeError('no timeouts specified');
1287
+ }
1288
+
1289
+ /**
1290
+ * @return {!Logs} The interface for managing driver logs.
1291
+ */
1292
+ logs() {
1293
+ return new Logs(this.driver_);
1342
1294
  }
1343
1295
 
1344
1296
  /**
@@ -1350,6 +1302,21 @@ class Options {
1350
1302
  }
1351
1303
 
1352
1304
 
1305
+ /**
1306
+ * @param {!WebDriver} driver
1307
+ * @param {string} type
1308
+ * @param {number} ms
1309
+ * @return {!Promise<void>}
1310
+ */
1311
+ function legacyTimeout(driver, type, ms) {
1312
+ return driver.execute(
1313
+ new command.Command(command.Name.SET_TIMEOUT)
1314
+ .setParameter('type', type)
1315
+ .setParameter('ms', ms));
1316
+ }
1317
+
1318
+
1319
+
1353
1320
  /**
1354
1321
  * A record object describing a browser cookie.
1355
1322
  *
@@ -1413,8 +1380,7 @@ Options.Cookie.prototype.httpOnly;
1413
1380
  * When the cookie expires.
1414
1381
  *
1415
1382
  * When {@linkplain Options#addCookie() adding a cookie}, this may be specified
1416
- * in _seconds_ since Unix epoch (January 1, 1970). The expiry will default to
1417
- * 20 years in the future if omitted.
1383
+ * as a {@link Date} object, or in _seconds_ since Unix epoch (January 1, 1970).
1418
1384
  *
1419
1385
  * The expiry is always returned in seconds since epoch when
1420
1386
  * {@linkplain Options#getCookies() retrieving cookies} from the browser.
@@ -1424,88 +1390,6 @@ Options.Cookie.prototype.httpOnly;
1424
1390
  Options.Cookie.prototype.expiry;
1425
1391
 
1426
1392
 
1427
- /**
1428
- * An interface for managing timeout behavior for WebDriver instances.
1429
- *
1430
- * This class should never be instantiated directly. Instead, obtain an instance
1431
- * with
1432
- *
1433
- * webdriver.manage().timeouts()
1434
- *
1435
- * @see WebDriver#manage()
1436
- * @see Options#timeouts()
1437
- */
1438
- class Timeouts {
1439
- /**
1440
- * @param {!WebDriver} driver The parent driver.
1441
- * @private
1442
- */
1443
- constructor(driver) {
1444
- /** @private {!WebDriver} */
1445
- this.driver_ = driver;
1446
- }
1447
-
1448
- /**
1449
- * Specifies the amount of time the driver should wait when searching for an
1450
- * element if it is not immediately present.
1451
- *
1452
- * When searching for a single element, the driver should poll the page
1453
- * until the element has been found, or this timeout expires before failing
1454
- * with a {@link bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching
1455
- * for multiple elements, the driver should poll the page until at least one
1456
- * element has been found or this timeout has expired.
1457
- *
1458
- * Setting the wait timeout to 0 (its default value), disables implicit
1459
- * waiting.
1460
- *
1461
- * Increasing the implicit wait timeout should be used judiciously as it
1462
- * will have an adverse effect on test run time, especially when used with
1463
- * slower location strategies like XPath.
1464
- *
1465
- * @param {number} ms The amount of time to wait, in milliseconds.
1466
- * @return {!promise.Thenable<void>} A promise that will be resolved
1467
- * when the implicit wait timeout has been set.
1468
- */
1469
- implicitlyWait(ms) {
1470
- return this._scheduleCommand(ms, 'implicit', 'implicitlyWait');
1471
- }
1472
-
1473
- /**
1474
- * Sets the amount of time to wait, in milliseconds, for an asynchronous
1475
- * script to finish execution before returning an error. If the timeout is
1476
- * less than or equal to 0, the script will be allowed to run indefinitely.
1477
- *
1478
- * @param {number} ms The amount of time to wait, in milliseconds.
1479
- * @return {!promise.Thenable<void>} A promise that will be resolved
1480
- * when the script timeout has been set.
1481
- */
1482
- setScriptTimeout(ms) {
1483
- return this._scheduleCommand(ms, 'script', 'setScriptTimeout');
1484
- }
1485
-
1486
- /**
1487
- * Sets the amount of time to wait for a page load to complete before
1488
- * returning an error. If the timeout is negative, page loads may be
1489
- * indefinite.
1490
- *
1491
- * @param {number} ms The amount of time to wait, in milliseconds.
1492
- * @return {!promise.Thenable<void>} A promise that will be resolved
1493
- * when the timeout has been set.
1494
- */
1495
- pageLoadTimeout(ms) {
1496
- return this._scheduleCommand(ms, 'page load', 'pageLoadTimeout');
1497
- }
1498
-
1499
- _scheduleCommand(ms, timeoutIdentifier, timeoutName) {
1500
- return this.driver_.schedule(
1501
- new command.Command(command.Name.SET_TIMEOUT).
1502
- setParameter('type', timeoutIdentifier).
1503
- setParameter('ms', ms),
1504
- `WebDriver.manage().timeouts().${timeoutName}(${ms})`);
1505
- }
1506
- }
1507
-
1508
-
1509
1393
  /**
1510
1394
  * An interface for managing the current window.
1511
1395
  *
@@ -1528,76 +1412,114 @@ class Window {
1528
1412
  }
1529
1413
 
1530
1414
  /**
1531
- * Retrieves the window's current position, relative to the top left corner of
1532
- * the screen.
1533
- * @return {!promise.Thenable<{x: number, y: number}>} A promise
1534
- * that will be resolved with the window's position in the form of a
1535
- * {x:number, y:number} object literal.
1536
- */
1537
- getPosition() {
1538
- return this.driver_.schedule(
1539
- new command.Command(command.Name.GET_WINDOW_POSITION).
1540
- setParameter('windowHandle', 'current'),
1541
- 'WebDriver.manage().window().getPosition()');
1415
+ * Retrieves the a rect describing the current top-level window's size and
1416
+ * position.
1417
+ *
1418
+ * @return {!Promise<{x: number, y: number, width: number, height: number}>}
1419
+ * A promise that will resolve to the window rect of the current window.
1420
+ */
1421
+ async getRect() {
1422
+ try {
1423
+ return await this.driver_.execute(
1424
+ new command.Command(command.Name.GET_WINDOW_RECT));
1425
+ } catch (ex) {
1426
+ if (ex instanceof error.UnknownCommandError) {
1427
+ let {width, height} =
1428
+ await this.driver_.execute(
1429
+ new command.Command(command.Name.GET_WINDOW_SIZE)
1430
+ .setParameter('windowHandle', 'current'));
1431
+ let {x, y} =
1432
+ await this.driver_.execute(
1433
+ new command.Command(command.Name.GET_WINDOW_POSITION)
1434
+ .setParameter('windowHandle', 'current'));
1435
+ return {x, y, width, height};
1436
+ }
1437
+ throw ex;
1438
+ }
1542
1439
  }
1543
1440
 
1544
1441
  /**
1545
- * Repositions the current window.
1546
- * @param {number} x The desired horizontal position, relative to the left
1547
- * side of the screen.
1548
- * @param {number} y The desired vertical position, relative to the top of the
1549
- * of the screen.
1550
- * @return {!promise.Thenable<void>} A promise that will be resolved
1551
- * when the command has completed.
1552
- */
1553
- setPosition(x, y) {
1554
- return this.driver_.schedule(
1555
- new command.Command(command.Name.SET_WINDOW_POSITION).
1556
- setParameter('windowHandle', 'current').
1557
- setParameter('x', x).
1558
- setParameter('y', y),
1559
- 'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');
1442
+ * Sets the current top-level window's size and position. You may update just
1443
+ * the size by omitting `width` & `height`, or just the position by omitting
1444
+ * `x` & `y` options.
1445
+ *
1446
+ * @param {{x: (number|undefined),
1447
+ * y: (number|undefined),
1448
+ * width: (number|undefined),
1449
+ * height: (number|undefined)}} options
1450
+ * The desired window size and position.
1451
+ * @return {!Promise<{x: number, y: number, width: number, height: number}>}
1452
+ * A promise that will resolve to the current widnow's updated window
1453
+ * rect.
1454
+ */
1455
+ async setRect({x, y, width, height}) {
1456
+ try {
1457
+ return await this.driver_.execute(
1458
+ new command.Command(command.Name.SET_WINDOW_RECT)
1459
+ .setParameters({x, y, width, height}));
1460
+ } catch (ex) {
1461
+ if (ex instanceof error.UnknownCommandError) {
1462
+ if (typeof x === 'number' && typeof y === 'number') {
1463
+ await this.driver_.execute(
1464
+ new command.Command(command.Name.SET_WINDOW_POSITION)
1465
+ .setParameter('windowHandle', 'current')
1466
+ .setParameter('x', x)
1467
+ .setParameter('y', y));
1468
+ }
1469
+
1470
+ if (typeof width === 'number' && typeof height === 'number') {
1471
+ await this.driver_.execute(
1472
+ new command.Command(command.Name.SET_WINDOW_SIZE)
1473
+ .setParameter('windowHandle', 'current')
1474
+ .setParameter('width', width)
1475
+ .setParameter('height', height));
1476
+ }
1477
+ return this.getRect();
1478
+ }
1479
+ throw ex;
1480
+ }
1560
1481
  }
1561
1482
 
1562
1483
  /**
1563
- * Retrieves the window's current size.
1564
- * @return {!promise.Thenable<{width: number, height: number}>} A
1565
- * promise that will be resolved with the window's size in the form of a
1566
- * {width:number, height:number} object literal.
1484
+ * Maximizes the current window. The exact behavior of this command is
1485
+ * specific to individual window managers, but typically involves increasing
1486
+ * the window to the maximum available size without going full-screen.
1487
+ *
1488
+ * @return {!Promise<void>} A promise that will be resolved when the command
1489
+ * has completed.
1567
1490
  */
1568
- getSize() {
1569
- return this.driver_.schedule(
1570
- new command.Command(command.Name.GET_WINDOW_SIZE).
1571
- setParameter('windowHandle', 'current'),
1572
- 'WebDriver.manage().window().getSize()');
1491
+ maximize() {
1492
+ return this.driver_.execute(
1493
+ new command.Command(command.Name.MAXIMIZE_WINDOW).
1494
+ setParameter('windowHandle', 'current'));
1573
1495
  }
1574
1496
 
1575
1497
  /**
1576
- * Resizes the current window.
1577
- * @param {number} width The desired window width.
1578
- * @param {number} height The desired window height.
1579
- * @return {!promise.Thenable<void>} A promise that will be resolved
1580
- * when the command has completed.
1498
+ * Minimizes the current window. The exact behavior of this command is
1499
+ * specific to individual window managers, but typicallly involves hiding
1500
+ * the window in the system tray.
1501
+ *
1502
+ * @return {!Promise<void>} A promise that will be resolved when the command
1503
+ * has completed.
1581
1504
  */
1582
- setSize(width, height) {
1583
- return this.driver_.schedule(
1584
- new command.Command(command.Name.SET_WINDOW_SIZE).
1585
- setParameter('windowHandle', 'current').
1586
- setParameter('width', width).
1587
- setParameter('height', height),
1588
- 'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');
1505
+ minimize() {
1506
+ return this.driver_.execute(
1507
+ new command.Command(command.Name.MINIMIZE_WINDOW));
1589
1508
  }
1590
1509
 
1591
1510
  /**
1592
- * Maximizes the current window.
1593
- * @return {!promise.Thenable<void>} A promise that will be resolved
1594
- * when the command has completed.
1511
+ * Invokes the "full screen" operation on the current window. The exact
1512
+ * behavior of this command is specific to individual window managers, but
1513
+ * this will typically increase the window size to the size of the physical
1514
+ * display and hide the browser chrome.
1515
+ *
1516
+ * @return {!Promise<void>} A promise that will be resolved when the command
1517
+ * has completed.
1518
+ * @see <https://fullscreen.spec.whatwg.org/#fullscreen-an-element>
1595
1519
  */
1596
- maximize() {
1597
- return this.driver_.schedule(
1598
- new command.Command(command.Name.MAXIMIZE_WINDOW).
1599
- setParameter('windowHandle', 'current'),
1600
- 'WebDriver.manage().window().maximize()');
1520
+ fullscreen() {
1521
+ return this.driver_.execute(
1522
+ new command.Command(command.Name.FULLSCREEN_WINDOW));
1601
1523
  }
1602
1524
  }
1603
1525
 
@@ -1632,15 +1554,14 @@ class Logs {
1632
1554
  * entries since the last call, or from the start of the session.
1633
1555
  *
1634
1556
  * @param {!logging.Type} type The desired log type.
1635
- * @return {!promise.Thenable<!Array.<!logging.Entry>>} A
1557
+ * @return {!Promise<!Array.<!logging.Entry>>} A
1636
1558
  * promise that will resolve to a list of log entries for the specified
1637
1559
  * type.
1638
1560
  */
1639
1561
  get(type) {
1640
1562
  let cmd = new command.Command(command.Name.GET_LOG).
1641
1563
  setParameter('type', type);
1642
- return this.driver_.schedule(
1643
- cmd, 'WebDriver.manage().logs().get(' + type + ')').
1564
+ return this.driver_.execute(cmd).
1644
1565
  then(function(entries) {
1645
1566
  return entries.map(function(entry) {
1646
1567
  if (!(entry instanceof logging.Entry)) {
@@ -1655,13 +1576,12 @@ class Logs {
1655
1576
 
1656
1577
  /**
1657
1578
  * Retrieves the log types available to this driver.
1658
- * @return {!promise.Thenable<!Array<!logging.Type>>} A
1579
+ * @return {!Promise<!Array<!logging.Type>>} A
1659
1580
  * promise that will resolve to a list of available log types.
1660
1581
  */
1661
1582
  getAvailableLogTypes() {
1662
- return this.driver_.schedule(
1663
- new command.Command(command.Name.GET_AVAILABLE_LOG_TYPES),
1664
- 'WebDriver.manage().logs().getAvailableLogTypes()');
1583
+ return this.driver_.execute(
1584
+ new command.Command(command.Name.GET_AVAILABLE_LOG_TYPES));
1665
1585
  }
1666
1586
  }
1667
1587
 
@@ -1687,35 +1607,34 @@ class TargetLocator {
1687
1607
  }
1688
1608
 
1689
1609
  /**
1690
- * Schedules a command retrieve the {@code document.activeElement} element on
1691
- * the current document, or {@code document.body} if activeElement is not
1610
+ * Locates the DOM element on the current page that corresponds to
1611
+ * `document.activeElement` or `document.body` if the active element is not
1692
1612
  * available.
1613
+ *
1693
1614
  * @return {!WebElementPromise} The active element.
1694
1615
  */
1695
1616
  activeElement() {
1696
- var id = this.driver_.schedule(
1697
- new command.Command(command.Name.GET_ACTIVE_ELEMENT),
1698
- 'WebDriver.switchTo().activeElement()');
1617
+ var id = this.driver_.execute(
1618
+ new command.Command(command.Name.GET_ACTIVE_ELEMENT));
1699
1619
  return new WebElementPromise(this.driver_, id);
1700
1620
  }
1701
1621
 
1702
1622
  /**
1703
- * Schedules a command to switch focus of all future commands to the topmost
1704
- * frame on the page.
1705
- * @return {!promise.Thenable<void>} A promise that will be resolved
1623
+ * Switches focus of all future commands to the topmost frame in the current
1624
+ * window.
1625
+ *
1626
+ * @return {!Promise<void>} A promise that will be resolved
1706
1627
  * when the driver has changed focus to the default content.
1707
1628
  */
1708
1629
  defaultContent() {
1709
- return this.driver_.schedule(
1630
+ return this.driver_.execute(
1710
1631
  new command.Command(command.Name.SWITCH_TO_FRAME).
1711
- setParameter('id', null),
1712
- 'WebDriver.switchTo().defaultContent()');
1632
+ setParameter('id', null));
1713
1633
  }
1714
1634
 
1715
1635
  /**
1716
- * Schedules a command to switch the focus of all future commands to another
1717
- * frame on the page. The target frame may be specified as one of the
1718
- * following:
1636
+ * Changes the focus of all future commands to another frame on the page. The
1637
+ * target frame may be specified as one of the following:
1719
1638
  *
1720
1639
  * - A number that specifies a (zero-based) index into [window.frames](
1721
1640
  * https://developer.mozilla.org/en-US/docs/Web/API/Window.frames).
@@ -1728,51 +1647,61 @@ class TargetLocator {
1728
1647
  * rejected with a {@linkplain error.NoSuchFrameError}.
1729
1648
  *
1730
1649
  * @param {(number|WebElement|null)} id The frame locator.
1731
- * @return {!promise.Thenable<void>} A promise that will be resolved
1650
+ * @return {!Promise<void>} A promise that will be resolved
1732
1651
  * when the driver has changed focus to the specified frame.
1733
1652
  */
1734
1653
  frame(id) {
1735
- return this.driver_.schedule(
1654
+ return this.driver_.execute(
1736
1655
  new command.Command(command.Name.SWITCH_TO_FRAME).
1737
- setParameter('id', id),
1738
- 'WebDriver.switchTo().frame(' + id + ')');
1656
+ setParameter('id', id));
1657
+ }
1658
+
1659
+ /**
1660
+ * Changes the focus of all future commands to the parent frame of the
1661
+ * currently selected frame. This command has no effect if the driver is
1662
+ * already focused on the top-level browsing context.
1663
+ *
1664
+ * @return {!Promise<void>} A promise that will be resolved when the command
1665
+ * has completed.
1666
+ */
1667
+ parentFrame() {
1668
+ return this.driver_.execute(
1669
+ new command.Command(command.Name.SWITCH_TO_FRAME_PARENT));
1739
1670
  }
1740
1671
 
1741
1672
  /**
1742
- * Schedules a command to switch the focus of all future commands to another
1743
- * window. Windows may be specified by their {@code window.name} attribute or
1744
- * by its handle (as returned by {@link WebDriver#getWindowHandles}).
1673
+ * Changes the focus of all future commands to another window. Windows may be
1674
+ * specified by their {@code window.name} attribute or by its handle
1675
+ * (as returned by {@link WebDriver#getWindowHandles}).
1745
1676
  *
1746
1677
  * If the specified window cannot be found, the returned promise will be
1747
1678
  * rejected with a {@linkplain error.NoSuchWindowError}.
1748
1679
  *
1749
1680
  * @param {string} nameOrHandle The name or window handle of the window to
1750
1681
  * switch focus to.
1751
- * @return {!promise.Thenable<void>} A promise that will be resolved
1682
+ * @return {!Promise<void>} A promise that will be resolved
1752
1683
  * when the driver has changed focus to the specified window.
1753
1684
  */
1754
1685
  window(nameOrHandle) {
1755
- return this.driver_.schedule(
1686
+ return this.driver_.execute(
1756
1687
  new command.Command(command.Name.SWITCH_TO_WINDOW).
1757
1688
  // "name" supports the legacy drivers. "handle" is the W3C
1758
1689
  // compliant parameter.
1759
1690
  setParameter('name', nameOrHandle).
1760
- setParameter('handle', nameOrHandle),
1761
- 'WebDriver.switchTo().window(' + nameOrHandle + ')');
1691
+ setParameter('handle', nameOrHandle));
1762
1692
  }
1763
1693
 
1764
1694
  /**
1765
- * Schedules a command to change focus to the active modal dialog, such as
1766
- * those opened by `window.alert()`, `window.confirm()`, and
1767
- * `window.prompt()`. The returned promise will be rejected with a
1695
+ * Changes focus to the active modal dialog, such as those opened by
1696
+ * `window.alert()`, `window.confirm()`, and `window.prompt()`. The returned
1697
+ * promise will be rejected with a
1768
1698
  * {@linkplain error.NoSuchAlertError} if there are no open alerts.
1769
1699
  *
1770
1700
  * @return {!AlertPromise} The open alert.
1771
1701
  */
1772
1702
  alert() {
1773
- var text = this.driver_.schedule(
1774
- new command.Command(command.Name.GET_ALERT_TEXT),
1775
- 'WebDriver.switchTo().alert()');
1703
+ var text = this.driver_.execute(
1704
+ new command.Command(command.Name.GET_ALERT_TEXT));
1776
1705
  var driver = this.driver_;
1777
1706
  return new AlertPromise(driver, text.then(function(text) {
1778
1707
  return new Alert(driver, text);
@@ -1812,17 +1741,17 @@ class WebElement {
1812
1741
  /** @private {!WebDriver} */
1813
1742
  this.driver_ = driver;
1814
1743
 
1815
- /** @private {!promise.Thenable<string>} */
1816
- this.id_ = driver.controlFlow().promise(resolve => resolve(id));
1744
+ /** @private {!Promise<string>} */
1745
+ this.id_ = Promise.resolve(id);
1817
1746
  }
1818
1747
 
1819
1748
  /**
1820
1749
  * @param {string} id The raw ID.
1821
- * @param {boolean=} opt_noLegacy Whether to exclude the legacy element key.
1750
+ * @param {boolean=} noLegacy Whether to exclude the legacy element key.
1822
1751
  * @return {!Object} The element ID for use with WebDriver's wire protocol.
1823
1752
  */
1824
- static buildId(id, opt_noLegacy) {
1825
- return opt_noLegacy
1753
+ static buildId(id, noLegacy = false) {
1754
+ return noLegacy
1826
1755
  ? {[ELEMENT_ID_KEY]: id}
1827
1756
  : {[ELEMENT_ID_KEY]: id, [LEGACY_ELEMENT_ID_KEY]: id};
1828
1757
  }
@@ -1860,27 +1789,14 @@ class WebElement {
1860
1789
  *
1861
1790
  * @param {!WebElement} a A WebElement.
1862
1791
  * @param {!WebElement} b A WebElement.
1863
- * @return {!promise.Thenable<boolean>} A promise that will be
1792
+ * @return {!Promise<boolean>} A promise that will be
1864
1793
  * resolved to whether the two WebElements are equal.
1865
1794
  */
1866
- static equals(a, b) {
1795
+ static async equals(a, b) {
1867
1796
  if (a === b) {
1868
- return a.driver_.controlFlow().promise(resolve => resolve(true));
1797
+ return true;
1869
1798
  }
1870
- let ids = [a.getId(), b.getId()];
1871
- return promise.all(ids).then(function(ids) {
1872
- // If the two element's have the same ID, they should be considered
1873
- // equal. Otherwise, they may still be equivalent, but we'll need to
1874
- // ask the server to check for us.
1875
- if (ids[0] === ids[1]) {
1876
- return true;
1877
- }
1878
-
1879
- let cmd = new command.Command(command.Name.ELEMENT_EQUALS);
1880
- cmd.setParameter('id', ids[0]);
1881
- cmd.setParameter('other', ids[1]);
1882
- return a.driver_.schedule(cmd, 'WebElement.equals()');
1883
- });
1799
+ return a.driver_.executeScript('arguments[0] === arguments[1]', a, b);
1884
1800
  }
1885
1801
 
1886
1802
  /** @return {!WebDriver} The parent driver for this instance. */
@@ -1889,7 +1805,7 @@ class WebElement {
1889
1805
  }
1890
1806
 
1891
1807
  /**
1892
- * @return {!promise.Thenable<string>} A promise that resolves to
1808
+ * @return {!Promise<string>} A promise that resolves to
1893
1809
  * the server-assigned opaque ID assigned to this element.
1894
1810
  */
1895
1811
  getId() {
@@ -1909,16 +1825,14 @@ class WebElement {
1909
1825
  * parameters under the "id" key.
1910
1826
  *
1911
1827
  * @param {!command.Command} command The command to schedule.
1912
- * @param {string} description A description of the command for debugging.
1913
- * @return {!promise.Thenable<T>} A promise that will be resolved
1914
- * with the command result.
1828
+ * @return {!Promise<T>} A promise that will be resolved with the result.
1915
1829
  * @template T
1916
1830
  * @see WebDriver#schedule
1917
1831
  * @private
1918
1832
  */
1919
- schedule_(command, description) {
1833
+ execute_(command) {
1920
1834
  command.setParameter('id', this);
1921
- return this.driver_.schedule(command, description);
1835
+ return this.driver_.execute(command);
1922
1836
  }
1923
1837
 
1924
1838
  /**
@@ -1965,48 +1879,46 @@ class WebElement {
1965
1879
  command.Name.FIND_CHILD_ELEMENT).
1966
1880
  setParameter('using', locator.using).
1967
1881
  setParameter('value', locator.value);
1968
- id = this.schedule_(cmd, 'WebElement.findElement(' + locator + ')');
1882
+ id = this.execute_(cmd);
1969
1883
  }
1970
1884
  return new WebElementPromise(this.driver_, id);
1971
1885
  }
1972
1886
 
1973
1887
  /**
1974
- * Schedules a command to find all of the descendants of this element that
1975
- * match the given search criteria.
1888
+ * Locates all of the descendants of this element that match the given search
1889
+ * criteria.
1976
1890
  *
1977
1891
  * @param {!(by.By|Function)} locator The locator strategy to use when
1978
1892
  * searching for the element.
1979
- * @return {!promise.Thenable<!Array<!WebElement>>} A
1980
- * promise that will resolve to an array of WebElements.
1893
+ * @return {!Promise<!Array<!WebElement>>} A promise that will resolve to an
1894
+ * array of WebElements.
1981
1895
  */
1982
- findElements(locator) {
1896
+ async findElements(locator) {
1983
1897
  locator = by.checkedLocator(locator);
1984
1898
  let id;
1985
1899
  if (typeof locator === 'function') {
1986
1900
  return this.driver_.findElementsInternal_(locator, this);
1987
1901
  } else {
1988
- var cmd = new command.Command(
1989
- command.Name.FIND_CHILD_ELEMENTS).
1990
- setParameter('using', locator.using).
1991
- setParameter('value', locator.value);
1992
- return this.schedule_(cmd, 'WebElement.findElements(' + locator + ')');
1902
+ let cmd = new command.Command(command.Name.FIND_CHILD_ELEMENTS)
1903
+ .setParameter('using', locator.using)
1904
+ .setParameter('value', locator.value);
1905
+ let result = await this.execute_(cmd);
1906
+ return Array.isArray(result) ? result : [];
1993
1907
  }
1994
1908
  }
1995
1909
 
1996
1910
  /**
1997
- * Schedules a command to click on this element.
1998
- * @return {!promise.Thenable<void>} A promise that will be resolved
1999
- * when the click command has completed.
1911
+ * Clicks on this element.
1912
+ *
1913
+ * @return {!Promise<void>} A promise that will be resolved when the click
1914
+ * command has completed.
2000
1915
  */
2001
1916
  click() {
2002
- return this.schedule_(
2003
- new command.Command(command.Name.CLICK_ELEMENT),
2004
- 'WebElement.click()');
1917
+ return this.execute_(new command.Command(command.Name.CLICK_ELEMENT));
2005
1918
  }
2006
1919
 
2007
1920
  /**
2008
- * Schedules a command to type a sequence on the DOM element represented by
2009
- * this instance.
1921
+ * Types a key sequence on the DOM element represented by this instance.
2010
1922
  *
2011
1923
  * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is
2012
1924
  * processed in the key sequence, that key state is toggled until one of the
@@ -2053,95 +1965,80 @@ class WebElement {
2053
1965
  * punctuation keys will be synthesized according to a standard QWERTY en-us
2054
1966
  * keyboard layout.
2055
1967
  *
2056
- * @param {...(number|string|!IThenable<(number|string)>)} var_args The
1968
+ * @param {...(number|string|!IThenable<(number|string)>)} args The
2057
1969
  * sequence of keys to type. Number keys may be referenced numerically or
2058
1970
  * by string (1 or '1'). All arguments will be joined into a single
2059
1971
  * sequence.
2060
- * @return {!promise.Thenable<void>} A promise that will be resolved
2061
- * when all keys have been typed.
2062
- */
2063
- sendKeys(var_args) {
2064
- let keys = Promise.all(Array.prototype.slice.call(arguments, 0)).
2065
- then(keys => {
2066
- let ret = [];
2067
- keys.forEach(key => {
2068
- let type = typeof key;
2069
- if (type === 'number') {
2070
- key = String(key);
2071
- } else if (type !== 'string') {
2072
- throw TypeError(
2073
- 'each key must be a number of string; got ' + type);
2074
- }
1972
+ * @return {!Promise<void>} A promise that will be resolved when all keys
1973
+ * have been typed.
1974
+ */
1975
+ async sendKeys(...args) {
1976
+ let keys = [];
1977
+ (await Promise.all(args)).forEach(key => {
1978
+ let type = typeof key;
1979
+ if (type === 'number') {
1980
+ key = String(key);
1981
+ } else if (type !== 'string') {
1982
+ throw TypeError('each key must be a number of string; got ' + type);
1983
+ }
2075
1984
 
2076
- // The W3C protocol requires keys to be specified as an array where
2077
- // each element is a single key.
2078
- ret.push.apply(ret, key.split(''));
2079
- });
2080
- return ret;
2081
- });
1985
+ // The W3C protocol requires keys to be specified as an array where
1986
+ // each element is a single key.
1987
+ keys.push(...key.split(''));
1988
+ });
2082
1989
 
2083
1990
  if (!this.driver_.fileDetector_) {
2084
- return this.schedule_(
2085
- new command.Command(command.Name.SEND_KEYS_TO_ELEMENT).
2086
- setParameter('value', keys),
2087
- 'WebElement.sendKeys()');
1991
+ return this.execute_(
1992
+ new command.Command(command.Name.SEND_KEYS_TO_ELEMENT)
1993
+ .setParameter('text', keys.join(''))
1994
+ .setParameter('value', keys));
2088
1995
  }
2089
1996
 
2090
- // Suppress unhandled rejection errors until the flow executes the command.
2091
- keys.catch(function() {});
2092
-
2093
- var element = this;
2094
- return this.getDriver().controlFlow().execute(function() {
2095
- return keys.then(function(keys) {
2096
- return element.driver_.fileDetector_
2097
- .handleFile(element.driver_, keys.join(''));
2098
- }).then(function(keys) {
2099
- return element.schedule_(
2100
- new command.Command(command.Name.SEND_KEYS_TO_ELEMENT).
2101
- setParameter('value', keys.split('')),
2102
- 'WebElement.sendKeys()');
2103
- });
2104
- }, 'WebElement.sendKeys()');
1997
+ keys =
1998
+ await this.driver_.fileDetector_.handleFile(
1999
+ this.driver_, keys.join(''));
2000
+ return this.execute_(
2001
+ new command.Command(command.Name.SEND_KEYS_TO_ELEMENT)
2002
+ .setParameter('text', keys)
2003
+ .setParameter('value', keys.split('')));
2105
2004
  }
2106
2005
 
2107
2006
  /**
2108
- * Schedules a command to query for the tag/node name of this element.
2109
- * @return {!promise.Thenable<string>} A promise that will be
2110
- * resolved with the element's tag name.
2007
+ * Retrieves the element's tag name.
2008
+ *
2009
+ * @return {!Promise<string>} A promise that will be resolved with the
2010
+ * element's tag name.
2111
2011
  */
2112
2012
  getTagName() {
2113
- return this.schedule_(
2114
- new command.Command(command.Name.GET_ELEMENT_TAG_NAME),
2115
- 'WebElement.getTagName()');
2013
+ return this.execute_(
2014
+ new command.Command(command.Name.GET_ELEMENT_TAG_NAME));
2116
2015
  }
2117
2016
 
2118
2017
  /**
2119
- * Schedules a command to query for the computed style of the element
2120
- * represented by this instance. If the element inherits the named style from
2121
- * its parent, the parent will be queried for its value. Where possible, color
2122
- * values will be converted to their hex representation (e.g. #00ff00 instead
2123
- * of rgb(0, 255, 0)).
2018
+ * Retrieves the value of a computed style property for this instance. If
2019
+ * the element inherits the named style from its parent, the parent will be
2020
+ * queried for its value. Where possible, color values will be converted to
2021
+ * their hex representation (e.g. #00ff00 instead of rgb(0, 255, 0)).
2124
2022
  *
2125
2023
  * _Warning:_ the value returned will be as the browser interprets it, so
2126
2024
  * it may be tricky to form a proper assertion.
2127
2025
  *
2128
2026
  * @param {string} cssStyleProperty The name of the CSS style property to look
2129
2027
  * up.
2130
- * @return {!promise.Thenable<string>} A promise that will be
2131
- * resolved with the requested CSS value.
2028
+ * @return {!Promise<string>} A promise that will be resolved with the
2029
+ * requested CSS value.
2132
2030
  */
2133
2031
  getCssValue(cssStyleProperty) {
2134
2032
  var name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;
2135
- return this.schedule_(
2033
+ return this.execute_(
2136
2034
  new command.Command(name).
2137
- setParameter('propertyName', cssStyleProperty),
2138
- 'WebElement.getCssValue(' + cssStyleProperty + ')');
2035
+ setParameter('propertyName', cssStyleProperty));
2139
2036
  }
2140
2037
 
2141
2038
  /**
2142
- * Schedules a command to query for the value of the given attribute of the
2143
- * element. Will return the current value, even if it has been modified after
2144
- * the page has been loaded. More exactly, this method will return the value
2039
+ * Retrieves the current value of the given attribute of this element.
2040
+ * Will return the current value, even if it has been modified after the page
2041
+ * has been loaded. More exactly, this method will return the value
2145
2042
  * of the given attribute, unless that attribute is not present, in which case
2146
2043
  * the value of the property with the same name is returned. If neither value
2147
2044
  * is set, null is returned (for example, the "value" property of a textarea
@@ -2163,131 +2060,122 @@ class WebElement {
2163
2060
  * - "readonly"
2164
2061
  *
2165
2062
  * @param {string} attributeName The name of the attribute to query.
2166
- * @return {!promise.Thenable<?string>} A promise that will be
2063
+ * @return {!Promise<?string>} A promise that will be
2167
2064
  * resolved with the attribute's value. The returned value will always be
2168
2065
  * either a string or null.
2169
2066
  */
2170
2067
  getAttribute(attributeName) {
2171
- return this.schedule_(
2068
+ return this.execute_(
2172
2069
  new command.Command(command.Name.GET_ELEMENT_ATTRIBUTE).
2173
- setParameter('name', attributeName),
2174
- 'WebElement.getAttribute(' + attributeName + ')');
2070
+ setParameter('name', attributeName));
2175
2071
  }
2176
2072
 
2177
2073
  /**
2178
2074
  * Get the visible (i.e. not hidden by CSS) innerText of this element,
2179
2075
  * including sub-elements, without any leading or trailing whitespace.
2180
2076
  *
2181
- * @return {!promise.Thenable<string>} A promise that will be
2077
+ * @return {!Promise<string>} A promise that will be
2182
2078
  * resolved with the element's visible text.
2183
2079
  */
2184
2080
  getText() {
2185
- return this.schedule_(
2186
- new command.Command(command.Name.GET_ELEMENT_TEXT),
2187
- 'WebElement.getText()');
2188
- }
2189
-
2190
- /**
2191
- * Schedules a command to compute the size of this element's bounding box, in
2192
- * pixels.
2193
- * @return {!promise.Thenable<{width: number, height: number}>} A
2194
- * promise that will be resolved with the element's size as a
2195
- * {@code {width:number, height:number}} object.
2196
- */
2197
- getSize() {
2198
- return this.schedule_(
2199
- new command.Command(command.Name.GET_ELEMENT_SIZE),
2200
- 'WebElement.getSize()');
2081
+ return this.execute_(new command.Command(command.Name.GET_ELEMENT_TEXT));
2201
2082
  }
2202
2083
 
2203
2084
  /**
2204
- * Schedules a command to compute the location of this element in page space.
2205
- * @return {!promise.Thenable<{x: number, y: number}>} A promise that
2206
- * will be resolved to the element's location as a
2207
- * {@code {x:number, y:number}} object.
2085
+ * Returns an object describing an element's location, in pixels relative to
2086
+ * the document element, and the element's size in pixels.
2087
+ *
2088
+ * @return {!Promise<{width: number, height: number, x: number, y: number}>}
2089
+ * A promise that will resolve with the element's rect.
2208
2090
  */
2209
- getLocation() {
2210
- return this.schedule_(
2211
- new command.Command(command.Name.GET_ELEMENT_LOCATION),
2212
- 'WebElement.getLocation()');
2091
+ async getRect() {
2092
+ try {
2093
+ return await this.execute_(
2094
+ new command.Command(command.Name.GET_ELEMENT_RECT));
2095
+ } catch (err) {
2096
+ if (err instanceof error.UnknownCommandError) {
2097
+ const {width, height} =
2098
+ await this.execute_(
2099
+ new command.Command(command.Name.GET_ELEMENT_SIZE));
2100
+ const {x, y} =
2101
+ await this.execute_(
2102
+ new command.Command(command.Name.GET_ELEMENT_LOCATION));
2103
+ return {x, y, width, height};
2104
+ }
2105
+ }
2213
2106
  }
2214
2107
 
2215
2108
  /**
2216
- * Schedules a command to query whether the DOM element represented by this
2217
- * instance is enabled, as dictated by the {@code disabled} attribute.
2218
- * @return {!promise.Thenable<boolean>} A promise that will be
2109
+ * Tests whether this element is enabled, as dictated by the `disabled`
2110
+ * attribute.
2111
+ *
2112
+ * @return {!Promise<boolean>} A promise that will be
2219
2113
  * resolved with whether this element is currently enabled.
2220
2114
  */
2221
2115
  isEnabled() {
2222
- return this.schedule_(
2223
- new command.Command(command.Name.IS_ELEMENT_ENABLED),
2224
- 'WebElement.isEnabled()');
2116
+ return this.execute_(new command.Command(command.Name.IS_ELEMENT_ENABLED));
2225
2117
  }
2226
2118
 
2227
2119
  /**
2228
- * Schedules a command to query whether this element is selected.
2229
- * @return {!promise.Thenable<boolean>} A promise that will be
2120
+ * Tests whether this element is selected.
2121
+ *
2122
+ * @return {!Promise<boolean>} A promise that will be
2230
2123
  * resolved with whether this element is currently selected.
2231
2124
  */
2232
2125
  isSelected() {
2233
- return this.schedule_(
2234
- new command.Command(command.Name.IS_ELEMENT_SELECTED),
2235
- 'WebElement.isSelected()');
2126
+ return this.execute_(
2127
+ new command.Command(command.Name.IS_ELEMENT_SELECTED));
2236
2128
  }
2237
2129
 
2238
2130
  /**
2239
- * Schedules a command to submit the form containing this element (or this
2240
- * element if it is a FORM element). This command is a no-op if the element is
2241
- * not contained in a form.
2242
- * @return {!promise.Thenable<void>} A promise that will be resolved
2131
+ * Submits the form containing this element (or this element if it is itself
2132
+ * a FORM element). his command is a no-op if the element is not contained in
2133
+ * a form.
2134
+ *
2135
+ * @return {!Promise<void>} A promise that will be resolved
2243
2136
  * when the form has been submitted.
2244
2137
  */
2245
2138
  submit() {
2246
- return this.schedule_(
2247
- new command.Command(command.Name.SUBMIT_ELEMENT),
2248
- 'WebElement.submit()');
2139
+ return this.execute_(new command.Command(command.Name.SUBMIT_ELEMENT));
2249
2140
  }
2250
2141
 
2251
2142
  /**
2252
- * Schedules a command to clear the `value` of this element. This command has
2253
- * no effect if the underlying DOM element is neither a text INPUT element
2254
- * nor a TEXTAREA element.
2255
- * @return {!promise.Thenable<void>} A promise that will be resolved
2143
+ * Clear the `value` of this element. This command has no effect if the
2144
+ * underlying DOM element is neither a text INPUT element nor a TEXTAREA
2145
+ * element.
2146
+ *
2147
+ * @return {!Promise<void>} A promise that will be resolved
2256
2148
  * when the element has been cleared.
2257
2149
  */
2258
2150
  clear() {
2259
- return this.schedule_(
2260
- new command.Command(command.Name.CLEAR_ELEMENT),
2261
- 'WebElement.clear()');
2151
+ return this.execute_(new command.Command(command.Name.CLEAR_ELEMENT));
2262
2152
  }
2263
2153
 
2264
2154
  /**
2265
- * Schedules a command to test whether this element is currently displayed.
2266
- * @return {!promise.Thenable<boolean>} A promise that will be
2155
+ * Test whether this element is currently displayed.
2156
+ *
2157
+ * @return {!Promise<boolean>} A promise that will be
2267
2158
  * resolved with whether this element is currently visible on the page.
2268
2159
  */
2269
2160
  isDisplayed() {
2270
- return this.schedule_(
2271
- new command.Command(command.Name.IS_ELEMENT_DISPLAYED),
2272
- 'WebElement.isDisplayed()');
2161
+ return this.execute_(
2162
+ new command.Command(command.Name.IS_ELEMENT_DISPLAYED));
2273
2163
  }
2274
2164
 
2275
2165
  /**
2276
2166
  * Take a screenshot of the visible region encompassed by this element's
2277
2167
  * bounding rectangle.
2278
2168
  *
2279
- * @param {boolean=} opt_scroll Optional argument that indicates whether the
2169
+ * @param {boolean=} scroll Optional argument that indicates whether the
2280
2170
  * element should be scrolled into view before taking a screenshot.
2281
2171
  * Defaults to false.
2282
- * @return {!promise.Thenable<string>} A promise that will be
2172
+ * @return {!Promise<string>} A promise that will be
2283
2173
  * resolved to the screenshot as a base-64 encoded PNG.
2284
2174
  */
2285
- takeScreenshot(opt_scroll) {
2286
- var scroll = !!opt_scroll;
2287
- return this.schedule_(
2175
+ takeScreenshot(scroll = false) {
2176
+ return this.execute_(
2288
2177
  new command.Command(command.Name.TAKE_ELEMENT_SCREENSHOT)
2289
- .setParameter('scroll', scroll),
2290
- 'WebElement.takeScreenshot(' + scroll + ')');
2178
+ .setParameter('scroll', scroll));
2291
2179
  }
2292
2180
  }
2293
2181
 
@@ -2304,31 +2192,19 @@ class WebElement {
2304
2192
  * return el.click();
2305
2193
  * });
2306
2194
  *
2307
- * @implements {promise.CancellableThenable<!WebElement>}
2195
+ * @implements {IThenable<!WebElement>}
2308
2196
  * @final
2309
2197
  */
2310
2198
  class WebElementPromise extends WebElement {
2311
2199
  /**
2312
2200
  * @param {!WebDriver} driver The parent WebDriver instance for this
2313
2201
  * element.
2314
- * @param {!promise.Thenable<!WebElement>} el A promise
2202
+ * @param {!Promise<!WebElement>} el A promise
2315
2203
  * that will resolve to the promised element.
2316
2204
  */
2317
2205
  constructor(driver, el) {
2318
2206
  super(driver, 'unused');
2319
2207
 
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
- };
2331
-
2332
2208
  /** @override */
2333
2209
  this.then = el.then.bind(el);
2334
2210
 
@@ -2347,7 +2223,6 @@ class WebElementPromise extends WebElement {
2347
2223
  };
2348
2224
  }
2349
2225
  }
2350
- promise.CancellableThenable.addImplementation(WebElementPromise);
2351
2226
 
2352
2227
 
2353
2228
  //////////////////////////////////////////////////////////////////////////////
@@ -2373,60 +2248,41 @@ class Alert {
2373
2248
  /** @private {!WebDriver} */
2374
2249
  this.driver_ = driver;
2375
2250
 
2376
- /** @private {!promise.Thenable<string>} */
2377
- this.text_ = driver.controlFlow().promise(resolve => resolve(text));
2251
+ /** @private {!Promise<string>} */
2252
+ this.text_ = Promise.resolve(text);
2378
2253
  }
2379
2254
 
2380
2255
  /**
2381
2256
  * Retrieves the message text displayed with this alert. For instance, if the
2382
2257
  * alert were opened with alert("hello"), then this would return "hello".
2383
2258
  *
2384
- * @return {!promise.Thenable<string>} A promise that will be
2259
+ * @return {!Promise<string>} A promise that will be
2385
2260
  * resolved to the text displayed with this alert.
2386
2261
  */
2387
2262
  getText() {
2388
2263
  return this.text_;
2389
2264
  }
2390
2265
 
2391
- /**
2392
- * Sets the username and password in an alert prompting for credentials (such
2393
- * as a Basic HTTP Auth prompt). This method will implicitly
2394
- * {@linkplain #accept() submit} the dialog.
2395
- *
2396
- * @param {string} username The username to send.
2397
- * @param {string} password The password to send.
2398
- * @return {!promise.Thenable<void>} A promise that will be resolved when this
2399
- * command has completed.
2400
- */
2401
- authenticateAs(username, password) {
2402
- return this.driver_.schedule(
2403
- new command.Command(command.Name.SET_ALERT_CREDENTIALS),
2404
- 'WebDriver.switchTo().alert()'
2405
- + `.authenticateAs("${username}", "${password}")`);
2406
- }
2407
-
2408
2266
  /**
2409
2267
  * Accepts this alert.
2410
2268
  *
2411
- * @return {!promise.Thenable<void>} A promise that will be resolved
2269
+ * @return {!Promise<void>} A promise that will be resolved
2412
2270
  * when this command has completed.
2413
2271
  */
2414
2272
  accept() {
2415
- return this.driver_.schedule(
2416
- new command.Command(command.Name.ACCEPT_ALERT),
2417
- 'WebDriver.switchTo().alert().accept()');
2273
+ return this.driver_.execute(
2274
+ new command.Command(command.Name.ACCEPT_ALERT));
2418
2275
  }
2419
2276
 
2420
2277
  /**
2421
2278
  * Dismisses this alert.
2422
2279
  *
2423
- * @return {!promise.Thenable<void>} A promise that will be resolved
2280
+ * @return {!Promise<void>} A promise that will be resolved
2424
2281
  * when this command has completed.
2425
2282
  */
2426
2283
  dismiss() {
2427
- return this.driver_.schedule(
2428
- new command.Command(command.Name.DISMISS_ALERT),
2429
- 'WebDriver.switchTo().alert().dismiss()');
2284
+ return this.driver_.execute(
2285
+ new command.Command(command.Name.DISMISS_ALERT));
2430
2286
  }
2431
2287
 
2432
2288
  /**
@@ -2435,14 +2291,13 @@ class Alert {
2435
2291
  * window.confirm).
2436
2292
  *
2437
2293
  * @param {string} text The text to set.
2438
- * @return {!promise.Thenable<void>} A promise that will be resolved
2294
+ * @return {!Promise<void>} A promise that will be resolved
2439
2295
  * when this command has completed.
2440
2296
  */
2441
2297
  sendKeys(text) {
2442
- return this.driver_.schedule(
2298
+ return this.driver_.execute(
2443
2299
  new command.Command(command.Name.SET_ALERT_TEXT).
2444
- setParameter('text', text),
2445
- 'WebDriver.switchTo().alert().sendKeys(' + text + ')');
2300
+ setParameter('text', text));
2446
2301
  }
2447
2302
  }
2448
2303
 
@@ -2458,31 +2313,19 @@ class Alert {
2458
2313
  * return alert.dismiss();
2459
2314
  * });
2460
2315
  *
2461
- * @implements {promise.CancellableThenable<!webdriver.Alert>}
2316
+ * @implements {IThenable<!Alert>}
2462
2317
  * @final
2463
2318
  */
2464
2319
  class AlertPromise extends Alert {
2465
2320
  /**
2466
2321
  * @param {!WebDriver} driver The driver controlling the browser this
2467
2322
  * alert is attached to.
2468
- * @param {!promise.Thenable<!Alert>} alert A thenable
2323
+ * @param {!Promise<!Alert>} alert A thenable
2469
2324
  * that will be fulfilled with the promised alert.
2470
2325
  */
2471
2326
  constructor(driver, alert) {
2472
2327
  super(driver, 'unused');
2473
2328
 
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
- };
2485
-
2486
2329
  /** @override */
2487
2330
  this.then = alert.then.bind(alert);
2488
2331
 
@@ -2499,16 +2342,6 @@ class AlertPromise extends Alert {
2499
2342
  });
2500
2343
  };
2501
2344
 
2502
- /**
2503
- * Defers action until the alert has been located.
2504
- * @override
2505
- */
2506
- this.authenticateAs = function(username, password) {
2507
- return alert.then(function(alert) {
2508
- return alert.authenticateAs(username, password);
2509
- });
2510
- };
2511
-
2512
2345
  /**
2513
2346
  * Defers action until the alert has been located.
2514
2347
  * @override
@@ -2540,25 +2373,23 @@ class AlertPromise extends Alert {
2540
2373
  };
2541
2374
  }
2542
2375
  }
2543
- promise.CancellableThenable.addImplementation(AlertPromise);
2544
2376
 
2545
2377
 
2546
2378
  // PUBLIC API
2547
2379
 
2548
2380
 
2549
2381
  module.exports = {
2550
- Alert: Alert,
2551
- AlertPromise: AlertPromise,
2552
- Condition: Condition,
2553
- Logs: Logs,
2554
- Navigation: Navigation,
2555
- Options: Options,
2556
- TargetLocator: TargetLocator,
2557
- Timeouts: Timeouts,
2558
- IWebDriver: IWebDriver,
2559
- WebDriver: WebDriver,
2560
- WebElement: WebElement,
2561
- WebElementCondition: WebElementCondition,
2562
- WebElementPromise: WebElementPromise,
2563
- Window: Window
2382
+ Alert,
2383
+ AlertPromise,
2384
+ Condition,
2385
+ Logs,
2386
+ Navigation,
2387
+ Options,
2388
+ TargetLocator,
2389
+ IWebDriver,
2390
+ WebDriver,
2391
+ WebElement,
2392
+ WebElementCondition,
2393
+ WebElementPromise,
2394
+ Window
2564
2395
  };