selenium-webdriver 4.0.0-beta.4 → 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 +55 -1
- package/README.md +4 -2
- package/chromium.js +67 -25
- package/devtools/CDPConnection.js +9 -4
- package/devtools/networkinterceptor.js +88 -0
- package/edge.js +14 -20
- package/firefox.js +25 -1
- package/http/index.js +36 -15
- package/http/util.js +14 -20
- package/ie.js +8 -8
- package/index.js +17 -1
- package/io/index.js +47 -46
- package/lib/atoms/find-elements.js +2 -2
- package/lib/atoms/get-attribute.js +25 -25
- package/lib/by.js +42 -24
- package/lib/capabilities.js +11 -2
- package/lib/command.js +5 -0
- package/lib/error.js +24 -0
- package/lib/http.js +12 -7
- package/lib/input.js +4 -15
- package/lib/webdriver.js +372 -129
- package/net/portprober.js +1 -1
- package/opera.js +3 -3
- package/package.json +2 -3
- package/remote/index.js +3 -3
- package/testing/index.js +9 -10
package/lib/webdriver.js
CHANGED
|
@@ -29,7 +29,6 @@ const input = require('./input')
|
|
|
29
29
|
const logging = require('./logging')
|
|
30
30
|
const promise = require('./promise')
|
|
31
31
|
const Symbols = require('./symbols')
|
|
32
|
-
const cdpTargets = ['page', 'browser']
|
|
33
32
|
const cdp = require('../devtools/CDPConnection')
|
|
34
33
|
const WebSocket = require('ws')
|
|
35
34
|
const http = require('../http/index')
|
|
@@ -37,19 +36,21 @@ const fs = require('fs')
|
|
|
37
36
|
const { Capabilities } = require('./capabilities')
|
|
38
37
|
const path = require('path')
|
|
39
38
|
const { NoSuchElementError } = require('./error')
|
|
39
|
+
const cdpTargets = ['page', 'browser']
|
|
40
40
|
|
|
41
41
|
// Capability names that are defined in the W3C spec.
|
|
42
42
|
const W3C_CAPABILITY_NAMES = new Set([
|
|
43
43
|
'acceptInsecureCerts',
|
|
44
44
|
'browserName',
|
|
45
45
|
'browserVersion',
|
|
46
|
-
'platformName',
|
|
47
46
|
'pageLoadStrategy',
|
|
47
|
+
'platformName',
|
|
48
48
|
'proxy',
|
|
49
49
|
'setWindowRect',
|
|
50
|
-
'timeouts',
|
|
51
50
|
'strictFileInteractability',
|
|
51
|
+
'timeouts',
|
|
52
52
|
'unhandledPromptBehavior',
|
|
53
|
+
'webSocketUrl',
|
|
53
54
|
])
|
|
54
55
|
|
|
55
56
|
/**
|
|
@@ -208,6 +209,9 @@ function fromWireValue(driver, value) {
|
|
|
208
209
|
} else if (WebElement.isId(value)) {
|
|
209
210
|
let id = WebElement.extractId(value)
|
|
210
211
|
value = new WebElement(driver, id)
|
|
212
|
+
} else if (ShadowRoot.isId(value)) {
|
|
213
|
+
let id = ShadowRoot.extractId(value)
|
|
214
|
+
value = new ShadowRoot(driver, id)
|
|
211
215
|
} else if (value && typeof value === 'object') {
|
|
212
216
|
let result = {}
|
|
213
217
|
for (let key in value) {
|
|
@@ -258,18 +262,18 @@ class IWebDriver {
|
|
|
258
262
|
/**
|
|
259
263
|
* @return {!command.Executor} The command executor used by this instance.
|
|
260
264
|
*/
|
|
261
|
-
getExecutor() {}
|
|
265
|
+
getExecutor() { }
|
|
262
266
|
|
|
263
267
|
/**
|
|
264
268
|
* @return {!Promise<!Session>} A promise for this client's session.
|
|
265
269
|
*/
|
|
266
|
-
getSession() {}
|
|
270
|
+
getSession() { }
|
|
267
271
|
|
|
268
272
|
/**
|
|
269
273
|
* @return {!Promise<!Capabilities>} A promise that will resolve with
|
|
270
274
|
* the this instance's capabilities.
|
|
271
275
|
*/
|
|
272
|
-
getCapabilities() {}
|
|
276
|
+
getCapabilities() { }
|
|
273
277
|
|
|
274
278
|
/**
|
|
275
279
|
* Terminates the browser session. After calling quit, this instance will be
|
|
@@ -279,7 +283,7 @@ class IWebDriver {
|
|
|
279
283
|
* @return {!Promise<void>} A promise that will be resolved when the
|
|
280
284
|
* command has completed.
|
|
281
285
|
*/
|
|
282
|
-
quit() {}
|
|
286
|
+
quit() { }
|
|
283
287
|
|
|
284
288
|
/**
|
|
285
289
|
* Creates a new action sequence using this driver. The sequence will not be
|
|
@@ -458,7 +462,7 @@ class IWebDriver {
|
|
|
458
462
|
timeout = undefined, // eslint-disable-line
|
|
459
463
|
message = undefined, // eslint-disable-line
|
|
460
464
|
pollTimeout = undefined // eslint-disable-line
|
|
461
|
-
) {}
|
|
465
|
+
) { }
|
|
462
466
|
|
|
463
467
|
/**
|
|
464
468
|
* Makes the driver sleep for the given amount of time.
|
|
@@ -475,7 +479,7 @@ class IWebDriver {
|
|
|
475
479
|
* @return {!Promise<string>} A promise that will be resolved with the current
|
|
476
480
|
* window handle.
|
|
477
481
|
*/
|
|
478
|
-
getWindowHandle() {}
|
|
482
|
+
getWindowHandle() { }
|
|
479
483
|
|
|
480
484
|
/**
|
|
481
485
|
* Retrieves a list of all available window handles.
|
|
@@ -483,7 +487,7 @@ class IWebDriver {
|
|
|
483
487
|
* @return {!Promise<!Array<string>>} A promise that will be resolved with an
|
|
484
488
|
* array of window handles.
|
|
485
489
|
*/
|
|
486
|
-
getAllWindowHandles() {}
|
|
490
|
+
getAllWindowHandles() { }
|
|
487
491
|
|
|
488
492
|
/**
|
|
489
493
|
* Retrieves the current page's source. The returned source is a representation
|
|
@@ -493,7 +497,7 @@ class IWebDriver {
|
|
|
493
497
|
* @return {!Promise<string>} A promise that will be resolved with the current
|
|
494
498
|
* page source.
|
|
495
499
|
*/
|
|
496
|
-
getPageSource() {}
|
|
500
|
+
getPageSource() { }
|
|
497
501
|
|
|
498
502
|
/**
|
|
499
503
|
* Closes the current window.
|
|
@@ -501,7 +505,7 @@ class IWebDriver {
|
|
|
501
505
|
* @return {!Promise<void>} A promise that will be resolved when this command
|
|
502
506
|
* has completed.
|
|
503
507
|
*/
|
|
504
|
-
close() {}
|
|
508
|
+
close() { }
|
|
505
509
|
|
|
506
510
|
/**
|
|
507
511
|
* Navigates to the given URL.
|
|
@@ -518,7 +522,7 @@ class IWebDriver {
|
|
|
518
522
|
* @return {!Promise<string>} A promise that will be resolved with the
|
|
519
523
|
* current URL.
|
|
520
524
|
*/
|
|
521
|
-
getCurrentUrl() {}
|
|
525
|
+
getCurrentUrl() { }
|
|
522
526
|
|
|
523
527
|
/**
|
|
524
528
|
* Retrieves the current page title.
|
|
@@ -526,7 +530,7 @@ class IWebDriver {
|
|
|
526
530
|
* @return {!Promise<string>} A promise that will be resolved with the current
|
|
527
531
|
* page's title.
|
|
528
532
|
*/
|
|
529
|
-
getTitle() {}
|
|
533
|
+
getTitle() { }
|
|
530
534
|
|
|
531
535
|
/**
|
|
532
536
|
* Locates an element on the page. If the element cannot be found, a
|
|
@@ -590,23 +594,23 @@ class IWebDriver {
|
|
|
590
594
|
* @return {!Promise<string>} A promise that will be resolved to the
|
|
591
595
|
* screenshot as a base-64 encoded PNG.
|
|
592
596
|
*/
|
|
593
|
-
takeScreenshot() {}
|
|
597
|
+
takeScreenshot() { }
|
|
594
598
|
|
|
595
599
|
/**
|
|
596
600
|
* @return {!Options} The options interface for this instance.
|
|
597
601
|
*/
|
|
598
|
-
manage() {}
|
|
602
|
+
manage() { }
|
|
599
603
|
|
|
600
604
|
/**
|
|
601
605
|
* @return {!Navigation} The navigation interface for this instance.
|
|
602
606
|
*/
|
|
603
|
-
navigate() {}
|
|
607
|
+
navigate() { }
|
|
604
608
|
|
|
605
609
|
/**
|
|
606
610
|
* @return {!TargetLocator} The target locator interface for this
|
|
607
611
|
* instance.
|
|
608
612
|
*/
|
|
609
|
-
switchTo() {}
|
|
613
|
+
switchTo() { }
|
|
610
614
|
|
|
611
615
|
/**
|
|
612
616
|
*
|
|
@@ -665,7 +669,7 @@ class WebDriver {
|
|
|
665
669
|
// If session is a rejected promise, add a no-op rejection handler.
|
|
666
670
|
// This effectively hides setup errors until users attempt to interact
|
|
667
671
|
// with the session.
|
|
668
|
-
this.session_.catch(function () {})
|
|
672
|
+
this.session_.catch(function () { })
|
|
669
673
|
|
|
670
674
|
/** @private {!command.Executor} */
|
|
671
675
|
this.executor_ = executor
|
|
@@ -761,12 +765,12 @@ class WebDriver {
|
|
|
761
765
|
this.session_ = Promise.reject(
|
|
762
766
|
new error.NoSuchSessionError(
|
|
763
767
|
'This driver instance does not have a valid session ID ' +
|
|
764
|
-
|
|
768
|
+
'(did you call WebDriver.quit()?) and may no longer be used.'
|
|
765
769
|
)
|
|
766
770
|
)
|
|
767
771
|
|
|
768
772
|
// Only want the session rejection to bubble if accessed.
|
|
769
|
-
this.session_.catch(function () {})
|
|
773
|
+
this.session_.catch(function () { })
|
|
770
774
|
|
|
771
775
|
if (this.onQuit_) {
|
|
772
776
|
return this.onQuit_.call(void 0)
|
|
@@ -827,18 +831,15 @@ class WebDriver {
|
|
|
827
831
|
let timeoutMessage = resolveWaitMessage(message)
|
|
828
832
|
reject(
|
|
829
833
|
new error.TimeoutError(
|
|
830
|
-
`${timeoutMessage}Timed out waiting for promise to resolve after ${
|
|
831
|
-
Date.now() - start
|
|
834
|
+
`${timeoutMessage}Timed out waiting for promise to resolve after ${Date.now() - start
|
|
832
835
|
}ms`
|
|
833
836
|
)
|
|
834
837
|
)
|
|
835
838
|
} catch (ex) {
|
|
836
839
|
reject(
|
|
837
840
|
new error.TimeoutError(
|
|
838
|
-
`${
|
|
839
|
-
|
|
840
|
-
}\nTimed out waiting for promise to resolve after ${
|
|
841
|
-
Date.now() - start
|
|
841
|
+
`${ex.message
|
|
842
|
+
}\nTimed out waiting for promise to resolve after ${Date.now() - start
|
|
842
843
|
}ms`
|
|
843
844
|
)
|
|
844
845
|
)
|
|
@@ -868,7 +869,7 @@ class WebDriver {
|
|
|
868
869
|
if (typeof fn !== 'function') {
|
|
869
870
|
throw TypeError(
|
|
870
871
|
'Wait condition must be a promise-like object, function, or a ' +
|
|
871
|
-
|
|
872
|
+
'Condition object'
|
|
872
873
|
)
|
|
873
874
|
}
|
|
874
875
|
|
|
@@ -920,7 +921,7 @@ class WebDriver {
|
|
|
920
921
|
if (!(value instanceof WebElement)) {
|
|
921
922
|
throw TypeError(
|
|
922
923
|
'WebElementCondition did not resolve to a WebElement: ' +
|
|
923
|
-
|
|
924
|
+
Object.prototype.toString.call(value)
|
|
924
925
|
)
|
|
925
926
|
}
|
|
926
927
|
return value
|
|
@@ -1201,19 +1202,43 @@ class WebDriver {
|
|
|
1201
1202
|
caps['map_'].get('moz:debuggerAddress') ||
|
|
1202
1203
|
new Map()
|
|
1203
1204
|
const debuggerUrl = seCdp || vendorInfo['debuggerAddress'] || vendorInfo
|
|
1204
|
-
this._wsUrl = await this.getWsUrl(debuggerUrl, target)
|
|
1205
|
-
|
|
1205
|
+
this._wsUrl = await this.getWsUrl(debuggerUrl, target, caps)
|
|
1206
1206
|
return new Promise((resolve, reject) => {
|
|
1207
1207
|
try {
|
|
1208
1208
|
this._wsConnection = new WebSocket(this._wsUrl)
|
|
1209
|
+
this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
|
|
1209
1210
|
} catch (err) {
|
|
1210
1211
|
reject(err)
|
|
1211
1212
|
return
|
|
1212
1213
|
}
|
|
1213
1214
|
|
|
1214
|
-
this._wsConnection.on('open', () => {
|
|
1215
|
-
this.
|
|
1216
|
-
|
|
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
|
+
}
|
|
1217
1242
|
})
|
|
1218
1243
|
|
|
1219
1244
|
this._wsConnection.on('error', (error) => {
|
|
@@ -1222,33 +1247,47 @@ class WebDriver {
|
|
|
1222
1247
|
})
|
|
1223
1248
|
}
|
|
1224
1249
|
|
|
1250
|
+
async getCdpTargets() {
|
|
1251
|
+
this._cdpConnection.execute('Target.getTargets')
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1225
1254
|
/**
|
|
1226
1255
|
* Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
|
|
1227
1256
|
* @param {string} debuggerAddress
|
|
1228
|
-
* @param
|
|
1257
|
+
* @param target
|
|
1258
|
+
* @param caps
|
|
1229
1259
|
* @return {string} Returns parsed webSocketDebuggerUrl obtained from the http request
|
|
1230
1260
|
*/
|
|
1231
|
-
async getWsUrl(debuggerAddress, target) {
|
|
1261
|
+
async getWsUrl(debuggerAddress, target, caps) {
|
|
1232
1262
|
if (target && cdpTargets.indexOf(target.toLowerCase()) === -1) {
|
|
1233
1263
|
throw new error.InvalidArgumentError('invalid target value')
|
|
1234
1264
|
}
|
|
1265
|
+
|
|
1266
|
+
if (debuggerAddress.match(/\/se\/cdp/)) {
|
|
1267
|
+
return debuggerAddress
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1235
1270
|
let path
|
|
1236
|
-
if (target === 'page') {
|
|
1271
|
+
if (target === 'page' && caps['map_'].get('browserName') !== 'firefox') {
|
|
1237
1272
|
path = '/json'
|
|
1273
|
+
} else if (
|
|
1274
|
+
target === 'page' &&
|
|
1275
|
+
caps['map_'].get('browserName') === 'firefox'
|
|
1276
|
+
) {
|
|
1277
|
+
path = '/json/list'
|
|
1238
1278
|
} else {
|
|
1239
1279
|
path = '/json/version'
|
|
1240
1280
|
}
|
|
1281
|
+
|
|
1241
1282
|
let request = new http.Request('GET', path)
|
|
1242
1283
|
let client = new http.HttpClient('http://' + debuggerAddress)
|
|
1243
1284
|
let response = await client.send(request)
|
|
1244
|
-
|
|
1285
|
+
|
|
1245
1286
|
if (target.toLowerCase() === 'page') {
|
|
1246
|
-
|
|
1287
|
+
return JSON.parse(response.body)[0]['webSocketDebuggerUrl']
|
|
1247
1288
|
} else {
|
|
1248
|
-
|
|
1289
|
+
return JSON.parse(response.body)['webSocketDebuggerUrl']
|
|
1249
1290
|
}
|
|
1250
|
-
|
|
1251
|
-
return url
|
|
1252
1291
|
}
|
|
1253
1292
|
|
|
1254
1293
|
/**
|
|
@@ -1260,54 +1299,81 @@ class WebDriver {
|
|
|
1260
1299
|
* @param connection CDP Connection
|
|
1261
1300
|
*/
|
|
1262
1301
|
async register(username, password, connection) {
|
|
1302
|
+
this._wsConnection.on('message', (message) => {
|
|
1303
|
+
const params = JSON.parse(message)
|
|
1304
|
+
|
|
1305
|
+
if (params.method === 'Fetch.authRequired') {
|
|
1306
|
+
const requestParams = params['params']
|
|
1307
|
+
connection.execute('Fetch.continueWithAuth', {
|
|
1308
|
+
requestId: requestParams['requestId'],
|
|
1309
|
+
authChallengeResponse: {
|
|
1310
|
+
response: 'ProvideCredentials',
|
|
1311
|
+
username: username,
|
|
1312
|
+
password: password,
|
|
1313
|
+
},
|
|
1314
|
+
})
|
|
1315
|
+
} else if (params.method === 'Fetch.requestPaused') {
|
|
1316
|
+
const requestPausedParams = params['params']
|
|
1317
|
+
connection.execute('Fetch.continueRequest', {
|
|
1318
|
+
requestId: requestPausedParams['requestId'],
|
|
1319
|
+
})
|
|
1320
|
+
}
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1323
|
+
await connection.execute(
|
|
1324
|
+
'Fetch.enable',
|
|
1325
|
+
{
|
|
1326
|
+
handleAuthRequests: true,
|
|
1327
|
+
},
|
|
1328
|
+
null
|
|
1329
|
+
)
|
|
1263
1330
|
await connection.execute(
|
|
1264
1331
|
'Network.setCacheDisabled',
|
|
1265
|
-
this.getRandomNumber(1, 10),
|
|
1266
1332
|
{
|
|
1267
1333
|
cacheDisabled: true,
|
|
1268
1334
|
},
|
|
1269
1335
|
null
|
|
1270
1336
|
)
|
|
1337
|
+
}
|
|
1271
1338
|
|
|
1339
|
+
/**
|
|
1340
|
+
* Handle Network interception requests
|
|
1341
|
+
* @param connection WebSocket connection to the browser
|
|
1342
|
+
* @param httpResponse Object representing what we are intercepting
|
|
1343
|
+
* as well as what should be returned.
|
|
1344
|
+
* @param callback callback called when we intercept requests.
|
|
1345
|
+
*/
|
|
1346
|
+
async onIntercept(connection, httpResponse, callback) {
|
|
1272
1347
|
this._wsConnection.on('message', (message) => {
|
|
1273
1348
|
const params = JSON.parse(message)
|
|
1274
|
-
|
|
1275
|
-
if (params.method === 'Fetch.authRequired') {
|
|
1276
|
-
const requestParams = params['params']
|
|
1277
|
-
connection.execute(
|
|
1278
|
-
'Fetch.continueWithAuth',
|
|
1279
|
-
this.getRandomNumber(1, 10),
|
|
1280
|
-
{
|
|
1281
|
-
requestId: requestParams['requestId'],
|
|
1282
|
-
authChallengeResponse: {
|
|
1283
|
-
response: 'ProvideCredentials',
|
|
1284
|
-
username: username,
|
|
1285
|
-
password: password,
|
|
1286
|
-
},
|
|
1287
|
-
}
|
|
1288
|
-
)
|
|
1289
|
-
} else if (params.method === 'Fetch.requestPaused') {
|
|
1349
|
+
if (params.method === 'Fetch.requestPaused') {
|
|
1290
1350
|
const requestPausedParams = params['params']
|
|
1291
|
-
|
|
1292
|
-
'Fetch.continueRequest',
|
|
1293
|
-
this.getRandomNumber(1, 10),
|
|
1294
|
-
{
|
|
1351
|
+
if (requestPausedParams.request.url == httpResponse.urlToIntercept) {
|
|
1352
|
+
connection.execute('Fetch.continueRequest', {
|
|
1295
1353
|
requestId: requestPausedParams['requestId'],
|
|
1296
|
-
|
|
1297
|
-
|
|
1354
|
+
url: httpResponse.urlToIntercept,
|
|
1355
|
+
method: httpResponse.method,
|
|
1356
|
+
headers: httpResponse.headers,
|
|
1357
|
+
postData: httpResponse.body,
|
|
1358
|
+
})
|
|
1359
|
+
callback()
|
|
1360
|
+
} else {
|
|
1361
|
+
connection.execute('Fetch.continueRequest', {
|
|
1362
|
+
requestId: requestPausedParams['requestId'],
|
|
1363
|
+
})
|
|
1364
|
+
}
|
|
1298
1365
|
}
|
|
1299
1366
|
})
|
|
1300
1367
|
|
|
1368
|
+
await connection.execute('Fetch.enable', {}, null)
|
|
1301
1369
|
await connection.execute(
|
|
1302
|
-
'
|
|
1303
|
-
1,
|
|
1370
|
+
'Network.setCacheDisabled',
|
|
1304
1371
|
{
|
|
1305
|
-
|
|
1372
|
+
cacheDisabled: true,
|
|
1306
1373
|
},
|
|
1307
1374
|
null
|
|
1308
1375
|
)
|
|
1309
1376
|
}
|
|
1310
|
-
|
|
1311
1377
|
/**
|
|
1312
1378
|
*
|
|
1313
1379
|
* @param connection
|
|
@@ -1315,16 +1381,8 @@ class WebDriver {
|
|
|
1315
1381
|
* @returns {Promise<void>}
|
|
1316
1382
|
*/
|
|
1317
1383
|
async onLogEvent(connection, callback) {
|
|
1318
|
-
await connection.execute(
|
|
1319
|
-
'Runtime.enable',
|
|
1320
|
-
this.getRandomNumber(1, 10),
|
|
1321
|
-
{},
|
|
1322
|
-
null
|
|
1323
|
-
)
|
|
1324
|
-
|
|
1325
1384
|
this._wsConnection.on('message', (message) => {
|
|
1326
1385
|
const params = JSON.parse(message)
|
|
1327
|
-
|
|
1328
1386
|
if (params.method === 'Runtime.consoleAPICalled') {
|
|
1329
1387
|
const consoleEventParams = params['params']
|
|
1330
1388
|
let event = {
|
|
@@ -1335,7 +1393,21 @@ class WebDriver {
|
|
|
1335
1393
|
|
|
1336
1394
|
callback(event)
|
|
1337
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
|
+
|
|
1338
1409
|
})
|
|
1410
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1339
1411
|
}
|
|
1340
1412
|
|
|
1341
1413
|
/**
|
|
@@ -1345,12 +1417,7 @@ class WebDriver {
|
|
|
1345
1417
|
* @returns {Promise<void>}
|
|
1346
1418
|
*/
|
|
1347
1419
|
async onLogException(connection, callback) {
|
|
1348
|
-
await connection.execute(
|
|
1349
|
-
'Runtime.enable',
|
|
1350
|
-
this.getRandomNumber(1, 10),
|
|
1351
|
-
{},
|
|
1352
|
-
null
|
|
1353
|
-
)
|
|
1420
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1354
1421
|
|
|
1355
1422
|
this._wsConnection.on('message', (message) => {
|
|
1356
1423
|
const params = JSON.parse(message)
|
|
@@ -1373,22 +1440,11 @@ class WebDriver {
|
|
|
1373
1440
|
* @returns {Promise<void>}
|
|
1374
1441
|
*/
|
|
1375
1442
|
async logMutationEvents(connection, callback) {
|
|
1376
|
-
await connection.execute(
|
|
1377
|
-
|
|
1378
|
-
this.getRandomNumber(1, 10),
|
|
1379
|
-
{},
|
|
1380
|
-
null
|
|
1381
|
-
)
|
|
1382
|
-
await connection.execute(
|
|
1383
|
-
'Page.enable',
|
|
1384
|
-
this.getRandomNumber(1, 10),
|
|
1385
|
-
{},
|
|
1386
|
-
null
|
|
1387
|
-
)
|
|
1443
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1444
|
+
await connection.execute('Page.enable', {}, null)
|
|
1388
1445
|
|
|
1389
1446
|
await connection.execute(
|
|
1390
1447
|
'Runtime.addBinding',
|
|
1391
|
-
this.getRandomNumber(1, 10),
|
|
1392
1448
|
{
|
|
1393
1449
|
name: '__webdriver_attribute',
|
|
1394
1450
|
},
|
|
@@ -1418,7 +1474,6 @@ class WebDriver {
|
|
|
1418
1474
|
|
|
1419
1475
|
await connection.execute(
|
|
1420
1476
|
'Page.addScriptToEvaluateOnNewDocument',
|
|
1421
|
-
this.getRandomNumber(1, 10),
|
|
1422
1477
|
{
|
|
1423
1478
|
source: mutationListener,
|
|
1424
1479
|
},
|
|
@@ -1430,7 +1485,7 @@ class WebDriver {
|
|
|
1430
1485
|
if (params.method === 'Runtime.bindingCalled') {
|
|
1431
1486
|
let payload = JSON.parse(params['params']['payload'])
|
|
1432
1487
|
let elements = await this.findElements({
|
|
1433
|
-
css: '*[data-__webdriver_id=' + payload['target'],
|
|
1488
|
+
css: '*[data-__webdriver_id=' + by.escapeCss(payload['target']) + ']',
|
|
1434
1489
|
})
|
|
1435
1490
|
|
|
1436
1491
|
if (elements.length === 0) {
|
|
@@ -1447,10 +1502,6 @@ class WebDriver {
|
|
|
1447
1502
|
}
|
|
1448
1503
|
})
|
|
1449
1504
|
}
|
|
1450
|
-
|
|
1451
|
-
getRandomNumber(min, max) {
|
|
1452
|
-
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
1453
|
-
}
|
|
1454
1505
|
}
|
|
1455
1506
|
|
|
1456
1507
|
/**
|
|
@@ -1732,7 +1783,7 @@ class Options {
|
|
|
1732
1783
|
} else if (typeof value !== 'undefined') {
|
|
1733
1784
|
throw TypeError(
|
|
1734
1785
|
'invalid timeouts configuration:' +
|
|
1735
|
-
|
|
1786
|
+
` expected "${key}" to be a number, got ${typeof value}`
|
|
1736
1787
|
)
|
|
1737
1788
|
}
|
|
1738
1789
|
}
|
|
@@ -1793,7 +1844,7 @@ function legacyTimeout(driver, type, ms) {
|
|
|
1793
1844
|
*
|
|
1794
1845
|
* @record
|
|
1795
1846
|
*/
|
|
1796
|
-
Options.Cookie = function () {}
|
|
1847
|
+
Options.Cookie = function () { }
|
|
1797
1848
|
|
|
1798
1849
|
/**
|
|
1799
1850
|
* The name of the cookie.
|
|
@@ -2108,7 +2159,7 @@ class TargetLocator {
|
|
|
2108
2159
|
* @return {!WebElementPromise} The active element.
|
|
2109
2160
|
*/
|
|
2110
2161
|
activeElement() {
|
|
2111
|
-
|
|
2162
|
+
const id = this.driver_.execute(
|
|
2112
2163
|
new command.Command(command.Name.GET_ACTIVE_ELEMENT)
|
|
2113
2164
|
)
|
|
2114
2165
|
return new WebElementPromise(this.driver_, id)
|
|
@@ -2200,7 +2251,7 @@ class TargetLocator {
|
|
|
2200
2251
|
* when the driver has changed focus to the new window.
|
|
2201
2252
|
*/
|
|
2202
2253
|
newWindow(typeHint) {
|
|
2203
|
-
|
|
2254
|
+
const driver = this.driver_
|
|
2204
2255
|
return this.driver_
|
|
2205
2256
|
.execute(
|
|
2206
2257
|
new command.Command(command.Name.SWITCH_TO_NEW_WINDOW).setParameter(
|
|
@@ -2222,10 +2273,10 @@ class TargetLocator {
|
|
|
2222
2273
|
* @return {!AlertPromise} The open alert.
|
|
2223
2274
|
*/
|
|
2224
2275
|
alert() {
|
|
2225
|
-
|
|
2276
|
+
const text = this.driver_.execute(
|
|
2226
2277
|
new command.Command(command.Name.GET_ALERT_TEXT)
|
|
2227
2278
|
)
|
|
2228
|
-
|
|
2279
|
+
const driver = this.driver_
|
|
2229
2280
|
return new AlertPromise(
|
|
2230
2281
|
driver,
|
|
2231
2282
|
text.then(function (text) {
|
|
@@ -2243,6 +2294,7 @@ class TargetLocator {
|
|
|
2243
2294
|
|
|
2244
2295
|
const LEGACY_ELEMENT_ID_KEY = 'ELEMENT'
|
|
2245
2296
|
const ELEMENT_ID_KEY = 'element-6066-11e4-a52e-4f735466cecf'
|
|
2297
|
+
const SHADOWROOT_ID_KEY = 'shadow-6066-11e4-a52e-4f735466cecf'
|
|
2246
2298
|
|
|
2247
2299
|
/**
|
|
2248
2300
|
* Represents a DOM element. WebElements can be found by searching from the
|
|
@@ -2498,18 +2550,18 @@ class WebElement {
|
|
|
2498
2550
|
*/
|
|
2499
2551
|
async sendKeys(...args) {
|
|
2500
2552
|
let keys = []
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2553
|
+
; (await Promise.all(args)).forEach((key) => {
|
|
2554
|
+
let type = typeof key
|
|
2555
|
+
if (type === 'number') {
|
|
2556
|
+
key = String(key)
|
|
2557
|
+
} else if (type !== 'string') {
|
|
2558
|
+
throw TypeError('each key must be a number of string; got ' + type)
|
|
2559
|
+
}
|
|
2508
2560
|
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2561
|
+
// The W3C protocol requires keys to be specified as an array where
|
|
2562
|
+
// each element is a single key.
|
|
2563
|
+
keys.push(...key.split(''))
|
|
2564
|
+
})
|
|
2513
2565
|
|
|
2514
2566
|
if (!this.driver_.fileDetector_) {
|
|
2515
2567
|
return this.execute_(
|
|
@@ -2555,7 +2607,7 @@ class WebElement {
|
|
|
2555
2607
|
* requested CSS value.
|
|
2556
2608
|
*/
|
|
2557
2609
|
getCssValue(cssStyleProperty) {
|
|
2558
|
-
|
|
2610
|
+
const name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY
|
|
2559
2611
|
return this.execute_(
|
|
2560
2612
|
new command.Command(name).setParameter('propertyName', cssStyleProperty)
|
|
2561
2613
|
)
|
|
@@ -2614,6 +2666,16 @@ class WebElement {
|
|
|
2614
2666
|
)
|
|
2615
2667
|
}
|
|
2616
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
|
+
|
|
2617
2679
|
/**
|
|
2618
2680
|
* Get the visible (i.e. not hidden by CSS) innerText of this element,
|
|
2619
2681
|
* including sub-elements, without any leading or trailing whitespace.
|
|
@@ -2699,7 +2761,13 @@ class WebElement {
|
|
|
2699
2761
|
* when the form has been submitted.
|
|
2700
2762
|
*/
|
|
2701
2763
|
submit() {
|
|
2702
|
-
|
|
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
|
+
)
|
|
2703
2771
|
}
|
|
2704
2772
|
|
|
2705
2773
|
/**
|
|
@@ -2728,18 +2796,12 @@ class WebElement {
|
|
|
2728
2796
|
* Take a screenshot of the visible region encompassed by this element's
|
|
2729
2797
|
* bounding rectangle.
|
|
2730
2798
|
*
|
|
2731
|
-
* @param {boolean=} scroll Optional argument that indicates whether the
|
|
2732
|
-
* element should be scrolled into view before taking a screenshot.
|
|
2733
|
-
* Defaults to false.
|
|
2734
2799
|
* @return {!Promise<string>} A promise that will be
|
|
2735
2800
|
* resolved to the screenshot as a base-64 encoded PNG.
|
|
2736
2801
|
*/
|
|
2737
|
-
takeScreenshot(
|
|
2802
|
+
takeScreenshot() {
|
|
2738
2803
|
return this.execute_(
|
|
2739
|
-
new command.Command(command.Name.TAKE_ELEMENT_SCREENSHOT)
|
|
2740
|
-
'scroll',
|
|
2741
|
-
scroll
|
|
2742
|
-
)
|
|
2804
|
+
new command.Command(command.Name.TAKE_ELEMENT_SCREENSHOT)
|
|
2743
2805
|
)
|
|
2744
2806
|
}
|
|
2745
2807
|
}
|
|
@@ -2788,6 +2850,186 @@ class WebElementPromise extends WebElement {
|
|
|
2788
2850
|
}
|
|
2789
2851
|
}
|
|
2790
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
|
+
|
|
2791
3033
|
//////////////////////////////////////////////////////////////////////////////
|
|
2792
3034
|
//
|
|
2793
3035
|
// Alert
|
|
@@ -2945,6 +3187,7 @@ module.exports = {
|
|
|
2945
3187
|
Logs,
|
|
2946
3188
|
Navigation,
|
|
2947
3189
|
Options,
|
|
3190
|
+
ShadowRoot,
|
|
2948
3191
|
TargetLocator,
|
|
2949
3192
|
IWebDriver,
|
|
2950
3193
|
WebDriver,
|