selenium-webdriver 4.0.0-rc-1 → 4.1.1
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 +51 -1
- package/LICENSE +1 -1
- package/NOTICE +1 -1
- package/chrome.js +10 -6
- package/chromium.js +75 -27
- package/devtools/CDPConnection.js +9 -4
- package/devtools/networkinterceptor.js +88 -0
- package/edge.js +8 -6
- package/example/google_search.js +1 -1
- package/firefox.js +39 -17
- package/http/index.js +13 -14
- package/http/util.js +14 -20
- package/index.js +26 -13
- package/io/index.js +58 -55
- package/io/zip.js +1 -3
- package/lib/atoms/find-elements.js +2 -2
- package/lib/atoms/get-attribute.js +25 -25
- package/lib/by.js +8 -9
- package/lib/capabilities.js +1 -1
- package/lib/command.js +6 -11
- package/lib/error.js +24 -19
- package/lib/http.js +64 -156
- package/lib/input.js +1 -1
- package/lib/promise.js +12 -17
- package/lib/proxy.js +15 -5
- package/lib/session.js +1 -1
- package/lib/webdriver.js +422 -176
- package/net/index.js +14 -7
- package/net/portprober.js +5 -4
- package/opera.js +19 -22
- package/package.json +1 -2
- package/remote/index.js +13 -10
- package/testing/index.js +16 -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) {
|
|
@@ -246,30 +250,30 @@ class IWebDriver {
|
|
|
246
250
|
* result.
|
|
247
251
|
* @template T
|
|
248
252
|
*/
|
|
249
|
-
execute(command) {
|
|
253
|
+
execute(command) {} // eslint-disable-line
|
|
250
254
|
|
|
251
255
|
/**
|
|
252
256
|
* Sets the {@linkplain input.FileDetector file detector} that should be
|
|
253
257
|
* used with this instance.
|
|
254
258
|
* @param {input.FileDetector} detector The detector to use or `null`.
|
|
255
259
|
*/
|
|
256
|
-
setFileDetector(detector) {
|
|
260
|
+
setFileDetector(detector) {} // eslint-disable-line
|
|
257
261
|
|
|
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
|
|
@@ -292,7 +296,7 @@ class IWebDriver {
|
|
|
292
296
|
* for details).
|
|
293
297
|
* @return {!input.Actions} A new action sequence for this instance.
|
|
294
298
|
*/
|
|
295
|
-
actions(options) {
|
|
299
|
+
actions(options) {} // eslint-disable-line
|
|
296
300
|
|
|
297
301
|
/**
|
|
298
302
|
* Executes a snippet of JavaScript in the context of the currently selected
|
|
@@ -330,7 +334,7 @@ class IWebDriver {
|
|
|
330
334
|
* scripts return value.
|
|
331
335
|
* @template T
|
|
332
336
|
*/
|
|
333
|
-
executeScript(script, ...args) {
|
|
337
|
+
executeScript(script, ...args) {} // eslint-disable-line
|
|
334
338
|
|
|
335
339
|
/**
|
|
336
340
|
* Executes a snippet of asynchronous JavaScript in the context of the
|
|
@@ -407,7 +411,7 @@ class IWebDriver {
|
|
|
407
411
|
* value.
|
|
408
412
|
* @template T
|
|
409
413
|
*/
|
|
410
|
-
executeAsyncScript(script, ...args) {
|
|
414
|
+
executeAsyncScript(script, ...args) {} // eslint-disable-line
|
|
411
415
|
|
|
412
416
|
/**
|
|
413
417
|
* Waits for a condition to evaluate to a "truthy" value. The condition may 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.
|
|
@@ -467,7 +471,7 @@ class IWebDriver {
|
|
|
467
471
|
* @return {!Promise<void>} A promise that will be resolved when the sleep has
|
|
468
472
|
* finished.
|
|
469
473
|
*/
|
|
470
|
-
sleep(ms) {
|
|
474
|
+
sleep(ms) {} // eslint-disable-line
|
|
471
475
|
|
|
472
476
|
/**
|
|
473
477
|
* Retrieves the current window handle.
|
|
@@ -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.
|
|
@@ -510,7 +514,7 @@ class IWebDriver {
|
|
|
510
514
|
* @return {!Promise<void>} A promise that will be resolved when the document
|
|
511
515
|
* has finished loading.
|
|
512
516
|
*/
|
|
513
|
-
get(url) {
|
|
517
|
+
get(url) {} // eslint-disable-line
|
|
514
518
|
|
|
515
519
|
/**
|
|
516
520
|
* Retrieves the URL for the current page.
|
|
@@ -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
|
|
@@ -566,7 +570,7 @@ class IWebDriver {
|
|
|
566
570
|
* commands against the located element. If the element is not found, the
|
|
567
571
|
* element will be invalidated and all scheduled commands aborted.
|
|
568
572
|
*/
|
|
569
|
-
findElement(locator) {
|
|
573
|
+
findElement(locator) {} // eslint-disable-line
|
|
570
574
|
|
|
571
575
|
/**
|
|
572
576
|
* Search for multiple elements on the page. Refer to the documentation on
|
|
@@ -576,7 +580,7 @@ class IWebDriver {
|
|
|
576
580
|
* @return {!Promise<!Array<!WebElement>>} A promise that will resolve to an
|
|
577
581
|
* array of WebElements.
|
|
578
582
|
*/
|
|
579
|
-
findElements(locator) {
|
|
583
|
+
findElements(locator) {} // eslint-disable-line
|
|
580
584
|
|
|
581
585
|
/**
|
|
582
586
|
* Takes a screenshot of the current page. The driver makes a best effort to
|
|
@@ -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
|
*
|
|
@@ -625,7 +629,7 @@ class IWebDriver {
|
|
|
625
629
|
* shrinkToFit: (boolean|undefined)
|
|
626
630
|
* pageRanges: (<Array>|undefined)}} options.
|
|
627
631
|
*/
|
|
628
|
-
printPage(options) {
|
|
632
|
+
printPage(options) {} // eslint-disable-line
|
|
629
633
|
}
|
|
630
634
|
|
|
631
635
|
/**
|
|
@@ -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,15 +831,18 @@ 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 ${
|
|
834
|
+
`${timeoutMessage}Timed out waiting for promise to resolve after ${
|
|
835
|
+
Date.now() - start
|
|
831
836
|
}ms`
|
|
832
837
|
)
|
|
833
838
|
)
|
|
834
839
|
} catch (ex) {
|
|
835
840
|
reject(
|
|
836
841
|
new error.TimeoutError(
|
|
837
|
-
`${
|
|
838
|
-
|
|
842
|
+
`${
|
|
843
|
+
ex.message
|
|
844
|
+
}\nTimed out waiting for promise to resolve after ${
|
|
845
|
+
Date.now() - start
|
|
839
846
|
}ms`
|
|
840
847
|
)
|
|
841
848
|
)
|
|
@@ -865,7 +872,7 @@ class WebDriver {
|
|
|
865
872
|
if (typeof fn !== 'function') {
|
|
866
873
|
throw TypeError(
|
|
867
874
|
'Wait condition must be a promise-like object, function, or a ' +
|
|
868
|
-
|
|
875
|
+
'Condition object'
|
|
869
876
|
)
|
|
870
877
|
}
|
|
871
878
|
|
|
@@ -917,7 +924,7 @@ class WebDriver {
|
|
|
917
924
|
if (!(value instanceof WebElement)) {
|
|
918
925
|
throw TypeError(
|
|
919
926
|
'WebElementCondition did not resolve to a WebElement: ' +
|
|
920
|
-
|
|
927
|
+
Object.prototype.toString.call(value)
|
|
921
928
|
)
|
|
922
929
|
}
|
|
923
930
|
return value
|
|
@@ -1198,19 +1205,43 @@ class WebDriver {
|
|
|
1198
1205
|
caps['map_'].get('moz:debuggerAddress') ||
|
|
1199
1206
|
new Map()
|
|
1200
1207
|
const debuggerUrl = seCdp || vendorInfo['debuggerAddress'] || vendorInfo
|
|
1201
|
-
this._wsUrl = await this.getWsUrl(debuggerUrl, target)
|
|
1202
|
-
|
|
1208
|
+
this._wsUrl = await this.getWsUrl(debuggerUrl, target, caps)
|
|
1203
1209
|
return new Promise((resolve, reject) => {
|
|
1204
1210
|
try {
|
|
1205
1211
|
this._wsConnection = new WebSocket(this._wsUrl)
|
|
1212
|
+
this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
|
|
1206
1213
|
} catch (err) {
|
|
1207
1214
|
reject(err)
|
|
1208
1215
|
return
|
|
1209
1216
|
}
|
|
1210
1217
|
|
|
1211
|
-
this._wsConnection.on('open', () => {
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1218
|
+
this._wsConnection.on('open', async () => {
|
|
1219
|
+
await this.getCdpTargets()
|
|
1220
|
+
})
|
|
1221
|
+
|
|
1222
|
+
this._wsConnection.on('message', async (message) => {
|
|
1223
|
+
const params = JSON.parse(message)
|
|
1224
|
+
if (params.result) {
|
|
1225
|
+
if (params.result.targetInfos) {
|
|
1226
|
+
const targets = params.result.targetInfos
|
|
1227
|
+
const page = targets.find((info) => info.type === 'page')
|
|
1228
|
+
if (page) {
|
|
1229
|
+
this.targetID = page.targetId
|
|
1230
|
+
this._cdpConnection.execute(
|
|
1231
|
+
'Target.attachToTarget',
|
|
1232
|
+
{ targetId: this.targetID, flatten: true },
|
|
1233
|
+
null
|
|
1234
|
+
)
|
|
1235
|
+
} else {
|
|
1236
|
+
reject('Unable to find Page target.')
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (params.result.sessionId) {
|
|
1240
|
+
this.sessionId = params.result.sessionId
|
|
1241
|
+
this._cdpConnection.sessionId = this.sessionId
|
|
1242
|
+
resolve(this._cdpConnection)
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1214
1245
|
})
|
|
1215
1246
|
|
|
1216
1247
|
this._wsConnection.on('error', (error) => {
|
|
@@ -1219,33 +1250,47 @@ class WebDriver {
|
|
|
1219
1250
|
})
|
|
1220
1251
|
}
|
|
1221
1252
|
|
|
1253
|
+
async getCdpTargets() {
|
|
1254
|
+
this._cdpConnection.execute('Target.getTargets')
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1222
1257
|
/**
|
|
1223
1258
|
* Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
|
|
1224
1259
|
* @param {string} debuggerAddress
|
|
1225
|
-
* @param
|
|
1260
|
+
* @param target
|
|
1261
|
+
* @param caps
|
|
1226
1262
|
* @return {string} Returns parsed webSocketDebuggerUrl obtained from the http request
|
|
1227
1263
|
*/
|
|
1228
|
-
async getWsUrl(debuggerAddress, target) {
|
|
1264
|
+
async getWsUrl(debuggerAddress, target, caps) {
|
|
1229
1265
|
if (target && cdpTargets.indexOf(target.toLowerCase()) === -1) {
|
|
1230
1266
|
throw new error.InvalidArgumentError('invalid target value')
|
|
1231
1267
|
}
|
|
1268
|
+
|
|
1269
|
+
if (debuggerAddress.match(/\/se\/cdp/)) {
|
|
1270
|
+
return debuggerAddress
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1232
1273
|
let path
|
|
1233
|
-
if (target === 'page') {
|
|
1274
|
+
if (target === 'page' && caps['map_'].get('browserName') !== 'firefox') {
|
|
1234
1275
|
path = '/json'
|
|
1276
|
+
} else if (
|
|
1277
|
+
target === 'page' &&
|
|
1278
|
+
caps['map_'].get('browserName') === 'firefox'
|
|
1279
|
+
) {
|
|
1280
|
+
path = '/json/list'
|
|
1235
1281
|
} else {
|
|
1236
1282
|
path = '/json/version'
|
|
1237
1283
|
}
|
|
1284
|
+
|
|
1238
1285
|
let request = new http.Request('GET', path)
|
|
1239
1286
|
let client = new http.HttpClient('http://' + debuggerAddress)
|
|
1240
1287
|
let response = await client.send(request)
|
|
1241
|
-
|
|
1288
|
+
|
|
1242
1289
|
if (target.toLowerCase() === 'page') {
|
|
1243
|
-
|
|
1290
|
+
return JSON.parse(response.body)[0]['webSocketDebuggerUrl']
|
|
1244
1291
|
} else {
|
|
1245
|
-
|
|
1292
|
+
return JSON.parse(response.body)['webSocketDebuggerUrl']
|
|
1246
1293
|
}
|
|
1247
|
-
|
|
1248
|
-
return url
|
|
1249
1294
|
}
|
|
1250
1295
|
|
|
1251
1296
|
/**
|
|
@@ -1257,54 +1302,81 @@ class WebDriver {
|
|
|
1257
1302
|
* @param connection CDP Connection
|
|
1258
1303
|
*/
|
|
1259
1304
|
async register(username, password, connection) {
|
|
1305
|
+
this._wsConnection.on('message', (message) => {
|
|
1306
|
+
const params = JSON.parse(message)
|
|
1307
|
+
|
|
1308
|
+
if (params.method === 'Fetch.authRequired') {
|
|
1309
|
+
const requestParams = params['params']
|
|
1310
|
+
connection.execute('Fetch.continueWithAuth', {
|
|
1311
|
+
requestId: requestParams['requestId'],
|
|
1312
|
+
authChallengeResponse: {
|
|
1313
|
+
response: 'ProvideCredentials',
|
|
1314
|
+
username: username,
|
|
1315
|
+
password: password,
|
|
1316
|
+
},
|
|
1317
|
+
})
|
|
1318
|
+
} else if (params.method === 'Fetch.requestPaused') {
|
|
1319
|
+
const requestPausedParams = params['params']
|
|
1320
|
+
connection.execute('Fetch.continueRequest', {
|
|
1321
|
+
requestId: requestPausedParams['requestId'],
|
|
1322
|
+
})
|
|
1323
|
+
}
|
|
1324
|
+
})
|
|
1325
|
+
|
|
1326
|
+
await connection.execute(
|
|
1327
|
+
'Fetch.enable',
|
|
1328
|
+
{
|
|
1329
|
+
handleAuthRequests: true,
|
|
1330
|
+
},
|
|
1331
|
+
null
|
|
1332
|
+
)
|
|
1260
1333
|
await connection.execute(
|
|
1261
1334
|
'Network.setCacheDisabled',
|
|
1262
|
-
this.getRandomNumber(1, 10),
|
|
1263
1335
|
{
|
|
1264
1336
|
cacheDisabled: true,
|
|
1265
1337
|
},
|
|
1266
1338
|
null
|
|
1267
1339
|
)
|
|
1340
|
+
}
|
|
1268
1341
|
|
|
1342
|
+
/**
|
|
1343
|
+
* Handle Network interception requests
|
|
1344
|
+
* @param connection WebSocket connection to the browser
|
|
1345
|
+
* @param httpResponse Object representing what we are intercepting
|
|
1346
|
+
* as well as what should be returned.
|
|
1347
|
+
* @param callback callback called when we intercept requests.
|
|
1348
|
+
*/
|
|
1349
|
+
async onIntercept(connection, httpResponse, callback) {
|
|
1269
1350
|
this._wsConnection.on('message', (message) => {
|
|
1270
1351
|
const params = JSON.parse(message)
|
|
1271
|
-
|
|
1272
|
-
if (params.method === 'Fetch.authRequired') {
|
|
1273
|
-
const requestParams = params['params']
|
|
1274
|
-
connection.execute(
|
|
1275
|
-
'Fetch.continueWithAuth',
|
|
1276
|
-
this.getRandomNumber(1, 10),
|
|
1277
|
-
{
|
|
1278
|
-
requestId: requestParams['requestId'],
|
|
1279
|
-
authChallengeResponse: {
|
|
1280
|
-
response: 'ProvideCredentials',
|
|
1281
|
-
username: username,
|
|
1282
|
-
password: password,
|
|
1283
|
-
},
|
|
1284
|
-
}
|
|
1285
|
-
)
|
|
1286
|
-
} else if (params.method === 'Fetch.requestPaused') {
|
|
1352
|
+
if (params.method === 'Fetch.requestPaused') {
|
|
1287
1353
|
const requestPausedParams = params['params']
|
|
1288
|
-
|
|
1289
|
-
'Fetch.continueRequest',
|
|
1290
|
-
this.getRandomNumber(1, 10),
|
|
1291
|
-
{
|
|
1354
|
+
if (requestPausedParams.request.url == httpResponse.urlToIntercept) {
|
|
1355
|
+
connection.execute('Fetch.continueRequest', {
|
|
1292
1356
|
requestId: requestPausedParams['requestId'],
|
|
1293
|
-
|
|
1294
|
-
|
|
1357
|
+
url: httpResponse.urlToIntercept,
|
|
1358
|
+
method: httpResponse.method,
|
|
1359
|
+
headers: httpResponse.headers,
|
|
1360
|
+
postData: httpResponse.body,
|
|
1361
|
+
})
|
|
1362
|
+
callback()
|
|
1363
|
+
} else {
|
|
1364
|
+
connection.execute('Fetch.continueRequest', {
|
|
1365
|
+
requestId: requestPausedParams['requestId'],
|
|
1366
|
+
})
|
|
1367
|
+
}
|
|
1295
1368
|
}
|
|
1296
1369
|
})
|
|
1297
1370
|
|
|
1371
|
+
await connection.execute('Fetch.enable', {}, null)
|
|
1298
1372
|
await connection.execute(
|
|
1299
|
-
'
|
|
1300
|
-
1,
|
|
1373
|
+
'Network.setCacheDisabled',
|
|
1301
1374
|
{
|
|
1302
|
-
|
|
1375
|
+
cacheDisabled: true,
|
|
1303
1376
|
},
|
|
1304
1377
|
null
|
|
1305
1378
|
)
|
|
1306
1379
|
}
|
|
1307
|
-
|
|
1308
1380
|
/**
|
|
1309
1381
|
*
|
|
1310
1382
|
* @param connection
|
|
@@ -1312,16 +1384,8 @@ class WebDriver {
|
|
|
1312
1384
|
* @returns {Promise<void>}
|
|
1313
1385
|
*/
|
|
1314
1386
|
async onLogEvent(connection, callback) {
|
|
1315
|
-
await connection.execute(
|
|
1316
|
-
'Runtime.enable',
|
|
1317
|
-
this.getRandomNumber(1, 10),
|
|
1318
|
-
{},
|
|
1319
|
-
null
|
|
1320
|
-
)
|
|
1321
|
-
|
|
1322
1387
|
this._wsConnection.on('message', (message) => {
|
|
1323
1388
|
const params = JSON.parse(message)
|
|
1324
|
-
|
|
1325
1389
|
if (params.method === 'Runtime.consoleAPICalled') {
|
|
1326
1390
|
const consoleEventParams = params['params']
|
|
1327
1391
|
let event = {
|
|
@@ -1332,7 +1396,20 @@ class WebDriver {
|
|
|
1332
1396
|
|
|
1333
1397
|
callback(event)
|
|
1334
1398
|
}
|
|
1399
|
+
|
|
1400
|
+
if (params.method === 'Log.entryAdded') {
|
|
1401
|
+
const logEventParams = params['params']
|
|
1402
|
+
const logEntry = logEventParams['entry']
|
|
1403
|
+
let event = {
|
|
1404
|
+
level: logEntry['level'],
|
|
1405
|
+
timestamp: new Date(logEntry['timestamp']),
|
|
1406
|
+
message: logEntry['text'],
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
callback(event)
|
|
1410
|
+
}
|
|
1335
1411
|
})
|
|
1412
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1336
1413
|
}
|
|
1337
1414
|
|
|
1338
1415
|
/**
|
|
@@ -1342,12 +1419,7 @@ class WebDriver {
|
|
|
1342
1419
|
* @returns {Promise<void>}
|
|
1343
1420
|
*/
|
|
1344
1421
|
async onLogException(connection, callback) {
|
|
1345
|
-
await connection.execute(
|
|
1346
|
-
'Runtime.enable',
|
|
1347
|
-
this.getRandomNumber(1, 10),
|
|
1348
|
-
{},
|
|
1349
|
-
null
|
|
1350
|
-
)
|
|
1422
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1351
1423
|
|
|
1352
1424
|
this._wsConnection.on('message', (message) => {
|
|
1353
1425
|
const params = JSON.parse(message)
|
|
@@ -1370,22 +1442,11 @@ class WebDriver {
|
|
|
1370
1442
|
* @returns {Promise<void>}
|
|
1371
1443
|
*/
|
|
1372
1444
|
async logMutationEvents(connection, callback) {
|
|
1373
|
-
await connection.execute(
|
|
1374
|
-
|
|
1375
|
-
this.getRandomNumber(1, 10),
|
|
1376
|
-
{},
|
|
1377
|
-
null
|
|
1378
|
-
)
|
|
1379
|
-
await connection.execute(
|
|
1380
|
-
'Page.enable',
|
|
1381
|
-
this.getRandomNumber(1, 10),
|
|
1382
|
-
{},
|
|
1383
|
-
null
|
|
1384
|
-
)
|
|
1445
|
+
await connection.execute('Runtime.enable', {}, null)
|
|
1446
|
+
await connection.execute('Page.enable', {}, null)
|
|
1385
1447
|
|
|
1386
1448
|
await connection.execute(
|
|
1387
1449
|
'Runtime.addBinding',
|
|
1388
|
-
this.getRandomNumber(1, 10),
|
|
1389
1450
|
{
|
|
1390
1451
|
name: '__webdriver_attribute',
|
|
1391
1452
|
},
|
|
@@ -1415,7 +1476,6 @@ class WebDriver {
|
|
|
1415
1476
|
|
|
1416
1477
|
await connection.execute(
|
|
1417
1478
|
'Page.addScriptToEvaluateOnNewDocument',
|
|
1418
|
-
this.getRandomNumber(1, 10),
|
|
1419
1479
|
{
|
|
1420
1480
|
source: mutationListener,
|
|
1421
1481
|
},
|
|
@@ -1427,7 +1487,7 @@ class WebDriver {
|
|
|
1427
1487
|
if (params.method === 'Runtime.bindingCalled') {
|
|
1428
1488
|
let payload = JSON.parse(params['params']['payload'])
|
|
1429
1489
|
let elements = await this.findElements({
|
|
1430
|
-
css: '*[data-__webdriver_id=' + payload['target'],
|
|
1490
|
+
css: '*[data-__webdriver_id=' + by.escapeCss(payload['target']) + ']',
|
|
1431
1491
|
})
|
|
1432
1492
|
|
|
1433
1493
|
if (elements.length === 0) {
|
|
@@ -1444,10 +1504,6 @@ class WebDriver {
|
|
|
1444
1504
|
}
|
|
1445
1505
|
})
|
|
1446
1506
|
}
|
|
1447
|
-
|
|
1448
|
-
getRandomNumber(min, max) {
|
|
1449
|
-
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
1450
|
-
}
|
|
1451
1507
|
}
|
|
1452
1508
|
|
|
1453
1509
|
/**
|
|
@@ -1729,7 +1785,7 @@ class Options {
|
|
|
1729
1785
|
} else if (typeof value !== 'undefined') {
|
|
1730
1786
|
throw TypeError(
|
|
1731
1787
|
'invalid timeouts configuration:' +
|
|
1732
|
-
|
|
1788
|
+
` expected "${key}" to be a number, got ${typeof value}`
|
|
1733
1789
|
)
|
|
1734
1790
|
}
|
|
1735
1791
|
}
|
|
@@ -1790,7 +1846,7 @@ function legacyTimeout(driver, type, ms) {
|
|
|
1790
1846
|
*
|
|
1791
1847
|
* @record
|
|
1792
1848
|
*/
|
|
1793
|
-
Options.Cookie = function () {
|
|
1849
|
+
Options.Cookie = function () {}
|
|
1794
1850
|
|
|
1795
1851
|
/**
|
|
1796
1852
|
* The name of the cookie.
|
|
@@ -1895,21 +1951,6 @@ class Window {
|
|
|
1895
1951
|
new command.Command(command.Name.GET_WINDOW_RECT)
|
|
1896
1952
|
)
|
|
1897
1953
|
} catch (ex) {
|
|
1898
|
-
if (ex instanceof error.UnknownCommandError) {
|
|
1899
|
-
let { width, height } = await this.driver_.execute(
|
|
1900
|
-
new command.Command(command.Name.GET_WINDOW_SIZE).setParameter(
|
|
1901
|
-
'windowHandle',
|
|
1902
|
-
'current'
|
|
1903
|
-
)
|
|
1904
|
-
)
|
|
1905
|
-
let { x, y } = await this.driver_.execute(
|
|
1906
|
-
new command.Command(command.Name.GET_WINDOW_POSITION).setParameter(
|
|
1907
|
-
'windowHandle',
|
|
1908
|
-
'current'
|
|
1909
|
-
)
|
|
1910
|
-
)
|
|
1911
|
-
return { x, y, width, height }
|
|
1912
|
-
}
|
|
1913
1954
|
throw ex
|
|
1914
1955
|
}
|
|
1915
1956
|
}
|
|
@@ -1939,26 +1980,6 @@ class Window {
|
|
|
1939
1980
|
})
|
|
1940
1981
|
)
|
|
1941
1982
|
} catch (ex) {
|
|
1942
|
-
if (ex instanceof error.UnknownCommandError) {
|
|
1943
|
-
if (typeof x === 'number' && typeof y === 'number') {
|
|
1944
|
-
await this.driver_.execute(
|
|
1945
|
-
new command.Command(command.Name.SET_WINDOW_POSITION)
|
|
1946
|
-
.setParameter('windowHandle', 'current')
|
|
1947
|
-
.setParameter('x', x)
|
|
1948
|
-
.setParameter('y', y)
|
|
1949
|
-
)
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
if (typeof width === 'number' && typeof height === 'number') {
|
|
1953
|
-
await this.driver_.execute(
|
|
1954
|
-
new command.Command(command.Name.SET_WINDOW_SIZE)
|
|
1955
|
-
.setParameter('windowHandle', 'current')
|
|
1956
|
-
.setParameter('width', width)
|
|
1957
|
-
.setParameter('height', height)
|
|
1958
|
-
)
|
|
1959
|
-
}
|
|
1960
|
-
return this.getRect()
|
|
1961
|
-
}
|
|
1962
1983
|
throw ex
|
|
1963
1984
|
}
|
|
1964
1985
|
}
|
|
@@ -2105,7 +2126,7 @@ class TargetLocator {
|
|
|
2105
2126
|
* @return {!WebElementPromise} The active element.
|
|
2106
2127
|
*/
|
|
2107
2128
|
activeElement() {
|
|
2108
|
-
|
|
2129
|
+
const id = this.driver_.execute(
|
|
2109
2130
|
new command.Command(command.Name.GET_ACTIVE_ELEMENT)
|
|
2110
2131
|
)
|
|
2111
2132
|
return new WebElementPromise(this.driver_, id)
|
|
@@ -2197,7 +2218,7 @@ class TargetLocator {
|
|
|
2197
2218
|
* when the driver has changed focus to the new window.
|
|
2198
2219
|
*/
|
|
2199
2220
|
newWindow(typeHint) {
|
|
2200
|
-
|
|
2221
|
+
const driver = this.driver_
|
|
2201
2222
|
return this.driver_
|
|
2202
2223
|
.execute(
|
|
2203
2224
|
new command.Command(command.Name.SWITCH_TO_NEW_WINDOW).setParameter(
|
|
@@ -2219,10 +2240,10 @@ class TargetLocator {
|
|
|
2219
2240
|
* @return {!AlertPromise} The open alert.
|
|
2220
2241
|
*/
|
|
2221
2242
|
alert() {
|
|
2222
|
-
|
|
2243
|
+
const text = this.driver_.execute(
|
|
2223
2244
|
new command.Command(command.Name.GET_ALERT_TEXT)
|
|
2224
2245
|
)
|
|
2225
|
-
|
|
2246
|
+
const driver = this.driver_
|
|
2226
2247
|
return new AlertPromise(
|
|
2227
2248
|
driver,
|
|
2228
2249
|
text.then(function (text) {
|
|
@@ -2240,6 +2261,7 @@ class TargetLocator {
|
|
|
2240
2261
|
|
|
2241
2262
|
const LEGACY_ELEMENT_ID_KEY = 'ELEMENT'
|
|
2242
2263
|
const ELEMENT_ID_KEY = 'element-6066-11e4-a52e-4f735466cecf'
|
|
2264
|
+
const SHADOWROOT_ID_KEY = 'shadow-6066-11e4-a52e-4f735466cecf'
|
|
2243
2265
|
|
|
2244
2266
|
/**
|
|
2245
2267
|
* Represents a DOM element. WebElements can be found by searching from the
|
|
@@ -2495,18 +2517,18 @@ class WebElement {
|
|
|
2495
2517
|
*/
|
|
2496
2518
|
async sendKeys(...args) {
|
|
2497
2519
|
let keys = []
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2520
|
+
;(await Promise.all(args)).forEach((key) => {
|
|
2521
|
+
let type = typeof key
|
|
2522
|
+
if (type === 'number') {
|
|
2523
|
+
key = String(key)
|
|
2524
|
+
} else if (type !== 'string') {
|
|
2525
|
+
throw TypeError('each key must be a number of string; got ' + type)
|
|
2526
|
+
}
|
|
2505
2527
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2528
|
+
// The W3C protocol requires keys to be specified as an array where
|
|
2529
|
+
// each element is a single key.
|
|
2530
|
+
keys.push(...key.split(''))
|
|
2531
|
+
})
|
|
2510
2532
|
|
|
2511
2533
|
if (!this.driver_.fileDetector_) {
|
|
2512
2534
|
return this.execute_(
|
|
@@ -2516,10 +2538,15 @@ class WebElement {
|
|
|
2516
2538
|
)
|
|
2517
2539
|
}
|
|
2518
2540
|
|
|
2519
|
-
|
|
2520
|
-
this.driver_
|
|
2521
|
-
|
|
2522
|
-
|
|
2541
|
+
try {
|
|
2542
|
+
keys = await this.driver_.fileDetector_.handleFile(
|
|
2543
|
+
this.driver_,
|
|
2544
|
+
keys.join('')
|
|
2545
|
+
)
|
|
2546
|
+
} catch (ex) {
|
|
2547
|
+
console.log('Error trying parse string as a file with file detector; sending keys instead' + ex)
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2523
2550
|
return this.execute_(
|
|
2524
2551
|
new command.Command(command.Name.SEND_KEYS_TO_ELEMENT)
|
|
2525
2552
|
.setParameter('text', keys)
|
|
@@ -2552,7 +2579,7 @@ class WebElement {
|
|
|
2552
2579
|
* requested CSS value.
|
|
2553
2580
|
*/
|
|
2554
2581
|
getCssValue(cssStyleProperty) {
|
|
2555
|
-
|
|
2582
|
+
const name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY
|
|
2556
2583
|
return this.execute_(
|
|
2557
2584
|
new command.Command(name).setParameter('propertyName', cssStyleProperty)
|
|
2558
2585
|
)
|
|
@@ -2596,6 +2623,36 @@ class WebElement {
|
|
|
2596
2623
|
)
|
|
2597
2624
|
}
|
|
2598
2625
|
|
|
2626
|
+
/**
|
|
2627
|
+
* Get the value of the given attribute of the element.
|
|
2628
|
+
* <p>
|
|
2629
|
+
* This method, unlike {@link #getAttribute(String)}, returns the value of the attribute with the
|
|
2630
|
+
* given name but not the property with the same name.
|
|
2631
|
+
* <p>
|
|
2632
|
+
* The following are deemed to be "boolean" attributes, and will return either "true" or null:
|
|
2633
|
+
* <p>
|
|
2634
|
+
* async, autofocus, autoplay, checked, compact, complete, controls, declare, defaultchecked,
|
|
2635
|
+
* defaultselected, defer, disabled, draggable, ended, formnovalidate, hidden, indeterminate,
|
|
2636
|
+
* iscontenteditable, ismap, itemscope, loop, multiple, muted, nohref, noresize, noshade,
|
|
2637
|
+
* novalidate, nowrap, open, paused, pubdate, readonly, required, reversed, scoped, seamless,
|
|
2638
|
+
* seeking, selected, truespeed, willvalidate
|
|
2639
|
+
* <p>
|
|
2640
|
+
* See <a href="https://w3c.github.io/webdriver/#get-element-attribute">W3C WebDriver specification</a>
|
|
2641
|
+
* for more details.
|
|
2642
|
+
*
|
|
2643
|
+
* @param attributeName The name of the attribute.
|
|
2644
|
+
* @return The attribute's value or null if the value is not set.
|
|
2645
|
+
*/
|
|
2646
|
+
|
|
2647
|
+
getDomAttribute(attributeName) {
|
|
2648
|
+
return this.execute_(
|
|
2649
|
+
new command.Command(command.Name.GET_DOM_ATTRIBUTE).setParameter(
|
|
2650
|
+
'name',
|
|
2651
|
+
attributeName
|
|
2652
|
+
)
|
|
2653
|
+
)
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2599
2656
|
/**
|
|
2600
2657
|
* Get the given property of the referenced web element
|
|
2601
2658
|
* @param {string} propertyName The name of the attribute to query.
|
|
@@ -2611,6 +2668,16 @@ class WebElement {
|
|
|
2611
2668
|
)
|
|
2612
2669
|
}
|
|
2613
2670
|
|
|
2671
|
+
/**
|
|
2672
|
+
* Get the shadow root of the current web element.
|
|
2673
|
+
* @returns {!Promise<ShadowRoot>} A promise that will be
|
|
2674
|
+
* resolved with the elements shadow root or rejected
|
|
2675
|
+
* with {@link NoSuchShadowRootError}
|
|
2676
|
+
*/
|
|
2677
|
+
getShadowRoot() {
|
|
2678
|
+
return this.execute_(new command.Command(command.Name.GET_SHADOW_ROOT))
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2614
2681
|
/**
|
|
2615
2682
|
* Get the visible (i.e. not hidden by CSS) innerText of this element,
|
|
2616
2683
|
* including sub-elements, without any leading or trailing whitespace.
|
|
@@ -2654,15 +2721,7 @@ class WebElement {
|
|
|
2654
2721
|
new command.Command(command.Name.GET_ELEMENT_RECT)
|
|
2655
2722
|
)
|
|
2656
2723
|
} catch (err) {
|
|
2657
|
-
|
|
2658
|
-
const { width, height } = await this.execute_(
|
|
2659
|
-
new command.Command(command.Name.GET_ELEMENT_SIZE)
|
|
2660
|
-
)
|
|
2661
|
-
const { x, y } = await this.execute_(
|
|
2662
|
-
new command.Command(command.Name.GET_ELEMENT_LOCATION)
|
|
2663
|
-
)
|
|
2664
|
-
return { x, y, width, height }
|
|
2665
|
-
}
|
|
2724
|
+
throw err;
|
|
2666
2725
|
}
|
|
2667
2726
|
}
|
|
2668
2727
|
|
|
@@ -2696,7 +2755,13 @@ class WebElement {
|
|
|
2696
2755
|
* when the form has been submitted.
|
|
2697
2756
|
*/
|
|
2698
2757
|
submit() {
|
|
2699
|
-
|
|
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
|
+
)
|
|
2700
2765
|
}
|
|
2701
2766
|
|
|
2702
2767
|
/**
|
|
@@ -2779,6 +2844,186 @@ class WebElementPromise extends WebElement {
|
|
|
2779
2844
|
}
|
|
2780
2845
|
}
|
|
2781
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
|
+
|
|
2782
3027
|
//////////////////////////////////////////////////////////////////////////////
|
|
2783
3028
|
//
|
|
2784
3029
|
// Alert
|
|
@@ -2936,6 +3181,7 @@ module.exports = {
|
|
|
2936
3181
|
Logs,
|
|
2937
3182
|
Navigation,
|
|
2938
3183
|
Options,
|
|
3184
|
+
ShadowRoot,
|
|
2939
3185
|
TargetLocator,
|
|
2940
3186
|
IWebDriver,
|
|
2941
3187
|
WebDriver,
|