27
27
import org .openqa .selenium .Dimension ;
28
28
import org .openqa .selenium .HasCapabilities ;
29
29
import org .openqa .selenium .ImmutableCapabilities ;
30
+ import org .openqa .selenium .InvalidArgumentException ;
30
31
import org .openqa .selenium .JavascriptExecutor ;
31
32
import org .openqa .selenium .MutableCapabilities ;
32
33
import org .openqa .selenium .NoSuchElementException ;
33
34
import org .openqa .selenium .NoSuchFrameException ;
34
35
import org .openqa .selenium .NoSuchWindowException ;
35
36
import org .openqa .selenium .OutputType ;
37
+ import org .openqa .selenium .Pdf ;
36
38
import org .openqa .selenium .Platform ;
37
39
import org .openqa .selenium .Point ;
40
+ import org .openqa .selenium .PrintsPage ;
38
41
import org .openqa .selenium .SearchContext ;
39
42
import org .openqa .selenium .SessionNotCreatedException ;
40
43
import org .openqa .selenium .TakesScreenshot ;
55
58
import org .openqa .selenium .logging .Logs ;
56
59
import org .openqa .selenium .logging .NeedsLocalLogs ;
57
60
import org .openqa .selenium .print .PrintOptions ;
58
- import org .openqa .selenium .Pdf ;
59
- import org .openqa .selenium .PrintsPage ;
60
61
import org .openqa .selenium .remote .internal .WebElementToJsonConverter ;
61
62
import org .openqa .selenium .virtualauthenticator .Credential ;
62
63
import org .openqa .selenium .virtualauthenticator .HasVirtualAuthenticator ;
69
70
import java .util .Collection ;
70
71
import java .util .Collections ;
71
72
import java .util .Date ;
73
+ import java .util .HashMap ;
72
74
import java .util .HashSet ;
73
75
import java .util .LinkedHashSet ;
74
76
import java .util .List ;
@@ -92,10 +94,22 @@ public class RemoteWebDriver implements WebDriver, JavascriptExecutor, HasInputD
92
94
HasCapabilities , Interactive , TakesScreenshot ,
93
95
HasVirtualAuthenticator , PrintsPage {
94
96
95
- // TODO(dawagner) : This static logger should be unified with the per-instance localLogs
97
+ // TODO: This static logger should be unified with the per-instance localLogs
96
98
private static final Logger logger = Logger .getLogger (RemoteWebDriver .class .getName ());
97
99
private Level level = Level .FINE ;
98
100
101
+ private Map <Class <? extends By >, Mechanism > remotableBys = new HashMap <>();
102
+ // We would do this in either `init` or a constructor, but there are
103
+ // multiple constructors and we occasionally add a new one, and `init`
104
+ // may be overridden by the user
105
+ {
106
+ remotableBys .put (By .cssSelector ("" ).getClass (), Mechanism .REMOTE );
107
+ remotableBys .put (By .linkText ("" ).getClass (), Mechanism .REMOTE );
108
+ remotableBys .put (By .partialLinkText ("" ).getClass (), Mechanism .REMOTE );
109
+ remotableBys .put (By .tagName ("" ).getClass (), Mechanism .REMOTE );
110
+ remotableBys .put (By .xpath ("" ).getClass (), Mechanism .REMOTE );
111
+ }
112
+
99
113
private ErrorHandler errorHandler = new ErrorHandler ();
100
114
private CommandExecutor executor ;
101
115
private Capabilities capabilities ;
@@ -112,7 +126,7 @@ public class RemoteWebDriver implements WebDriver, JavascriptExecutor, HasInputD
112
126
113
127
// For cglib
114
128
protected RemoteWebDriver () {
115
- this .capabilities = init (new ImmutableCapabilities ());
129
+ this .capabilities = init (new ImmutableCapabilities ());
116
130
}
117
131
118
132
public RemoteWebDriver (Capabilities capabilities ) {
@@ -129,7 +143,7 @@ public RemoteWebDriver(CommandExecutor executor, Capabilities capabilities) {
129
143
}
130
144
this .executor = executor ;
131
145
132
- capabilities = init (capabilities );
146
+ capabilities = init (capabilities );
133
147
134
148
if (executor instanceof NeedsLocalLogs ) {
135
149
((NeedsLocalLogs ) executor ).setLocalLogs (localLogs );
@@ -342,69 +356,121 @@ public Pdf print(PrintOptions printOptions) throws WebDriverException {
342
356
343
357
@ Override
344
358
public WebElement findElement (By locator ) {
345
- if (locator instanceof By .StandardLocator ) {
346
- return ((By .StandardLocator ) locator ).findElement (this , this ::findElement );
347
- } else {
348
- return locator .findElement (this );
359
+ Require .nonNull ("Locator" , locator );
360
+
361
+ return findElement (this , this , locator );
362
+ }
363
+
364
+ WebElement findElement (RemoteWebDriver parent , SearchContext context , By locator ) {
365
+ Mechanism mechanism = remotableBys .get (locator .getClass ());
366
+ if (mechanism != null ) {
367
+ WebElement element = mechanism .findElement (parent , context , locator );
368
+ return massage (parent , context , element , locator );
369
+ }
370
+
371
+ // Attempt to find the element remotely
372
+ if (locator instanceof By .Remotable ) {
373
+ try {
374
+ WebElement element = Mechanism .REMOTE .findElement (parent , context , locator );
375
+ remotableBys .put (locator .getClass (), Mechanism .REMOTE );
376
+ return massage (parent , context , element , locator );
377
+ } catch (NoSuchElementException e ) {
378
+ remotableBys .put (locator .getClass (), Mechanism .REMOTE );
379
+ throw e ;
380
+ } catch (InvalidArgumentException e ) {
381
+ // Fall through
382
+ }
383
+ }
384
+
385
+ try {
386
+ WebElement element = Mechanism .CONTEXT .findElement (parent , context , locator );
387
+ remotableBys .put (locator .getClass (), Mechanism .CONTEXT );
388
+ return massage (parent , context , element , locator );
389
+ } catch (NoSuchElementException e ) {
390
+ remotableBys .put (locator .getClass (), Mechanism .CONTEXT );
391
+ throw e ;
349
392
}
350
393
}
351
394
352
395
@ Override
353
396
public List <WebElement > findElements (By locator ) {
354
- if (locator instanceof By .StandardLocator ) {
355
- return ((By .StandardLocator ) locator ).findElements (this , this ::findElements );
356
- } else {
357
- return locator .findElements (this );
358
- }
397
+ Require .nonNull ("Locator" , locator );
398
+
399
+ return findElements (this , this , locator );
359
400
}
360
401
361
- protected WebElement findElement (String by , String using ) {
362
- if (using == null ) {
363
- throw new IllegalArgumentException ("Cannot find elements when the selector is null." );
402
+ public List <WebElement > findElements (RemoteWebDriver parent , SearchContext context , By locator ) {
403
+ Mechanism mechanism = remotableBys .get (locator .getClass ());
404
+ if (mechanism != null ) {
405
+ List <WebElement > elements = mechanism .findElements (parent , context , locator );
406
+ elements .forEach (e -> massage (parent , context , e , locator ));
407
+ return elements ;
364
408
}
365
409
366
- Response response = execute (DriverCommand .FIND_ELEMENT (by , using ));
367
- Object value = response .getValue ();
368
- if (value == null ) { // see https://github.com/SeleniumHQ/selenium/issues/5809
369
- throw new NoSuchElementException (String .format ("Cannot locate an element using %s=%s" , by , using ));
410
+ // Attempt to find the element remotely
411
+ if (locator instanceof By .Remotable ) {
412
+ try {
413
+ List <WebElement > elements = Mechanism .REMOTE .findElements (parent , context , locator );
414
+ remotableBys .put (locator .getClass (), Mechanism .REMOTE );
415
+ elements .forEach (e -> massage (parent , context , e , locator ));
416
+ return elements ;
417
+ } catch (NoSuchElementException e ) {
418
+ remotableBys .put (locator .getClass (), Mechanism .REMOTE );
419
+ throw e ;
420
+ } catch (InvalidArgumentException e ) {
421
+ // Fall through
422
+ }
370
423
}
371
- if (!(value instanceof WebElement )) {
372
- throw new WebDriverException ("Returned value cannot be converted to WebElement: " + value );
424
+
425
+ try {
426
+ List <WebElement > elements = Mechanism .CONTEXT .findElements (parent , context , locator );
427
+ remotableBys .put (locator .getClass (), Mechanism .CONTEXT );
428
+ elements .forEach (e -> massage (parent , context , e , locator ));
429
+ return elements ;
430
+ } catch (NoSuchElementException e ) {
431
+ remotableBys .put (locator .getClass (), Mechanism .CONTEXT );
432
+ throw e ;
373
433
}
374
- WebElement element = (WebElement ) value ;
375
- setFoundBy (this , element , by , using );
376
- return element ;
377
434
}
378
435
379
- protected void setFoundBy (SearchContext context , WebElement element , String by , String using ) {
380
- if (element instanceof RemoteWebElement ) {
381
- RemoteWebElement remoteElement = (RemoteWebElement ) element ;
382
- remoteElement .setFoundBy (context , by , using );
383
- remoteElement .setFileDetector (getFileDetector ());
436
+ private WebElement massage (RemoteWebDriver parent , SearchContext context , WebElement element , By locator ) {
437
+ if (!(element instanceof RemoteWebElement )) {
438
+ return element ;
439
+ }
440
+
441
+ RemoteWebElement remoteElement = (RemoteWebElement ) element ;
442
+ if (locator instanceof By .Remotable ) {
443
+ By .Remotable .Parameters params = ((By .Remotable ) locator ).getRemoteParameters ();
444
+ remoteElement .setFoundBy (context , params .using (), String .valueOf (params .value ()));
384
445
}
446
+ remoteElement .setFileDetector (parent .getFileDetector ());
447
+ remoteElement .setParent (parent );
448
+
449
+ return remoteElement ;
385
450
}
386
451
387
- @ SuppressWarnings ("unchecked" )
452
+ /**
453
+ * @deprecated Rely on using {@link By.Remotable} instead
454
+ */
455
+ @ Deprecated
456
+ protected WebElement findElement (String by , String using ) {
457
+ throw new UnsupportedOperationException ("`findElement` has been replaced by usages of " + By .Remotable .class );
458
+ }
459
+
460
+ /**
461
+ * @deprecated Rely on using {@link By.Remotable} instead
462
+ */
463
+ @ Deprecated
388
464
protected List <WebElement > findElements (String by , String using ) {
389
- if (using == null ) {
390
- throw new IllegalArgumentException ("Cannot find elements when the selector is null." );
391
- }
465
+ throw new UnsupportedOperationException ("`findElement` has been replaced by usages of " + By .Remotable .class );
466
+ }
392
467
393
- Response response = execute (DriverCommand .FIND_ELEMENTS (by , using ));
394
- Object value = response .getValue ();
395
- if (value == null ) { // see https://github.com/SeleniumHQ/selenium/issues/4555
396
- return Collections .emptyList ();
397
- }
398
- List <WebElement > allElements ;
399
- try {
400
- allElements = (List <WebElement >) value ;
401
- } catch (ClassCastException ex ) {
402
- throw new WebDriverException ("Returned value cannot be converted to List<WebElement>: " + value , ex );
403
- }
404
- for (WebElement element : allElements ) {
405
- setFoundBy (this , element , by , using );
468
+ protected void setFoundBy (SearchContext context , WebElement element , String by , String using ) {
469
+ if (element instanceof RemoteWebElement ) {
470
+ RemoteWebElement remoteElement = (RemoteWebElement ) element ;
471
+ remoteElement .setFoundBy (context , by , using );
472
+ remoteElement .setFileDetector (getFileDetector ());
406
473
}
407
- return allElements ;
408
474
}
409
475
410
476
// Misc
@@ -1111,4 +1177,82 @@ public String toString() {
1111
1177
platform ,
1112
1178
getSessionId ());
1113
1179
}
1180
+
1181
+ private enum Mechanism {
1182
+ CONTEXT {
1183
+ @ Override
1184
+ WebElement findElement (RemoteWebDriver parent , SearchContext context , By locator ) {
1185
+ return locator .findElement (context );
1186
+ }
1187
+
1188
+ @ Override
1189
+ List <WebElement > findElements (RemoteWebDriver parent , SearchContext context , By locator ) {
1190
+ return locator .findElements (context );
1191
+ }
1192
+ },
1193
+ REMOTE {
1194
+ @ Override
1195
+ WebElement findElement (RemoteWebDriver parent , SearchContext context , By locator ) {
1196
+ String commandName ;
1197
+ Map <String , Object > params = new HashMap <>();
1198
+
1199
+ By .Remotable .Parameters fromLocator = ((By .Remotable ) locator ).getRemoteParameters ();
1200
+ params .put ("using" , fromLocator .using ());
1201
+ params .put ("value" , fromLocator .value ());
1202
+
1203
+ if (context instanceof RemoteWebElement ) {
1204
+ commandName = DriverCommand .FIND_CHILD_ELEMENT ;
1205
+ params .put ("id" , ((RemoteWebElement ) context ).getId ());
1206
+ } else {
1207
+ commandName = DriverCommand .FIND_ELEMENT ;
1208
+ }
1209
+
1210
+ Response response = parent .execute (new CommandPayload (commandName , params ));
1211
+ Object value = response .getValue ();
1212
+ if (value == null ) {
1213
+ throw new NoSuchElementException ("Unable to find element with locator " + locator );
1214
+ }
1215
+ try {
1216
+ return (WebElement ) value ;
1217
+ } catch (ClassCastException ex ) {
1218
+ throw new WebDriverException (
1219
+ "Returned value cannot be converted to WebElement: " + value , ex );
1220
+ }
1221
+ }
1222
+
1223
+ @ Override
1224
+ List <WebElement > findElements (RemoteWebDriver parent , SearchContext context , By locator ) {
1225
+ String commandName ;
1226
+ Map <String , Object > params = new HashMap <>();
1227
+
1228
+ By .Remotable .Parameters fromLocator = ((By .Remotable ) locator ).getRemoteParameters ();
1229
+ params .put ("using" , fromLocator .using ());
1230
+ params .put ("value" , fromLocator .value ());
1231
+
1232
+ if (context instanceof RemoteWebElement ) {
1233
+ commandName = DriverCommand .FIND_CHILD_ELEMENTS ;
1234
+ params .put ("id" , ((RemoteWebElement ) context ).getId ());
1235
+ } else {
1236
+ commandName = DriverCommand .FIND_ELEMENTS ;
1237
+ }
1238
+
1239
+ Response response = parent .execute (new CommandPayload (commandName , params ));
1240
+ Object value = response .getValue ();
1241
+ if (value == null ) { // see https://github.com/SeleniumHQ/selenium/issues/4555
1242
+ return Collections .emptyList ();
1243
+ }
1244
+ try {
1245
+ @ SuppressWarnings ("unchecked" ) List <WebElement > toReturn = (List <WebElement >) value ;
1246
+ return toReturn ;
1247
+ } catch (ClassCastException ex ) {
1248
+ throw new WebDriverException (
1249
+ "Returned value cannot be converted to WebElement: " + value , ex );
1250
+ }
1251
+ }
1252
+ }
1253
+ ;
1254
+
1255
+ abstract WebElement findElement (RemoteWebDriver parent , SearchContext context , By locator );
1256
+ abstract List <WebElement > findElements (RemoteWebDriver parent , SearchContext context , By locator );
1257
+ }
1114
1258
}
0 commit comments