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.
- package/CHANGES.md +92 -0
- package/NOTICE +1 -1
- package/README.md +11 -14
- package/chrome.js +125 -36
- package/edge.js +3 -3
- package/example/async_await_test.js +68 -0
- package/example/chrome_headless.js +47 -0
- package/example/chrome_mobile_emulation.js +1 -1
- package/example/firefox_channels.js +73 -0
- package/example/google_search_test.js +1 -1
- package/firefox/binary.js +114 -57
- package/firefox/extension.js +118 -81
- package/firefox/index.js +67 -135
- package/firefox/profile.js +13 -49
- package/ie.js +2 -2
- package/index.js +6 -3
- package/io/index.js +59 -0
- package/io/zip.js +214 -0
- package/lib/README +2 -3
- package/lib/actions.js +4 -4
- package/lib/atoms/getAttribute.js +9 -11
- package/lib/atoms/is-displayed.js +102 -0
- package/lib/by.js +9 -1
- package/lib/capabilities.js +5 -5
- package/lib/command.js +2 -0
- package/lib/error.js +29 -4
- package/lib/events.js +1 -1
- package/lib/firefox/webdriver.json +2 -1
- package/lib/http.js +84 -69
- package/lib/input.js +1 -1
- package/lib/promise.js +205 -81
- package/lib/symbols.js +1 -1
- package/lib/test/data/click_tests/disabled_element.html +12 -0
- package/lib/test/data/firefox/webextension.xpi +0 -0
- package/lib/test/data/key_logger.html +34 -0
- package/lib/test/data/nestedElements.html +10 -1
- package/lib/test/data/single_text_input.html +12 -0
- package/lib/test/index.js +9 -35
- package/lib/until.js +1 -1
- package/lib/webdriver.js +130 -34
- package/net/portprober.js +1 -1
- package/opera.js +2 -2
- package/package.json +2 -2
- package/phantomjs.js +2 -2
- package/remote/index.js +17 -11
- package/safari.js +47 -8
- package/test/firefox/extension_test.js +41 -17
- package/test/firefox/firefox_test.js +70 -23
- package/test/firefox/profile_test.js +14 -59
- package/test/http/http_test.js +18 -0
- package/test/{io_test.js → io/io_test.js} +40 -1
- package/test/io/zip_test.js +128 -0
- package/test/lib/by_test.js +21 -0
- package/test/lib/error_test.js +20 -1
- package/test/lib/http_test.js +41 -2
- package/test/lib/promise_test.js +19 -0
- package/test/lib/until_test.js +28 -13
- package/test/lib/webdriver_test.js +107 -3
- package/test/page_loading_test.js +0 -6
- package/test/rect_test.js +60 -0
- package/test/remote_test.js +3 -3
- package/test/session_test.js +68 -23
- package/testing/index.js +16 -5
- package/lib/atoms/isDisplayed.js +0 -106
- package/lib/firefox/amd64/libnoblur64.so +0 -0
- package/lib/firefox/i386/libnoblur.so +0 -0
- 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
|
|
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
|
|
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
|
|
495
|
+
if (isErrorResponse(data)) {
|
|
476
496
|
let ctor = ERROR_CODE_TO_TYPE.get(data.error) || WebDriverError;
|
|
477
|
-
|
|
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
|
|
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 {
|
|
128
|
+
/** @enum {!Function} */
|
|
120
129
|
const Atom = {
|
|
121
|
-
GET_ATTRIBUTE:
|
|
122
|
-
|
|
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
|
|
178
|
-
|
|
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
|
|
440
|
-
|
|
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 (!
|
|
445
|
+
if (!value || !value.sessionId) {
|
|
445
446
|
throw new error.WebDriverError(
|
|
446
|
-
|
|
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
|
|
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 ||
|
|
454
|
+
this.w3c = this.w3c || isW3C;
|
|
454
455
|
}
|
|
455
456
|
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
490
|
-
*
|
|
491
|
-
*
|
|
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(
|
|
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
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|