selenium-webdriver 3.0.1 → 3.5.0

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 (67) hide show
  1. package/CHANGES.md +92 -0
  2. package/NOTICE +1 -1
  3. package/README.md +11 -14
  4. package/chrome.js +125 -36
  5. package/edge.js +3 -3
  6. package/example/async_await_test.js +68 -0
  7. package/example/chrome_headless.js +47 -0
  8. package/example/chrome_mobile_emulation.js +1 -1
  9. package/example/firefox_channels.js +73 -0
  10. package/example/google_search_test.js +1 -1
  11. package/firefox/binary.js +114 -57
  12. package/firefox/extension.js +118 -81
  13. package/firefox/index.js +67 -135
  14. package/firefox/profile.js +13 -49
  15. package/ie.js +2 -2
  16. package/index.js +6 -3
  17. package/io/index.js +59 -0
  18. package/io/zip.js +214 -0
  19. package/lib/README +2 -3
  20. package/lib/actions.js +4 -4
  21. package/lib/atoms/getAttribute.js +9 -11
  22. package/lib/atoms/is-displayed.js +102 -0
  23. package/lib/by.js +9 -1
  24. package/lib/capabilities.js +5 -5
  25. package/lib/command.js +2 -0
  26. package/lib/error.js +29 -4
  27. package/lib/events.js +1 -1
  28. package/lib/firefox/webdriver.json +2 -1
  29. package/lib/http.js +84 -69
  30. package/lib/input.js +1 -1
  31. package/lib/promise.js +205 -81
  32. package/lib/symbols.js +1 -1
  33. package/lib/test/data/click_tests/disabled_element.html +12 -0
  34. package/lib/test/data/firefox/webextension.xpi +0 -0
  35. package/lib/test/data/key_logger.html +34 -0
  36. package/lib/test/data/nestedElements.html +10 -1
  37. package/lib/test/data/single_text_input.html +12 -0
  38. package/lib/test/index.js +9 -35
  39. package/lib/until.js +1 -1
  40. package/lib/webdriver.js +130 -34
  41. package/net/portprober.js +1 -1
  42. package/opera.js +2 -2
  43. package/package.json +2 -2
  44. package/phantomjs.js +2 -2
  45. package/remote/index.js +17 -11
  46. package/safari.js +47 -8
  47. package/test/firefox/extension_test.js +41 -17
  48. package/test/firefox/firefox_test.js +70 -23
  49. package/test/firefox/profile_test.js +14 -59
  50. package/test/http/http_test.js +18 -0
  51. package/test/{io_test.js → io/io_test.js} +40 -1
  52. package/test/io/zip_test.js +128 -0
  53. package/test/lib/by_test.js +21 -0
  54. package/test/lib/error_test.js +20 -1
  55. package/test/lib/http_test.js +41 -2
  56. package/test/lib/promise_test.js +19 -0
  57. package/test/lib/until_test.js +28 -13
  58. package/test/lib/webdriver_test.js +107 -3
  59. package/test/page_loading_test.js +0 -6
  60. package/test/rect_test.js +60 -0
  61. package/test/remote_test.js +3 -3
  62. package/test/session_test.js +68 -23
  63. package/testing/index.js +16 -5
  64. package/lib/atoms/isDisplayed.js +0 -106
  65. package/lib/firefox/amd64/libnoblur64.so +0 -0
  66. package/lib/firefox/i386/libnoblur.so +0 -0
  67. package/lib/firefox/webdriver.xpi +0 -0
package/lib/error.js CHANGED
@@ -28,6 +28,14 @@ class WebDriverError extends Error {
28
28
 
29
29
  /** @override */
30
30
  this.name = this.constructor.name;
31
+
32
+ /**
33
+ * A stacktrace reported by the remote webdriver endpoint that initially
34
+ * reported this error. This property will be an empty string if the remote
35
+ * end did not provide a stacktrace.
36
+ * @type {string}
37
+ */
38
+ this.remoteStacktrace = '';
31
39
  }
32
40
  }
33
41
 
@@ -311,7 +319,7 @@ class UnknownMethodError extends WebDriverError {
311
319
 
312
320
 
313
321
  /**
314
- * Reports an unsupport operation.
322
+ * Reports an unsupported operation.
315
323
  */
316
324
  class UnsupportedOperationError extends WebDriverError {
317
325
  /** @param {string=} opt_error the error message, if any. */
@@ -462,19 +470,35 @@ function checkResponse(data) {
462
470
  return data;
463
471
  }
464
472
 
473
+ /**
474
+ * Tests if the given value is a valid error response object according to the
475
+ * W3C WebDriver spec.
476
+ *
477
+ * @param {?} data The value to test.
478
+ * @return {boolean} Whether the given value data object is a valid error
479
+ * response.
480
+ * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol
481
+ */
482
+ function isErrorResponse(data) {
483
+ return data && typeof data === 'object' && typeof data.error === 'string';
484
+ }
465
485
 
466
486
  /**
467
487
  * Throws an error coded from the W3C protocol. A generic error will be thrown
468
- * if the privded `data` is not a valid encoded error.
488
+ * if the provided `data` is not a valid encoded error.
469
489
  *
470
490
  * @param {{error: string, message: string}} data The error data to decode.
471
491
  * @throws {WebDriverError} the decoded error.
472
492
  * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol
473
493
  */
474
494
  function throwDecodedError(data) {
475
- if (data && typeof data === 'object' && typeof data.error === 'string') {
495
+ if (isErrorResponse(data)) {
476
496
  let ctor = ERROR_CODE_TO_TYPE.get(data.error) || WebDriverError;
477
- throw new ctor(data.message);
497
+ let err = new ctor(data.message);
498
+ if (typeof data.stacktrace === 'string') {
499
+ err.remoteStacktrace = data.stacktrace;
500
+ }
501
+ throw err;
478
502
  }
479
503
  throw new WebDriverError('Unknown error: ' + JSON.stringify(data));
480
504
  }
@@ -551,5 +575,6 @@ module.exports = {
551
575
  checkResponse: checkResponse,
552
576
  checkLegacyResponse: checkLegacyResponse,
553
577
  encodeError: encodeError,
578
+ isErrorResponse: isErrorResponse,
554
579
  throwDecodedError: throwDecodedError,
555
580
  };
package/lib/events.js CHANGED
@@ -22,7 +22,7 @@
22
22
  */
23
23
  class Listener {
24
24
  /**
25
- * @param {!Function} fn The acutal listener function.
25
+ * @param {!Function} fn The actual listener function.
26
26
  * @param {(Object|undefined)} scope The object in whose scope to invoke the
27
27
  * listener.
28
28
  * @param {boolean} oneshot Whether this listener should only be used once.
@@ -37,7 +37,6 @@
37
37
  "network.http.phishy-userpass-length": 255,
38
38
  "offline-apps.allow_by_default": true,
39
39
  "prompts.tab_modal.enabled": false,
40
- "security.csp.enable": false,
41
40
  "security.fileuri.origin_policy": 3,
42
41
  "security.fileuri.strict_origin_policy": false,
43
42
  "signon.rememberSignons": false,
@@ -61,6 +60,8 @@
61
60
  "dom.max_script_run_time": 30,
62
61
  "dom.report_all_js_exceptions": true,
63
62
  "javascript.options.showInConsole": true,
63
+ "network.captive-portal-service.enabled": false,
64
+ "security.csp.enable": false,
64
65
  "startup.homepage_welcome_url": "about:blank",
65
66
  "startup.homepage_welcome_url.additional": "about:blank",
66
67
  "webdriver_accept_untrusted_certs": true,
package/lib/http.js CHANGED
@@ -25,17 +25,26 @@
25
25
 
26
26
  'use strict';
27
27
 
28
- const fs = require('fs');
29
- const path = require('path');
30
-
31
28
  const cmd = require('./command');
32
- const devmode = require('./devmode');
33
29
  const error = require('./error');
34
30
  const logging = require('./logging');
35
31
  const promise = require('./promise');
36
32
  const Session = require('./session').Session;
37
33
  const WebElement = require('./webdriver').WebElement;
38
34
 
35
+ const {getAttribute, isDisplayed} = /** @suppress {undefinedVars|uselessCode} */(function() {
36
+ try {
37
+ return {
38
+ getAttribute: require('./atoms/getAttribute.js'),
39
+ isDisplayed: require('./atoms/is-displayed.js')
40
+ };
41
+ } catch (ex) {
42
+ throw Error(
43
+ 'Failed to import atoms modules. If running in devmode, you need to run'
44
+ + ' `./go node:atoms` from the project root: ' + ex);
45
+ }
46
+ })();
47
+
39
48
 
40
49
  /**
41
50
  * Converts a headers map to a HTTP header block string.
@@ -116,43 +125,15 @@ class Response {
116
125
 
117
126
  const DEV_ROOT = '../../../../buck-out/gen/javascript/';
118
127
 
119
- /** @enum {string} */
128
+ /** @enum {!Function} */
120
129
  const Atom = {
121
- GET_ATTRIBUTE: devmode
122
- ? path.join(__dirname, DEV_ROOT, 'webdriver/atoms/getAttribute.js')
123
- : path.join(__dirname, 'atoms/getAttribute.js'),
124
- IS_DISPLAYED: devmode
125
- ? path.join(__dirname, DEV_ROOT, 'atoms/fragments/is-displayed.js')
126
- : path.join(__dirname, 'atoms/isDisplayed.js'),
130
+ GET_ATTRIBUTE: getAttribute,
131
+ IS_DISPLAYED: isDisplayed
127
132
  };
128
133
 
129
134
 
130
- const ATOMS = /** !Map<string, !Promise<string>> */new Map();
131
135
  const LOG = logging.getLogger('webdriver.http');
132
136
 
133
- /**
134
- * @param {Atom} file The atom file to load.
135
- * @return {!Promise<string>} A promise that will resolve to the contents of the
136
- * file.
137
- */
138
- function loadAtom(file) {
139
- if (ATOMS.has(file)) {
140
- return ATOMS.get(file);
141
- }
142
- let contents = /** !Promise<string> */new Promise((resolve, reject) => {
143
- LOG.finest(() => `Loading atom ${file}`);
144
- fs.readFile(file, 'utf8', function(err, data) {
145
- if (err) {
146
- reject(err);
147
- } else {
148
- resolve(data);
149
- }
150
- });
151
- });
152
- ATOMS.set(file, contents);
153
- return contents;
154
- }
155
-
156
137
 
157
138
  function post(path) { return resource('POST', path); }
158
139
  function del(path) { return resource('DELETE', path); }
@@ -168,17 +149,26 @@ var CommandSpec;
168
149
  var CommandTransformer;
169
150
 
170
151
 
152
+ class InternalTypeError extends TypeError {}
153
+
154
+
171
155
  /**
172
156
  * @param {!cmd.Command} command The initial command.
173
157
  * @param {Atom} atom The name of the atom to execute.
174
158
  * @return {!Promise<!cmd.Command>} The transformed command to execute.
175
159
  */
176
160
  function toExecuteAtomCommand(command, atom, ...params) {
177
- return loadAtom(atom).then(atom => {
178
- return new cmd.Command(cmd.Name.EXECUTE_SCRIPT)
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
+ }
166
+
167
+ let newCmd = new cmd.Command(cmd.Name.EXECUTE_SCRIPT)
179
168
  .setParameter('sessionId', command.getParameter('sessionId'))
180
169
  .setParameter('script', `return (${atom}).apply(null, arguments)`)
181
170
  .setParameter('args', params.map(param => command.getParameter(param)));
171
+ resolve(newCmd);
182
172
  });
183
173
  }
184
174
 
@@ -235,6 +225,7 @@ const COMMAND_MAP = new Map([
235
225
  [cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute')],
236
226
  [cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute_async')],
237
227
  [cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
228
+ [cmd.Name.GET_TIMEOUT, get('/session/:sessionId/timeouts')],
238
229
  [cmd.Name.SET_TIMEOUT, post('/session/:sessionId/timeouts')],
239
230
  [cmd.Name.MOVE_TO, post('/session/:sessionId/moveto')],
240
231
  [cmd.Name.CLICK, post('/session/:sessionId/click')],
@@ -266,17 +257,27 @@ const COMMAND_MAP = new Map([
266
257
  /** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
267
258
  const W3C_COMMAND_MAP = new Map([
268
259
  [cmd.Name.GET_ACTIVE_ELEMENT, get('/session/:sessionId/element/active')],
260
+ [cmd.Name.GET_ALERT_TEXT, get('/session/:sessionId/alert/text')],
261
+ [cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert/text')],
262
+ [cmd.Name.ACCEPT_ALERT, post('/session/:sessionId/alert/accept')],
263
+ [cmd.Name.DISMISS_ALERT, post('/session/:sessionId/alert/dismiss')],
269
264
  [cmd.Name.GET_ELEMENT_ATTRIBUTE, (cmd) => {
270
265
  return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name');
271
266
  }],
267
+ [cmd.Name.GET_ELEMENT_LOCATION, get('/session/:sessionId/element/:id/rect')],
268
+ [cmd.Name.GET_ELEMENT_SIZE, get('/session/:sessionId/element/:id/rect')],
272
269
  [cmd.Name.IS_ELEMENT_DISPLAYED, (cmd) => {
273
270
  return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id');
274
271
  }],
272
+ [cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute/sync')],
273
+ [cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute/async')],
275
274
  [cmd.Name.MAXIMIZE_WINDOW, post('/session/:sessionId/window/maximize')],
276
275
  [cmd.Name.GET_WINDOW_POSITION, get('/session/:sessionId/window/position')],
277
276
  [cmd.Name.SET_WINDOW_POSITION, post('/session/:sessionId/window/position')],
278
277
  [cmd.Name.GET_WINDOW_SIZE, get('/session/:sessionId/window/size')],
279
278
  [cmd.Name.SET_WINDOW_SIZE, post('/session/:sessionId/window/size')],
279
+ [cmd.Name.GET_CURRENT_WINDOW_HANDLE, get('/session/:sessionId/window')],
280
+ [cmd.Name.GET_WINDOW_HANDLES, get('/session/:sessionId/window/handles')],
280
281
  ]);
281
282
 
282
283
 
@@ -436,33 +437,29 @@ class Executor {
436
437
  return doSend(this, request).then(response => {
437
438
  this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`);
438
439
 
439
- let parsed =
440
- parseHttpResponse(/** @type {!Response} */ (response), this.w3c);
440
+ let httpResponse = /** @type {!Response} */(response);
441
+ let {isW3C, value} = parseHttpResponse(command, httpResponse);
441
442
 
442
443
  if (command.getName() === cmd.Name.NEW_SESSION
443
444
  || command.getName() === cmd.Name.DESCRIBE_SESSION) {
444
- if (!parsed || !parsed['sessionId']) {
445
+ if (!value || !value.sessionId) {
445
446
  throw new error.WebDriverError(
446
- 'Unable to parse new session response: ' + response.body);
447
+ `Unable to parse new session response: ${response.body}`);
447
448
  }
448
449
 
449
450
  // The remote end is a W3C compliant server if there is no `status`
450
- // field in the response. This is not appliable for the DESCRIBE_SESSION
451
+ // field in the response. This is not applicable for the DESCRIBE_SESSION
451
452
  // command, which is not defined in the W3C spec.
452
453
  if (command.getName() === cmd.Name.NEW_SESSION) {
453
- this.w3c = this.w3c || !('status' in parsed);
454
+ this.w3c = this.w3c || isW3C;
454
455
  }
455
456
 
456
- return new Session(parsed['sessionId'], parsed['value']);
457
+ // No implementations use the `capabilities` key yet...
458
+ let capabilities = value.capabilities || value.value;
459
+ return new Session(value.sessionId, capabilities);
457
460
  }
458
461
 
459
- if (parsed
460
- && typeof parsed === 'object'
461
- && 'value' in parsed) {
462
- let value = parsed['value'];
463
- return typeof value === 'undefined' ? null : value;
464
- }
465
- return parsed;
462
+ return typeof value === 'undefined' ? null : value;
466
463
  });
467
464
  });
468
465
  }
@@ -485,30 +482,48 @@ function tryParse(str) {
485
482
  /**
486
483
  * Callback used to parse {@link Response} objects from a
487
484
  * {@link HttpClient}.
485
+ *
486
+ * @param {!cmd.Command} command The command the response is for.
488
487
  * @param {!Response} httpResponse The HTTP response to parse.
489
- * @param {boolean} w3c Whether the response should be processed using the
490
- * W3C wire protocol.
491
- * @return {?} The parsed response.
488
+ * @return {{isW3C: boolean, value: ?}} An object describing the parsed
489
+ * response. This object will have two fields: `isW3C` indicates whether
490
+ * the response looks like it came from a remote end that conforms with the
491
+ * W3C WebDriver spec, and `value`, the actual response value.
492
492
  * @throws {WebDriverError} If the HTTP response is an error.
493
493
  */
494
- function parseHttpResponse(httpResponse, w3c) {
494
+ function parseHttpResponse(command, httpResponse) {
495
+ if (httpResponse.status < 200) {
496
+ // This should never happen, but throw the raw response so users report it.
497
+ throw new error.WebDriverError(
498
+ `Unexpected HTTP response:\n${httpResponse}`);
499
+ }
500
+
495
501
  let parsed = tryParse(httpResponse.body);
496
- if (parsed !== undefined) {
497
- if (w3c) {
498
- if (httpResponse.status > 399) {
499
- error.throwDecodedError(parsed);
500
- }
502
+ if (parsed && typeof parsed === 'object') {
503
+ let value = parsed.value;
504
+ let isW3C =
505
+ value !== null && typeof value === 'object'
506
+ && typeof parsed.status === 'undefined';
501
507
 
502
- if (httpResponse.status < 200) {
503
- // This should never happen, but throw the raw response so
504
- // users report it.
505
- throw new error.WebDriverError(
506
- `Unexpected HTTP response:\n${httpResponse}`);
507
- }
508
- } else {
508
+ if (!isW3C) {
509
509
  error.checkLegacyResponse(parsed);
510
+
511
+ // Adjust legacy new session responses to look like W3C to simplify
512
+ // later processing.
513
+ if (command.getName() === cmd.Name.NEW_SESSION
514
+ || command.getName() == cmd.Name.DESCRIBE_SESSION) {
515
+ value = parsed;
516
+ }
517
+
518
+ } else if (httpResponse.status > 399) {
519
+ error.throwDecodedError(value);
510
520
  }
511
- return parsed;
521
+
522
+ return {isW3C, value};
523
+ }
524
+
525
+ if (parsed !== undefined) {
526
+ return {isW3C: false, value: parsed};
512
527
  }
513
528
 
514
529
  let value = httpResponse.body.replace(/\r\n/g, '\n');
@@ -521,7 +536,7 @@ function parseHttpResponse(httpResponse, w3c) {
521
536
  throw new error.WebDriverError(value);
522
537
  }
523
538
 
524
- return value || null;
539
+ return {isW3C: false, value: value || null};
525
540
  }
526
541
 
527
542
 
package/lib/input.js CHANGED
@@ -144,7 +144,7 @@ class FileDetector {
144
144
  /**
145
145
  * Handles the file specified by the given path, preparing it for use with
146
146
  * the current browser. If the path does not refer to a valid file, it will
147
- * be returned unchanged, otherwisee a path suitable for use with the current
147
+ * be returned unchanged, otherwise a path suitable for use with the current
148
148
  * browser will be returned.
149
149
  *
150
150
  * This default implementation is a no-op. Subtypes may override this function