selenium-webdriver 4.1.1 → 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 +500 -566
- package/README.md +4 -4
- package/chrome.js +2 -3
- package/chromium.js +17 -4
- package/devtools/CDPConnection.js +1 -1
- package/edge.js +2 -2
- package/firefox.js +1 -1
- package/ie.js +3 -3
- package/index.js +1 -47
- package/lib/by.js +1 -1
- package/lib/capabilities.js +0 -8
- package/lib/command.js +9 -0
- package/lib/http.js +48 -7
- package/lib/input.js +123 -4
- package/lib/virtual_authenticator.js +230 -0
- package/lib/webdriver.js +176 -53
- package/net/index.js +9 -2
- package/package.json +10 -10
- package/testing/index.js +11 -15
- package/opera.js +0 -403
|
@@ -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)
|
|
@@ -1197,14 +1202,25 @@ class WebDriver {
|
|
|
1197
1202
|
* @return {!Promise<resolved>} A new CDP instance.
|
|
1198
1203
|
*/
|
|
1199
1204
|
async createCDPConnection(target) {
|
|
1205
|
+
let debuggerUrl = null
|
|
1206
|
+
|
|
1200
1207
|
const caps = await this.getCapabilities()
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
+
|
|
1209
|
+
if (process.env.SELENIUM_REMOTE_URL) {
|
|
1210
|
+
const host = new URL(process.env.SELENIUM_REMOTE_URL).host
|
|
1211
|
+
const sessionId = await this.getSession().then((session) =>
|
|
1212
|
+
session.getId()
|
|
1213
|
+
)
|
|
1214
|
+
debuggerUrl = `ws://${host}/session/${sessionId}/se/cdp`
|
|
1215
|
+
} else {
|
|
1216
|
+
const seCdp = caps['map_'].get('se:cdp')
|
|
1217
|
+
const vendorInfo =
|
|
1218
|
+
caps['map_'].get(this.VENDOR_COMMAND_PREFIX + ':chromeOptions') ||
|
|
1219
|
+
caps['map_'].get(this.VENDOR_CAPABILITY_PREFIX + ':edgeOptions') ||
|
|
1220
|
+
caps['map_'].get('moz:debuggerAddress') ||
|
|
1221
|
+
new Map()
|
|
1222
|
+
debuggerUrl = seCdp || vendorInfo['debuggerAddress'] || vendorInfo
|
|
1223
|
+
}
|
|
1208
1224
|
this._wsUrl = await this.getWsUrl(debuggerUrl, target, caps)
|
|
1209
1225
|
return new Promise((resolve, reject) => {
|
|
1210
1226
|
try {
|
|
@@ -1352,12 +1368,11 @@ class WebDriver {
|
|
|
1352
1368
|
if (params.method === 'Fetch.requestPaused') {
|
|
1353
1369
|
const requestPausedParams = params['params']
|
|
1354
1370
|
if (requestPausedParams.request.url == httpResponse.urlToIntercept) {
|
|
1355
|
-
connection.execute('Fetch.
|
|
1371
|
+
connection.execute('Fetch.fulfillRequest', {
|
|
1356
1372
|
requestId: requestPausedParams['requestId'],
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
postData: httpResponse.body,
|
|
1373
|
+
responseCode: 200,
|
|
1374
|
+
responseHeaders: httpResponse.headers,
|
|
1375
|
+
body: httpResponse.body,
|
|
1361
1376
|
})
|
|
1362
1377
|
callback()
|
|
1363
1378
|
} else {
|
|
@@ -1504,6 +1519,110 @@ class WebDriver {
|
|
|
1504
1519
|
}
|
|
1505
1520
|
})
|
|
1506
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
|
+
}
|
|
1507
1626
|
}
|
|
1508
1627
|
|
|
1509
1628
|
/**
|
|
@@ -1945,14 +2064,10 @@ class Window {
|
|
|
1945
2064
|
* @return {!Promise<{x: number, y: number, width: number, height: number}>}
|
|
1946
2065
|
* A promise that will resolve to the window rect of the current window.
|
|
1947
2066
|
*/
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
)
|
|
1953
|
-
} catch (ex) {
|
|
1954
|
-
throw ex
|
|
1955
|
-
}
|
|
2067
|
+
getRect() {
|
|
2068
|
+
return this.driver_.execute(
|
|
2069
|
+
new command.Command(command.Name.GET_WINDOW_RECT)
|
|
2070
|
+
)
|
|
1956
2071
|
}
|
|
1957
2072
|
|
|
1958
2073
|
/**
|
|
@@ -1969,19 +2084,15 @@ class Window {
|
|
|
1969
2084
|
* A promise that will resolve to the current window's updated window
|
|
1970
2085
|
* rect.
|
|
1971
2086
|
*/
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
)
|
|
1982
|
-
} catch (ex) {
|
|
1983
|
-
throw ex
|
|
1984
|
-
}
|
|
2087
|
+
setRect({ x, y, width, height }) {
|
|
2088
|
+
return this.driver_.execute(
|
|
2089
|
+
new command.Command(command.Name.SET_WINDOW_RECT).setParameters({
|
|
2090
|
+
x,
|
|
2091
|
+
y,
|
|
2092
|
+
width,
|
|
2093
|
+
height,
|
|
2094
|
+
})
|
|
2095
|
+
)
|
|
1985
2096
|
}
|
|
1986
2097
|
|
|
1987
2098
|
/**
|
|
@@ -2159,13 +2270,23 @@ class TargetLocator {
|
|
|
2159
2270
|
* If the specified frame can not be found, the returned promise will be
|
|
2160
2271
|
* rejected with a {@linkplain error.NoSuchFrameError}.
|
|
2161
2272
|
*
|
|
2162
|
-
* @param {(number|WebElement|null)} id The frame locator.
|
|
2273
|
+
* @param {(number|string|WebElement|null)} id The frame locator.
|
|
2163
2274
|
* @return {!Promise<void>} A promise that will be resolved
|
|
2164
2275
|
* when the driver has changed focus to the specified frame.
|
|
2165
2276
|
*/
|
|
2166
2277
|
frame(id) {
|
|
2278
|
+
let frameReference = id
|
|
2279
|
+
if (typeof id === 'string') {
|
|
2280
|
+
frameReference = this.driver_
|
|
2281
|
+
.findElement({ id })
|
|
2282
|
+
.catch((_) => this.driver_.findElement({ name: id }))
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2167
2285
|
return this.driver_.execute(
|
|
2168
|
-
new command.Command(command.Name.SWITCH_TO_FRAME).setParameter(
|
|
2286
|
+
new command.Command(command.Name.SWITCH_TO_FRAME).setParameter(
|
|
2287
|
+
'id',
|
|
2288
|
+
frameReference
|
|
2289
|
+
)
|
|
2169
2290
|
)
|
|
2170
2291
|
}
|
|
2171
2292
|
|
|
@@ -2522,12 +2643,12 @@ class WebElement {
|
|
|
2522
2643
|
if (type === 'number') {
|
|
2523
2644
|
key = String(key)
|
|
2524
2645
|
} else if (type !== 'string') {
|
|
2525
|
-
throw TypeError('each key must be a number
|
|
2646
|
+
throw TypeError('each key must be a number or string; got ' + type)
|
|
2526
2647
|
}
|
|
2527
2648
|
|
|
2528
2649
|
// The W3C protocol requires keys to be specified as an array where
|
|
2529
2650
|
// each element is a single key.
|
|
2530
|
-
keys.push(...key
|
|
2651
|
+
keys.push(...key)
|
|
2531
2652
|
})
|
|
2532
2653
|
|
|
2533
2654
|
if (!this.driver_.fileDetector_) {
|
|
@@ -2544,7 +2665,10 @@ class WebElement {
|
|
|
2544
2665
|
keys.join('')
|
|
2545
2666
|
)
|
|
2546
2667
|
} catch (ex) {
|
|
2547
|
-
console.log(
|
|
2668
|
+
console.log(
|
|
2669
|
+
'Error trying parse string as a file with file detector; sending keys instead' +
|
|
2670
|
+
ex
|
|
2671
|
+
)
|
|
2548
2672
|
}
|
|
2549
2673
|
|
|
2550
2674
|
return this.execute_(
|
|
@@ -2715,14 +2839,8 @@ class WebElement {
|
|
|
2715
2839
|
* @return {!Promise<{width: number, height: number, x: number, y: number}>}
|
|
2716
2840
|
* A promise that will resolve with the element's rect.
|
|
2717
2841
|
*/
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
return await this.execute_(
|
|
2721
|
-
new command.Command(command.Name.GET_ELEMENT_RECT)
|
|
2722
|
-
)
|
|
2723
|
-
} catch (err) {
|
|
2724
|
-
throw err;
|
|
2725
|
-
}
|
|
2842
|
+
getRect() {
|
|
2843
|
+
return this.execute_(new command.Command(command.Name.GET_ELEMENT_RECT))
|
|
2726
2844
|
}
|
|
2727
2845
|
|
|
2728
2846
|
/**
|
|
@@ -2755,13 +2873,18 @@ class WebElement {
|
|
|
2755
2873
|
* when the form has been submitted.
|
|
2756
2874
|
*/
|
|
2757
2875
|
submit() {
|
|
2758
|
-
const
|
|
2759
|
-
|
|
2760
|
-
"
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
form
|
|
2764
|
-
|
|
2876
|
+
const script =
|
|
2877
|
+
'var form = arguments[0];\n' +
|
|
2878
|
+
'while (form.nodeName != "FORM" && form.parentNode) {\n' +
|
|
2879
|
+
' form = form.parentNode;\n' +
|
|
2880
|
+
'}\n' +
|
|
2881
|
+
"if (!form) { throw Error('Unable to find containing form element'); }\n" +
|
|
2882
|
+
"if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" +
|
|
2883
|
+
"var e = form.ownerDocument.createEvent('Event');\n" +
|
|
2884
|
+
"e.initEvent('submit', true, true);\n" +
|
|
2885
|
+
'if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n'
|
|
2886
|
+
|
|
2887
|
+
this.driver_.executeScript(script, this)
|
|
2765
2888
|
}
|
|
2766
2889
|
|
|
2767
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,8 +39,7 @@ const firefox = require('../firefox')
|
|
|
39
39
|
const ie = require('../ie')
|
|
40
40
|
const remote = require('../remote')
|
|
41
41
|
const safari = require('../safari')
|
|
42
|
-
const
|
|
43
|
-
const { Browser, Capabilities } = require('../lib/capabilities')
|
|
42
|
+
const { Browser } = require('../lib/capabilities')
|
|
44
43
|
const { Builder } = require('../index')
|
|
45
44
|
|
|
46
45
|
/**
|
|
@@ -123,7 +122,6 @@ function getAvailableBrowsers() {
|
|
|
123
122
|
[firefox.locateSynchronously, Browser.FIREFOX],
|
|
124
123
|
[ie.locateSynchronously, Browser.INTERNET_EXPLORER],
|
|
125
124
|
[safari.locateSynchronously, Browser.SAFARI],
|
|
126
|
-
[opera.locateSynchronously, Browser.OPERA],
|
|
127
125
|
]
|
|
128
126
|
|
|
129
127
|
let availableBrowsers = []
|
|
@@ -236,7 +234,8 @@ function init(force = false) {
|
|
|
236
234
|
}
|
|
237
235
|
|
|
238
236
|
const TARGET_MAP = /** !WeakMap<!Environment, !TargetBrowser> */ new WeakMap()
|
|
239
|
-
const URL_MAP =
|
|
237
|
+
const URL_MAP =
|
|
238
|
+
/** !WeakMap<!Environment, ?(string|remote.SeleniumServer)> */ new WeakMap()
|
|
240
239
|
|
|
241
240
|
/**
|
|
242
241
|
* Defines the environment a {@linkplain suite test suite} is running against.
|
|
@@ -249,9 +248,9 @@ class Environment {
|
|
|
249
248
|
* Selenium server to test against.
|
|
250
249
|
*/
|
|
251
250
|
constructor(browser, url = undefined) {
|
|
252
|
-
browser = /** @type {!TargetBrowser} */ (
|
|
253
|
-
Object.assign({}, browser)
|
|
254
|
-
)
|
|
251
|
+
browser = /** @type {!TargetBrowser} */ (
|
|
252
|
+
Object.seal(Object.assign({}, browser))
|
|
253
|
+
)
|
|
255
254
|
|
|
256
255
|
TARGET_MAP.set(this, browser)
|
|
257
256
|
URL_MAP.set(this, url || null)
|
|
@@ -286,19 +285,16 @@ class Environment {
|
|
|
286
285
|
const builder = new Builder()
|
|
287
286
|
builder.disableEnvironmentOverrides()
|
|
288
287
|
|
|
289
|
-
|
|
290
|
-
|
|
291
288
|
const realBuild = builder.build
|
|
292
289
|
builder.build = function () {
|
|
293
|
-
builder.forBrowser(browser.name, browser.version, browser.platform)
|
|
294
|
-
|
|
290
|
+
builder.forBrowser(browser.name, browser.version, browser.platform)
|
|
295
291
|
|
|
296
292
|
if (browser.capabilities) {
|
|
297
|
-
builder.getCapabilities().merge(browser.capabilities)
|
|
293
|
+
builder.getCapabilities().merge(browser.capabilities)
|
|
298
294
|
}
|
|
299
295
|
|
|
300
|
-
if(browser.name === 'firefox') {
|
|
301
|
-
builder.setCapability('moz:debuggerAddress', true)
|
|
296
|
+
if (browser.name === 'firefox') {
|
|
297
|
+
builder.setCapability('moz:debuggerAddress', true)
|
|
302
298
|
}
|
|
303
299
|
|
|
304
300
|
if (typeof urlOrServer === 'string') {
|
|
@@ -444,7 +440,7 @@ function suite(fn, options = undefined) {
|
|
|
444
440
|
* @param {function(): boolean} predicateFn A predicate to call to determine
|
|
445
441
|
* if the test should be suppressed. This function MUST be synchronous.
|
|
446
442
|
* @return {{describe: !Function, it: !Function}} an object with wrapped
|
|
447
|
-
* versions of the `describe` and `it`
|
|
443
|
+
* versions of the `describe` and `it` test functions.
|
|
448
444
|
*/
|
|
449
445
|
function ignore(predicateFn) {
|
|
450
446
|
const isJasmine = global.jasmine && typeof global.jasmine === 'object'
|