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/http.js CHANGED
@@ -29,13 +29,13 @@ const cmd = require('./command');
29
29
  const error = require('./error');
30
30
  const logging = require('./logging');
31
31
  const promise = require('./promise');
32
- const Session = require('./session').Session;
33
- const WebElement = require('./webdriver').WebElement;
32
+ const {Session} = require('./session');
33
+ const {WebElement} = require('./webdriver');
34
34
 
35
- const {getAttribute, isDisplayed} = (function() {
35
+ const {getAttribute, isDisplayed} = /** @suppress {undefinedVars|uselessCode} */(function() {
36
36
  try {
37
37
  return {
38
- getAttribute: require('./atoms/getAttribute.js'),
38
+ getAttribute: require('./atoms/get-attribute.js'),
39
39
  isDisplayed: require('./atoms/is-displayed.js')
40
40
  };
41
41
  } catch (ex) {
@@ -145,7 +145,7 @@ function resource(method, path) { return {method: method, path: path}; }
145
145
  var CommandSpec;
146
146
 
147
147
 
148
- /** @typedef {function(!cmd.Command): !Promise<!cmd.Command>} */
148
+ /** @typedef {function(!cmd.Command): !cmd.Command} */
149
149
  var CommandTransformer;
150
150
 
151
151
 
@@ -155,21 +155,17 @@ class InternalTypeError extends TypeError {}
155
155
  /**
156
156
  * @param {!cmd.Command} command The initial command.
157
157
  * @param {Atom} atom The name of the atom to execute.
158
- * @return {!Promise<!cmd.Command>} The transformed command to execute.
158
+ * @return {!cmd.Command} The transformed command to execute.
159
159
  */
160
160
  function toExecuteAtomCommand(command, atom, ...params) {
161
- return new Promise((resolve, reject) => {
162
- if (typeof atom !== 'function') {
163
- reject(new InternalTypeError('atom is not a function: ' + typeof atom));
164
- return;
165
- }
161
+ if (typeof atom !== 'function') {
162
+ throw new InternalTypeError('atom is not a function: ' + typeof atom);
163
+ }
166
164
 
167
- let newCmd = new cmd.Command(cmd.Name.EXECUTE_SCRIPT)
168
- .setParameter('sessionId', command.getParameter('sessionId'))
169
- .setParameter('script', `return (${atom}).apply(null, arguments)`)
170
- .setParameter('args', params.map(param => command.getParameter(param)));
171
- resolve(newCmd);
172
- });
165
+ return new cmd.Command(cmd.Name.EXECUTE_SCRIPT)
166
+ .setParameter('sessionId', command.getParameter('sessionId'))
167
+ .setParameter('script', `return (${atom}).apply(null, arguments)`)
168
+ .setParameter('args', params.map(param => command.getParameter(param)));
173
169
  }
174
170
 
175
171
 
@@ -179,7 +175,6 @@ const COMMAND_MAP = new Map([
179
175
  [cmd.Name.GET_SERVER_STATUS, get('/status')],
180
176
  [cmd.Name.NEW_SESSION, post('/session')],
181
177
  [cmd.Name.GET_SESSIONS, get('/sessions')],
182
- [cmd.Name.DESCRIBE_SESSION, get('/session/:sessionId')],
183
178
  [cmd.Name.QUIT, del('/session/:sessionId')],
184
179
  [cmd.Name.CLOSE, del('/session/:sessionId/window')],
185
180
  [cmd.Name.GET_CURRENT_WINDOW_HANDLE, get('/session/:sessionId/window_handle')],
@@ -211,7 +206,6 @@ const COMMAND_MAP = new Map([
211
206
  [cmd.Name.GET_ELEMENT_SIZE, get('/session/:sessionId/element/:id/size')],
212
207
  [cmd.Name.GET_ELEMENT_ATTRIBUTE, get('/session/:sessionId/element/:id/attribute/:name')],
213
208
  [cmd.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, get('/session/:sessionId/element/:id/css/:propertyName')],
214
- [cmd.Name.ELEMENT_EQUALS, get('/session/:sessionId/element/:id/equals/:other')],
215
209
  [cmd.Name.TAKE_ELEMENT_SCREENSHOT, get('/session/:sessionId/element/:id/screenshot')],
216
210
  [cmd.Name.SWITCH_TO_WINDOW, post('/session/:sessionId/window')],
217
211
  [cmd.Name.MAXIMIZE_WINDOW, post('/session/:sessionId/window/current/maximize')],
@@ -225,50 +219,105 @@ const COMMAND_MAP = new Map([
225
219
  [cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute')],
226
220
  [cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute_async')],
227
221
  [cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
222
+ [cmd.Name.GET_TIMEOUT, get('/session/:sessionId/timeouts')],
228
223
  [cmd.Name.SET_TIMEOUT, post('/session/:sessionId/timeouts')],
229
- [cmd.Name.MOVE_TO, post('/session/:sessionId/moveto')],
230
- [cmd.Name.CLICK, post('/session/:sessionId/click')],
231
- [cmd.Name.DOUBLE_CLICK, post('/session/:sessionId/doubleclick')],
232
- [cmd.Name.MOUSE_DOWN, post('/session/:sessionId/buttondown')],
233
- [cmd.Name.MOUSE_UP, post('/session/:sessionId/buttonup')],
234
- [cmd.Name.MOVE_TO, post('/session/:sessionId/moveto')],
235
- [cmd.Name.SEND_KEYS_TO_ACTIVE_ELEMENT, post('/session/:sessionId/keys')],
236
- [cmd.Name.TOUCH_SINGLE_TAP, post('/session/:sessionId/touch/click')],
237
- [cmd.Name.TOUCH_DOUBLE_TAP, post('/session/:sessionId/touch/doubleclick')],
238
- [cmd.Name.TOUCH_DOWN, post('/session/:sessionId/touch/down')],
239
- [cmd.Name.TOUCH_UP, post('/session/:sessionId/touch/up')],
240
- [cmd.Name.TOUCH_MOVE, post('/session/:sessionId/touch/move')],
241
- [cmd.Name.TOUCH_SCROLL, post('/session/:sessionId/touch/scroll')],
242
- [cmd.Name.TOUCH_LONG_PRESS, post('/session/:sessionId/touch/longclick')],
243
- [cmd.Name.TOUCH_FLICK, post('/session/:sessionId/touch/flick')],
244
224
  [cmd.Name.ACCEPT_ALERT, post('/session/:sessionId/accept_alert')],
245
225
  [cmd.Name.DISMISS_ALERT, post('/session/:sessionId/dismiss_alert')],
246
226
  [cmd.Name.GET_ALERT_TEXT, get('/session/:sessionId/alert_text')],
247
227
  [cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert_text')],
248
- [cmd.Name.SET_ALERT_CREDENTIALS, post('/session/:sessionId/alert/credentials')],
249
228
  [cmd.Name.GET_LOG, post('/session/:sessionId/log')],
250
229
  [cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/log/types')],
251
230
  [cmd.Name.GET_SESSION_LOGS, post('/logs')],
252
231
  [cmd.Name.UPLOAD_FILE, post('/session/:sessionId/file')],
232
+ [cmd.Name.LEGACY_ACTION_CLICK, post('/session/:sessionId/click')],
233
+ [cmd.Name.LEGACY_ACTION_DOUBLE_CLICK, post('/session/:sessionId/doubleclick')],
234
+ [cmd.Name.LEGACY_ACTION_MOUSE_DOWN, post('/session/:sessionId/buttondown')],
235
+ [cmd.Name.LEGACY_ACTION_MOUSE_UP, post('/session/:sessionId/buttonup')],
236
+ [cmd.Name.LEGACY_ACTION_MOUSE_MOVE, post('/session/:sessionId/moveto')],
237
+ [cmd.Name.LEGACY_ACTION_SEND_KEYS, post('/session/:sessionId/keys')],
238
+ [cmd.Name.LEGACY_ACTION_TOUCH_DOWN, post('/session/:sessionId/touch/down')],
239
+ [cmd.Name.LEGACY_ACTION_TOUCH_UP, post('/session/:sessionId/touch/up')],
240
+ [cmd.Name.LEGACY_ACTION_TOUCH_MOVE, post('/session/:sessionId/touch/move')],
241
+ [cmd.Name.LEGACY_ACTION_TOUCH_SCROLL, post('/session/:sessionId/touch/scroll')],
242
+ [cmd.Name.LEGACY_ACTION_TOUCH_LONG_PRESS, post('/session/:sessionId/touch/longclick')],
243
+ [cmd.Name.LEGACY_ACTION_TOUCH_FLICK, post('/session/:sessionId/touch/flick')],
244
+ [cmd.Name.LEGACY_ACTION_TOUCH_SINGLE_TAP, post('/session/:sessionId/touch/click')],
245
+ [cmd.Name.LEGACY_ACTION_TOUCH_DOUBLE_TAP, post('/session/:sessionId/touch/doubleclick')],
253
246
  ]);
254
247
 
255
248
 
256
249
  /** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
257
250
  const W3C_COMMAND_MAP = new Map([
251
+ // Server status.
252
+ [cmd.Name.GET_SERVER_STATUS, get('/status')],
253
+ // Session management.
254
+ [cmd.Name.NEW_SESSION, post('/session')],
255
+ [cmd.Name.QUIT, del('/session/:sessionId')],
256
+ [cmd.Name.GET_TIMEOUT, get('/session/:sessionId/timeouts')],
257
+ [cmd.Name.SET_TIMEOUT, post('/session/:sessionId/timeouts')],
258
+ // Navigation.
259
+ [cmd.Name.GET_CURRENT_URL, get('/session/:sessionId/url')],
260
+ [cmd.Name.GET, post('/session/:sessionId/url')],
261
+ [cmd.Name.GO_BACK, post('/session/:sessionId/back')],
262
+ [cmd.Name.GO_FORWARD, post('/session/:sessionId/forward')],
263
+ [cmd.Name.REFRESH, post('/session/:sessionId/refresh')],
264
+ // Page inspection.
265
+ [cmd.Name.GET_PAGE_SOURCE, get('/session/:sessionId/source')],
266
+ [cmd.Name.GET_TITLE, get('/session/:sessionId/title')],
267
+ // Script execution.
268
+ [cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute/sync')],
269
+ [cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute/async')],
270
+ // Frame selection.
271
+ [cmd.Name.SWITCH_TO_FRAME, post('/session/:sessionId/frame')],
272
+ [cmd.Name.SWITCH_TO_FRAME_PARENT, post('/session/:sessionId/frame/parent')],
273
+ // Window management.
274
+ [cmd.Name.GET_CURRENT_WINDOW_HANDLE, get('/session/:sessionId/window')],
275
+ [cmd.Name.CLOSE, del('/session/:sessionId/window')],
276
+ [cmd.Name.SWITCH_TO_WINDOW, post('/session/:sessionId/window')],
277
+ [cmd.Name.GET_WINDOW_HANDLES, get('/session/:sessionId/window/handles')],
278
+ [cmd.Name.GET_WINDOW_RECT, get('/session/:sessionId/window/rect')],
279
+ [cmd.Name.SET_WINDOW_RECT, post('/session/:sessionId/window/rect')],
280
+ [cmd.Name.MAXIMIZE_WINDOW, post('/session/:sessionId/window/maximize')],
281
+ [cmd.Name.MINIMIZE_WINDOW, post('/session/:sessionId/window/minimize')],
282
+ [cmd.Name.FULLSCREEN_WINDOW, post('/session/:sessionId/window/fullscreen')],
283
+ // Actions.
284
+ [cmd.Name.ACTIONS, post('/session/:sessionId/actions')],
285
+ [cmd.Name.CLEAR_ACTIONS, del('/session/:sessionId/actions')],
286
+ // Locating elements.
258
287
  [cmd.Name.GET_ACTIVE_ELEMENT, get('/session/:sessionId/element/active')],
288
+ [cmd.Name.FIND_ELEMENT, post('/session/:sessionId/element')],
289
+ [cmd.Name.FIND_ELEMENTS, post('/session/:sessionId/elements')],
290
+ [cmd.Name.FIND_CHILD_ELEMENT, post('/session/:sessionId/element/:id/element')],
291
+ [cmd.Name.FIND_CHILD_ELEMENTS, post('/session/:sessionId/element/:id/elements')],
292
+ // Element interaction.
293
+ [cmd.Name.GET_ELEMENT_TAG_NAME, get('/session/:sessionId/element/:id/name')],
294
+ [cmd.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, get('/session/:sessionId/element/:id/css/:propertyName')],
295
+ [cmd.Name.GET_ELEMENT_RECT, get('/session/:sessionId/element/:id/rect')],
296
+ [cmd.Name.CLEAR_ELEMENT, post('/session/:sessionId/element/:id/clear')],
297
+ [cmd.Name.CLICK_ELEMENT, post('/session/:sessionId/element/:id/click')],
298
+ [cmd.Name.SEND_KEYS_TO_ELEMENT, post('/session/:sessionId/element/:id/value')],
299
+ [cmd.Name.GET_ELEMENT_TEXT, get('/session/:sessionId/element/:id/text')],
300
+ [cmd.Name.IS_ELEMENT_ENABLED, get('/session/:sessionId/element/:id/enabled')],
259
301
  [cmd.Name.GET_ELEMENT_ATTRIBUTE, (cmd) => {
260
302
  return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name');
261
303
  }],
262
- [cmd.Name.GET_ELEMENT_LOCATION, get('/session/:sessionId/element/:id/rect')],
263
- [cmd.Name.GET_ELEMENT_SIZE, get('/session/:sessionId/element/:id/rect')],
264
304
  [cmd.Name.IS_ELEMENT_DISPLAYED, (cmd) => {
265
305
  return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id');
266
306
  }],
267
- [cmd.Name.MAXIMIZE_WINDOW, post('/session/:sessionId/window/maximize')],
268
- [cmd.Name.GET_WINDOW_POSITION, get('/session/:sessionId/window/position')],
269
- [cmd.Name.SET_WINDOW_POSITION, post('/session/:sessionId/window/position')],
270
- [cmd.Name.GET_WINDOW_SIZE, get('/session/:sessionId/window/size')],
271
- [cmd.Name.SET_WINDOW_SIZE, post('/session/:sessionId/window/size')],
307
+ // Cookie management.
308
+ [cmd.Name.GET_ALL_COOKIES, get('/session/:sessionId/cookie')],
309
+ [cmd.Name.ADD_COOKIE, post('/session/:sessionId/cookie')],
310
+ [cmd.Name.DELETE_ALL_COOKIES, del('/session/:sessionId/cookie')],
311
+ [cmd.Name.GET_COOKIE, get('/session/:sessionId/cookie/:name')],
312
+ [cmd.Name.DELETE_COOKIE, del('/session/:sessionId/cookie/:name')],
313
+ // Alert management.
314
+ [cmd.Name.ACCEPT_ALERT, post('/session/:sessionId/alert/accept')],
315
+ [cmd.Name.DISMISS_ALERT, post('/session/:sessionId/alert/dismiss')],
316
+ [cmd.Name.GET_ALERT_TEXT, get('/session/:sessionId/alert/text')],
317
+ [cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert/text')],
318
+ // Screenshots.
319
+ [cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
320
+ [cmd.Name.TAKE_ELEMENT_SCREENSHOT, get('/session/:sessionId/element/:id/screenshot')],
272
321
  ]);
273
322
 
274
323
 
@@ -292,35 +341,13 @@ class Client {
292
341
  }
293
342
 
294
343
 
295
- const CLIENTS =
296
- /** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */new WeakMap;
297
-
298
-
299
- /**
300
- * Sends a request using the given executor.
301
- * @param {!Executor} executor
302
- * @param {!Request} request
303
- * @return {!Promise<Response>}
304
- */
305
- function doSend(executor, request) {
306
- const client = CLIENTS.get(executor);
307
- if (promise.isPromise(client)) {
308
- return client.then(client => {
309
- CLIENTS.set(executor, client);
310
- return client.send(request);
311
- });
312
- } else {
313
- return client.send(request);
314
- }
315
- }
316
-
317
344
 
318
345
  /**
319
346
  * @param {Map<string, CommandSpec>} customCommands
320
347
  * A map of custom command definitions.
321
348
  * @param {boolean} w3c Whether to use W3C command mappings.
322
349
  * @param {!cmd.Command} command The command to resolve.
323
- * @return {!Promise<!Request>} A promise that will resolve with the
350
+ * @return {!Request} A promise that will resolve with the
324
351
  * command to execute.
325
352
  */
326
353
  function buildRequest(customCommands, w3c, command) {
@@ -334,8 +361,8 @@ function buildRequest(customCommands, w3c, command) {
334
361
  spec = W3C_COMMAND_MAP.get(command.getName());
335
362
  if (typeof spec === 'function') {
336
363
  LOG.finest(() => `Transforming command for W3C: ${command.getName()}`);
337
- return spec(command)
338
- .then(newCommand => buildRequest(customCommands, w3c, newCommand));
364
+ let newCommand = spec(command);
365
+ return buildRequest(customCommands, w3c, newCommand);
339
366
  } else if (spec) {
340
367
  return toHttpRequest(spec);
341
368
  }
@@ -345,23 +372,26 @@ function buildRequest(customCommands, w3c, command) {
345
372
  if (spec) {
346
373
  return toHttpRequest(spec);
347
374
  }
348
- return Promise.reject(
349
- new error.UnknownCommandError(
350
- 'Unrecognized command: ' + command.getName()));
375
+ throw new error.UnknownCommandError(
376
+ 'Unrecognized command: ' + command.getName());
351
377
 
352
378
  /**
353
379
  * @param {CommandSpec} resource
354
- * @return {!Promise<!Request>}
380
+ * @return {!Request}
355
381
  */
356
382
  function toHttpRequest(resource) {
357
383
  LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`);
358
384
  let parameters = command.getParameters();
359
385
  let path = buildPath(resource.path, parameters);
360
- return Promise.resolve(new Request(resource.method, path, parameters));
386
+ return new Request(resource.method, path, parameters);
361
387
  }
362
388
  }
363
389
 
364
390
 
391
+ const CLIENTS =
392
+ /** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */new WeakMap;
393
+
394
+
365
395
  /**
366
396
  * A command executor that communicates with the server using JSON over HTTP.
367
397
  *
@@ -421,43 +451,41 @@ class Executor {
421
451
  }
422
452
 
423
453
  /** @override */
424
- execute(command) {
454
+ async execute(command) {
425
455
  let request = buildRequest(this.customCommands_, this.w3c, command);
426
- return request.then(request => {
427
- this.log_.finer(() => `>>> ${request.method} ${request.path}`);
428
- return doSend(this, request).then(response => {
429
- this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`);
430
-
431
- let parsed =
432
- parseHttpResponse(
433
- command, /** @type {!Response} */ (response), this.w3c);
434
-
435
- if (command.getName() === cmd.Name.NEW_SESSION
436
- || command.getName() === cmd.Name.DESCRIBE_SESSION) {
437
- if (!parsed || !parsed['sessionId']) {
438
- throw new error.WebDriverError(
439
- 'Unable to parse new session response: ' + response.body);
440
- }
441
-
442
- // The remote end is a W3C compliant server if there is no `status`
443
- // field in the response. This is not applicable for the DESCRIBE_SESSION
444
- // command, which is not defined in the W3C spec.
445
- if (command.getName() === cmd.Name.NEW_SESSION) {
446
- this.w3c = this.w3c || !('status' in parsed);
447
- }
448
-
449
- return new Session(parsed['sessionId'], parsed['value']);
450
- }
456
+ this.log_.finer(() => `>>> ${request.method} ${request.path}`);
451
457
 
452
- if (parsed
453
- && typeof parsed === 'object'
454
- && 'value' in parsed) {
455
- let value = parsed['value'];
456
- return typeof value === 'undefined' ? null : value;
457
- }
458
- return parsed;
459
- });
460
- });
458
+ let client = CLIENTS.get(this);
459
+ if (promise.isPromise(client)) {
460
+ client = await client;
461
+ CLIENTS.set(this, client);
462
+ }
463
+
464
+ let response = await client.send(request);
465
+ this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`);
466
+
467
+ let httpResponse = /** @type {!Response} */(response);
468
+ let {isW3C, value} = parseHttpResponse(command, httpResponse);
469
+
470
+ if (command.getName() === cmd.Name.NEW_SESSION) {
471
+ if (!value || !value.sessionId) {
472
+ throw new error.WebDriverError(
473
+ `Unable to parse new session response: ${response.body}`);
474
+ }
475
+
476
+ // The remote end is a W3C compliant server if there is no `status`
477
+ // field in the response.
478
+ if (command.getName() === cmd.Name.NEW_SESSION) {
479
+ this.w3c = this.w3c || isW3C;
480
+ }
481
+
482
+ // No implementations use the `capabilities` key yet...
483
+ let capabilities = value.capabilities || value.value;
484
+ return new Session(
485
+ /** @type {{sessionId: string}} */(value).sessionId, capabilities);
486
+ }
487
+
488
+ return typeof value === 'undefined' ? null : value;
461
489
  }
462
490
  }
463
491
 
@@ -481,40 +509,44 @@ function tryParse(str) {
481
509
  *
482
510
  * @param {!cmd.Command} command The command the response is for.
483
511
  * @param {!Response} httpResponse The HTTP response to parse.
484
- * @param {boolean} w3c Whether the response should be processed using the
485
- * W3C wire protocol.
486
- * @return {?} The parsed response.
512
+ * @return {{isW3C: boolean, value: ?}} An object describing the parsed
513
+ * response. This object will have two fields: `isW3C` indicates whether
514
+ * the response looks like it came from a remote end that conforms with the
515
+ * W3C WebDriver spec, and `value`, the actual response value.
487
516
  * @throws {WebDriverError} If the HTTP response is an error.
488
517
  */
489
- function parseHttpResponse(command, httpResponse, w3c) {
490
- let parsed = tryParse(httpResponse.body);
491
- if (parsed !== undefined) {
492
- if (httpResponse.status < 200) {
493
- // This should never happen, but throw the raw response so
494
- // users report it.
495
- throw new error.WebDriverError(
496
- `Unexpected HTTP response:\n${httpResponse}`);
497
- }
518
+ function parseHttpResponse(command, httpResponse) {
519
+ if (httpResponse.status < 200) {
520
+ // This should never happen, but throw the raw response so users report it.
521
+ throw new error.WebDriverError(
522
+ `Unexpected HTTP response:\n${httpResponse}`);
523
+ }
498
524
 
499
- if (w3c) {
500
- if (httpResponse.status > 399) {
501
- error.throwDecodedError(parsed);
525
+ let parsed = tryParse(httpResponse.body);
526
+ if (parsed && typeof parsed === 'object') {
527
+ let value = parsed.value;
528
+ let isW3C =
529
+ value !== null && typeof value === 'object'
530
+ && typeof parsed.status === 'undefined';
531
+
532
+ if (!isW3C) {
533
+ error.checkLegacyResponse(parsed);
534
+
535
+ // Adjust legacy new session responses to look like W3C to simplify
536
+ // later processing.
537
+ if (command.getName() === cmd.Name.NEW_SESSION) {
538
+ value = parsed;
502
539
  }
503
- return parsed;
504
- }
505
540
 
506
- // If this is a new session command, we need to check for a W3C compliant
507
- // error object. This is necessary since a successful new session command
508
- // is what puts the executor into W3C mode.
509
- if (httpResponse.status > 399
510
- && (command.getName() == cmd.Name.NEW_SESSION
511
- || command.getName() === cmd.Name.DESCRIBE_SESSION)
512
- && error.isErrorResponse(parsed)) {
513
- error.throwDecodedError(parsed);
541
+ } else if (httpResponse.status > 399) {
542
+ error.throwDecodedError(value);
514
543
  }
515
544
 
516
- error.checkLegacyResponse(parsed);
517
- return parsed;
545
+ return {isW3C, value};
546
+ }
547
+
548
+ if (parsed !== undefined) {
549
+ return {isW3C: false, value: parsed};
518
550
  }
519
551
 
520
552
  let value = httpResponse.body.replace(/\r\n/g, '\n');
@@ -527,7 +559,7 @@ function parseHttpResponse(command, httpResponse, w3c) {
527
559
  throw new error.WebDriverError(value);
528
560
  }
529
561
 
530
- return value || null;
562
+ return {isW3C: false, value: value || null};
531
563
  }
532
564
 
533
565