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/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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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) { } // eslint-disable-line
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
- '(did you call WebDriver.quit()?) and may no longer be used.'
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 ${Date.now() - start
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
- `${ex.message
838
- }\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
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
- 'Condition object'
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
- Object.prototype.toString.call(value)
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._cdpConnection = new cdp.CdpConnection(this._wsConnection)
1213
- resolve(this._cdpConnection)
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 {string} target
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
- let url
1288
+
1242
1289
  if (target.toLowerCase() === 'page') {
1243
- url = JSON.parse(response.body)[0]['webSocketDebuggerUrl']
1290
+ return JSON.parse(response.body)[0]['webSocketDebuggerUrl']
1244
1291
  } else {
1245
- url = JSON.parse(response.body)['webSocketDebuggerUrl']
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
- connection.execute(
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
- 'Fetch.enable',
1300
- 1,
1373
+ 'Network.setCacheDisabled',
1301
1374
  {
1302
- handleAuthRequests: true,
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
- 'Runtime.enable',
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
- ` expected "${key}" to be a number, got ${typeof value}`
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
- var id = this.driver_.execute(
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
- var driver = this.driver_
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
- var text = this.driver_.execute(
2243
+ const text = this.driver_.execute(
2223
2244
  new command.Command(command.Name.GET_ALERT_TEXT)
2224
2245
  )
2225
- var driver = this.driver_
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
- ; (await Promise.all(args)).forEach((key) => {
2499
- let type = typeof key
2500
- if (type === 'number') {
2501
- key = String(key)
2502
- } else if (type !== 'string') {
2503
- throw TypeError('each key must be a number of string; got ' + type)
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
- // The W3C protocol requires keys to be specified as an array where
2507
- // each element is a single key.
2508
- keys.push(...key.split(''))
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
- keys = await this.driver_.fileDetector_.handleFile(
2520
- this.driver_,
2521
- keys.join('')
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
- var name = command.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY
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
- if (err instanceof error.UnknownCommandError) {
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
- 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
+ )
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,