selenium-webdriver 3.1.0 → 3.6.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 +112 -0
- package/NOTICE +1 -1
- package/README.md +5 -11
- package/chrome.js +122 -33
- package/edge.js +2 -2
- package/example/async_await_test.js +69 -0
- package/example/chrome_android.js +2 -3
- package/example/chrome_mobile_emulation.js +3 -4
- package/example/firefox_channels.js +3 -3
- package/example/google_search.js +3 -3
- package/example/google_search_generator.js +2 -5
- package/example/google_search_test.js +5 -6
- package/example/headless.js +55 -0
- package/example/logging.js +3 -3
- package/example/parallel_flows.js +4 -7
- package/firefox/binary.js +3 -0
- package/firefox/extension.js +118 -81
- package/firefox/index.js +117 -263
- package/firefox/profile.js +18 -137
- package/ie.js +2 -2
- package/index.js +4 -1
- package/io/index.js +59 -0
- package/io/zip.js +214 -0
- package/lib/atoms/get-attribute.js +9 -0
- package/lib/atoms/is-displayed.js +73 -78
- package/lib/by.js +8 -0
- package/lib/capabilities.js +61 -22
- package/lib/command.js +2 -0
- package/lib/error.js +32 -2
- package/lib/http.js +53 -44
- package/lib/promise.js +72 -25
- package/lib/test/data/click_tests/disabled_element.html +12 -0
- package/lib/test/data/content-editable.html +10 -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/scrolling_tests/page_with_partially_hidden_element.html +14 -0
- package/lib/test/data/single_text_input.html +12 -0
- package/lib/test/index.js +4 -36
- package/lib/webdriver.js +147 -55
- package/net/portprober.js +1 -1
- package/opera.js +1 -1
- package/package.json +2 -2
- package/phantomjs.js +2 -2
- package/remote/index.js +21 -14
- package/safari.js +2 -2
- package/test/firefox/extension_test.js +41 -17
- package/test/firefox/firefox_test.js +37 -19
- 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 +21 -1
- package/test/lib/http_test.js +11 -3
- package/test/lib/webdriver_test.js +107 -3
- package/test/page_loading_test.js +0 -6
- package/test/remote_test.js +3 -3
- package/test/session_test.js +68 -23
- package/testing/index.js +2 -3
- package/lib/atoms/getAttribute.js +0 -12
- package/lib/firefox/amd64/libnoblur64.so +0 -0
- package/lib/firefox/i386/libnoblur.so +0 -0
- package/lib/firefox/webdriver.json +0 -69
- package/lib/firefox/webdriver.xpi +0 -0
package/lib/capabilities.js
CHANGED
|
@@ -132,7 +132,7 @@ const Capability = {
|
|
|
132
132
|
* Defines how the driver should handle unexpected alerts. The value should
|
|
133
133
|
* be one of "accept", "dismiss", or "ignore".
|
|
134
134
|
*/
|
|
135
|
-
UNEXPECTED_ALERT_BEHAVIOR: '
|
|
135
|
+
UNEXPECTED_ALERT_BEHAVIOR: 'unexpectedAlertBehaviour',
|
|
136
136
|
|
|
137
137
|
/** Defines the browser version. */
|
|
138
138
|
VERSION: 'version'
|
|
@@ -204,16 +204,19 @@ function toMap(hash) {
|
|
|
204
204
|
/**
|
|
205
205
|
* Describes a set of capabilities for a WebDriver session.
|
|
206
206
|
*/
|
|
207
|
-
class Capabilities
|
|
207
|
+
class Capabilities {
|
|
208
208
|
/**
|
|
209
|
-
* @param {(Capabilities|Map<string, ?>|Object)=}
|
|
209
|
+
* @param {(Capabilities|Map<string, ?>|Object)=} other Another set of
|
|
210
210
|
* capabilities to initialize this instance from.
|
|
211
211
|
*/
|
|
212
|
-
constructor(
|
|
213
|
-
if (
|
|
214
|
-
|
|
212
|
+
constructor(other = undefined) {
|
|
213
|
+
if (other instanceof Capabilities) {
|
|
214
|
+
other = other.map_;
|
|
215
|
+
} else if (other && !(other instanceof Map)) {
|
|
216
|
+
other = toMap(other);
|
|
215
217
|
}
|
|
216
|
-
|
|
218
|
+
/** @private @const {!Map<string, ?>} */
|
|
219
|
+
this.map_ = new Map(other);
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
/**
|
|
@@ -221,8 +224,7 @@ class Capabilities extends Map {
|
|
|
221
224
|
*/
|
|
222
225
|
static android() {
|
|
223
226
|
return new Capabilities()
|
|
224
|
-
.set(Capability.BROWSER_NAME, Browser.ANDROID)
|
|
225
|
-
.set(Capability.PLATFORM, 'ANDROID');
|
|
227
|
+
.set(Capability.BROWSER_NAME, Browser.ANDROID);
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
/**
|
|
@@ -237,8 +239,7 @@ class Capabilities extends Map {
|
|
|
237
239
|
*/
|
|
238
240
|
static edge() {
|
|
239
241
|
return new Capabilities()
|
|
240
|
-
.set(Capability.BROWSER_NAME, Browser.EDGE)
|
|
241
|
-
.set(Capability.PLATFORM, 'WINDOWS');
|
|
242
|
+
.set(Capability.BROWSER_NAME, Browser.EDGE);
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
/**
|
|
@@ -253,8 +254,7 @@ class Capabilities extends Map {
|
|
|
253
254
|
*/
|
|
254
255
|
static ie() {
|
|
255
256
|
return new Capabilities().
|
|
256
|
-
set(Capability.BROWSER_NAME, Browser.INTERNET_EXPLORER)
|
|
257
|
-
set(Capability.PLATFORM, 'WINDOWS');
|
|
257
|
+
set(Capability.BROWSER_NAME, Browser.INTERNET_EXPLORER);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
/**
|
|
@@ -262,8 +262,7 @@ class Capabilities extends Map {
|
|
|
262
262
|
*/
|
|
263
263
|
static ipad() {
|
|
264
264
|
return new Capabilities().
|
|
265
|
-
set(Capability.BROWSER_NAME, Browser.IPAD)
|
|
266
|
-
set(Capability.PLATFORM, 'MAC');
|
|
265
|
+
set(Capability.BROWSER_NAME, Browser.IPAD);
|
|
267
266
|
}
|
|
268
267
|
|
|
269
268
|
/**
|
|
@@ -271,8 +270,7 @@ class Capabilities extends Map {
|
|
|
271
270
|
*/
|
|
272
271
|
static iphone() {
|
|
273
272
|
return new Capabilities().
|
|
274
|
-
set(Capability.BROWSER_NAME, Browser.IPHONE)
|
|
275
|
-
set(Capability.PLATFORM, 'MAC');
|
|
273
|
+
set(Capability.BROWSER_NAME, Browser.IPHONE);
|
|
276
274
|
}
|
|
277
275
|
|
|
278
276
|
/**
|
|
@@ -296,8 +294,7 @@ class Capabilities extends Map {
|
|
|
296
294
|
*/
|
|
297
295
|
static safari() {
|
|
298
296
|
return new Capabilities().
|
|
299
|
-
set(Capability.BROWSER_NAME, Browser.SAFARI)
|
|
300
|
-
set(Capability.PLATFORM, 'MAC');
|
|
297
|
+
set(Capability.BROWSER_NAME, Browser.SAFARI);
|
|
301
298
|
}
|
|
302
299
|
|
|
303
300
|
/**
|
|
@@ -328,6 +325,35 @@ class Capabilities extends Map {
|
|
|
328
325
|
return serialize(this);
|
|
329
326
|
}
|
|
330
327
|
|
|
328
|
+
/**
|
|
329
|
+
* @param {string} key the parameter key to get.
|
|
330
|
+
* @return {T} the stored parameter value.
|
|
331
|
+
* @template T
|
|
332
|
+
*/
|
|
333
|
+
get(key) {
|
|
334
|
+
return this.map_.get(key);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @param {string} key the key to test.
|
|
339
|
+
* @return {boolean} whether this capability set has the specified key.
|
|
340
|
+
*/
|
|
341
|
+
has(key) {
|
|
342
|
+
return this.map_.has(key);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @return {!Iterator<string>} an iterator of the keys set.
|
|
347
|
+
*/
|
|
348
|
+
keys() {
|
|
349
|
+
return this.map_.keys();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** @return {number} The number of capabilities set. */
|
|
353
|
+
get size() {
|
|
354
|
+
return this.map_.size;
|
|
355
|
+
}
|
|
356
|
+
|
|
331
357
|
/**
|
|
332
358
|
* Merges another set of capabilities into this instance.
|
|
333
359
|
* @param {!(Capabilities|Map<String, ?>|Object<string, ?>)} other The other
|
|
@@ -339,7 +365,12 @@ class Capabilities extends Map {
|
|
|
339
365
|
throw new TypeError('no capabilities provided for merge');
|
|
340
366
|
}
|
|
341
367
|
|
|
342
|
-
|
|
368
|
+
let map;
|
|
369
|
+
if (other instanceof Capabilities) {
|
|
370
|
+
map = other.map_;
|
|
371
|
+
} else if (other instanceof Map) {
|
|
372
|
+
map = other;
|
|
373
|
+
} else {
|
|
343
374
|
other = toMap(other);
|
|
344
375
|
}
|
|
345
376
|
|
|
@@ -350,18 +381,26 @@ class Capabilities extends Map {
|
|
|
350
381
|
return this;
|
|
351
382
|
}
|
|
352
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Deletes an entry from this set of capabilities.
|
|
386
|
+
*
|
|
387
|
+
* @param {string} key the capability key to delete.
|
|
388
|
+
*/
|
|
389
|
+
delete(key) {
|
|
390
|
+
this.map_.delete(key);
|
|
391
|
+
}
|
|
392
|
+
|
|
353
393
|
/**
|
|
354
394
|
* @param {string} key The capability key.
|
|
355
395
|
* @param {*} value The capability value.
|
|
356
396
|
* @return {!Capabilities} A self reference.
|
|
357
397
|
* @throws {TypeError} If the `key` is not a string.
|
|
358
|
-
* @override
|
|
359
398
|
*/
|
|
360
399
|
set(key, value) {
|
|
361
400
|
if (typeof key !== 'string') {
|
|
362
401
|
throw new TypeError('Capability keys must be strings: ' + typeof key);
|
|
363
402
|
}
|
|
364
|
-
|
|
403
|
+
this.map_.set(key, value);
|
|
365
404
|
return this;
|
|
366
405
|
}
|
|
367
406
|
|
package/lib/command.js
CHANGED
|
@@ -150,6 +150,8 @@ const Name = {
|
|
|
150
150
|
TAKE_ELEMENT_SCREENSHOT: 'takeElementScreenshot',
|
|
151
151
|
IMPLICITLY_WAIT: 'implicitlyWait',
|
|
152
152
|
SET_SCRIPT_TIMEOUT: 'setScriptTimeout',
|
|
153
|
+
|
|
154
|
+
GET_TIMEOUT: 'getTimeout',
|
|
153
155
|
SET_TIMEOUT: 'setTimeout',
|
|
154
156
|
|
|
155
157
|
ACCEPT_ALERT: 'acceptAlert',
|
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
|
|
|
@@ -43,11 +51,27 @@ class ElementNotSelectableError extends WebDriverError {
|
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Indicates a command could not be completed because the target element is
|
|
56
|
+
* not pointer or keyboard interactable. This will often occur if an element
|
|
57
|
+
* is present in the DOM, but not rendered (i.e. its CSS style has
|
|
58
|
+
* "display: none").
|
|
59
|
+
*/
|
|
60
|
+
class ElementNotInteractableError extends WebDriverError {
|
|
61
|
+
/** @param {string=} opt_error the error message, if any. */
|
|
62
|
+
constructor(opt_error) {
|
|
63
|
+
super(opt_error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
46
68
|
/**
|
|
47
69
|
* An element command could not be completed because the element is not visible
|
|
48
70
|
* on the page.
|
|
71
|
+
*
|
|
72
|
+
* @deprecated Use {@link ElementNotInteractable} instead.
|
|
49
73
|
*/
|
|
50
|
-
class ElementNotVisibleError extends
|
|
74
|
+
class ElementNotVisibleError extends ElementNotInteractableError {
|
|
51
75
|
/** @param {string=} opt_error the error message, if any. */
|
|
52
76
|
constructor(opt_error) {
|
|
53
77
|
super(opt_error);
|
|
@@ -392,6 +416,7 @@ const LEGACY_ERROR_CODE_TO_TYPE = new Map([
|
|
|
392
416
|
|
|
393
417
|
const ERROR_CODE_TO_TYPE = new Map([
|
|
394
418
|
['unknown error', WebDriverError],
|
|
419
|
+
['element not interactable', ElementNotInteractableError],
|
|
395
420
|
['element not selectable', ElementNotSelectableError],
|
|
396
421
|
['element not visible', ElementNotVisibleError],
|
|
397
422
|
['invalid argument', InvalidArgumentError],
|
|
@@ -486,7 +511,11 @@ function isErrorResponse(data) {
|
|
|
486
511
|
function throwDecodedError(data) {
|
|
487
512
|
if (isErrorResponse(data)) {
|
|
488
513
|
let ctor = ERROR_CODE_TO_TYPE.get(data.error) || WebDriverError;
|
|
489
|
-
|
|
514
|
+
let err = new ctor(data.message);
|
|
515
|
+
if (typeof data.stacktrace === 'string') {
|
|
516
|
+
err.remoteStacktrace = data.stacktrace;
|
|
517
|
+
}
|
|
518
|
+
throw err;
|
|
490
519
|
}
|
|
491
520
|
throw new WebDriverError('Unknown error: ' + JSON.stringify(data));
|
|
492
521
|
}
|
|
@@ -535,6 +564,7 @@ module.exports = {
|
|
|
535
564
|
ErrorCode: ErrorCode,
|
|
536
565
|
|
|
537
566
|
WebDriverError: WebDriverError,
|
|
567
|
+
ElementNotInteractableError: ElementNotInteractableError,
|
|
538
568
|
ElementNotSelectableError: ElementNotSelectableError,
|
|
539
569
|
ElementNotVisibleError: ElementNotVisibleError,
|
|
540
570
|
InvalidArgumentError: InvalidArgumentError,
|
package/lib/http.js
CHANGED
|
@@ -32,10 +32,10 @@ const promise = require('./promise');
|
|
|
32
32
|
const Session = require('./session').Session;
|
|
33
33
|
const WebElement = require('./webdriver').WebElement;
|
|
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/
|
|
38
|
+
getAttribute: require('./atoms/get-attribute.js'),
|
|
39
39
|
isDisplayed: require('./atoms/is-displayed.js')
|
|
40
40
|
};
|
|
41
41
|
} catch (ex) {
|
|
@@ -225,6 +225,7 @@ const COMMAND_MAP = new Map([
|
|
|
225
225
|
[cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute')],
|
|
226
226
|
[cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute_async')],
|
|
227
227
|
[cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
|
|
228
|
+
[cmd.Name.GET_TIMEOUT, get('/session/:sessionId/timeouts')],
|
|
228
229
|
[cmd.Name.SET_TIMEOUT, post('/session/:sessionId/timeouts')],
|
|
229
230
|
[cmd.Name.MOVE_TO, post('/session/:sessionId/moveto')],
|
|
230
231
|
[cmd.Name.CLICK, post('/session/:sessionId/click')],
|
|
@@ -256,6 +257,10 @@ const COMMAND_MAP = new Map([
|
|
|
256
257
|
/** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
|
|
257
258
|
const W3C_COMMAND_MAP = new Map([
|
|
258
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')],
|
|
259
264
|
[cmd.Name.GET_ELEMENT_ATTRIBUTE, (cmd) => {
|
|
260
265
|
return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name');
|
|
261
266
|
}],
|
|
@@ -264,11 +269,15 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
264
269
|
[cmd.Name.IS_ELEMENT_DISPLAYED, (cmd) => {
|
|
265
270
|
return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id');
|
|
266
271
|
}],
|
|
272
|
+
[cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute/sync')],
|
|
273
|
+
[cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute/async')],
|
|
267
274
|
[cmd.Name.MAXIMIZE_WINDOW, post('/session/:sessionId/window/maximize')],
|
|
268
275
|
[cmd.Name.GET_WINDOW_POSITION, get('/session/:sessionId/window/position')],
|
|
269
276
|
[cmd.Name.SET_WINDOW_POSITION, post('/session/:sessionId/window/position')],
|
|
270
277
|
[cmd.Name.GET_WINDOW_SIZE, get('/session/:sessionId/window/size')],
|
|
271
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')],
|
|
272
281
|
]);
|
|
273
282
|
|
|
274
283
|
|
|
@@ -428,34 +437,29 @@ class Executor {
|
|
|
428
437
|
return doSend(this, request).then(response => {
|
|
429
438
|
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`);
|
|
430
439
|
|
|
431
|
-
let
|
|
432
|
-
|
|
433
|
-
command, /** @type {!Response} */ (response), this.w3c);
|
|
440
|
+
let httpResponse = /** @type {!Response} */(response);
|
|
441
|
+
let {isW3C, value} = parseHttpResponse(command, httpResponse);
|
|
434
442
|
|
|
435
443
|
if (command.getName() === cmd.Name.NEW_SESSION
|
|
436
444
|
|| command.getName() === cmd.Name.DESCRIBE_SESSION) {
|
|
437
|
-
if (!
|
|
445
|
+
if (!value || !value.sessionId) {
|
|
438
446
|
throw new error.WebDriverError(
|
|
439
|
-
|
|
447
|
+
`Unable to parse new session response: ${response.body}`);
|
|
440
448
|
}
|
|
441
449
|
|
|
442
450
|
// The remote end is a W3C compliant server if there is no `status`
|
|
443
451
|
// field in the response. This is not applicable for the DESCRIBE_SESSION
|
|
444
452
|
// command, which is not defined in the W3C spec.
|
|
445
453
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
446
|
-
this.w3c = this.w3c ||
|
|
454
|
+
this.w3c = this.w3c || isW3C;
|
|
447
455
|
}
|
|
448
456
|
|
|
449
|
-
|
|
457
|
+
// No implementations use the `capabilities` key yet...
|
|
458
|
+
let capabilities = value.capabilities || value.value;
|
|
459
|
+
return new Session(value.sessionId, capabilities);
|
|
450
460
|
}
|
|
451
461
|
|
|
452
|
-
|
|
453
|
-
&& typeof parsed === 'object'
|
|
454
|
-
&& 'value' in parsed) {
|
|
455
|
-
let value = parsed['value'];
|
|
456
|
-
return typeof value === 'undefined' ? null : value;
|
|
457
|
-
}
|
|
458
|
-
return parsed;
|
|
462
|
+
return typeof value === 'undefined' ? null : value;
|
|
459
463
|
});
|
|
460
464
|
});
|
|
461
465
|
}
|
|
@@ -481,40 +485,45 @@ function tryParse(str) {
|
|
|
481
485
|
*
|
|
482
486
|
* @param {!cmd.Command} command The command the response is for.
|
|
483
487
|
* @param {!Response} httpResponse The HTTP response to parse.
|
|
484
|
-
* @
|
|
485
|
-
*
|
|
486
|
-
*
|
|
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.
|
|
487
492
|
* @throws {WebDriverError} If the HTTP response is an error.
|
|
488
493
|
*/
|
|
489
|
-
function parseHttpResponse(command, httpResponse
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
throw new error.WebDriverError(
|
|
496
|
-
`Unexpected HTTP response:\n${httpResponse}`);
|
|
497
|
-
}
|
|
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
|
+
}
|
|
498
500
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
501
|
+
let parsed = tryParse(httpResponse.body);
|
|
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';
|
|
507
|
+
|
|
508
|
+
if (!isW3C) {
|
|
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;
|
|
502
516
|
}
|
|
503
|
-
return parsed;
|
|
504
|
-
}
|
|
505
517
|
|
|
506
|
-
|
|
507
|
-
|
|
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);
|
|
518
|
+
} else if (httpResponse.status > 399) {
|
|
519
|
+
error.throwDecodedError(value);
|
|
514
520
|
}
|
|
515
521
|
|
|
516
|
-
|
|
517
|
-
|
|
522
|
+
return {isW3C, value};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (parsed !== undefined) {
|
|
526
|
+
return {isW3C: false, value: parsed};
|
|
518
527
|
}
|
|
519
528
|
|
|
520
529
|
let value = httpResponse.body.replace(/\r\n/g, '\n');
|
|
@@ -527,7 +536,7 @@ function parseHttpResponse(command, httpResponse, w3c) {
|
|
|
527
536
|
throw new error.WebDriverError(value);
|
|
528
537
|
}
|
|
529
538
|
|
|
530
|
-
return value || null;
|
|
539
|
+
return {isW3C: false, value: value || null};
|
|
531
540
|
}
|
|
532
541
|
|
|
533
542
|
|
package/lib/promise.js
CHANGED
|
@@ -33,13 +33,12 @@
|
|
|
33
33
|
* > style tests:
|
|
34
34
|
* >
|
|
35
35
|
* > ```js
|
|
36
|
-
* > const {Builder, By, promise, until} = require('selenium-webdriver');
|
|
36
|
+
* > const {Builder, By, Key, promise, until} = require('selenium-webdriver');
|
|
37
37
|
* >
|
|
38
38
|
* > let result = promise.consume(function* doGoogleSearch() {
|
|
39
39
|
* > let driver = new Builder().forBrowser('firefox').build();
|
|
40
40
|
* > yield driver.get('http://www.google.com/ncr');
|
|
41
|
-
* > yield driver.findElement(By.name('q')).sendKeys('webdriver');
|
|
42
|
-
* > yield driver.findElement(By.name('btnG')).click();
|
|
41
|
+
* > yield driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN);
|
|
43
42
|
* > yield driver.wait(until.titleIs('webdriver - Google Search'), 1000);
|
|
44
43
|
* > yield driver.quit();
|
|
45
44
|
* > });
|
|
@@ -63,8 +62,7 @@
|
|
|
63
62
|
* control flow, allowing users to write
|
|
64
63
|
*
|
|
65
64
|
* driver.get('http://www.google.com/ncr');
|
|
66
|
-
* driver.findElement({name: 'q'}).sendKeys('webdriver');
|
|
67
|
-
* driver.findElement({name: 'btnGn'}).click();
|
|
65
|
+
* driver.findElement({name: 'q'}).sendKeys('webdriver', Key.RETURN);
|
|
68
66
|
*
|
|
69
67
|
* instead of
|
|
70
68
|
*
|
|
@@ -73,13 +71,7 @@
|
|
|
73
71
|
* return driver.findElement({name: 'q'});
|
|
74
72
|
* })
|
|
75
73
|
* .then(function(q) {
|
|
76
|
-
* return q.sendKeys('webdriver');
|
|
77
|
-
* })
|
|
78
|
-
* .then(function() {
|
|
79
|
-
* return driver.findElement({name: 'btnG'});
|
|
80
|
-
* })
|
|
81
|
-
* .then(function(btnG) {
|
|
82
|
-
* return btnG.click();
|
|
74
|
+
* return q.sendKeys('webdriver', Key.RETURN);
|
|
83
75
|
* });
|
|
84
76
|
*
|
|
85
77
|
* ## Tasks and Task Queues
|
|
@@ -1003,6 +995,9 @@ const PromiseState = {
|
|
|
1003
995
|
*/
|
|
1004
996
|
const ON_CANCEL_HANDLER = new WeakMap;
|
|
1005
997
|
|
|
998
|
+
const SKIP_LOG = Symbol('skip-log');
|
|
999
|
+
const FLOW_LOG = logging.getLogger('promise.ControlFlow');
|
|
1000
|
+
|
|
1006
1001
|
|
|
1007
1002
|
/**
|
|
1008
1003
|
* Represents the eventual value of a completed operation. Each promise may be
|
|
@@ -1025,14 +1020,29 @@ class ManagedPromise {
|
|
|
1025
1020
|
* functions, one for fulfilling the promise and another for rejecting it.
|
|
1026
1021
|
* @param {ControlFlow=} opt_flow The control flow
|
|
1027
1022
|
* this instance was created under. Defaults to the currently active flow.
|
|
1023
|
+
* @param {?=} opt_skipLog An internal parameter used to skip logging the
|
|
1024
|
+
* creation of this promise. This parameter has no effect unless it is
|
|
1025
|
+
* strictly equal to an internal symbol. In other words, this parameter
|
|
1026
|
+
* is always ignored for external code.
|
|
1028
1027
|
*/
|
|
1029
|
-
constructor(resolver, opt_flow) {
|
|
1028
|
+
constructor(resolver, opt_flow, opt_skipLog) {
|
|
1030
1029
|
if (!usePromiseManager()) {
|
|
1031
1030
|
throw TypeError(
|
|
1032
1031
|
'Unable to create a managed promise instance: the promise manager has'
|
|
1033
1032
|
+ ' been disabled by the SELENIUM_PROMISE_MANAGER environment'
|
|
1034
1033
|
+ ' variable: ' + process.env['SELENIUM_PROMISE_MANAGER']);
|
|
1034
|
+
} else if (opt_skipLog !== SKIP_LOG) {
|
|
1035
|
+
FLOW_LOG.warning(() => {
|
|
1036
|
+
let e =
|
|
1037
|
+
captureStackTrace(
|
|
1038
|
+
'ManagedPromiseError',
|
|
1039
|
+
'Creating a new managed Promise. This call will fail when the'
|
|
1040
|
+
+ ' promise manager is disabled',
|
|
1041
|
+
ManagedPromise)
|
|
1042
|
+
return e.stack;
|
|
1043
|
+
});
|
|
1035
1044
|
}
|
|
1045
|
+
|
|
1036
1046
|
getUid(this);
|
|
1037
1047
|
|
|
1038
1048
|
/** @private {!ControlFlow} */
|
|
@@ -1432,15 +1442,19 @@ class Deferred {
|
|
|
1432
1442
|
/**
|
|
1433
1443
|
* @param {ControlFlow=} opt_flow The control flow this instance was
|
|
1434
1444
|
* created under. This should only be provided during unit tests.
|
|
1445
|
+
* @param {?=} opt_skipLog An internal parameter used to skip logging the
|
|
1446
|
+
* creation of this promise. This parameter has no effect unless it is
|
|
1447
|
+
* strictly equal to an internal symbol. In other words, this parameter
|
|
1448
|
+
* is always ignored for external code.
|
|
1435
1449
|
*/
|
|
1436
|
-
constructor(opt_flow) {
|
|
1450
|
+
constructor(opt_flow, opt_skipLog) {
|
|
1437
1451
|
var fulfill, reject;
|
|
1438
1452
|
|
|
1439
1453
|
/** @type {!ManagedPromise<T>} */
|
|
1440
1454
|
this.promise = new ManagedPromise(function(f, r) {
|
|
1441
1455
|
fulfill = f;
|
|
1442
1456
|
reject = r;
|
|
1443
|
-
}, opt_flow);
|
|
1457
|
+
}, opt_flow, opt_skipLog);
|
|
1444
1458
|
|
|
1445
1459
|
var self = this;
|
|
1446
1460
|
var checkNotSelf = function(value) {
|
|
@@ -1563,9 +1577,17 @@ function defer() {
|
|
|
1563
1577
|
function fulfilled(opt_value) {
|
|
1564
1578
|
let ctor = usePromiseManager() ? ManagedPromise : NativePromise;
|
|
1565
1579
|
if (opt_value instanceof ctor) {
|
|
1566
|
-
return /** @type {!Thenable
|
|
1580
|
+
return /** @type {!Thenable} */(opt_value);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
if (usePromiseManager()) {
|
|
1584
|
+
// We can skip logging warnings about creating a managed promise because
|
|
1585
|
+
// this function will automatically switch to use a native promise when
|
|
1586
|
+
// the promise manager is disabled.
|
|
1587
|
+
return new ManagedPromise(
|
|
1588
|
+
resolve => resolve(opt_value), undefined, SKIP_LOG);
|
|
1567
1589
|
}
|
|
1568
|
-
return
|
|
1590
|
+
return NativePromise.resolve(opt_value);
|
|
1569
1591
|
}
|
|
1570
1592
|
|
|
1571
1593
|
|
|
@@ -1582,7 +1604,11 @@ function fulfilled(opt_value) {
|
|
|
1582
1604
|
*/
|
|
1583
1605
|
function rejected(opt_reason) {
|
|
1584
1606
|
if (usePromiseManager()) {
|
|
1585
|
-
|
|
1607
|
+
// We can skip logging warnings about creating a managed promise because
|
|
1608
|
+
// this function will automatically switch to use a native promise when
|
|
1609
|
+
// the promise manager is disabled.
|
|
1610
|
+
return new ManagedPromise(
|
|
1611
|
+
(_, reject) => reject(opt_reason), undefined, SKIP_LOG);
|
|
1586
1612
|
}
|
|
1587
1613
|
return NativePromise.reject(opt_reason);
|
|
1588
1614
|
}
|
|
@@ -2444,15 +2470,31 @@ class ControlFlow extends events.EventEmitter {
|
|
|
2444
2470
|
}
|
|
2445
2471
|
|
|
2446
2472
|
if (!this.hold_) {
|
|
2447
|
-
|
|
2473
|
+
let holdIntervalMs = 2147483647; // 2^31-1; max timer length for Node.js
|
|
2448
2474
|
this.hold_ = setInterval(function() {}, holdIntervalMs);
|
|
2449
2475
|
}
|
|
2450
2476
|
|
|
2451
|
-
|
|
2477
|
+
let task = new Task(
|
|
2452
2478
|
this, fn, opt_description || '<anonymous>',
|
|
2453
|
-
{name: 'Task', top: ControlFlow.prototype.execute}
|
|
2479
|
+
{name: 'Task', top: ControlFlow.prototype.execute},
|
|
2480
|
+
true);
|
|
2481
|
+
|
|
2482
|
+
let q = this.getActiveQueue_();
|
|
2483
|
+
|
|
2484
|
+
for (let i = q.tasks_.length; i > 0; i--) {
|
|
2485
|
+
let previousTask = q.tasks_[i - 1];
|
|
2486
|
+
if (previousTask.userTask_) {
|
|
2487
|
+
FLOW_LOG.warning(() => {
|
|
2488
|
+
return `Detected scheduling of an unchained task.
|
|
2489
|
+
When the promise manager is disabled, unchained tasks will not wait for
|
|
2490
|
+
previously scheduled tasks to finish before starting to execute.
|
|
2491
|
+
New task: ${task.promise.stack_.stack}
|
|
2492
|
+
Previous task: ${previousTask.promise.stack_.stack}`.split(/\n/).join('\n ');
|
|
2493
|
+
});
|
|
2494
|
+
break;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2454
2497
|
|
|
2455
|
-
var q = this.getActiveQueue_();
|
|
2456
2498
|
q.enqueue(task);
|
|
2457
2499
|
this.emit(ControlFlow.EventType.SCHEDULE_TASK, task.description);
|
|
2458
2500
|
return task.promise;
|
|
@@ -2460,7 +2502,7 @@ class ControlFlow extends events.EventEmitter {
|
|
|
2460
2502
|
|
|
2461
2503
|
/** @override */
|
|
2462
2504
|
promise(resolver) {
|
|
2463
|
-
return new ManagedPromise(resolver, this);
|
|
2505
|
+
return new ManagedPromise(resolver, this, SKIP_LOG);
|
|
2464
2506
|
}
|
|
2465
2507
|
|
|
2466
2508
|
/** @override */
|
|
@@ -2729,9 +2771,11 @@ class Task extends Deferred {
|
|
|
2729
2771
|
* @param {string} description A description of the task for debugging.
|
|
2730
2772
|
* @param {{name: string, top: !Function}=} opt_stackOptions Options to use
|
|
2731
2773
|
* when capturing the stacktrace for when this task was created.
|
|
2774
|
+
* @param {boolean=} opt_isUserTask Whether this task was explicitly scheduled
|
|
2775
|
+
* by the use of the promise manager.
|
|
2732
2776
|
*/
|
|
2733
|
-
constructor(flow, fn, description, opt_stackOptions) {
|
|
2734
|
-
super(flow);
|
|
2777
|
+
constructor(flow, fn, description, opt_stackOptions, opt_isUserTask) {
|
|
2778
|
+
super(flow, SKIP_LOG);
|
|
2735
2779
|
getUid(this);
|
|
2736
2780
|
|
|
2737
2781
|
/** @type {function(): (T|!ManagedPromise<T>)} */
|
|
@@ -2743,6 +2787,9 @@ class Task extends Deferred {
|
|
|
2743
2787
|
/** @type {TaskQueue} */
|
|
2744
2788
|
this.queue = null;
|
|
2745
2789
|
|
|
2790
|
+
/** @private @const {boolean} */
|
|
2791
|
+
this.userTask_ = !!opt_isUserTask;
|
|
2792
|
+
|
|
2746
2793
|
/**
|
|
2747
2794
|
* Whether this task is considered block. A blocked task may be registered
|
|
2748
2795
|
* in a task queue, but will be dropped if it is still blocked when it
|
|
Binary file
|