selenium-webdriver 4.0.0 → 4.1.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 +18 -1
- package/chrome.js +4 -3
- package/chromium.js +43 -21
- package/devtools/CDPConnection.js +9 -4
- package/devtools/networkinterceptor.js +60 -61
- package/http/index.js +13 -14
- package/http/util.js +14 -20
- package/index.js +11 -11
- package/io/exec.js +1 -2
- package/io/index.js +47 -46
- package/lib/atoms/find-elements.js +2 -2
- package/lib/by.js +4 -5
- package/lib/capabilities.js +1 -1
- package/lib/command.js +5 -0
- package/lib/error.js +24 -0
- package/lib/http.js +10 -6
- package/lib/until.js +16 -14
- package/lib/webdriver.js +287 -97
- package/net/portprober.js +1 -1
- package/package.json +1 -2
- package/remote/index.js +1 -1
- package/safari.js +3 -3
- package/testing/index.js +11 -13
package/lib/webdriver.js
CHANGED
|
@@ -50,7 +50,7 @@ const W3C_CAPABILITY_NAMES = new Set([
|
|
|
50
50
|
'strictFileInteractability',
|
|
51
51
|
'timeouts',
|
|
52
52
|
'unhandledPromptBehavior',
|
|
53
|
-
'webSocketUrl'
|
|
53
|
+
'webSocketUrl',
|
|
54
54
|
])
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -209,6 +209,9 @@ function fromWireValue(driver, value) {
|
|
|
209
209
|
} else if (WebElement.isId(value)) {
|
|
210
210
|
let id = WebElement.extractId(value)
|
|
211
211
|
value = new WebElement(driver, id)
|
|
212
|
+
} else if (ShadowRoot.isId(value)) {
|
|
213
|
+
let id = ShadowRoot.extractId(value)
|
|
214
|
+
value = new ShadowRoot(driver, id)
|
|
212
215
|
} else if (value && typeof value === 'object') {
|
|
213
216
|
let result = {}
|
|
214
217
|
for (let key in value) {
|
|
@@ -1203,14 +1206,39 @@ class WebDriver {
|
|
|
1203
1206
|
return new Promise((resolve, reject) => {
|
|
1204
1207
|
try {
|
|
1205
1208
|
this._wsConnection = new WebSocket(this._wsUrl)
|
|
1209
|
+
this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
|
|
1206
1210
|
} catch (err) {
|
|
1207
1211
|
reject(err)
|
|
1208
1212
|
return
|
|
1209
1213
|
}
|
|
1210
1214
|
|
|
1211
|
-
this._wsConnection.on('open', () => {
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1215
|
+
this._wsConnection.on('open', async () => {
|
|
1216
|
+
await this.getCdpTargets()
|
|
1217
|
+
})
|
|
1218
|
+
|
|
1219
|
+
this._wsConnection.on('message', async (message) => {
|
|
1220
|
+
const params = JSON.parse(message)
|
|
1221
|
+
if (params.result) {
|
|
1222
|
+
if (params.result.targetInfos) {
|
|
1223
|
+
const targets = params.result.targetInfos
|
|
1224
|
+
const page = targets.find((info) => info.type === 'page')
|
|
1225
|
+
if (page) {
|
|
1226
|
+
this.targetID = page.targetId
|
|
1227
|
+
this._cdpConnection.execute(
|
|
1228
|
+
'Target.attachToTarget',
|
|
1229
|
+
{ targetId: this.targetID, flatten: true },
|
|
1230
|
+
null
|
|
1231
|
+
)
|
|
1232
|
+
} else {
|
|
1233
|
+
reject('Unable to find Page target.')
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
if (params.result.sessionId) {
|
|
1237
|
+
this.sessionId = params.result.sessionId
|
|
1238
|
+
this._cdpConnection.sessionId = this.sessionId
|
|
1239
|
+
resolve(this._cdpConnection)
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1214
1242
|
})
|
|
1215
1243
|
|
|
1216
1244
|
this._wsConnection.on('error', (error) => {
|
|
@@ -1219,6 +1247,10 @@ class WebDriver {
|
|
|
1219
1247
|
})
|
|
1220
1248
|
}
|
|
1221
1249
|
|
|
1250
|
+
async getCdpTargets() {
|
|
1251
|
+
this._cdpConnection.execute('Target.getTargets')
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1222
1254
|
/**
|
|
1223
1255
|
* Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
|
|
1224
1256
|
* @param {string} debuggerAddress
|
|
@@ -1232,16 +1264,18 @@ class WebDriver {
|
|
|
1232
1264
|
}
|
|
1233
1265
|
|
|
1234
1266
|
if (debuggerAddress.match(/\/se\/cdp/)) {
|
|
1235
|
-
return debuggerAddress
|
|
1267
|
+
return debuggerAddress
|
|
1236
1268
|
}
|
|
1237
1269
|
|
|
1238
1270
|
let path
|
|
1239
|
-
if (target === 'page' && caps['map_'].get('browserName')!=='firefox'
|
|
1271
|
+
if (target === 'page' && caps['map_'].get('browserName') !== 'firefox') {
|
|
1240
1272
|
path = '/json'
|
|
1241
|
-
} else if
|
|
1273
|
+
} else if (
|
|
1274
|
+
target === 'page' &&
|
|
1275
|
+
caps['map_'].get('browserName') === 'firefox'
|
|
1276
|
+
) {
|
|
1242
1277
|
path = '/json/list'
|
|
1243
|
-
}
|
|
1244
|
-
else {
|
|
1278
|
+
} else {
|
|
1245
1279
|
path = '/json/version'
|
|
1246
1280
|
}
|
|
1247
1281
|
|
|
@@ -1265,39 +1299,29 @@ class WebDriver {
|
|
|
1265
1299
|
* @param connection CDP Connection
|
|
1266
1300
|
*/
|
|
1267
1301
|
async register(username, password, connection) {
|
|
1268
|
-
|
|
1269
1302
|
this._wsConnection.on('message', (message) => {
|
|
1270
1303
|
const params = JSON.parse(message)
|
|
1271
1304
|
|
|
1272
1305
|
if (params.method === 'Fetch.authRequired') {
|
|
1273
1306
|
const requestParams = params['params']
|
|
1274
|
-
connection.execute(
|
|
1275
|
-
'
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
password: password,
|
|
1283
|
-
},
|
|
1284
|
-
}
|
|
1285
|
-
)
|
|
1307
|
+
connection.execute('Fetch.continueWithAuth', {
|
|
1308
|
+
requestId: requestParams['requestId'],
|
|
1309
|
+
authChallengeResponse: {
|
|
1310
|
+
response: 'ProvideCredentials',
|
|
1311
|
+
username: username,
|
|
1312
|
+
password: password,
|
|
1313
|
+
},
|
|
1314
|
+
})
|
|
1286
1315
|
} else if (params.method === 'Fetch.requestPaused') {
|
|
1287
1316
|
const requestPausedParams = params['params']
|
|
1288
|
-
connection.execute(
|
|
1289
|
-
'
|
|
1290
|
-
|
|
1291
|
-
{
|
|
1292
|
-
requestId: requestPausedParams['requestId'],
|
|
1293
|
-
}
|
|
1294
|
-
)
|
|
1317
|
+
connection.execute('Fetch.continueRequest', {
|
|
1318
|
+
requestId: requestPausedParams['requestId'],
|
|
1319
|
+
})
|
|
1295
1320
|
}
|
|
1296
1321
|
})
|
|
1297
1322
|
|
|
1298
1323
|
await connection.execute(
|
|
1299
1324
|
'Fetch.enable',
|
|
1300
|
-
1,
|
|
1301
1325
|
{
|
|
1302
1326
|
handleAuthRequests: true,
|
|
1303
1327
|
},
|
|
@@ -1305,7 +1329,6 @@ class WebDriver {
|
|
|
1305
1329
|
)
|
|
1306
1330
|
await connection.execute(
|
|
1307
1331
|
'Network.setCacheDisabled',
|
|
1308
|
-
this.getRandomNumber(1, 10),
|
|
1309
1332
|
{
|
|
1310
1333
|
cacheDisabled: true,
|
|
1311
1334
|
},
|
|
@@ -1321,45 +1344,30 @@ class WebDriver {
|
|
|
1321
1344
|
* @param callback callback called when we intercept requests.
|
|
1322
1345
|
*/
|
|
1323
1346
|
async onIntercept(connection, httpResponse, callback) {
|
|
1324
|
-
|
|
1325
1347
|
this._wsConnection.on('message', (message) => {
|
|
1326
1348
|
const params = JSON.parse(message)
|
|
1327
1349
|
if (params.method === 'Fetch.requestPaused') {
|
|
1328
1350
|
const requestPausedParams = params['params']
|
|
1329
1351
|
if (requestPausedParams.request.url == httpResponse.urlToIntercept) {
|
|
1330
|
-
connection.execute(
|
|
1331
|
-
'
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
headers: httpResponse.headers,
|
|
1338
|
-
postData: httpResponse.body
|
|
1339
|
-
}
|
|
1340
|
-
)
|
|
1352
|
+
connection.execute('Fetch.continueRequest', {
|
|
1353
|
+
requestId: requestPausedParams['requestId'],
|
|
1354
|
+
url: httpResponse.urlToIntercept,
|
|
1355
|
+
method: httpResponse.method,
|
|
1356
|
+
headers: httpResponse.headers,
|
|
1357
|
+
postData: httpResponse.body,
|
|
1358
|
+
})
|
|
1341
1359
|
callback()
|
|
1342
1360
|
} else {
|
|
1343
|
-
connection.execute(
|
|
1344
|
-
'
|
|
1345
|
-
|
|
1346
|
-
{
|
|
1347
|
-
requestId: requestPausedParams['requestId'],
|
|
1348
|
-
}
|
|
1349
|
-
)
|
|
1361
|
+
connection.execute('Fetch.continueRequest', {
|
|
1362
|
+
requestId: requestPausedParams['requestId'],
|
|
1363
|
+
})
|
|
1350
1364
|
}
|
|
1351
1365
|
}
|
|
1352
1366
|
})
|
|
1353
1367
|
|
|
1354
|
-
await connection.execute(
|
|
1355
|
-
'Fetch.enable',
|
|
1356
|
-
1,
|
|
1357
|
-
{},
|
|
1358
|
-
null
|
|
1359
|
-
)
|
|
1368
|
+
await connection.execute('Fetch.enable', {}, null)
|
|
1360
1369
|
await connection.execute(
|
|
1361
1370
|
'Network.setCacheDisabled',
|
|
1362
|
-
this.getRandomNumber(1, 10),
|
|
1363
1371
|
{
|
|
1364
1372
|
cacheDisabled: true,
|
|
1365
1373
|
},
|
|
@@ -1373,11 +1381,8 @@ class WebDriver {
|
|
|
1373
1381
|
* @returns {Promise<void>}
|
|
1374
1382
|
*/
|
|
1375
1383
|
async onLogEvent(connection, callback) {
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
1384
|
this._wsConnection.on('message', (message) => {
|
|
1379
1385
|
const params = JSON.parse(message)
|
|
1380
|
-
|
|
1381
1386
|
if (params.method === 'Runtime.consoleAPICalled') {
|
|
1382
1387
|
const consoleEventParams = params['params']
|
|
1383
1388
|
let event = {
|
|
@@ -1388,13 +1393,21 @@ class WebDriver {
|
|
|
1388
1393
|
|
|
1389
1394
|
callback(event)
|
|
1390
1395
|
}
|
|
1396
|
+
|
|
1397
|
+
if (params.method === 'Log.entryAdded') {
|
|
1398
|
+
const logEventParams = params['params']
|
|
1399
|
+
const logEntry = logEventParams['entry']
|
|
1400
|
+
let event = {
|
|
1401
|
+
level: logEntry['level'],
|
|
1402
|
+
timestamp: new Date(logEntry['timestamp']),
|
|
1403
|
+
message: logEntry['text'],
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
callback(event)
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1391
1409
|
})
|
|
1392
|
-
await connection.execute(
|
|
1393
|
-
'Runtime.enable',
|
|
1394
|
-
this.getRandomNumber(1, 10),
|
|
1395
|
-
{},
|
|
1396
|
-
null
|
|
1397
|
-
)
|
|
1410
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1398
1411
|
}
|
|
1399
1412
|
|
|
1400
1413
|
/**
|
|
@@ -1404,12 +1417,7 @@ class WebDriver {
|
|
|
1404
1417
|
* @returns {Promise<void>}
|
|
1405
1418
|
*/
|
|
1406
1419
|
async onLogException(connection, callback) {
|
|
1407
|
-
await connection.execute(
|
|
1408
|
-
'Runtime.enable',
|
|
1409
|
-
this.getRandomNumber(1, 10),
|
|
1410
|
-
{},
|
|
1411
|
-
null
|
|
1412
|
-
)
|
|
1420
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1413
1421
|
|
|
1414
1422
|
this._wsConnection.on('message', (message) => {
|
|
1415
1423
|
const params = JSON.parse(message)
|
|
@@ -1432,22 +1440,11 @@ class WebDriver {
|
|
|
1432
1440
|
* @returns {Promise<void>}
|
|
1433
1441
|
*/
|
|
1434
1442
|
async logMutationEvents(connection, callback) {
|
|
1435
|
-
await connection.execute(
|
|
1436
|
-
|
|
1437
|
-
this.getRandomNumber(1, 10),
|
|
1438
|
-
{},
|
|
1439
|
-
null
|
|
1440
|
-
)
|
|
1441
|
-
await connection.execute(
|
|
1442
|
-
'Page.enable',
|
|
1443
|
-
this.getRandomNumber(1, 10),
|
|
1444
|
-
{},
|
|
1445
|
-
null
|
|
1446
|
-
)
|
|
1443
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1444
|
+
await connection.execute('Page.enable', {}, null)
|
|
1447
1445
|
|
|
1448
1446
|
await connection.execute(
|
|
1449
1447
|
'Runtime.addBinding',
|
|
1450
|
-
this.getRandomNumber(1, 10),
|
|
1451
1448
|
{
|
|
1452
1449
|
name: '__webdriver_attribute',
|
|
1453
1450
|
},
|
|
@@ -1477,7 +1474,6 @@ class WebDriver {
|
|
|
1477
1474
|
|
|
1478
1475
|
await connection.execute(
|
|
1479
1476
|
'Page.addScriptToEvaluateOnNewDocument',
|
|
1480
|
-
this.getRandomNumber(1, 10),
|
|
1481
1477
|
{
|
|
1482
1478
|
source: mutationListener,
|
|
1483
1479
|
},
|
|
@@ -1489,7 +1485,7 @@ class WebDriver {
|
|
|
1489
1485
|
if (params.method === 'Runtime.bindingCalled') {
|
|
1490
1486
|
let payload = JSON.parse(params['params']['payload'])
|
|
1491
1487
|
let elements = await this.findElements({
|
|
1492
|
-
css: '*[data-__webdriver_id=' + payload['target'],
|
|
1488
|
+
css: '*[data-__webdriver_id=' + by.escapeCss(payload['target']) + ']',
|
|
1493
1489
|
})
|
|
1494
1490
|
|
|
1495
1491
|
if (elements.length === 0) {
|
|
@@ -1506,10 +1502,6 @@ class WebDriver {
|
|
|
1506
1502
|
}
|
|
1507
1503
|
})
|
|
1508
1504
|
}
|
|
1509
|
-
|
|
1510
|
-
getRandomNumber(min, max) {
|
|
1511
|
-
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
1512
|
-
}
|
|
1513
1505
|
}
|
|
1514
1506
|
|
|
1515
1507
|
/**
|
|
@@ -2167,7 +2159,7 @@ class TargetLocator {
|
|
|
2167
2159
|
* @return {!WebElementPromise} The active element.
|
|
2168
2160
|
*/
|
|
2169
2161
|
activeElement() {
|
|
2170
|
-
|
|
2162
|
+
const id = this.driver_.execute(
|
|
2171
2163
|
new command.Command(command.Name.GET_ACTIVE_ELEMENT)
|
|
2172
2164
|
)
|
|
2173
2165
|
return new WebElementPromise(this.driver_, id)
|
|
@@ -2259,7 +2251,7 @@ class TargetLocator {
|
|
|
2259
2251
|
* when the driver has changed focus to the new window.
|
|
2260
2252
|
*/
|
|
2261
2253
|
newWindow(typeHint) {
|
|
2262
|
-
|
|
2254
|
+
const driver = this.driver_
|
|
2263
2255
|
return this.driver_
|
|
2264
2256
|
.execute(
|
|
2265
2257
|
new command.Command(command.Name.SWITCH_TO_NEW_WINDOW).setParameter(
|
|
@@ -2281,10 +2273,10 @@ class TargetLocator {
|
|
|
2281
2273
|
* @return {!AlertPromise} The open alert.
|
|
2282
2274
|
*/
|
|
2283
2275
|
alert() {
|
|
2284
|
-
|
|
2276
|
+
const text = this.driver_.execute(
|
|
2285
2277
|
new command.Command(command.Name.GET_ALERT_TEXT)
|
|
2286
2278
|
)
|
|
2287
|
-
|
|
2279
|
+
const driver = this.driver_
|
|
2288
2280
|
return new AlertPromise(
|
|
2289
2281
|
driver,
|
|
2290
2282
|
text.then(function (text) {
|
|
@@ -2302,6 +2294,7 @@ class TargetLocator {
|
|
|
2302
2294
|
|
|
2303
2295
|
const LEGACY_ELEMENT_ID_KEY = 'ELEMENT'
|
|
2304
2296
|
const ELEMENT_ID_KEY = 'element-6066-11e4-a52e-4f735466cecf'
|
|
2297
|
+
const SHADOWROOT_ID_KEY = 'shadow-6066-11e4-a52e-4f735466cecf'
|
|
2305
2298
|
|
|
2306
2299
|
/**
|
|
2307
2300
|
* Represents a DOM element. WebElements can be found by searching from the
|
|
@@ -2614,7 +2607,7 @@ class WebElement {
|
|
|
2614
2607
|
* requested CSS value.
|
|
2615
2608
|
*/
|
|
2616
2609
|
getCssValue(cssStyleProperty) {
|
|
2617
|
-
|
|
2610
|
+
const name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY
|
|
2618
2611
|
return this.execute_(
|
|
2619
2612
|
new command.Command(name).setParameter('propertyName', cssStyleProperty)
|
|
2620
2613
|
)
|
|
@@ -2673,6 +2666,16 @@ class WebElement {
|
|
|
2673
2666
|
)
|
|
2674
2667
|
}
|
|
2675
2668
|
|
|
2669
|
+
/**
|
|
2670
|
+
* Get the shadow root of the current web element.
|
|
2671
|
+
* @returns {!Promise<ShadowRoot>} A promise that will be
|
|
2672
|
+
* resolved with the elements shadow root or rejected
|
|
2673
|
+
* with {@link NoSuchShadowRootError}
|
|
2674
|
+
*/
|
|
2675
|
+
getShadowRoot() {
|
|
2676
|
+
return this.execute_(new command.Command(command.Name.GET_SHADOW_ROOT))
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2676
2679
|
/**
|
|
2677
2680
|
* Get the visible (i.e. not hidden by CSS) innerText of this element,
|
|
2678
2681
|
* including sub-elements, without any leading or trailing whitespace.
|
|
@@ -2758,7 +2761,13 @@ class WebElement {
|
|
|
2758
2761
|
* when the form has been submitted.
|
|
2759
2762
|
*/
|
|
2760
2763
|
submit() {
|
|
2761
|
-
|
|
2764
|
+
const form = this.findElement({ xpath: './ancestor-or-self::form' })
|
|
2765
|
+
this.driver_.executeScript(
|
|
2766
|
+
"var e = arguments[0].ownerDocument.createEvent('Event');" +
|
|
2767
|
+
"e.initEvent('submit', true, true);" +
|
|
2768
|
+
'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }',
|
|
2769
|
+
form
|
|
2770
|
+
)
|
|
2762
2771
|
}
|
|
2763
2772
|
|
|
2764
2773
|
/**
|
|
@@ -2841,6 +2850,186 @@ class WebElementPromise extends WebElement {
|
|
|
2841
2850
|
}
|
|
2842
2851
|
}
|
|
2843
2852
|
|
|
2853
|
+
//////////////////////////////////////////////////////////////////////////////
|
|
2854
|
+
//
|
|
2855
|
+
// ShadowRoot
|
|
2856
|
+
//
|
|
2857
|
+
//////////////////////////////////////////////////////////////////////////////
|
|
2858
|
+
|
|
2859
|
+
/**
|
|
2860
|
+
* Represents a ShadowRoot of a {@link WebElement}. Provides functions to
|
|
2861
|
+
* retrieve elements that live in the DOM below the ShadowRoot.
|
|
2862
|
+
*/
|
|
2863
|
+
class ShadowRoot {
|
|
2864
|
+
|
|
2865
|
+
constructor(driver, id) {
|
|
2866
|
+
this.driver_ = driver
|
|
2867
|
+
this.id_ = id
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
/**
|
|
2871
|
+
* Extracts the encoded ShadowRoot ID from the object.
|
|
2872
|
+
*
|
|
2873
|
+
* @param {?} obj The object to extract the ID from.
|
|
2874
|
+
* @return {string} the extracted ID.
|
|
2875
|
+
* @throws {TypeError} if the object is not a valid encoded ID.
|
|
2876
|
+
*/
|
|
2877
|
+
static extractId(obj) {
|
|
2878
|
+
if (obj && typeof obj === 'object') {
|
|
2879
|
+
if (typeof obj[SHADOWROOT_ID_KEY] === 'string') {
|
|
2880
|
+
return obj[SHADOWROOT_ID_KEY]
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
throw new TypeError('object is not a ShadowRoot ID')
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
/**
|
|
2887
|
+
* @param {?} obj the object to test.
|
|
2888
|
+
* @return {boolean} whether the object is a valid encoded WebElement ID.
|
|
2889
|
+
*/
|
|
2890
|
+
static isId(obj) {
|
|
2891
|
+
return (
|
|
2892
|
+
obj &&
|
|
2893
|
+
typeof obj === 'object' &&
|
|
2894
|
+
typeof obj[SHADOWROOT_ID_KEY] === 'string')
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
/**
|
|
2898
|
+
* @return {!Object} Returns the serialized representation of this ShadowRoot.
|
|
2899
|
+
*/
|
|
2900
|
+
[Symbols.serialize]() {
|
|
2901
|
+
return this.getId()
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
/**
|
|
2905
|
+
* Schedules a command that targets this element with the parent WebDriver
|
|
2906
|
+
* instance. Will ensure this element's ID is included in the command
|
|
2907
|
+
* parameters under the "id" key.
|
|
2908
|
+
*
|
|
2909
|
+
* @param {!command.Command} command The command to schedule.
|
|
2910
|
+
* @return {!Promise<T>} A promise that will be resolved with the result.
|
|
2911
|
+
* @template T
|
|
2912
|
+
* @see WebDriver#schedule
|
|
2913
|
+
* @private
|
|
2914
|
+
*/
|
|
2915
|
+
execute_(command) {
|
|
2916
|
+
command.setParameter('id', this)
|
|
2917
|
+
return this.driver_.execute(command)
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
/**
|
|
2921
|
+
* Schedule a command to find a descendant of this ShadowROot. If the element
|
|
2922
|
+
* cannot be found, the returned promise will be rejected with a
|
|
2923
|
+
* {@linkplain error.NoSuchElementError NoSuchElementError}.
|
|
2924
|
+
*
|
|
2925
|
+
* The search criteria for an element may be defined using one of the static
|
|
2926
|
+
* factories on the {@link by.By} class, or as a short-hand
|
|
2927
|
+
* {@link ./by.ByHash} object. For example, the following two statements
|
|
2928
|
+
* are equivalent:
|
|
2929
|
+
*
|
|
2930
|
+
* var e1 = shadowroot.findElement(By.id('foo'));
|
|
2931
|
+
* var e2 = shadowroot.findElement({id:'foo'});
|
|
2932
|
+
*
|
|
2933
|
+
* You may also provide a custom locator function, which takes as input this
|
|
2934
|
+
* instance and returns a {@link WebElement}, or a promise that will resolve
|
|
2935
|
+
* to a WebElement. If the returned promise resolves to an array of
|
|
2936
|
+
* WebElements, WebDriver will use the first element. For example, to find the
|
|
2937
|
+
* first visible link on a page, you could write:
|
|
2938
|
+
*
|
|
2939
|
+
* var link = element.findElement(firstVisibleLink);
|
|
2940
|
+
*
|
|
2941
|
+
* function firstVisibleLink(shadowRoot) {
|
|
2942
|
+
* var links = shadowRoot.findElements(By.tagName('a'));
|
|
2943
|
+
* return promise.filter(links, function(link) {
|
|
2944
|
+
* return link.isDisplayed();
|
|
2945
|
+
* });
|
|
2946
|
+
* }
|
|
2947
|
+
*
|
|
2948
|
+
* @param {!(by.By|Function)} locator The locator strategy to use when
|
|
2949
|
+
* searching for the element.
|
|
2950
|
+
* @return {!WebElementPromise} A WebElement that can be used to issue
|
|
2951
|
+
* commands against the located element. If the element is not found, the
|
|
2952
|
+
* element will be invalidated and all scheduled commands aborted.
|
|
2953
|
+
*/
|
|
2954
|
+
findElement(locator) {
|
|
2955
|
+
locator = by.checkedLocator(locator)
|
|
2956
|
+
let id
|
|
2957
|
+
if (typeof locator === 'function') {
|
|
2958
|
+
id = this.driver_.findElementInternal_(locator, this)
|
|
2959
|
+
} else {
|
|
2960
|
+
let cmd = new command.Command(command.Name.FIND_ELEMENT_FROM_SHADOWROOT)
|
|
2961
|
+
.setParameter('using', locator.using)
|
|
2962
|
+
.setParameter('value', locator.value)
|
|
2963
|
+
id = this.execute_(cmd)
|
|
2964
|
+
}
|
|
2965
|
+
return new ShadowRootPromise(this.driver_, id)
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
/**
|
|
2969
|
+
* Locates all of the descendants of this element that match the given search
|
|
2970
|
+
* criteria.
|
|
2971
|
+
*
|
|
2972
|
+
* @param {!(by.By|Function)} locator The locator strategy to use when
|
|
2973
|
+
* searching for the element.
|
|
2974
|
+
* @return {!Promise<!Array<!WebElement>>} A promise that will resolve to an
|
|
2975
|
+
* array of WebElements.
|
|
2976
|
+
*/
|
|
2977
|
+
async findElements(locator) {
|
|
2978
|
+
locator = by.checkedLocator(locator)
|
|
2979
|
+
if (typeof locator === 'function') {
|
|
2980
|
+
return this.driver_.findElementsInternal_(locator, this)
|
|
2981
|
+
} else {
|
|
2982
|
+
let cmd = new command.Command(command.Name.FIND_ELEMENTS_FROM_SHADOWROOT)
|
|
2983
|
+
.setParameter('using', locator.using)
|
|
2984
|
+
.setParameter('value', locator.value)
|
|
2985
|
+
let result = await this.execute_(cmd)
|
|
2986
|
+
return Array.isArray(result) ? result : []
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
getId() {
|
|
2991
|
+
return this.id_
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
/**
|
|
2996
|
+
* ShadowRootPromise is a promise that will be fulfilled with a WebElement.
|
|
2997
|
+
* This serves as a forward proxy on ShadowRoot, allowing calls to be
|
|
2998
|
+
* scheduled without directly on this instance before the underlying
|
|
2999
|
+
* ShadowRoot has been fulfilled.
|
|
3000
|
+
*
|
|
3001
|
+
* @implements { IThenable<!ShadowRoot>}
|
|
3002
|
+
* @final
|
|
3003
|
+
*/
|
|
3004
|
+
class ShadowRootPromise extends ShadowRoot {
|
|
3005
|
+
/**
|
|
3006
|
+
* @param {!WebDriver} driver The parent WebDriver instance for this
|
|
3007
|
+
* element.
|
|
3008
|
+
* @param {!Promise<!ShadowRoot>} el A promise
|
|
3009
|
+
* that will resolve to the promised element.
|
|
3010
|
+
*/
|
|
3011
|
+
constructor(driver, shadow) {
|
|
3012
|
+
super(driver, 'unused')
|
|
3013
|
+
|
|
3014
|
+
/** @override */
|
|
3015
|
+
this.then = shadow.then.bind(shadow)
|
|
3016
|
+
|
|
3017
|
+
/** @override */
|
|
3018
|
+
this.catch = shadow.catch.bind(shadow)
|
|
3019
|
+
|
|
3020
|
+
/**
|
|
3021
|
+
* Defers returning the ShadowRoot ID until the wrapped WebElement has been
|
|
3022
|
+
* resolved.
|
|
3023
|
+
* @override
|
|
3024
|
+
*/
|
|
3025
|
+
this.getId = function () {
|
|
3026
|
+
return shadow.then(function (shadow) {
|
|
3027
|
+
return shadow.getId()
|
|
3028
|
+
})
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
|
|
2844
3033
|
//////////////////////////////////////////////////////////////////////////////
|
|
2845
3034
|
//
|
|
2846
3035
|
// Alert
|
|
@@ -2998,6 +3187,7 @@ module.exports = {
|
|
|
2998
3187
|
Logs,
|
|
2999
3188
|
Navigation,
|
|
3000
3189
|
Options,
|
|
3190
|
+
ShadowRoot,
|
|
3001
3191
|
TargetLocator,
|
|
3002
3192
|
IWebDriver,
|
|
3003
3193
|
WebDriver,
|
package/net/portprober.js
CHANGED
|
@@ -29,7 +29,7 @@ const net = require('net')
|
|
|
29
29
|
*/
|
|
30
30
|
function isFree(port, opt_host) {
|
|
31
31
|
return new Promise((resolve, reject) => {
|
|
32
|
-
|
|
32
|
+
const server = net.createServer().on('error', function (e) {
|
|
33
33
|
if (e.code === 'EADDRINUSE' || e.code === 'EACCES') {
|
|
34
34
|
resolve(false)
|
|
35
35
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selenium-webdriver",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "The official WebDriver JavaScript bindings from the Selenium project",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"jszip": "^3.6.0",
|
|
27
|
-
"rimraf": "^3.0.2",
|
|
28
27
|
"tmp": "^0.2.1",
|
|
29
28
|
"ws": ">=7.4.6"
|
|
30
29
|
},
|
package/remote/index.js
CHANGED
|
@@ -464,7 +464,7 @@ class SeleniumServer extends DriverService {
|
|
|
464
464
|
throw Error('Path to the Selenium jar not specified')
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
-
|
|
467
|
+
const options = opt_options || {}
|
|
468
468
|
|
|
469
469
|
if (options.port < 0) {
|
|
470
470
|
throw Error('Port must be >= 0: ' + options.port)
|
package/safari.js
CHANGED
|
@@ -154,9 +154,9 @@ class Driver extends webdriver.WebDriver {
|
|
|
154
154
|
service.start().then((url) => new http.HttpClient(url))
|
|
155
155
|
)
|
|
156
156
|
|
|
157
|
-
return /** @type {!Driver} */ (
|
|
158
|
-
|
|
159
|
-
)
|
|
157
|
+
return /** @type {!Driver} */ (super.createSession(executor, caps, () =>
|
|
158
|
+
service.kill()
|
|
159
|
+
))
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|