selenium-webdriver 4.2.0 → 4.3.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 +8 -0
- package/chrome.js +2 -3
- package/chromium.js +1 -1
- package/devtools/CDPConnection.js +1 -1
- package/firefox.js +1 -1
- package/ie.js +3 -3
- package/index.js +1 -1
- package/lib/command.js +9 -0
- package/lib/http.js +40 -0
- package/lib/input.js +74 -21
- package/lib/virtual_authenticator.js +230 -0
- package/lib/webdriver.js +120 -11
- package/net/index.js +9 -2
- package/package.json +10 -10
- package/testing/index.js +17 -19
package/CHANGES.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## v4.3.0
|
|
2
|
+
|
|
3
|
+
* Fix: using fulfillRequest when intercepting (#10764)
|
|
4
|
+
* Add support CDP 103 and remove CDP 100
|
|
5
|
+
* Remove unused param in input.js & minor code cleanup
|
|
6
|
+
* Fixes lint issues and updates npm packages
|
|
7
|
+
* feat: Added virtual authenticator (#10663)
|
|
8
|
+
|
|
1
9
|
## v4.2.0
|
|
2
10
|
|
|
3
11
|
* Delete old android-driver
|
package/chrome.js
CHANGED
|
@@ -290,9 +290,8 @@ Driver.prototype.VENDOR_COMMAND_PREFIX = 'goog'
|
|
|
290
290
|
module.exports = {
|
|
291
291
|
Driver: Driver,
|
|
292
292
|
Options,
|
|
293
|
-
ServiceBuilder
|
|
293
|
+
ServiceBuilder,
|
|
294
294
|
getDefaultService,
|
|
295
295
|
setDefaultService,
|
|
296
|
-
locateSynchronously
|
|
296
|
+
locateSynchronously,
|
|
297
297
|
}
|
|
298
|
-
|
package/chromium.js
CHANGED
|
@@ -860,7 +860,7 @@ class Driver extends webdriver.WebDriver {
|
|
|
860
860
|
* @return {!promise.Thenable<void>} A promise that will be resolved
|
|
861
861
|
* when the mirror command has been issued to the device.
|
|
862
862
|
*/
|
|
863
|
-
|
|
863
|
+
startDesktopMirroring(deviceName) {
|
|
864
864
|
return this.schedule(
|
|
865
865
|
new command.Command(Command.START_CAST_DESKTOP_MIRRORING).setParameter(
|
|
866
866
|
'sinkName',
|
package/firefox.js
CHANGED
package/ie.js
CHANGED
|
@@ -402,9 +402,9 @@ function createServiceFromCapabilities(capabilities) {
|
|
|
402
402
|
if (!exe || !fs.existsSync(exe)) {
|
|
403
403
|
throw Error(
|
|
404
404
|
`${IEDRIVER_EXE} could not be found on the current PATH. Please ` +
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
405
|
+
`download the latest version of ${IEDRIVER_EXE} from ` +
|
|
406
|
+
'https://www.selenium.dev/downloads/ and ' +
|
|
407
|
+
'ensure it can be found on your system PATH.'
|
|
408
408
|
)
|
|
409
409
|
}
|
|
410
410
|
|
package/index.js
CHANGED
|
@@ -77,7 +77,7 @@ function startSeleniumServer(jar) {
|
|
|
77
77
|
function ensureFileDetectorsAreEnabled(ctor) {
|
|
78
78
|
return class extends ctor {
|
|
79
79
|
/** @param {input.FileDetector} detector */
|
|
80
|
-
setFileDetector
|
|
80
|
+
setFileDetector(detector) {
|
|
81
81
|
webdriver.WebDriver.prototype.setFileDetector.call(this, detector)
|
|
82
82
|
}
|
|
83
83
|
}
|
package/lib/command.js
CHANGED
|
@@ -164,6 +164,15 @@ const Name = {
|
|
|
164
164
|
FIND_ELEMENT_FROM_SHADOWROOT: 'findElementFromShadowRoot',
|
|
165
165
|
FIND_ELEMENTS_FROM_SHADOWROOT: 'findElementsFromShadowRoot',
|
|
166
166
|
|
|
167
|
+
// Virtual Authenticator Commands
|
|
168
|
+
ADD_VIRTUAL_AUTHENTICATOR: 'addVirtualAuthenticator',
|
|
169
|
+
REMOVE_VIRTUAL_AUTHENTICATOR: 'removeVirtualAuthenticator',
|
|
170
|
+
ADD_CREDENTIAL: 'addCredential',
|
|
171
|
+
GET_CREDENTIALS: 'getCredentials',
|
|
172
|
+
REMOVE_CREDENTIAL: 'removeCredential',
|
|
173
|
+
REMOVE_ALL_CREDENTIALS: 'removeAllCredentials',
|
|
174
|
+
SET_USER_VERIFIED: 'setUserVerified',
|
|
175
|
+
|
|
167
176
|
GET_AVAILABLE_LOG_TYPES: 'getAvailableLogTypes',
|
|
168
177
|
GET_LOG: 'getLog',
|
|
169
178
|
|
package/lib/http.js
CHANGED
|
@@ -347,6 +347,44 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
347
347
|
|
|
348
348
|
// Server Extensions
|
|
349
349
|
[cmd.Name.UPLOAD_FILE, post('/session/:sessionId/se/file')],
|
|
350
|
+
|
|
351
|
+
// Virtual Authenticator
|
|
352
|
+
[
|
|
353
|
+
cmd.Name.ADD_VIRTUAL_AUTHENTICATOR,
|
|
354
|
+
post('/session/:sessionId/webauthn/authenticator'),
|
|
355
|
+
],
|
|
356
|
+
[
|
|
357
|
+
cmd.Name.REMOVE_VIRTUAL_AUTHENTICATOR,
|
|
358
|
+
del('/session/:sessionId/webauthn/authenticator/:authenticatorId'),
|
|
359
|
+
],
|
|
360
|
+
[
|
|
361
|
+
cmd.Name.ADD_CREDENTIAL,
|
|
362
|
+
post(
|
|
363
|
+
'/session/:sessionId/webauthn/authenticator/:authenticatorId/credential'
|
|
364
|
+
),
|
|
365
|
+
],
|
|
366
|
+
[
|
|
367
|
+
cmd.Name.GET_CREDENTIALS,
|
|
368
|
+
get(
|
|
369
|
+
'/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials'
|
|
370
|
+
),
|
|
371
|
+
],
|
|
372
|
+
[
|
|
373
|
+
cmd.Name.REMOVE_CREDENTIAL,
|
|
374
|
+
del(
|
|
375
|
+
'/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials/:credentialId'
|
|
376
|
+
),
|
|
377
|
+
],
|
|
378
|
+
[
|
|
379
|
+
cmd.Name.REMOVE_ALL_CREDENTIALS,
|
|
380
|
+
del(
|
|
381
|
+
'/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials'
|
|
382
|
+
),
|
|
383
|
+
],
|
|
384
|
+
[
|
|
385
|
+
cmd.Name.SET_USER_VERIFIED,
|
|
386
|
+
post('/session/:sessionId/webauthn/authenticator/:authenticatorId/uv'),
|
|
387
|
+
],
|
|
350
388
|
])
|
|
351
389
|
|
|
352
390
|
/**
|
|
@@ -472,6 +510,7 @@ class Executor {
|
|
|
472
510
|
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
473
511
|
|
|
474
512
|
let httpResponse = /** @type {!Response} */ (response)
|
|
513
|
+
|
|
475
514
|
let { isW3C, value } = parseHttpResponse(command, httpResponse)
|
|
476
515
|
|
|
477
516
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
@@ -530,6 +569,7 @@ function parseHttpResponse(command, httpResponse) {
|
|
|
530
569
|
}
|
|
531
570
|
|
|
532
571
|
let parsed = tryParse(httpResponse.body)
|
|
572
|
+
|
|
533
573
|
if (parsed && typeof parsed === 'object') {
|
|
534
574
|
let value = parsed.value
|
|
535
575
|
let isW3C =
|
package/lib/input.js
CHANGED
|
@@ -346,16 +346,42 @@ class Pointer extends Device {
|
|
|
346
346
|
|
|
347
347
|
/**
|
|
348
348
|
* @param {!Button=} button The button to press.
|
|
349
|
+
* @param width
|
|
350
|
+
* @param height
|
|
351
|
+
* @param pressure
|
|
352
|
+
* @param tangentialPressure
|
|
353
|
+
* @param tiltX
|
|
354
|
+
* @param tiltY
|
|
355
|
+
* @param twist
|
|
356
|
+
* @param altitudeAngle
|
|
357
|
+
* @param azimuthAngle
|
|
349
358
|
* @return {!Action} An action to press the specified button with this device.
|
|
350
359
|
* @package
|
|
351
360
|
*/
|
|
352
|
-
press(
|
|
353
|
-
|
|
354
|
-
|
|
361
|
+
press(
|
|
362
|
+
button = Button.LEFT,
|
|
363
|
+
width = 0,
|
|
364
|
+
height = 0,
|
|
365
|
+
pressure = 0,
|
|
366
|
+
tangentialPressure = 0,
|
|
367
|
+
tiltX = 0,
|
|
368
|
+
tiltY = 0,
|
|
369
|
+
twist = 0,
|
|
370
|
+
altitudeAngle = 0,
|
|
371
|
+
azimuthAngle = 0
|
|
372
|
+
) {
|
|
355
373
|
return {
|
|
356
|
-
type: Action.Type.POINTER_DOWN,
|
|
357
|
-
|
|
358
|
-
|
|
374
|
+
type: Action.Type.POINTER_DOWN,
|
|
375
|
+
button,
|
|
376
|
+
width,
|
|
377
|
+
height,
|
|
378
|
+
pressure,
|
|
379
|
+
tangentialPressure,
|
|
380
|
+
tiltX,
|
|
381
|
+
tiltY,
|
|
382
|
+
twist,
|
|
383
|
+
altitudeAngle,
|
|
384
|
+
azimuthAngle,
|
|
359
385
|
}
|
|
360
386
|
}
|
|
361
387
|
|
|
@@ -385,15 +411,36 @@ class Pointer extends Device {
|
|
|
385
411
|
* @return {!Action} The new action.
|
|
386
412
|
* @package
|
|
387
413
|
*/
|
|
388
|
-
move({
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
414
|
+
move({
|
|
415
|
+
x = 0,
|
|
416
|
+
y = 0,
|
|
417
|
+
duration = 100,
|
|
418
|
+
origin = Origin.VIEWPORT,
|
|
419
|
+
width = 0,
|
|
420
|
+
height = 0,
|
|
421
|
+
pressure = 0,
|
|
422
|
+
tangentialPressure = 0,
|
|
423
|
+
tiltX = 0,
|
|
424
|
+
tiltY = 0,
|
|
425
|
+
twist = 0,
|
|
426
|
+
altitudeAngle = 0,
|
|
427
|
+
azimuthAngle = 0,
|
|
392
428
|
}) {
|
|
393
429
|
return {
|
|
394
|
-
type: Action.Type.POINTER_MOVE,
|
|
395
|
-
|
|
396
|
-
|
|
430
|
+
type: Action.Type.POINTER_MOVE,
|
|
431
|
+
origin,
|
|
432
|
+
duration,
|
|
433
|
+
x,
|
|
434
|
+
y,
|
|
435
|
+
width,
|
|
436
|
+
height,
|
|
437
|
+
pressure,
|
|
438
|
+
tangentialPressure,
|
|
439
|
+
tiltX,
|
|
440
|
+
tiltY,
|
|
441
|
+
twist,
|
|
442
|
+
altitudeAngle,
|
|
443
|
+
azimuthAngle,
|
|
397
444
|
}
|
|
398
445
|
}
|
|
399
446
|
}
|
|
@@ -410,10 +457,9 @@ Pointer.Type = {
|
|
|
410
457
|
|
|
411
458
|
class Wheel extends Device {
|
|
412
459
|
/**
|
|
413
|
-
* @param {string} id the device ID
|
|
414
|
-
* @param {Pointer.Type} type the pointer type.
|
|
460
|
+
* @param {string} id the device ID..
|
|
415
461
|
*/
|
|
416
|
-
constructor(id
|
|
462
|
+
constructor(id) {
|
|
417
463
|
super(Device.Type.WHEEL, id)
|
|
418
464
|
}
|
|
419
465
|
|
|
@@ -423,15 +469,19 @@ class Wheel extends Device {
|
|
|
423
469
|
* @param {number} y starting y coordinate
|
|
424
470
|
* @param {number} deltaX Delta X to scroll to target
|
|
425
471
|
* @param {number} deltaY Delta Y to scroll to target
|
|
472
|
+
* @param {WebElement} origin element origin
|
|
426
473
|
* @param {number} duration duration ratio be the ratio of time delta and duration
|
|
427
474
|
* @returns {!Action} An action to scroll with this device.
|
|
428
475
|
*/
|
|
429
476
|
scroll(x, y, deltaX, deltaY, origin, duration) {
|
|
430
477
|
return {
|
|
431
478
|
type: Action.Type.SCROLL,
|
|
432
|
-
duration: duration,
|
|
433
|
-
|
|
434
|
-
|
|
479
|
+
duration: duration,
|
|
480
|
+
x: x,
|
|
481
|
+
y: y,
|
|
482
|
+
deltaX: deltaX,
|
|
483
|
+
deltaY: deltaY,
|
|
484
|
+
origin: origin,
|
|
435
485
|
}
|
|
436
486
|
}
|
|
437
487
|
}
|
|
@@ -795,10 +845,13 @@ class Actions {
|
|
|
795
845
|
* @param {number} deltax delta x to scroll to target
|
|
796
846
|
* @param {number} deltay delta y to scroll to target
|
|
797
847
|
* @param {number} duration duration ratio be the ratio of time delta and duration
|
|
798
|
-
* @returns {!
|
|
848
|
+
* @returns {!Actions} An action to scroll with this device.
|
|
799
849
|
*/
|
|
800
850
|
scroll(x, y, targetDeltaX, targetDeltaY, origin, duration) {
|
|
801
|
-
return this.insert(
|
|
851
|
+
return this.insert(
|
|
852
|
+
this.wheel_,
|
|
853
|
+
this.wheel_.scroll(x, y, targetDeltaX, targetDeltaY, origin, duration)
|
|
854
|
+
)
|
|
802
855
|
}
|
|
803
856
|
|
|
804
857
|
/**
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
|
|
18
|
+
'use strict'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for the creation of virtual authenticators.
|
|
22
|
+
* @see http://w3c.github.io/webauthn/#sctn-automation
|
|
23
|
+
*/
|
|
24
|
+
class VirtualAuthenticatorOptions {
|
|
25
|
+
|
|
26
|
+
static Protocol = {
|
|
27
|
+
"CTAP2": 'ctap2',
|
|
28
|
+
"U2F": 'ctap1/u2f',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static Transport = {
|
|
32
|
+
"BLE": 'ble',
|
|
33
|
+
"USB": 'usb',
|
|
34
|
+
"NFC": 'nfc',
|
|
35
|
+
"INTERNAL": 'internal',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Constructor to initialise VirtualAuthenticatorOptions object.
|
|
40
|
+
*/
|
|
41
|
+
constructor() {
|
|
42
|
+
this._protocol = VirtualAuthenticatorOptions.Protocol["CTAP2"]
|
|
43
|
+
this._transport = VirtualAuthenticatorOptions.Transport["USB"]
|
|
44
|
+
this._hasResidentKey = false
|
|
45
|
+
this._hasUserVerification = false
|
|
46
|
+
this._isUserConsenting = true
|
|
47
|
+
this._isUserVerified = false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getProtocol() {
|
|
51
|
+
return this._protocol
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setProtocol(protocol) {
|
|
55
|
+
this._protocol = protocol
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getTransport() {
|
|
59
|
+
return this._transport
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setTransport(transport) {
|
|
63
|
+
this._transport = transport
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getHasResidentKey() {
|
|
67
|
+
return this._hasResidentKey
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setHasResidentKey(value) {
|
|
71
|
+
this._hasResidentKey = value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getHasUserVerification() {
|
|
75
|
+
return this._hasUserVerification
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setHasUserVerification(value) {
|
|
79
|
+
this._hasUserVerification = value
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getIsUserConsenting() {
|
|
83
|
+
return this._isUserConsenting
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setIsUserConsenting(value) {
|
|
87
|
+
this._isUserConsenting = value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getIsUserVerified() {
|
|
91
|
+
return this._isUserVerified
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setIsUserVerified(value) {
|
|
95
|
+
this._isUserVerified = value
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
toDict() {
|
|
99
|
+
return {
|
|
100
|
+
"protocol": this.getProtocol(),
|
|
101
|
+
"transport": this.getTransport(),
|
|
102
|
+
"hasResidentKey": this.getHasResidentKey(),
|
|
103
|
+
"hasUserVerification": this.getHasUserVerification(),
|
|
104
|
+
"isUserConsenting": this.getIsUserConsenting(),
|
|
105
|
+
"isUserVerified": this.getIsUserVerified(),
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A credential stored in a virtual authenticator.
|
|
113
|
+
* @see https://w3c.github.io/webauthn/#credential-parameters
|
|
114
|
+
*/
|
|
115
|
+
class Credential {
|
|
116
|
+
constructor(
|
|
117
|
+
credentialId,
|
|
118
|
+
isResidentCredential,
|
|
119
|
+
rpId,
|
|
120
|
+
userHandle,
|
|
121
|
+
privateKey,
|
|
122
|
+
signCount
|
|
123
|
+
) {
|
|
124
|
+
this._id = credentialId
|
|
125
|
+
this._isResidentCredential = isResidentCredential
|
|
126
|
+
this._rpId = rpId
|
|
127
|
+
this._userHandle = userHandle
|
|
128
|
+
this._privateKey = privateKey
|
|
129
|
+
this._signCount = signCount
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
id() {
|
|
133
|
+
return this._id
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
isResidentCredential() {
|
|
137
|
+
return this._isResidentCredential
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
rpId() {
|
|
141
|
+
return this._rpId
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
userHandle() {
|
|
145
|
+
if (this._userHandle != null) {
|
|
146
|
+
return this._userHandle
|
|
147
|
+
}
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
privateKey() {
|
|
152
|
+
return this._privateKey
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
signCount() {
|
|
156
|
+
return this._signCount
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Creates a resident (i.e. stateless) credential.
|
|
161
|
+
* @param id Unique base64 encoded string.
|
|
162
|
+
* @param rpId Relying party identifier.
|
|
163
|
+
* @param userHandle userHandle associated to the credential. Must be Base64 encoded string.
|
|
164
|
+
* @param privateKey Base64 encoded PKCS
|
|
165
|
+
* @param signCount initial value for a signature counter.
|
|
166
|
+
* @returns A resident credential
|
|
167
|
+
*/
|
|
168
|
+
createResidentCredential(id, rpId, userHandle, privateKey, signCount) {
|
|
169
|
+
return new Credential(id, true, rpId, userHandle, privateKey, signCount)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates a non-resident (i.e. stateless) credential.
|
|
174
|
+
* @param id Unique base64 encoded string.
|
|
175
|
+
* @param rpId Relying party identifier.
|
|
176
|
+
* @param privateKey Base64 encoded PKCS
|
|
177
|
+
* @param signCount initial value for a signature counter.
|
|
178
|
+
* @returns A non-resident credential
|
|
179
|
+
*/
|
|
180
|
+
createNonResidentCredential(id, rpId, privateKey, signCount) {
|
|
181
|
+
return new Credential(id, false, rpId, null, privateKey, signCount)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
toDict() {
|
|
185
|
+
let credentialData = {
|
|
186
|
+
'credentialId': Buffer.from(this._id).toString('base64url'),
|
|
187
|
+
'isResidentCredential': this._isResidentCredential,
|
|
188
|
+
'rpId': this._rpId,
|
|
189
|
+
'privateKey': Buffer.from(this._privateKey, 'binary').toString('base64url'),
|
|
190
|
+
'signCount': this._signCount,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (this.userHandle() != null) {
|
|
194
|
+
credentialData['userHandle'] = Buffer.from(this._userHandle).toString('base64url')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return credentialData
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Creates a credential from a map.
|
|
202
|
+
*/
|
|
203
|
+
fromDict(data) {
|
|
204
|
+
let id = new Uint8Array(Buffer.from(data['credentialId'], 'base64url'))
|
|
205
|
+
let isResidentCredential = data['isResidentCredential']
|
|
206
|
+
let rpId = data['rpId']
|
|
207
|
+
let privateKey = Buffer.from(data['privateKey'], 'base64url').toString('binary')
|
|
208
|
+
let signCount = data['signCount']
|
|
209
|
+
let userHandle
|
|
210
|
+
|
|
211
|
+
if ('userHandle' in data) {
|
|
212
|
+
userHandle = new Uint8Array(Buffer.from(data['userHandle'], 'base64url'))
|
|
213
|
+
} else {
|
|
214
|
+
userHandle = null
|
|
215
|
+
}
|
|
216
|
+
return new Credential(
|
|
217
|
+
id,
|
|
218
|
+
isResidentCredential,
|
|
219
|
+
rpId,
|
|
220
|
+
userHandle,
|
|
221
|
+
privateKey,
|
|
222
|
+
signCount
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
Credential,
|
|
229
|
+
VirtualAuthenticatorOptions,
|
|
230
|
+
}
|
package/lib/webdriver.js
CHANGED
|
@@ -37,6 +37,7 @@ const { Capabilities } = require('./capabilities')
|
|
|
37
37
|
const path = require('path')
|
|
38
38
|
const { NoSuchElementError } = require('./error')
|
|
39
39
|
const cdpTargets = ['page', 'browser']
|
|
40
|
+
const Credential = require('./virtual_authenticator').Credential
|
|
40
41
|
|
|
41
42
|
// Capability names that are defined in the W3C spec.
|
|
42
43
|
const W3C_CAPABILITY_NAMES = new Set([
|
|
@@ -679,6 +680,9 @@ class WebDriver {
|
|
|
679
680
|
|
|
680
681
|
/** @private @const {(function(this: void): ?|undefined)} */
|
|
681
682
|
this.onQuit_ = onQuit
|
|
683
|
+
|
|
684
|
+
/** @private {./virtual_authenticator}*/
|
|
685
|
+
this.authenticatorId_ = null
|
|
682
686
|
}
|
|
683
687
|
|
|
684
688
|
/**
|
|
@@ -730,6 +734,7 @@ class WebDriver {
|
|
|
730
734
|
/** @override */
|
|
731
735
|
async execute(command) {
|
|
732
736
|
command.setParameter('sessionId', this.session_)
|
|
737
|
+
|
|
733
738
|
let parameters = await toWireValue(command.getParameters())
|
|
734
739
|
command.setParameters(parameters)
|
|
735
740
|
let value = await this.executor_.execute(command)
|
|
@@ -1363,12 +1368,11 @@ class WebDriver {
|
|
|
1363
1368
|
if (params.method === 'Fetch.requestPaused') {
|
|
1364
1369
|
const requestPausedParams = params['params']
|
|
1365
1370
|
if (requestPausedParams.request.url == httpResponse.urlToIntercept) {
|
|
1366
|
-
connection.execute('Fetch.
|
|
1371
|
+
connection.execute('Fetch.fulfillRequest', {
|
|
1367
1372
|
requestId: requestPausedParams['requestId'],
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
postData: httpResponse.body,
|
|
1373
|
+
responseCode: 200,
|
|
1374
|
+
responseHeaders: httpResponse.headers,
|
|
1375
|
+
body: httpResponse.body,
|
|
1372
1376
|
})
|
|
1373
1377
|
callback()
|
|
1374
1378
|
} else {
|
|
@@ -1515,6 +1519,110 @@ class WebDriver {
|
|
|
1515
1519
|
}
|
|
1516
1520
|
})
|
|
1517
1521
|
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
*
|
|
1525
|
+
* @returns The value of authenticator ID added
|
|
1526
|
+
*/
|
|
1527
|
+
virtualAuthenticatorId() {
|
|
1528
|
+
return this.authenticatorId_
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Adds a virtual authenticator with the given options.
|
|
1533
|
+
* @param options VirtualAuthenticatorOptions object to set authenticator optons.
|
|
1534
|
+
*/
|
|
1535
|
+
async addVirtualAuthenticator(options) {
|
|
1536
|
+
this.authenticatorId_ = await this.execute(
|
|
1537
|
+
new command.Command(command.Name.ADD_VIRTUAL_AUTHENTICATOR).setParameters(
|
|
1538
|
+
options.toDict()
|
|
1539
|
+
)
|
|
1540
|
+
)
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* Removes a previously added virtual authenticator. The authenticator is no
|
|
1545
|
+
* longer valid after removal, so no methods may be called.
|
|
1546
|
+
*/
|
|
1547
|
+
async removeVirtualAuthenticator() {
|
|
1548
|
+
await this.execute(
|
|
1549
|
+
new command.Command(
|
|
1550
|
+
command.Name.REMOVE_VIRTUAL_AUTHENTICATOR
|
|
1551
|
+
).setParameter('authenticatorId', this.authenticatorId_)
|
|
1552
|
+
)
|
|
1553
|
+
this.authenticatorId_ = null
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Injects a credential into the authenticator.
|
|
1558
|
+
* @param credential Credential to be added
|
|
1559
|
+
*/
|
|
1560
|
+
async addCredential(credential) {
|
|
1561
|
+
credential = credential.toDict()
|
|
1562
|
+
credential['authenticatorId'] = this.authenticatorId_
|
|
1563
|
+
await this.execute(
|
|
1564
|
+
new command.Command(command.Name.ADD_CREDENTIAL).setParameters(credential)
|
|
1565
|
+
)
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
*
|
|
1570
|
+
* @returns The list of credentials owned by the authenticator.
|
|
1571
|
+
*/
|
|
1572
|
+
async getCredentials() {
|
|
1573
|
+
let credential_data = await this.execute(
|
|
1574
|
+
new command.Command(command.Name.GET_CREDENTIALS).setParameter(
|
|
1575
|
+
'authenticatorId',
|
|
1576
|
+
this.virtualAuthenticatorId()
|
|
1577
|
+
)
|
|
1578
|
+
)
|
|
1579
|
+
var credential_list = []
|
|
1580
|
+
for (var i = 0; i < credential_data.length; i++) {
|
|
1581
|
+
credential_list.push(new Credential().fromDict(credential_data[i]))
|
|
1582
|
+
}
|
|
1583
|
+
return credential_list
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Removes a credential from the authenticator.
|
|
1588
|
+
* @param credential_id The ID of the credential to be removed.
|
|
1589
|
+
*/
|
|
1590
|
+
async removeCredential(credential_id) {
|
|
1591
|
+
// If credential_id is not a base64url, then convert it to base64url.
|
|
1592
|
+
if (Array.isArray(credential_id)) {
|
|
1593
|
+
credential_id = Buffer.from(credential_id).toString('base64url')
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
await this.execute(
|
|
1597
|
+
new command.Command(command.Name.REMOVE_CREDENTIAL)
|
|
1598
|
+
.setParameter('credentialId', credential_id)
|
|
1599
|
+
.setParameter('authenticatorId', this.authenticatorId_)
|
|
1600
|
+
)
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Removes all the credentials from the authenticator.
|
|
1605
|
+
*/
|
|
1606
|
+
async removeAllCredentials() {
|
|
1607
|
+
await this.execute(
|
|
1608
|
+
new command.Command(command.Name.REMOVE_ALL_CREDENTIALS).setParameter(
|
|
1609
|
+
'authenticatorId',
|
|
1610
|
+
this.authenticatorId_
|
|
1611
|
+
)
|
|
1612
|
+
)
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* Sets whether the authenticator will simulate success or fail on user verification.
|
|
1617
|
+
* @param verified true if the authenticator will pass user verification, false otherwise.
|
|
1618
|
+
*/
|
|
1619
|
+
async setUserVerified(verified) {
|
|
1620
|
+
await this.execute(
|
|
1621
|
+
new command.Command(command.Name.SET_USER_VERIFIED)
|
|
1622
|
+
.setParameter('authenticatorId', this.authenticatorId_)
|
|
1623
|
+
.setParameter('isUserVerified', verified)
|
|
1624
|
+
)
|
|
1625
|
+
}
|
|
1518
1626
|
}
|
|
1519
1627
|
|
|
1520
1628
|
/**
|
|
@@ -2765,17 +2873,18 @@ class WebElement {
|
|
|
2765
2873
|
* when the form has been submitted.
|
|
2766
2874
|
*/
|
|
2767
2875
|
submit() {
|
|
2768
|
-
const script =
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2876
|
+
const script =
|
|
2877
|
+
'var form = arguments[0];\n' +
|
|
2878
|
+
'while (form.nodeName != "FORM" && form.parentNode) {\n' +
|
|
2879
|
+
' form = form.parentNode;\n' +
|
|
2880
|
+
'}\n' +
|
|
2772
2881
|
"if (!form) { throw Error('Unable to find containing form element'); }\n" +
|
|
2773
2882
|
"if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" +
|
|
2774
2883
|
"var e = form.ownerDocument.createEvent('Event');\n" +
|
|
2775
2884
|
"e.initEvent('submit', true, true);\n" +
|
|
2776
|
-
|
|
2885
|
+
'if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n'
|
|
2777
2886
|
|
|
2778
|
-
this.driver_.executeScript(script, this)
|
|
2887
|
+
this.driver_.executeScript(script, this)
|
|
2779
2888
|
}
|
|
2780
2889
|
|
|
2781
2890
|
/**
|
package/net/index.js
CHANGED
|
@@ -48,7 +48,10 @@ function getIPAddress(loopback, family) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
for (let ipAddress of interfaces[key]) {
|
|
51
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
(ipAddress.family === family || `IPv${ipAddress.family}` === family) &&
|
|
53
|
+
ipAddress.internal === loopback
|
|
54
|
+
) {
|
|
52
55
|
return ipAddress.address
|
|
53
56
|
}
|
|
54
57
|
}
|
|
@@ -73,7 +76,11 @@ function getAddress(family = 'IPv4') {
|
|
|
73
76
|
* @return {(string|undefined)} The IP address or undefined if not available.
|
|
74
77
|
*/
|
|
75
78
|
function getLoopbackAddress(family = 'IPv4') {
|
|
76
|
-
|
|
79
|
+
let address = getIPAddress(true, family)
|
|
80
|
+
if (address === '127.0.0.1') {
|
|
81
|
+
address = 'localhost'
|
|
82
|
+
}
|
|
83
|
+
return address
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selenium-webdriver",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "The official WebDriver JavaScript bindings from the Selenium project",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -23,26 +23,26 @@
|
|
|
23
23
|
"node": ">= 10.15.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"jszip": "^3.
|
|
26
|
+
"jszip": "^3.10.0",
|
|
27
27
|
"tmp": "^0.2.1",
|
|
28
|
-
"ws": ">=7.
|
|
28
|
+
"ws": ">=8.7.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"eslint": "^
|
|
32
|
-
"eslint-config-prettier": "^8.
|
|
31
|
+
"eslint": "^8.16.0",
|
|
32
|
+
"eslint-config-prettier": "^8.5.0",
|
|
33
33
|
"eslint-plugin-no-only-tests": "^2.6.0",
|
|
34
34
|
"eslint-plugin-node": "^11.1.0",
|
|
35
|
-
"eslint-plugin-prettier": "^
|
|
35
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
36
36
|
"express": "^4.17.1",
|
|
37
37
|
"jasmine": "^3.8.0",
|
|
38
|
-
"mocha": "^
|
|
39
|
-
"multer": "^1.4.
|
|
40
|
-
"prettier": "^2.
|
|
38
|
+
"mocha": "^10.0.0",
|
|
39
|
+
"multer": "^1.4.5-lts.1",
|
|
40
|
+
"prettier": "^2.6.1",
|
|
41
41
|
"serve-index": "^1.9.1",
|
|
42
42
|
"sinon": "^10.0.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
|
-
"lint": "eslint --ignore-pattern node_modules --ignore-pattern generator --ext js lib/http.js \"**/*.js\"",
|
|
45
|
+
"lint": "eslint --ignore-pattern node_modules --ignore-pattern generator --fix --ext js lib/http.js \"**/*.js\"",
|
|
46
46
|
"test": "npm run lint && mocha -t 600000 --recursive test",
|
|
47
47
|
"test-jasmine": "jasmine JASMINE_CONFIG_PATH=jasmine.json"
|
|
48
48
|
},
|
package/testing/index.js
CHANGED
|
@@ -39,14 +39,14 @@ const firefox = require('../firefox')
|
|
|
39
39
|
const ie = require('../ie')
|
|
40
40
|
const remote = require('../remote')
|
|
41
41
|
const safari = require('../safari')
|
|
42
|
-
const { Browser
|
|
42
|
+
const { Browser } = require('../lib/capabilities')
|
|
43
43
|
const { Builder } = require('../index')
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Describes a browser targeted by a {@linkplain suite test suite}.
|
|
47
47
|
* @record
|
|
48
48
|
*/
|
|
49
|
-
function TargetBrowser() {
|
|
49
|
+
function TargetBrowser() {}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* The {@linkplain Browser name} of the targeted browser.
|
|
@@ -208,7 +208,7 @@ function init(force = false) {
|
|
|
208
208
|
if (seleniumJar && seleniumUrl) {
|
|
209
209
|
throw Error(
|
|
210
210
|
'Ambiguous test configuration: both SELENIUM_REMOTE_URL' +
|
|
211
|
-
|
|
211
|
+
' && SELENIUM_SERVER_JAR environment variables are set'
|
|
212
212
|
)
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -216,8 +216,8 @@ function init(force = false) {
|
|
|
216
216
|
if ((seleniumJar || seleniumUrl) && envBrowsers.length === 0) {
|
|
217
217
|
throw Error(
|
|
218
218
|
'Ambiguous test configuration: when either the SELENIUM_REMOTE_URL or' +
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
' SELENIUM_SERVER_JAR environment variable is set, the' +
|
|
220
|
+
' SELENIUM_BROWSER variable must also be set.'
|
|
221
221
|
)
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -234,7 +234,8 @@ function init(force = false) {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
const TARGET_MAP = /** !WeakMap<!Environment, !TargetBrowser> */ new WeakMap()
|
|
237
|
-
const URL_MAP =
|
|
237
|
+
const URL_MAP =
|
|
238
|
+
/** !WeakMap<!Environment, ?(string|remote.SeleniumServer)> */ new WeakMap()
|
|
238
239
|
|
|
239
240
|
/**
|
|
240
241
|
* Defines the environment a {@linkplain suite test suite} is running against.
|
|
@@ -247,9 +248,9 @@ class Environment {
|
|
|
247
248
|
* Selenium server to test against.
|
|
248
249
|
*/
|
|
249
250
|
constructor(browser, url = undefined) {
|
|
250
|
-
browser = /** @type {!TargetBrowser} */ (
|
|
251
|
-
Object.assign({}, browser)
|
|
252
|
-
)
|
|
251
|
+
browser = /** @type {!TargetBrowser} */ (
|
|
252
|
+
Object.seal(Object.assign({}, browser))
|
|
253
|
+
)
|
|
253
254
|
|
|
254
255
|
TARGET_MAP.set(this, browser)
|
|
255
256
|
URL_MAP.set(this, url || null)
|
|
@@ -284,19 +285,16 @@ class Environment {
|
|
|
284
285
|
const builder = new Builder()
|
|
285
286
|
builder.disableEnvironmentOverrides()
|
|
286
287
|
|
|
287
|
-
|
|
288
|
-
|
|
289
288
|
const realBuild = builder.build
|
|
290
289
|
builder.build = function () {
|
|
291
|
-
builder.forBrowser(browser.name, browser.version, browser.platform)
|
|
292
|
-
|
|
290
|
+
builder.forBrowser(browser.name, browser.version, browser.platform)
|
|
293
291
|
|
|
294
292
|
if (browser.capabilities) {
|
|
295
|
-
builder.getCapabilities().merge(browser.capabilities)
|
|
293
|
+
builder.getCapabilities().merge(browser.capabilities)
|
|
296
294
|
}
|
|
297
295
|
|
|
298
296
|
if (browser.name === 'firefox') {
|
|
299
|
-
builder.setCapability('moz:debuggerAddress', true)
|
|
297
|
+
builder.setCapability('moz:debuggerAddress', true)
|
|
300
298
|
}
|
|
301
299
|
|
|
302
300
|
if (typeof urlOrServer === 'string') {
|
|
@@ -315,7 +313,7 @@ class Environment {
|
|
|
315
313
|
* Configuration options for a {@linkplain ./index.suite test suite}.
|
|
316
314
|
* @record
|
|
317
315
|
*/
|
|
318
|
-
function SuiteOptions() {
|
|
316
|
+
function SuiteOptions() {}
|
|
319
317
|
|
|
320
318
|
/**
|
|
321
319
|
* The browsers to run the test suite against.
|
|
@@ -442,7 +440,7 @@ function suite(fn, options = undefined) {
|
|
|
442
440
|
* @param {function(): boolean} predicateFn A predicate to call to determine
|
|
443
441
|
* if the test should be suppressed. This function MUST be synchronous.
|
|
444
442
|
* @return {{describe: !Function, it: !Function}} an object with wrapped
|
|
445
|
-
* versions of the `describe` and `it`
|
|
443
|
+
* versions of the `describe` and `it` test functions.
|
|
446
444
|
*/
|
|
447
445
|
function ignore(predicateFn) {
|
|
448
446
|
const isJasmine = global.jasmine && typeof global.jasmine === 'object'
|
|
@@ -490,8 +488,8 @@ function getTestHook(name) {
|
|
|
490
488
|
if (type !== 'function') {
|
|
491
489
|
throw TypeError(
|
|
492
490
|
`Expected global.${name} to be a function, but is ${type}.` +
|
|
493
|
-
|
|
494
|
-
|
|
491
|
+
' This can happen if you try using this module when running with' +
|
|
492
|
+
' node directly instead of using jasmine or mocha'
|
|
495
493
|
)
|
|
496
494
|
}
|
|
497
495
|
return fn
|