@@ -36,6 +36,7 @@ const http = require('../http/index')
36
36
const fs = require ( 'fs' )
37
37
const { Capabilities } = require ( './capabilities' )
38
38
const path = require ( 'path' )
39
+ const { NoSuchElementError } = require ( './error' )
39
40
40
41
// Capability names that are defined in the W3C spec.
41
42
const W3C_CAPABILITY_NAMES = new Set ( [
@@ -257,18 +258,18 @@ class IWebDriver {
257
258
/**
258
259
* @return {!command.Executor } The command executor used by this instance.
259
260
*/
260
- getExecutor ( ) { }
261
+ getExecutor ( ) { }
261
262
262
263
/**
263
264
* @return {!Promise<!Session> } A promise for this client's session.
264
265
*/
265
- getSession ( ) { }
266
+ getSession ( ) { }
266
267
267
268
/**
268
269
* @return {!Promise<!Capabilities> } A promise that will resolve with
269
270
* the this instance's capabilities.
270
271
*/
271
- getCapabilities ( ) { }
272
+ getCapabilities ( ) { }
272
273
273
274
/**
274
275
* Terminates the browser session. After calling quit, this instance will be
@@ -278,7 +279,7 @@ class IWebDriver {
278
279
* @return {!Promise<void> } A promise that will be resolved when the
279
280
* command has completed.
280
281
*/
281
- quit ( ) { }
282
+ quit ( ) { }
282
283
283
284
/**
284
285
* Creates a new action sequence using this driver. The sequence will not be
@@ -457,7 +458,7 @@ class IWebDriver {
457
458
timeout = undefined , // eslint-disable-line
458
459
message = undefined , // eslint-disable-line
459
460
pollTimeout = undefined // eslint-disable-line
460
- ) { }
461
+ ) { }
461
462
462
463
/**
463
464
* Makes the driver sleep for the given amount of time.
@@ -474,15 +475,15 @@ class IWebDriver {
474
475
* @return {!Promise<string> } A promise that will be resolved with the current
475
476
* window handle.
476
477
*/
477
- getWindowHandle ( ) { }
478
+ getWindowHandle ( ) { }
478
479
479
480
/**
480
481
* Retrieves a list of all available window handles.
481
482
*
482
483
* @return {!Promise<!Array<string>> } A promise that will be resolved with an
483
484
* array of window handles.
484
485
*/
485
- getAllWindowHandles ( ) { }
486
+ getAllWindowHandles ( ) { }
486
487
487
488
/**
488
489
* Retrieves the current page's source. The returned source is a representation
@@ -492,15 +493,15 @@ class IWebDriver {
492
493
* @return {!Promise<string> } A promise that will be resolved with the current
493
494
* page source.
494
495
*/
495
- getPageSource ( ) { }
496
+ getPageSource ( ) { }
496
497
497
498
/**
498
499
* Closes the current window.
499
500
*
500
501
* @return {!Promise<void> } A promise that will be resolved when this command
501
502
* has completed.
502
503
*/
503
- close ( ) { }
504
+ close ( ) { }
504
505
505
506
/**
506
507
* Navigates to the given URL.
@@ -517,15 +518,15 @@ class IWebDriver {
517
518
* @return {!Promise<string> } A promise that will be resolved with the
518
519
* current URL.
519
520
*/
520
- getCurrentUrl ( ) { }
521
+ getCurrentUrl ( ) { }
521
522
522
523
/**
523
524
* Retrieves the current page title.
524
525
*
525
526
* @return {!Promise<string> } A promise that will be resolved with the current
526
527
* page's title.
527
528
*/
528
- getTitle ( ) { }
529
+ getTitle ( ) { }
529
530
530
531
/**
531
532
* Locates an element on the page. If the element cannot be found, a
@@ -589,23 +590,23 @@ class IWebDriver {
589
590
* @return {!Promise<string> } A promise that will be resolved to the
590
591
* screenshot as a base-64 encoded PNG.
591
592
*/
592
- takeScreenshot ( ) { }
593
+ takeScreenshot ( ) { }
593
594
594
595
/**
595
596
* @return {!Options } The options interface for this instance.
596
597
*/
597
- manage ( ) { }
598
+ manage ( ) { }
598
599
599
600
/**
600
601
* @return {!Navigation } The navigation interface for this instance.
601
602
*/
602
- navigate ( ) { }
603
+ navigate ( ) { }
603
604
604
605
/**
605
606
* @return {!TargetLocator } The target locator interface for this
606
607
* instance.
607
608
*/
608
- switchTo ( ) { }
609
+ switchTo ( ) { }
609
610
610
611
/**
611
612
*
@@ -649,7 +650,6 @@ function filterNonW3CCaps(capabilities) {
649
650
* @implements {IWebDriver}
650
651
*/
651
652
class WebDriver {
652
-
653
653
/**
654
654
* @param {!(./session.Session|IThenable<!./session.Session>) } session Either
655
655
* a known session or a promise that will be resolved to a session.
@@ -665,7 +665,7 @@ class WebDriver {
665
665
// If session is a rejected promise, add a no-op rejection handler.
666
666
// This effectively hides setup errors until users attempt to interact
667
667
// with the session.
668
- this . session_ . catch ( function ( ) { } )
668
+ this . session_ . catch ( function ( ) { } )
669
669
670
670
/** @private {!command.Executor} */
671
671
this . executor_ = executor
@@ -761,12 +761,12 @@ class WebDriver {
761
761
this . session_ = Promise . reject (
762
762
new error . NoSuchSessionError (
763
763
'This driver instance does not have a valid session ID ' +
764
- '(did you call WebDriver.quit()?) and may no longer be used.'
764
+ '(did you call WebDriver.quit()?) and may no longer be used.'
765
765
)
766
766
)
767
767
768
768
// Only want the session rejection to bubble if accessed.
769
- this . session_ . catch ( function ( ) { } )
769
+ this . session_ . catch ( function ( ) { } )
770
770
771
771
if ( this . onQuit_ ) {
772
772
return this . onQuit_ . call ( void 0 )
@@ -827,15 +827,18 @@ class WebDriver {
827
827
let timeoutMessage = resolveWaitMessage ( message )
828
828
reject (
829
829
new error . TimeoutError (
830
- `${ timeoutMessage } Timed out waiting for promise to resolve after ${ Date . now ( ) - start
830
+ `${ timeoutMessage } Timed out waiting for promise to resolve after ${
831
+ Date . now ( ) - start
831
832
} ms`
832
833
)
833
834
)
834
835
} catch ( ex ) {
835
836
reject (
836
837
new error . TimeoutError (
837
- `${ ex . message
838
- } \nTimed out waiting for promise to resolve after ${ Date . now ( ) - start
838
+ `${
839
+ ex . message
840
+ } \nTimed out waiting for promise to resolve after ${
841
+ Date . now ( ) - start
839
842
} ms`
840
843
)
841
844
)
@@ -865,7 +868,7 @@ class WebDriver {
865
868
if ( typeof fn !== 'function' ) {
866
869
throw TypeError (
867
870
'Wait condition must be a promise-like object, function, or a ' +
868
- 'Condition object'
871
+ 'Condition object'
869
872
)
870
873
}
871
874
@@ -917,7 +920,7 @@ class WebDriver {
917
920
if ( ! ( value instanceof WebElement ) ) {
918
921
throw TypeError (
919
922
'WebElementCondition did not resolve to a WebElement: ' +
920
- Object . prototype . toString . call ( value )
923
+ Object . prototype . toString . call ( value )
921
924
)
922
925
}
923
926
return value
@@ -972,16 +975,46 @@ class WebDriver {
972
975
/** @override */
973
976
findElement ( locator ) {
974
977
let id
975
- locator = by . checkedLocator ( locator )
978
+ let cmd = null
979
+
980
+ if ( locator instanceof RelativeBy ) {
981
+ cmd = new command . Command (
982
+ command . Name . FIND_ELEMENTS_RELATIVE
983
+ ) . setParameter ( 'args' , locator . marshall ( ) )
984
+ } else {
985
+ locator = by . checkedLocator ( locator )
986
+ }
987
+
976
988
if ( typeof locator === 'function' ) {
977
989
id = this . findElementInternal_ ( locator , this )
978
- } else {
979
- let cmd = new command . Command ( command . Name . FIND_ELEMENT )
990
+ return new WebElementPromise ( this , id )
991
+ } else if ( cmd === null ) {
992
+ cmd = new command . Command ( command . Name . FIND_ELEMENT )
980
993
. setParameter ( 'using' , locator . using )
981
994
. setParameter ( 'value' , locator . value )
982
- id = this . execute ( cmd )
983
995
}
984
- return new WebElementPromise ( this , id )
996
+
997
+ id = this . execute ( cmd )
998
+ if ( locator instanceof RelativeBy ) {
999
+ return this . normalize_ ( id )
1000
+ } else {
1001
+ return new WebElementPromise ( this , id )
1002
+ }
1003
+ }
1004
+
1005
+ /**
1006
+ * @param {!Function } webElementPromise The webElement in unresolved state
1007
+ * @return {!Promise<!WebElement> } First single WebElement from array of resolved promises
1008
+ */
1009
+ async normalize_ ( webElementPromise ) {
1010
+ let result = await webElementPromise
1011
+ if ( result . length === 0 ) {
1012
+ throw new NoSuchElementError (
1013
+ 'Cannot locate an element with provided parameters'
1014
+ )
1015
+ } else {
1016
+ return result [ 0 ]
1017
+ }
985
1018
}
986
1019
987
1020
/**
@@ -1166,7 +1199,7 @@ class WebDriver {
1166
1199
caps [ 'map_' ] . get ( this . VENDOR_COMMAND_PREFIX + ':chromeOptions' ) ||
1167
1200
caps [ 'map_' ] . get ( this . VENDOR_CAPABILITY_PREFIX + ':edgeOptions' ) ||
1168
1201
caps [ 'map_' ] . get ( 'moz:debuggerAddress' ) ||
1169
- new Map ( ) ;
1202
+ new Map ( )
1170
1203
const debuggerUrl = seCdp || vendorInfo [ 'debuggerAddress' ] || vendorInfo
1171
1204
this . _wsUrl = await this . getWsUrl ( debuggerUrl , target )
1172
1205
@@ -1374,7 +1407,10 @@ class WebDriver {
1374
1407
. toString ( )
1375
1408
} catch {
1376
1409
mutationListener = fs
1377
- . readFileSync ( path . resolve ( __dirname , './atoms/mutation-listener.js' ) , 'utf-8' )
1410
+ . readFileSync (
1411
+ path . resolve ( __dirname , './atoms/mutation-listener.js' ) ,
1412
+ 'utf-8'
1413
+ )
1378
1414
. toString ( )
1379
1415
}
1380
1416
@@ -1696,7 +1732,7 @@ class Options {
1696
1732
} else if ( typeof value !== 'undefined' ) {
1697
1733
throw TypeError (
1698
1734
'invalid timeouts configuration:' +
1699
- ` expected "${ key } " to be a number, got ${ typeof value } `
1735
+ ` expected "${ key } " to be a number, got ${ typeof value } `
1700
1736
)
1701
1737
}
1702
1738
}
@@ -1757,7 +1793,7 @@ function legacyTimeout(driver, type, ms) {
1757
1793
*
1758
1794
* @record
1759
1795
*/
1760
- Options . Cookie = function ( ) { }
1796
+ Options . Cookie = function ( ) { }
1761
1797
1762
1798
/**
1763
1799
* The name of the cookie.
@@ -2462,18 +2498,18 @@ class WebElement {
2462
2498
*/
2463
2499
async sendKeys ( ...args ) {
2464
2500
let keys = [ ]
2465
- ; ( await Promise . all ( args ) ) . forEach ( ( key ) => {
2466
- let type = typeof key
2467
- if ( type === 'number' ) {
2468
- key = String ( key )
2469
- } else if ( type !== 'string' ) {
2470
- throw TypeError ( 'each key must be a number of string; got ' + type )
2471
- }
2501
+ ; ( await Promise . all ( args ) ) . forEach ( ( key ) => {
2502
+ let type = typeof key
2503
+ if ( type === 'number' ) {
2504
+ key = String ( key )
2505
+ } else if ( type !== 'string' ) {
2506
+ throw TypeError ( 'each key must be a number of string; got ' + type )
2507
+ }
2472
2508
2473
- // The W3C protocol requires keys to be specified as an array where
2474
- // each element is a single key.
2475
- keys . push ( ...key . split ( '' ) )
2476
- } )
2509
+ // The W3C protocol requires keys to be specified as an array where
2510
+ // each element is a single key.
2511
+ keys . push ( ...key . split ( '' ) )
2512
+ } )
2477
2513
2478
2514
if ( ! this . driver_ . fileDetector_ ) {
2479
2515
return this . execute_ (
0 commit comments