selenium-webdriver 4.0.0 → 4.1.2
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 +523 -552
- package/LICENSE +1 -1
- package/NOTICE +1 -1
- package/README.md +4 -4
- package/chrome.js +10 -6
- package/chromium.js +91 -30
- package/devtools/CDPConnection.js +9 -4
- package/devtools/networkinterceptor.js +60 -61
- package/edge.js +10 -8
- package/example/google_search.js +1 -1
- package/firefox.js +32 -17
- package/http/index.js +13 -14
- package/http/util.js +14 -20
- package/index.js +15 -15
- package/io/index.js +58 -55
- package/io/zip.js +1 -3
- package/lib/atoms/find-elements.js +2 -2
- package/lib/by.js +9 -10
- package/lib/capabilities.js +1 -1
- package/lib/command.js +6 -11
- package/lib/error.js +24 -19
- package/lib/http.js +62 -153
- package/lib/input.js +71 -5
- package/lib/promise.js +12 -17
- package/lib/proxy.js +15 -5
- package/lib/session.js +1 -1
- package/lib/webdriver.js +410 -226
- package/net/index.js +14 -7
- package/net/portprober.js +5 -4
- package/opera.js +19 -22
- package/package.json +2 -3
- package/remote/index.js +13 -10
- package/testing/index.js +21 -16
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) {
|
|
@@ -247,30 +250,30 @@ class IWebDriver {
|
|
|
247
250
|
* result.
|
|
248
251
|
* @template T
|
|
249
252
|
*/
|
|
250
|
-
execute(command) {
|
|
253
|
+
execute(command) {} // eslint-disable-line
|
|
251
254
|
|
|
252
255
|
/**
|
|
253
256
|
* Sets the {@linkplain input.FileDetector file detector} that should be
|
|
254
257
|
* used with this instance.
|
|
255
258
|
* @param {input.FileDetector} detector The detector to use or `null`.
|
|
256
259
|
*/
|
|
257
|
-
setFileDetector(detector) {
|
|
260
|
+
setFileDetector(detector) {} // eslint-disable-line
|
|
258
261
|
|
|
259
262
|
/**
|
|
260
263
|
* @return {!command.Executor} The command executor used by this instance.
|
|
261
264
|
*/
|
|
262
|
-
getExecutor() {
|
|
265
|
+
getExecutor() {}
|
|
263
266
|
|
|
264
267
|
/**
|
|
265
268
|
* @return {!Promise<!Session>} A promise for this client's session.
|
|
266
269
|
*/
|
|
267
|
-
getSession() {
|
|
270
|
+
getSession() {}
|
|
268
271
|
|
|
269
272
|
/**
|
|
270
273
|
* @return {!Promise<!Capabilities>} A promise that will resolve with
|
|
271
274
|
* the this instance's capabilities.
|
|
272
275
|
*/
|
|
273
|
-
getCapabilities() {
|
|
276
|
+
getCapabilities() {}
|
|
274
277
|
|
|
275
278
|
/**
|
|
276
279
|
* Terminates the browser session. After calling quit, this instance will be
|
|
@@ -280,7 +283,7 @@ class IWebDriver {
|
|
|
280
283
|
* @return {!Promise<void>} A promise that will be resolved when the
|
|
281
284
|
* command has completed.
|
|
282
285
|
*/
|
|
283
|
-
quit() {
|
|
286
|
+
quit() {}
|
|
284
287
|
|
|
285
288
|
/**
|
|
286
289
|
* Creates a new action sequence using this driver. The sequence will not be
|
|
@@ -293,7 +296,7 @@ class IWebDriver {
|
|
|
293
296
|
* for details).
|
|
294
297
|
* @return {!input.Actions} A new action sequence for this instance.
|
|
295
298
|
*/
|
|
296
|
-
actions(options) {
|
|
299
|
+
actions(options) {} // eslint-disable-line
|
|
297
300
|
|
|
298
301
|
/**
|
|
299
302
|
* Executes a snippet of JavaScript in the context of the currently selected
|
|
@@ -331,7 +334,7 @@ class IWebDriver {
|
|
|
331
334
|
* scripts return value.
|
|
332
335
|
* @template T
|
|
333
336
|
*/
|
|
334
|
-
executeScript(script, ...args) {
|
|
337
|
+
executeScript(script, ...args) {} // eslint-disable-line
|
|
335
338
|
|
|
336
339
|
/**
|
|
337
340
|
* Executes a snippet of asynchronous JavaScript in the context of the
|
|
@@ -408,7 +411,7 @@ class IWebDriver {
|
|
|
408
411
|
* value.
|
|
409
412
|
* @template T
|
|
410
413
|
*/
|
|
411
|
-
executeAsyncScript(script, ...args) {
|
|
414
|
+
executeAsyncScript(script, ...args) {} // eslint-disable-line
|
|
412
415
|
|
|
413
416
|
/**
|
|
414
417
|
* Waits for a condition to evaluate to a "truthy" value. The condition may be
|
|
@@ -459,7 +462,7 @@ class IWebDriver {
|
|
|
459
462
|
timeout = undefined, // eslint-disable-line
|
|
460
463
|
message = undefined, // eslint-disable-line
|
|
461
464
|
pollTimeout = undefined // eslint-disable-line
|
|
462
|
-
) {
|
|
465
|
+
) {}
|
|
463
466
|
|
|
464
467
|
/**
|
|
465
468
|
* Makes the driver sleep for the given amount of time.
|
|
@@ -468,7 +471,7 @@ class IWebDriver {
|
|
|
468
471
|
* @return {!Promise<void>} A promise that will be resolved when the sleep has
|
|
469
472
|
* finished.
|
|
470
473
|
*/
|
|
471
|
-
sleep(ms) {
|
|
474
|
+
sleep(ms) {} // eslint-disable-line
|
|
472
475
|
|
|
473
476
|
/**
|
|
474
477
|
* Retrieves the current window handle.
|
|
@@ -476,7 +479,7 @@ class IWebDriver {
|
|
|
476
479
|
* @return {!Promise<string>} A promise that will be resolved with the current
|
|
477
480
|
* window handle.
|
|
478
481
|
*/
|
|
479
|
-
getWindowHandle() {
|
|
482
|
+
getWindowHandle() {}
|
|
480
483
|
|
|
481
484
|
/**
|
|
482
485
|
* Retrieves a list of all available window handles.
|
|
@@ -484,7 +487,7 @@ class IWebDriver {
|
|
|
484
487
|
* @return {!Promise<!Array<string>>} A promise that will be resolved with an
|
|
485
488
|
* array of window handles.
|
|
486
489
|
*/
|
|
487
|
-
getAllWindowHandles() {
|
|
490
|
+
getAllWindowHandles() {}
|
|
488
491
|
|
|
489
492
|
/**
|
|
490
493
|
* Retrieves the current page's source. The returned source is a representation
|
|
@@ -494,7 +497,7 @@ class IWebDriver {
|
|
|
494
497
|
* @return {!Promise<string>} A promise that will be resolved with the current
|
|
495
498
|
* page source.
|
|
496
499
|
*/
|
|
497
|
-
getPageSource() {
|
|
500
|
+
getPageSource() {}
|
|
498
501
|
|
|
499
502
|
/**
|
|
500
503
|
* Closes the current window.
|
|
@@ -502,7 +505,7 @@ class IWebDriver {
|
|
|
502
505
|
* @return {!Promise<void>} A promise that will be resolved when this command
|
|
503
506
|
* has completed.
|
|
504
507
|
*/
|
|
505
|
-
close() {
|
|
508
|
+
close() {}
|
|
506
509
|
|
|
507
510
|
/**
|
|
508
511
|
* Navigates to the given URL.
|
|
@@ -511,7 +514,7 @@ class IWebDriver {
|
|
|
511
514
|
* @return {!Promise<void>} A promise that will be resolved when the document
|
|
512
515
|
* has finished loading.
|
|
513
516
|
*/
|
|
514
|
-
get(url) {
|
|
517
|
+
get(url) {} // eslint-disable-line
|
|
515
518
|
|
|
516
519
|
/**
|
|
517
520
|
* Retrieves the URL for the current page.
|
|
@@ -519,7 +522,7 @@ class IWebDriver {
|
|
|
519
522
|
* @return {!Promise<string>} A promise that will be resolved with the
|
|
520
523
|
* current URL.
|
|
521
524
|
*/
|
|
522
|
-
getCurrentUrl() {
|
|
525
|
+
getCurrentUrl() {}
|
|
523
526
|
|
|
524
527
|
/**
|
|
525
528
|
* Retrieves the current page title.
|
|
@@ -527,7 +530,7 @@ class IWebDriver {
|
|
|
527
530
|
* @return {!Promise<string>} A promise that will be resolved with the current
|
|
528
531
|
* page's title.
|
|
529
532
|
*/
|
|
530
|
-
getTitle() {
|
|
533
|
+
getTitle() {}
|
|
531
534
|
|
|
532
535
|
/**
|
|
533
536
|
* Locates an element on the page. If the element cannot be found, a
|
|
@@ -567,7 +570,7 @@ class IWebDriver {
|
|
|
567
570
|
* commands against the located element. If the element is not found, the
|
|
568
571
|
* element will be invalidated and all scheduled commands aborted.
|
|
569
572
|
*/
|
|
570
|
-
findElement(locator) {
|
|
573
|
+
findElement(locator) {} // eslint-disable-line
|
|
571
574
|
|
|
572
575
|
/**
|
|
573
576
|
* Search for multiple elements on the page. Refer to the documentation on
|
|
@@ -577,7 +580,7 @@ class IWebDriver {
|
|
|
577
580
|
* @return {!Promise<!Array<!WebElement>>} A promise that will resolve to an
|
|
578
581
|
* array of WebElements.
|
|
579
582
|
*/
|
|
580
|
-
findElements(locator) {
|
|
583
|
+
findElements(locator) {} // eslint-disable-line
|
|
581
584
|
|
|
582
585
|
/**
|
|
583
586
|
* Takes a screenshot of the current page. The driver makes a best effort to
|
|
@@ -591,23 +594,23 @@ class IWebDriver {
|
|
|
591
594
|
* @return {!Promise<string>} A promise that will be resolved to the
|
|
592
595
|
* screenshot as a base-64 encoded PNG.
|
|
593
596
|
*/
|
|
594
|
-
takeScreenshot() {
|
|
597
|
+
takeScreenshot() {}
|
|
595
598
|
|
|
596
599
|
/**
|
|
597
600
|
* @return {!Options} The options interface for this instance.
|
|
598
601
|
*/
|
|
599
|
-
manage() {
|
|
602
|
+
manage() {}
|
|
600
603
|
|
|
601
604
|
/**
|
|
602
605
|
* @return {!Navigation} The navigation interface for this instance.
|
|
603
606
|
*/
|
|
604
|
-
navigate() {
|
|
607
|
+
navigate() {}
|
|
605
608
|
|
|
606
609
|
/**
|
|
607
610
|
* @return {!TargetLocator} The target locator interface for this
|
|
608
611
|
* instance.
|
|
609
612
|
*/
|
|
610
|
-
switchTo() {
|
|
613
|
+
switchTo() {}
|
|
611
614
|
|
|
612
615
|
/**
|
|
613
616
|
*
|
|
@@ -626,7 +629,7 @@ class IWebDriver {
|
|
|
626
629
|
* shrinkToFit: (boolean|undefined)
|
|
627
630
|
* pageRanges: (<Array>|undefined)}} options.
|
|
628
631
|
*/
|
|
629
|
-
printPage(options) {
|
|
632
|
+
printPage(options) {} // eslint-disable-line
|
|
630
633
|
}
|
|
631
634
|
|
|
632
635
|
/**
|
|
@@ -666,7 +669,7 @@ class WebDriver {
|
|
|
666
669
|
// If session is a rejected promise, add a no-op rejection handler.
|
|
667
670
|
// This effectively hides setup errors until users attempt to interact
|
|
668
671
|
// with the session.
|
|
669
|
-
this.session_.catch(function () {
|
|
672
|
+
this.session_.catch(function () {})
|
|
670
673
|
|
|
671
674
|
/** @private {!command.Executor} */
|
|
672
675
|
this.executor_ = executor
|
|
@@ -762,12 +765,12 @@ class WebDriver {
|
|
|
762
765
|
this.session_ = Promise.reject(
|
|
763
766
|
new error.NoSuchSessionError(
|
|
764
767
|
'This driver instance does not have a valid session ID ' +
|
|
765
|
-
|
|
768
|
+
'(did you call WebDriver.quit()?) and may no longer be used.'
|
|
766
769
|
)
|
|
767
770
|
)
|
|
768
771
|
|
|
769
772
|
// Only want the session rejection to bubble if accessed.
|
|
770
|
-
this.session_.catch(function () {
|
|
773
|
+
this.session_.catch(function () {})
|
|
771
774
|
|
|
772
775
|
if (this.onQuit_) {
|
|
773
776
|
return this.onQuit_.call(void 0)
|
|
@@ -828,15 +831,18 @@ class WebDriver {
|
|
|
828
831
|
let timeoutMessage = resolveWaitMessage(message)
|
|
829
832
|
reject(
|
|
830
833
|
new error.TimeoutError(
|
|
831
|
-
`${timeoutMessage}Timed out waiting for promise to resolve after ${
|
|
834
|
+
`${timeoutMessage}Timed out waiting for promise to resolve after ${
|
|
835
|
+
Date.now() - start
|
|
832
836
|
}ms`
|
|
833
837
|
)
|
|
834
838
|
)
|
|
835
839
|
} catch (ex) {
|
|
836
840
|
reject(
|
|
837
841
|
new error.TimeoutError(
|
|
838
|
-
`${
|
|
839
|
-
|
|
842
|
+
`${
|
|
843
|
+
ex.message
|
|
844
|
+
}\nTimed out waiting for promise to resolve after ${
|
|
845
|
+
Date.now() - start
|
|
840
846
|
}ms`
|
|
841
847
|
)
|
|
842
848
|
)
|
|
@@ -866,7 +872,7 @@ class WebDriver {
|
|
|
866
872
|
if (typeof fn !== 'function') {
|
|
867
873
|
throw TypeError(
|
|
868
874
|
'Wait condition must be a promise-like object, function, or a ' +
|
|
869
|
-
|
|
875
|
+
'Condition object'
|
|
870
876
|
)
|
|
871
877
|
}
|
|
872
878
|
|
|
@@ -918,7 +924,7 @@ class WebDriver {
|
|
|
918
924
|
if (!(value instanceof WebElement)) {
|
|
919
925
|
throw TypeError(
|
|
920
926
|
'WebElementCondition did not resolve to a WebElement: ' +
|
|
921
|
-
|
|
927
|
+
Object.prototype.toString.call(value)
|
|
922
928
|
)
|
|
923
929
|
}
|
|
924
930
|
return value
|
|
@@ -1191,26 +1197,62 @@ class WebDriver {
|
|
|
1191
1197
|
* @return {!Promise<resolved>} A new CDP instance.
|
|
1192
1198
|
*/
|
|
1193
1199
|
async createCDPConnection(target) {
|
|
1200
|
+
let debuggerUrl = null
|
|
1201
|
+
|
|
1194
1202
|
const caps = await this.getCapabilities()
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1203
|
+
|
|
1204
|
+
if (process.env.SELENIUM_REMOTE_URL) {
|
|
1205
|
+
const host = new URL(process.env.SELENIUM_REMOTE_URL).host
|
|
1206
|
+
const sessionId = await this.getSession().then((session) =>
|
|
1207
|
+
session.getId()
|
|
1208
|
+
)
|
|
1209
|
+
debuggerUrl = `ws://${host}/session/${sessionId}/se/cdp`
|
|
1210
|
+
} else {
|
|
1211
|
+
const seCdp = caps['map_'].get('se:cdp')
|
|
1212
|
+
const vendorInfo =
|
|
1213
|
+
caps['map_'].get(this.VENDOR_COMMAND_PREFIX + ':chromeOptions') ||
|
|
1214
|
+
caps['map_'].get(this.VENDOR_CAPABILITY_PREFIX + ':edgeOptions') ||
|
|
1215
|
+
caps['map_'].get('moz:debuggerAddress') ||
|
|
1216
|
+
new Map()
|
|
1217
|
+
debuggerUrl = seCdp || vendorInfo['debuggerAddress'] || vendorInfo
|
|
1218
|
+
}
|
|
1202
1219
|
this._wsUrl = await this.getWsUrl(debuggerUrl, target, caps)
|
|
1203
1220
|
return new Promise((resolve, reject) => {
|
|
1204
1221
|
try {
|
|
1205
1222
|
this._wsConnection = new WebSocket(this._wsUrl)
|
|
1223
|
+
this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
|
|
1206
1224
|
} catch (err) {
|
|
1207
1225
|
reject(err)
|
|
1208
1226
|
return
|
|
1209
1227
|
}
|
|
1210
1228
|
|
|
1211
|
-
this._wsConnection.on('open', () => {
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1229
|
+
this._wsConnection.on('open', async () => {
|
|
1230
|
+
await this.getCdpTargets()
|
|
1231
|
+
})
|
|
1232
|
+
|
|
1233
|
+
this._wsConnection.on('message', async (message) => {
|
|
1234
|
+
const params = JSON.parse(message)
|
|
1235
|
+
if (params.result) {
|
|
1236
|
+
if (params.result.targetInfos) {
|
|
1237
|
+
const targets = params.result.targetInfos
|
|
1238
|
+
const page = targets.find((info) => info.type === 'page')
|
|
1239
|
+
if (page) {
|
|
1240
|
+
this.targetID = page.targetId
|
|
1241
|
+
this._cdpConnection.execute(
|
|
1242
|
+
'Target.attachToTarget',
|
|
1243
|
+
{ targetId: this.targetID, flatten: true },
|
|
1244
|
+
null
|
|
1245
|
+
)
|
|
1246
|
+
} else {
|
|
1247
|
+
reject('Unable to find Page target.')
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
if (params.result.sessionId) {
|
|
1251
|
+
this.sessionId = params.result.sessionId
|
|
1252
|
+
this._cdpConnection.sessionId = this.sessionId
|
|
1253
|
+
resolve(this._cdpConnection)
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1214
1256
|
})
|
|
1215
1257
|
|
|
1216
1258
|
this._wsConnection.on('error', (error) => {
|
|
@@ -1219,6 +1261,10 @@ class WebDriver {
|
|
|
1219
1261
|
})
|
|
1220
1262
|
}
|
|
1221
1263
|
|
|
1264
|
+
async getCdpTargets() {
|
|
1265
|
+
this._cdpConnection.execute('Target.getTargets')
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1222
1268
|
/**
|
|
1223
1269
|
* Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
|
|
1224
1270
|
* @param {string} debuggerAddress
|
|
@@ -1232,16 +1278,18 @@ class WebDriver {
|
|
|
1232
1278
|
}
|
|
1233
1279
|
|
|
1234
1280
|
if (debuggerAddress.match(/\/se\/cdp/)) {
|
|
1235
|
-
return debuggerAddress
|
|
1281
|
+
return debuggerAddress
|
|
1236
1282
|
}
|
|
1237
1283
|
|
|
1238
1284
|
let path
|
|
1239
|
-
if (target === 'page' && caps['map_'].get('browserName')!=='firefox'
|
|
1285
|
+
if (target === 'page' && caps['map_'].get('browserName') !== 'firefox') {
|
|
1240
1286
|
path = '/json'
|
|
1241
|
-
} else if
|
|
1287
|
+
} else if (
|
|
1288
|
+
target === 'page' &&
|
|
1289
|
+
caps['map_'].get('browserName') === 'firefox'
|
|
1290
|
+
) {
|
|
1242
1291
|
path = '/json/list'
|
|
1243
|
-
}
|
|
1244
|
-
else {
|
|
1292
|
+
} else {
|
|
1245
1293
|
path = '/json/version'
|
|
1246
1294
|
}
|
|
1247
1295
|
|
|
@@ -1265,39 +1313,29 @@ class WebDriver {
|
|
|
1265
1313
|
* @param connection CDP Connection
|
|
1266
1314
|
*/
|
|
1267
1315
|
async register(username, password, connection) {
|
|
1268
|
-
|
|
1269
1316
|
this._wsConnection.on('message', (message) => {
|
|
1270
1317
|
const params = JSON.parse(message)
|
|
1271
1318
|
|
|
1272
1319
|
if (params.method === 'Fetch.authRequired') {
|
|
1273
1320
|
const requestParams = params['params']
|
|
1274
|
-
connection.execute(
|
|
1275
|
-
'
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
password: password,
|
|
1283
|
-
},
|
|
1284
|
-
}
|
|
1285
|
-
)
|
|
1321
|
+
connection.execute('Fetch.continueWithAuth', {
|
|
1322
|
+
requestId: requestParams['requestId'],
|
|
1323
|
+
authChallengeResponse: {
|
|
1324
|
+
response: 'ProvideCredentials',
|
|
1325
|
+
username: username,
|
|
1326
|
+
password: password,
|
|
1327
|
+
},
|
|
1328
|
+
})
|
|
1286
1329
|
} else if (params.method === 'Fetch.requestPaused') {
|
|
1287
1330
|
const requestPausedParams = params['params']
|
|
1288
|
-
connection.execute(
|
|
1289
|
-
'
|
|
1290
|
-
|
|
1291
|
-
{
|
|
1292
|
-
requestId: requestPausedParams['requestId'],
|
|
1293
|
-
}
|
|
1294
|
-
)
|
|
1331
|
+
connection.execute('Fetch.continueRequest', {
|
|
1332
|
+
requestId: requestPausedParams['requestId'],
|
|
1333
|
+
})
|
|
1295
1334
|
}
|
|
1296
1335
|
})
|
|
1297
1336
|
|
|
1298
1337
|
await connection.execute(
|
|
1299
1338
|
'Fetch.enable',
|
|
1300
|
-
1,
|
|
1301
1339
|
{
|
|
1302
1340
|
handleAuthRequests: true,
|
|
1303
1341
|
},
|
|
@@ -1305,7 +1343,6 @@ class WebDriver {
|
|
|
1305
1343
|
)
|
|
1306
1344
|
await connection.execute(
|
|
1307
1345
|
'Network.setCacheDisabled',
|
|
1308
|
-
this.getRandomNumber(1, 10),
|
|
1309
1346
|
{
|
|
1310
1347
|
cacheDisabled: true,
|
|
1311
1348
|
},
|
|
@@ -1321,45 +1358,30 @@ class WebDriver {
|
|
|
1321
1358
|
* @param callback callback called when we intercept requests.
|
|
1322
1359
|
*/
|
|
1323
1360
|
async onIntercept(connection, httpResponse, callback) {
|
|
1324
|
-
|
|
1325
1361
|
this._wsConnection.on('message', (message) => {
|
|
1326
1362
|
const params = JSON.parse(message)
|
|
1327
1363
|
if (params.method === 'Fetch.requestPaused') {
|
|
1328
1364
|
const requestPausedParams = params['params']
|
|
1329
1365
|
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
|
-
)
|
|
1366
|
+
connection.execute('Fetch.continueRequest', {
|
|
1367
|
+
requestId: requestPausedParams['requestId'],
|
|
1368
|
+
url: httpResponse.urlToIntercept,
|
|
1369
|
+
method: httpResponse.method,
|
|
1370
|
+
headers: httpResponse.headers,
|
|
1371
|
+
postData: httpResponse.body,
|
|
1372
|
+
})
|
|
1341
1373
|
callback()
|
|
1342
1374
|
} else {
|
|
1343
|
-
connection.execute(
|
|
1344
|
-
'
|
|
1345
|
-
|
|
1346
|
-
{
|
|
1347
|
-
requestId: requestPausedParams['requestId'],
|
|
1348
|
-
}
|
|
1349
|
-
)
|
|
1375
|
+
connection.execute('Fetch.continueRequest', {
|
|
1376
|
+
requestId: requestPausedParams['requestId'],
|
|
1377
|
+
})
|
|
1350
1378
|
}
|
|
1351
1379
|
}
|
|
1352
1380
|
})
|
|
1353
1381
|
|
|
1354
|
-
await connection.execute(
|
|
1355
|
-
'Fetch.enable',
|
|
1356
|
-
1,
|
|
1357
|
-
{},
|
|
1358
|
-
null
|
|
1359
|
-
)
|
|
1382
|
+
await connection.execute('Fetch.enable', {}, null)
|
|
1360
1383
|
await connection.execute(
|
|
1361
1384
|
'Network.setCacheDisabled',
|
|
1362
|
-
this.getRandomNumber(1, 10),
|
|
1363
1385
|
{
|
|
1364
1386
|
cacheDisabled: true,
|
|
1365
1387
|
},
|
|
@@ -1373,11 +1395,8 @@ class WebDriver {
|
|
|
1373
1395
|
* @returns {Promise<void>}
|
|
1374
1396
|
*/
|
|
1375
1397
|
async onLogEvent(connection, callback) {
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
1398
|
this._wsConnection.on('message', (message) => {
|
|
1379
1399
|
const params = JSON.parse(message)
|
|
1380
|
-
|
|
1381
1400
|
if (params.method === 'Runtime.consoleAPICalled') {
|
|
1382
1401
|
const consoleEventParams = params['params']
|
|
1383
1402
|
let event = {
|
|
@@ -1388,13 +1407,20 @@ class WebDriver {
|
|
|
1388
1407
|
|
|
1389
1408
|
callback(event)
|
|
1390
1409
|
}
|
|
1410
|
+
|
|
1411
|
+
if (params.method === 'Log.entryAdded') {
|
|
1412
|
+
const logEventParams = params['params']
|
|
1413
|
+
const logEntry = logEventParams['entry']
|
|
1414
|
+
let event = {
|
|
1415
|
+
level: logEntry['level'],
|
|
1416
|
+
timestamp: new Date(logEntry['timestamp']),
|
|
1417
|
+
message: logEntry['text'],
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
callback(event)
|
|
1421
|
+
}
|
|
1391
1422
|
})
|
|
1392
|
-
await connection.execute(
|
|
1393
|
-
'Runtime.enable',
|
|
1394
|
-
this.getRandomNumber(1, 10),
|
|
1395
|
-
{},
|
|
1396
|
-
null
|
|
1397
|
-
)
|
|
1423
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1398
1424
|
}
|
|
1399
1425
|
|
|
1400
1426
|
/**
|
|
@@ -1404,12 +1430,7 @@ class WebDriver {
|
|
|
1404
1430
|
* @returns {Promise<void>}
|
|
1405
1431
|
*/
|
|
1406
1432
|
async onLogException(connection, callback) {
|
|
1407
|
-
await connection.execute(
|
|
1408
|
-
'Runtime.enable',
|
|
1409
|
-
this.getRandomNumber(1, 10),
|
|
1410
|
-
{},
|
|
1411
|
-
null
|
|
1412
|
-
)
|
|
1433
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1413
1434
|
|
|
1414
1435
|
this._wsConnection.on('message', (message) => {
|
|
1415
1436
|
const params = JSON.parse(message)
|
|
@@ -1432,22 +1453,11 @@ class WebDriver {
|
|
|
1432
1453
|
* @returns {Promise<void>}
|
|
1433
1454
|
*/
|
|
1434
1455
|
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
|
-
)
|
|
1456
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1457
|
+
await connection.execute('Page.enable', {}, null)
|
|
1447
1458
|
|
|
1448
1459
|
await connection.execute(
|
|
1449
1460
|
'Runtime.addBinding',
|
|
1450
|
-
this.getRandomNumber(1, 10),
|
|
1451
1461
|
{
|
|
1452
1462
|
name: '__webdriver_attribute',
|
|
1453
1463
|
},
|
|
@@ -1477,7 +1487,6 @@ class WebDriver {
|
|
|
1477
1487
|
|
|
1478
1488
|
await connection.execute(
|
|
1479
1489
|
'Page.addScriptToEvaluateOnNewDocument',
|
|
1480
|
-
this.getRandomNumber(1, 10),
|
|
1481
1490
|
{
|
|
1482
1491
|
source: mutationListener,
|
|
1483
1492
|
},
|
|
@@ -1489,7 +1498,7 @@ class WebDriver {
|
|
|
1489
1498
|
if (params.method === 'Runtime.bindingCalled') {
|
|
1490
1499
|
let payload = JSON.parse(params['params']['payload'])
|
|
1491
1500
|
let elements = await this.findElements({
|
|
1492
|
-
css: '*[data-__webdriver_id=' + payload['target'],
|
|
1501
|
+
css: '*[data-__webdriver_id=' + by.escapeCss(payload['target']) + ']',
|
|
1493
1502
|
})
|
|
1494
1503
|
|
|
1495
1504
|
if (elements.length === 0) {
|
|
@@ -1506,10 +1515,6 @@ class WebDriver {
|
|
|
1506
1515
|
}
|
|
1507
1516
|
})
|
|
1508
1517
|
}
|
|
1509
|
-
|
|
1510
|
-
getRandomNumber(min, max) {
|
|
1511
|
-
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
1512
|
-
}
|
|
1513
1518
|
}
|
|
1514
1519
|
|
|
1515
1520
|
/**
|
|
@@ -1791,7 +1796,7 @@ class Options {
|
|
|
1791
1796
|
} else if (typeof value !== 'undefined') {
|
|
1792
1797
|
throw TypeError(
|
|
1793
1798
|
'invalid timeouts configuration:' +
|
|
1794
|
-
|
|
1799
|
+
` expected "${key}" to be a number, got ${typeof value}`
|
|
1795
1800
|
)
|
|
1796
1801
|
}
|
|
1797
1802
|
}
|
|
@@ -1852,7 +1857,7 @@ function legacyTimeout(driver, type, ms) {
|
|
|
1852
1857
|
*
|
|
1853
1858
|
* @record
|
|
1854
1859
|
*/
|
|
1855
|
-
Options.Cookie = function () {
|
|
1860
|
+
Options.Cookie = function () {}
|
|
1856
1861
|
|
|
1857
1862
|
/**
|
|
1858
1863
|
* The name of the cookie.
|
|
@@ -1951,29 +1956,10 @@ class Window {
|
|
|
1951
1956
|
* @return {!Promise<{x: number, y: number, width: number, height: number}>}
|
|
1952
1957
|
* A promise that will resolve to the window rect of the current window.
|
|
1953
1958
|
*/
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
)
|
|
1959
|
-
} catch (ex) {
|
|
1960
|
-
if (ex instanceof error.UnknownCommandError) {
|
|
1961
|
-
let { width, height } = await this.driver_.execute(
|
|
1962
|
-
new command.Command(command.Name.GET_WINDOW_SIZE).setParameter(
|
|
1963
|
-
'windowHandle',
|
|
1964
|
-
'current'
|
|
1965
|
-
)
|
|
1966
|
-
)
|
|
1967
|
-
let { x, y } = await this.driver_.execute(
|
|
1968
|
-
new command.Command(command.Name.GET_WINDOW_POSITION).setParameter(
|
|
1969
|
-
'windowHandle',
|
|
1970
|
-
'current'
|
|
1971
|
-
)
|
|
1972
|
-
)
|
|
1973
|
-
return { x, y, width, height }
|
|
1974
|
-
}
|
|
1975
|
-
throw ex
|
|
1976
|
-
}
|
|
1959
|
+
getRect() {
|
|
1960
|
+
return this.driver_.execute(
|
|
1961
|
+
new command.Command(command.Name.GET_WINDOW_RECT)
|
|
1962
|
+
)
|
|
1977
1963
|
}
|
|
1978
1964
|
|
|
1979
1965
|
/**
|
|
@@ -1990,39 +1976,15 @@ class Window {
|
|
|
1990
1976
|
* A promise that will resolve to the current window's updated window
|
|
1991
1977
|
* rect.
|
|
1992
1978
|
*/
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
)
|
|
2003
|
-
} catch (ex) {
|
|
2004
|
-
if (ex instanceof error.UnknownCommandError) {
|
|
2005
|
-
if (typeof x === 'number' && typeof y === 'number') {
|
|
2006
|
-
await this.driver_.execute(
|
|
2007
|
-
new command.Command(command.Name.SET_WINDOW_POSITION)
|
|
2008
|
-
.setParameter('windowHandle', 'current')
|
|
2009
|
-
.setParameter('x', x)
|
|
2010
|
-
.setParameter('y', y)
|
|
2011
|
-
)
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
if (typeof width === 'number' && typeof height === 'number') {
|
|
2015
|
-
await this.driver_.execute(
|
|
2016
|
-
new command.Command(command.Name.SET_WINDOW_SIZE)
|
|
2017
|
-
.setParameter('windowHandle', 'current')
|
|
2018
|
-
.setParameter('width', width)
|
|
2019
|
-
.setParameter('height', height)
|
|
2020
|
-
)
|
|
2021
|
-
}
|
|
2022
|
-
return this.getRect()
|
|
2023
|
-
}
|
|
2024
|
-
throw ex
|
|
2025
|
-
}
|
|
1979
|
+
setRect({ x, y, width, height }) {
|
|
1980
|
+
return this.driver_.execute(
|
|
1981
|
+
new command.Command(command.Name.SET_WINDOW_RECT).setParameters({
|
|
1982
|
+
x,
|
|
1983
|
+
y,
|
|
1984
|
+
width,
|
|
1985
|
+
height,
|
|
1986
|
+
})
|
|
1987
|
+
)
|
|
2026
1988
|
}
|
|
2027
1989
|
|
|
2028
1990
|
/**
|
|
@@ -2167,7 +2129,7 @@ class TargetLocator {
|
|
|
2167
2129
|
* @return {!WebElementPromise} The active element.
|
|
2168
2130
|
*/
|
|
2169
2131
|
activeElement() {
|
|
2170
|
-
|
|
2132
|
+
const id = this.driver_.execute(
|
|
2171
2133
|
new command.Command(command.Name.GET_ACTIVE_ELEMENT)
|
|
2172
2134
|
)
|
|
2173
2135
|
return new WebElementPromise(this.driver_, id)
|
|
@@ -2259,7 +2221,7 @@ class TargetLocator {
|
|
|
2259
2221
|
* when the driver has changed focus to the new window.
|
|
2260
2222
|
*/
|
|
2261
2223
|
newWindow(typeHint) {
|
|
2262
|
-
|
|
2224
|
+
const driver = this.driver_
|
|
2263
2225
|
return this.driver_
|
|
2264
2226
|
.execute(
|
|
2265
2227
|
new command.Command(command.Name.SWITCH_TO_NEW_WINDOW).setParameter(
|
|
@@ -2281,10 +2243,10 @@ class TargetLocator {
|
|
|
2281
2243
|
* @return {!AlertPromise} The open alert.
|
|
2282
2244
|
*/
|
|
2283
2245
|
alert() {
|
|
2284
|
-
|
|
2246
|
+
const text = this.driver_.execute(
|
|
2285
2247
|
new command.Command(command.Name.GET_ALERT_TEXT)
|
|
2286
2248
|
)
|
|
2287
|
-
|
|
2249
|
+
const driver = this.driver_
|
|
2288
2250
|
return new AlertPromise(
|
|
2289
2251
|
driver,
|
|
2290
2252
|
text.then(function (text) {
|
|
@@ -2302,6 +2264,7 @@ class TargetLocator {
|
|
|
2302
2264
|
|
|
2303
2265
|
const LEGACY_ELEMENT_ID_KEY = 'ELEMENT'
|
|
2304
2266
|
const ELEMENT_ID_KEY = 'element-6066-11e4-a52e-4f735466cecf'
|
|
2267
|
+
const SHADOWROOT_ID_KEY = 'shadow-6066-11e4-a52e-4f735466cecf'
|
|
2305
2268
|
|
|
2306
2269
|
/**
|
|
2307
2270
|
* Represents a DOM element. WebElements can be found by searching from the
|
|
@@ -2557,18 +2520,18 @@ class WebElement {
|
|
|
2557
2520
|
*/
|
|
2558
2521
|
async sendKeys(...args) {
|
|
2559
2522
|
let keys = []
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2523
|
+
;(await Promise.all(args)).forEach((key) => {
|
|
2524
|
+
let type = typeof key
|
|
2525
|
+
if (type === 'number') {
|
|
2526
|
+
key = String(key)
|
|
2527
|
+
} else if (type !== 'string') {
|
|
2528
|
+
throw TypeError('each key must be a number or string; got ' + type)
|
|
2529
|
+
}
|
|
2567
2530
|
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2531
|
+
// The W3C protocol requires keys to be specified as an array where
|
|
2532
|
+
// each element is a single key.
|
|
2533
|
+
keys.push(...key)
|
|
2534
|
+
})
|
|
2572
2535
|
|
|
2573
2536
|
if (!this.driver_.fileDetector_) {
|
|
2574
2537
|
return this.execute_(
|
|
@@ -2578,10 +2541,18 @@ class WebElement {
|
|
|
2578
2541
|
)
|
|
2579
2542
|
}
|
|
2580
2543
|
|
|
2581
|
-
|
|
2582
|
-
this.driver_
|
|
2583
|
-
|
|
2584
|
-
|
|
2544
|
+
try {
|
|
2545
|
+
keys = await this.driver_.fileDetector_.handleFile(
|
|
2546
|
+
this.driver_,
|
|
2547
|
+
keys.join('')
|
|
2548
|
+
)
|
|
2549
|
+
} catch (ex) {
|
|
2550
|
+
console.log(
|
|
2551
|
+
'Error trying parse string as a file with file detector; sending keys instead' +
|
|
2552
|
+
ex
|
|
2553
|
+
)
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2585
2556
|
return this.execute_(
|
|
2586
2557
|
new command.Command(command.Name.SEND_KEYS_TO_ELEMENT)
|
|
2587
2558
|
.setParameter('text', keys)
|
|
@@ -2614,7 +2585,7 @@ class WebElement {
|
|
|
2614
2585
|
* requested CSS value.
|
|
2615
2586
|
*/
|
|
2616
2587
|
getCssValue(cssStyleProperty) {
|
|
2617
|
-
|
|
2588
|
+
const name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY
|
|
2618
2589
|
return this.execute_(
|
|
2619
2590
|
new command.Command(name).setParameter('propertyName', cssStyleProperty)
|
|
2620
2591
|
)
|
|
@@ -2658,6 +2629,36 @@ class WebElement {
|
|
|
2658
2629
|
)
|
|
2659
2630
|
}
|
|
2660
2631
|
|
|
2632
|
+
/**
|
|
2633
|
+
* Get the value of the given attribute of the element.
|
|
2634
|
+
* <p>
|
|
2635
|
+
* This method, unlike {@link #getAttribute(String)}, returns the value of the attribute with the
|
|
2636
|
+
* given name but not the property with the same name.
|
|
2637
|
+
* <p>
|
|
2638
|
+
* The following are deemed to be "boolean" attributes, and will return either "true" or null:
|
|
2639
|
+
* <p>
|
|
2640
|
+
* async, autofocus, autoplay, checked, compact, complete, controls, declare, defaultchecked,
|
|
2641
|
+
* defaultselected, defer, disabled, draggable, ended, formnovalidate, hidden, indeterminate,
|
|
2642
|
+
* iscontenteditable, ismap, itemscope, loop, multiple, muted, nohref, noresize, noshade,
|
|
2643
|
+
* novalidate, nowrap, open, paused, pubdate, readonly, required, reversed, scoped, seamless,
|
|
2644
|
+
* seeking, selected, truespeed, willvalidate
|
|
2645
|
+
* <p>
|
|
2646
|
+
* See <a href="https://w3c.github.io/webdriver/#get-element-attribute">W3C WebDriver specification</a>
|
|
2647
|
+
* for more details.
|
|
2648
|
+
*
|
|
2649
|
+
* @param attributeName The name of the attribute.
|
|
2650
|
+
* @return The attribute's value or null if the value is not set.
|
|
2651
|
+
*/
|
|
2652
|
+
|
|
2653
|
+
getDomAttribute(attributeName) {
|
|
2654
|
+
return this.execute_(
|
|
2655
|
+
new command.Command(command.Name.GET_DOM_ATTRIBUTE).setParameter(
|
|
2656
|
+
'name',
|
|
2657
|
+
attributeName
|
|
2658
|
+
)
|
|
2659
|
+
)
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2661
2662
|
/**
|
|
2662
2663
|
* Get the given property of the referenced web element
|
|
2663
2664
|
* @param {string} propertyName The name of the attribute to query.
|
|
@@ -2673,6 +2674,16 @@ class WebElement {
|
|
|
2673
2674
|
)
|
|
2674
2675
|
}
|
|
2675
2676
|
|
|
2677
|
+
/**
|
|
2678
|
+
* Get the shadow root of the current web element.
|
|
2679
|
+
* @returns {!Promise<ShadowRoot>} A promise that will be
|
|
2680
|
+
* resolved with the elements shadow root or rejected
|
|
2681
|
+
* with {@link NoSuchShadowRootError}
|
|
2682
|
+
*/
|
|
2683
|
+
getShadowRoot() {
|
|
2684
|
+
return this.execute_(new command.Command(command.Name.GET_SHADOW_ROOT))
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2676
2687
|
/**
|
|
2677
2688
|
* Get the visible (i.e. not hidden by CSS) innerText of this element,
|
|
2678
2689
|
* including sub-elements, without any leading or trailing whitespace.
|
|
@@ -2710,22 +2721,8 @@ class WebElement {
|
|
|
2710
2721
|
* @return {!Promise<{width: number, height: number, x: number, y: number}>}
|
|
2711
2722
|
* A promise that will resolve with the element's rect.
|
|
2712
2723
|
*/
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
return await this.execute_(
|
|
2716
|
-
new command.Command(command.Name.GET_ELEMENT_RECT)
|
|
2717
|
-
)
|
|
2718
|
-
} catch (err) {
|
|
2719
|
-
if (err instanceof error.UnknownCommandError) {
|
|
2720
|
-
const { width, height } = await this.execute_(
|
|
2721
|
-
new command.Command(command.Name.GET_ELEMENT_SIZE)
|
|
2722
|
-
)
|
|
2723
|
-
const { x, y } = await this.execute_(
|
|
2724
|
-
new command.Command(command.Name.GET_ELEMENT_LOCATION)
|
|
2725
|
-
)
|
|
2726
|
-
return { x, y, width, height }
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2724
|
+
getRect() {
|
|
2725
|
+
return this.execute_(new command.Command(command.Name.GET_ELEMENT_RECT))
|
|
2729
2726
|
}
|
|
2730
2727
|
|
|
2731
2728
|
/**
|
|
@@ -2758,7 +2755,13 @@ class WebElement {
|
|
|
2758
2755
|
* when the form has been submitted.
|
|
2759
2756
|
*/
|
|
2760
2757
|
submit() {
|
|
2761
|
-
|
|
2758
|
+
const form = this.findElement({ xpath: './ancestor-or-self::form' })
|
|
2759
|
+
this.driver_.executeScript(
|
|
2760
|
+
"var e = arguments[0].ownerDocument.createEvent('Event');" +
|
|
2761
|
+
"e.initEvent('submit', true, true);" +
|
|
2762
|
+
'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }',
|
|
2763
|
+
form
|
|
2764
|
+
)
|
|
2762
2765
|
}
|
|
2763
2766
|
|
|
2764
2767
|
/**
|
|
@@ -2841,6 +2844,186 @@ class WebElementPromise extends WebElement {
|
|
|
2841
2844
|
}
|
|
2842
2845
|
}
|
|
2843
2846
|
|
|
2847
|
+
//////////////////////////////////////////////////////////////////////////////
|
|
2848
|
+
//
|
|
2849
|
+
// ShadowRoot
|
|
2850
|
+
//
|
|
2851
|
+
//////////////////////////////////////////////////////////////////////////////
|
|
2852
|
+
|
|
2853
|
+
/**
|
|
2854
|
+
* Represents a ShadowRoot of a {@link WebElement}. Provides functions to
|
|
2855
|
+
* retrieve elements that live in the DOM below the ShadowRoot.
|
|
2856
|
+
*/
|
|
2857
|
+
class ShadowRoot {
|
|
2858
|
+
constructor(driver, id) {
|
|
2859
|
+
this.driver_ = driver
|
|
2860
|
+
this.id_ = id
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
/**
|
|
2864
|
+
* Extracts the encoded ShadowRoot ID from the object.
|
|
2865
|
+
*
|
|
2866
|
+
* @param {?} obj The object to extract the ID from.
|
|
2867
|
+
* @return {string} the extracted ID.
|
|
2868
|
+
* @throws {TypeError} if the object is not a valid encoded ID.
|
|
2869
|
+
*/
|
|
2870
|
+
static extractId(obj) {
|
|
2871
|
+
if (obj && typeof obj === 'object') {
|
|
2872
|
+
if (typeof obj[SHADOWROOT_ID_KEY] === 'string') {
|
|
2873
|
+
return obj[SHADOWROOT_ID_KEY]
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
throw new TypeError('object is not a ShadowRoot ID')
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
/**
|
|
2880
|
+
* @param {?} obj the object to test.
|
|
2881
|
+
* @return {boolean} whether the object is a valid encoded WebElement ID.
|
|
2882
|
+
*/
|
|
2883
|
+
static isId(obj) {
|
|
2884
|
+
return (
|
|
2885
|
+
obj &&
|
|
2886
|
+
typeof obj === 'object' &&
|
|
2887
|
+
typeof obj[SHADOWROOT_ID_KEY] === 'string'
|
|
2888
|
+
)
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
/**
|
|
2892
|
+
* @return {!Object} Returns the serialized representation of this ShadowRoot.
|
|
2893
|
+
*/
|
|
2894
|
+
[Symbols.serialize]() {
|
|
2895
|
+
return this.getId()
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
/**
|
|
2899
|
+
* Schedules a command that targets this element with the parent WebDriver
|
|
2900
|
+
* instance. Will ensure this element's ID is included in the command
|
|
2901
|
+
* parameters under the "id" key.
|
|
2902
|
+
*
|
|
2903
|
+
* @param {!command.Command} command The command to schedule.
|
|
2904
|
+
* @return {!Promise<T>} A promise that will be resolved with the result.
|
|
2905
|
+
* @template T
|
|
2906
|
+
* @see WebDriver#schedule
|
|
2907
|
+
* @private
|
|
2908
|
+
*/
|
|
2909
|
+
execute_(command) {
|
|
2910
|
+
command.setParameter('id', this)
|
|
2911
|
+
return this.driver_.execute(command)
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
/**
|
|
2915
|
+
* Schedule a command to find a descendant of this ShadowROot. If the element
|
|
2916
|
+
* cannot be found, the returned promise will be rejected with a
|
|
2917
|
+
* {@linkplain error.NoSuchElementError NoSuchElementError}.
|
|
2918
|
+
*
|
|
2919
|
+
* The search criteria for an element may be defined using one of the static
|
|
2920
|
+
* factories on the {@link by.By} class, or as a short-hand
|
|
2921
|
+
* {@link ./by.ByHash} object. For example, the following two statements
|
|
2922
|
+
* are equivalent:
|
|
2923
|
+
*
|
|
2924
|
+
* var e1 = shadowroot.findElement(By.id('foo'));
|
|
2925
|
+
* var e2 = shadowroot.findElement({id:'foo'});
|
|
2926
|
+
*
|
|
2927
|
+
* You may also provide a custom locator function, which takes as input this
|
|
2928
|
+
* instance and returns a {@link WebElement}, or a promise that will resolve
|
|
2929
|
+
* to a WebElement. If the returned promise resolves to an array of
|
|
2930
|
+
* WebElements, WebDriver will use the first element. For example, to find the
|
|
2931
|
+
* first visible link on a page, you could write:
|
|
2932
|
+
*
|
|
2933
|
+
* var link = element.findElement(firstVisibleLink);
|
|
2934
|
+
*
|
|
2935
|
+
* function firstVisibleLink(shadowRoot) {
|
|
2936
|
+
* var links = shadowRoot.findElements(By.tagName('a'));
|
|
2937
|
+
* return promise.filter(links, function(link) {
|
|
2938
|
+
* return link.isDisplayed();
|
|
2939
|
+
* });
|
|
2940
|
+
* }
|
|
2941
|
+
*
|
|
2942
|
+
* @param {!(by.By|Function)} locator The locator strategy to use when
|
|
2943
|
+
* searching for the element.
|
|
2944
|
+
* @return {!WebElementPromise} A WebElement that can be used to issue
|
|
2945
|
+
* commands against the located element. If the element is not found, the
|
|
2946
|
+
* element will be invalidated and all scheduled commands aborted.
|
|
2947
|
+
*/
|
|
2948
|
+
findElement(locator) {
|
|
2949
|
+
locator = by.checkedLocator(locator)
|
|
2950
|
+
let id
|
|
2951
|
+
if (typeof locator === 'function') {
|
|
2952
|
+
id = this.driver_.findElementInternal_(locator, this)
|
|
2953
|
+
} else {
|
|
2954
|
+
let cmd = new command.Command(command.Name.FIND_ELEMENT_FROM_SHADOWROOT)
|
|
2955
|
+
.setParameter('using', locator.using)
|
|
2956
|
+
.setParameter('value', locator.value)
|
|
2957
|
+
id = this.execute_(cmd)
|
|
2958
|
+
}
|
|
2959
|
+
return new ShadowRootPromise(this.driver_, id)
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
/**
|
|
2963
|
+
* Locates all of the descendants of this element that match the given search
|
|
2964
|
+
* criteria.
|
|
2965
|
+
*
|
|
2966
|
+
* @param {!(by.By|Function)} locator The locator strategy to use when
|
|
2967
|
+
* searching for the element.
|
|
2968
|
+
* @return {!Promise<!Array<!WebElement>>} A promise that will resolve to an
|
|
2969
|
+
* array of WebElements.
|
|
2970
|
+
*/
|
|
2971
|
+
async findElements(locator) {
|
|
2972
|
+
locator = by.checkedLocator(locator)
|
|
2973
|
+
if (typeof locator === 'function') {
|
|
2974
|
+
return this.driver_.findElementsInternal_(locator, this)
|
|
2975
|
+
} else {
|
|
2976
|
+
let cmd = new command.Command(command.Name.FIND_ELEMENTS_FROM_SHADOWROOT)
|
|
2977
|
+
.setParameter('using', locator.using)
|
|
2978
|
+
.setParameter('value', locator.value)
|
|
2979
|
+
let result = await this.execute_(cmd)
|
|
2980
|
+
return Array.isArray(result) ? result : []
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
getId() {
|
|
2985
|
+
return this.id_
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
/**
|
|
2990
|
+
* ShadowRootPromise is a promise that will be fulfilled with a WebElement.
|
|
2991
|
+
* This serves as a forward proxy on ShadowRoot, allowing calls to be
|
|
2992
|
+
* scheduled without directly on this instance before the underlying
|
|
2993
|
+
* ShadowRoot has been fulfilled.
|
|
2994
|
+
*
|
|
2995
|
+
* @implements { IThenable<!ShadowRoot>}
|
|
2996
|
+
* @final
|
|
2997
|
+
*/
|
|
2998
|
+
class ShadowRootPromise extends ShadowRoot {
|
|
2999
|
+
/**
|
|
3000
|
+
* @param {!WebDriver} driver The parent WebDriver instance for this
|
|
3001
|
+
* element.
|
|
3002
|
+
* @param {!Promise<!ShadowRoot>} shadow A promise
|
|
3003
|
+
* that will resolve to the promised element.
|
|
3004
|
+
*/
|
|
3005
|
+
constructor(driver, shadow) {
|
|
3006
|
+
super(driver, 'unused')
|
|
3007
|
+
|
|
3008
|
+
/** @override */
|
|
3009
|
+
this.then = shadow.then.bind(shadow)
|
|
3010
|
+
|
|
3011
|
+
/** @override */
|
|
3012
|
+
this.catch = shadow.catch.bind(shadow)
|
|
3013
|
+
|
|
3014
|
+
/**
|
|
3015
|
+
* Defers returning the ShadowRoot ID until the wrapped WebElement has been
|
|
3016
|
+
* resolved.
|
|
3017
|
+
* @override
|
|
3018
|
+
*/
|
|
3019
|
+
this.getId = function () {
|
|
3020
|
+
return shadow.then(function (shadow) {
|
|
3021
|
+
return shadow.getId()
|
|
3022
|
+
})
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
|
|
2844
3027
|
//////////////////////////////////////////////////////////////////////////////
|
|
2845
3028
|
//
|
|
2846
3029
|
// Alert
|
|
@@ -2998,6 +3181,7 @@ module.exports = {
|
|
|
2998
3181
|
Logs,
|
|
2999
3182
|
Navigation,
|
|
3000
3183
|
Options,
|
|
3184
|
+
ShadowRoot,
|
|
3001
3185
|
TargetLocator,
|
|
3002
3186
|
IWebDriver,
|
|
3003
3187
|
WebDriver,
|