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/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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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
- '(did you call WebDriver.quit()?) and may no longer be used.'
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 ${Date.now() - start
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
- `${ex.message
839
- }\nTimed out waiting for promise to resolve after ${Date.now() - start
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
- 'Condition object'
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
- Object.prototype.toString.call(value)
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
- const seCdp = caps['map_'].get('se:cdp')
1196
- const vendorInfo =
1197
- caps['map_'].get(this.VENDOR_COMMAND_PREFIX + ':chromeOptions') ||
1198
- caps['map_'].get(this.VENDOR_CAPABILITY_PREFIX + ':edgeOptions') ||
1199
- caps['map_'].get('moz:debuggerAddress') ||
1200
- new Map()
1201
- const debuggerUrl = seCdp || vendorInfo['debuggerAddress'] || vendorInfo
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._cdpConnection = new cdp.CdpConnection(this._wsConnection)
1213
- resolve(this._cdpConnection)
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(target === 'page' && caps['map_'].get('browserName')==='firefox'){
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
- '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
- )
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
- 'Fetch.continueRequest',
1290
- this.getRandomNumber(1, 10),
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
- 'Fetch.continueRequest',
1332
- this.getRandomNumber(1, 10),
1333
- {
1334
- requestId: requestPausedParams['requestId'],
1335
- url: httpResponse.urlToIntercept,
1336
- method: httpResponse.method,
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
- 'Fetch.continueRequest',
1345
- this.getRandomNumber(1, 10),
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
- 'Runtime.enable',
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
- ` expected "${key}" to be a number, got ${typeof value}`
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
- async getRect() {
1955
- try {
1956
- return await this.driver_.execute(
1957
- new command.Command(command.Name.GET_WINDOW_RECT)
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
- async setRect({ x, y, width, height }) {
1994
- try {
1995
- return await this.driver_.execute(
1996
- new command.Command(command.Name.SET_WINDOW_RECT).setParameters({
1997
- x,
1998
- y,
1999
- width,
2000
- height,
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
- var id = this.driver_.execute(
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
- var driver = this.driver_
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
- var text = this.driver_.execute(
2246
+ const text = this.driver_.execute(
2285
2247
  new command.Command(command.Name.GET_ALERT_TEXT)
2286
2248
  )
2287
- var driver = this.driver_
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
- ; (await Promise.all(args)).forEach((key) => {
2561
- let type = typeof key
2562
- if (type === 'number') {
2563
- key = String(key)
2564
- } else if (type !== 'string') {
2565
- throw TypeError('each key must be a number of string; got ' + type)
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
- // The W3C protocol requires keys to be specified as an array where
2569
- // each element is a single key.
2570
- keys.push(...key.split(''))
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
- keys = await this.driver_.fileDetector_.handleFile(
2582
- this.driver_,
2583
- keys.join('')
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
- var name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY
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
- async getRect() {
2714
- try {
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
- return this.execute_(new command.Command(command.Name.SUBMIT_ELEMENT))
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,