Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/java/src/org/openqa/selenium/By.java
1865 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
package org.openqa.selenium;
19
20
import java.util.Collections;
21
import java.util.HashMap;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.Objects;
25
import java.util.regex.Pattern;
26
import org.jspecify.annotations.NullMarked;
27
import org.jspecify.annotations.Nullable;
28
import org.openqa.selenium.internal.Require;
29
30
/**
31
* Mechanism used to locate elements within a document. In order to create your own locating
32
* mechanisms, it is possible to subclass this class and override the protected methods as required,
33
* though it is expected that all subclasses rely on the basic finding mechanisms provided through
34
* static methods of this class:
35
*
36
* <pre><code>
37
* public WebElement findElement(WebDriver driver) {
38
* WebElement element = driver.findElement(By.id(getSelector()));
39
* if (element == null)
40
* element = driver.findElement(By.name(getSelector());
41
* return element;
42
* }
43
* </code></pre>
44
*/
45
@NullMarked
46
public abstract class By {
47
/**
48
* @param id The value of the "id" attribute to search for.
49
* @return A By which locates elements by the value of the "id" attribute.
50
*/
51
public static By id(String id) {
52
return new ById(id);
53
}
54
55
/**
56
* @param linkText The exact text to match against.
57
* @return A By which locates A elements by the exact text it displays.
58
*/
59
public static By linkText(String linkText) {
60
return new ByLinkText(linkText);
61
}
62
63
/**
64
* @param partialLinkText The partial text to match against
65
* @return a By which locates elements that contain the given link text.
66
*/
67
public static By partialLinkText(String partialLinkText) {
68
return new ByPartialLinkText(partialLinkText);
69
}
70
71
/**
72
* @param name The value of the "name" attribute to search for.
73
* @return A By which locates elements by the value of the "name" attribute.
74
*/
75
public static By name(String name) {
76
return new ByName(name);
77
}
78
79
/**
80
* @param tagName The element's tag name.
81
* @return A By which locates elements by their tag name.
82
*/
83
public static By tagName(String tagName) {
84
return new ByTagName(tagName);
85
}
86
87
/**
88
* @param xpathExpression The XPath to use.
89
* @return A By which locates elements via XPath.
90
*/
91
public static By xpath(String xpathExpression) {
92
return new ByXPath(xpathExpression);
93
}
94
95
/**
96
* Find elements based on the value of the "class" attribute. Only one class name should be used.
97
* If an element has multiple classes, please use {@link By#cssSelector(String)}.
98
*
99
* @param className The value of the "class" attribute to search for.
100
* @return A By which locates elements by the value of the "class" attribute.
101
*/
102
public static By className(String className) {
103
return new ByClassName(className);
104
}
105
106
/**
107
* Find elements via the driver's underlying W3C Selector engine. If the browser does not
108
* implement the Selector API, the best effort is made to emulate the API. In this case, we strive
109
* for at least CSS2 support, but offer no guarantees.
110
*
111
* @param cssSelector CSS expression.
112
* @return A By which locates elements by CSS.
113
*/
114
public static By cssSelector(String cssSelector) {
115
return new ByCssSelector(cssSelector);
116
}
117
118
/**
119
* Find a single element. Override this method if necessary.
120
*
121
* @param context A context to use to find the element.
122
* @return The WebElement that matches the selector.
123
*/
124
public WebElement findElement(SearchContext context) {
125
List<WebElement> allElements = findElements(context);
126
if (allElements == null || allElements.isEmpty()) {
127
throw new NoSuchElementException("Cannot locate an element using " + this);
128
}
129
return allElements.get(0);
130
}
131
132
/**
133
* Find many elements.
134
*
135
* @param context A context to use to find the elements.
136
* @return A list of WebElements matching the selector.
137
*/
138
public abstract List<WebElement> findElements(SearchContext context);
139
140
protected WebDriver getWebDriver(SearchContext context) {
141
if (context instanceof WebDriver) {
142
return (WebDriver) context;
143
}
144
145
if (!(context instanceof WrapsDriver)) {
146
throw new IllegalArgumentException("Context does not wrap a webdriver: " + context);
147
}
148
149
return ((WrapsDriver) context).getWrappedDriver();
150
}
151
152
protected JavascriptExecutor getJavascriptExecutor(SearchContext context) {
153
WebDriver driver = getWebDriver(context);
154
155
if (!(context instanceof JavascriptExecutor)) {
156
throw new IllegalArgumentException(
157
"Context does not provide a mechanism to execute JS: " + context);
158
}
159
160
return (JavascriptExecutor) driver;
161
}
162
163
@Override
164
public boolean equals(@Nullable Object o) {
165
if (!(o instanceof By)) {
166
return false;
167
}
168
169
By that = (By) o;
170
171
return this.toString().equals(that.toString());
172
}
173
174
@Override
175
public int hashCode() {
176
return toString().hashCode();
177
}
178
179
@Override
180
public String toString() {
181
// A stub to prevent endless recursion in hashCode()
182
return "[unknown locator]";
183
}
184
185
public static class ById extends PreW3CLocator {
186
187
private final String id;
188
189
public ById(String id) {
190
super(
191
"id", Require.argument("Id", id).nonNull("Cannot find elements when id is null."), "#%s");
192
193
this.id = id;
194
}
195
196
@Override
197
public String toString() {
198
return "By.id: " + id;
199
}
200
}
201
202
public static class ByLinkText extends BaseW3CLocator {
203
204
private final String linkText;
205
206
public ByLinkText(String linkText) {
207
super(
208
"link text",
209
Require.argument("Link text", linkText)
210
.nonNull("Cannot find elements when the link text is null."));
211
212
this.linkText = linkText;
213
}
214
215
@Override
216
public String toString() {
217
return "By.linkText: " + linkText;
218
}
219
}
220
221
public static class ByPartialLinkText extends BaseW3CLocator {
222
223
private final String partialLinkText;
224
225
public ByPartialLinkText(String partialLinkText) {
226
super(
227
"partial link text",
228
Require.argument("Partial link text", partialLinkText)
229
.nonNull("Cannot find elements when the link text is null."));
230
231
this.partialLinkText = partialLinkText;
232
}
233
234
@Override
235
public String toString() {
236
return "By.partialLinkText: " + partialLinkText;
237
}
238
}
239
240
public static class ByName extends PreW3CLocator {
241
private final String name;
242
243
public ByName(String name) {
244
super(
245
"name",
246
Require.argument("Name", name).nonNull("Cannot find elements when name text is null."),
247
String.format("*[name='%s']", name.replace("'", "\\'")));
248
249
this.name = name;
250
}
251
252
@Override
253
public String toString() {
254
return "By.name: " + name;
255
}
256
}
257
258
public static class ByTagName extends BaseW3CLocator {
259
260
private final String tagName;
261
262
public ByTagName(String tagName) {
263
super(
264
"tag name",
265
Require.argument("Tag name", tagName)
266
.nonNull("Cannot find elements when the tag name is null."));
267
268
if (tagName.isEmpty()) {
269
throw new InvalidSelectorException("Tag name must not be blank");
270
}
271
272
this.tagName = tagName;
273
}
274
275
@Override
276
public String toString() {
277
return "By.tagName: " + tagName;
278
}
279
}
280
281
public static class ByXPath extends BaseW3CLocator {
282
283
private final String xpathExpression;
284
285
public ByXPath(String xpathExpression) {
286
super(
287
"xpath",
288
Require.argument("XPath", xpathExpression)
289
.nonNull("Cannot find elements when the XPath is null."));
290
291
this.xpathExpression = xpathExpression;
292
}
293
294
@Override
295
public String toString() {
296
return "By.xpath: " + xpathExpression;
297
}
298
}
299
300
public static class ByClassName extends PreW3CLocator {
301
302
private static final Pattern AT_LEAST_ONE_WHITESPACE = Pattern.compile(".*\\s.*");
303
private final String className;
304
305
public ByClassName(String className) {
306
super(
307
"class name",
308
Require.argument("Class name", className)
309
.nonNull("Cannot find elements when the class name expression is null."),
310
".%s");
311
312
if (AT_LEAST_ONE_WHITESPACE.matcher(className).matches()) {
313
throw new InvalidSelectorException("Compound class names not permitted");
314
}
315
316
this.className = className;
317
}
318
319
@Override
320
public String toString() {
321
return "By.className: " + className;
322
}
323
}
324
325
public static class ByCssSelector extends BaseW3CLocator {
326
private final String cssSelector;
327
328
public ByCssSelector(String cssSelector) {
329
super(
330
"css selector",
331
Require.argument("CSS selector", cssSelector)
332
.nonNull("Cannot find elements when the selector is null"));
333
334
this.cssSelector = cssSelector;
335
}
336
337
@Override
338
public String toString() {
339
return "By.cssSelector: " + cssSelector;
340
}
341
}
342
343
public interface Remotable {
344
Parameters getRemoteParameters();
345
346
class Parameters {
347
private final String using;
348
private final @Nullable Object value;
349
350
public Parameters(String using, @Nullable Object value) {
351
this.using = Require.nonNull("Search mechanism", using);
352
// There may be subclasses where the value is optional. Allow for this.
353
this.value = value;
354
}
355
356
public String using() {
357
return using;
358
}
359
360
public @Nullable Object value() {
361
return value;
362
}
363
364
@Override
365
public String toString() {
366
return "[" + using + ": " + value + "]";
367
}
368
369
@Override
370
public boolean equals(@Nullable Object o) {
371
if (!(o instanceof Parameters)) {
372
return false;
373
}
374
Parameters that = (Parameters) o;
375
return using.equals(that.using) && Objects.equals(value, that.value);
376
}
377
378
@Override
379
public int hashCode() {
380
return Objects.hash(using, value);
381
}
382
383
private Map<String, @Nullable Object> toJson() {
384
Map<String, @Nullable Object> params = new HashMap<>();
385
params.put("using", using);
386
params.put("value", value);
387
return Collections.unmodifiableMap(params);
388
}
389
}
390
}
391
392
private abstract static class BaseW3CLocator extends By implements Remotable {
393
private final Parameters params;
394
395
protected BaseW3CLocator(String using, String value) {
396
this.params = new Parameters(using, value);
397
}
398
399
@Override
400
public WebElement findElement(SearchContext context) {
401
Require.nonNull("Search Context", context);
402
return context.findElement(this);
403
}
404
405
@Override
406
public List<WebElement> findElements(SearchContext context) {
407
Require.nonNull("Search Context", context);
408
return context.findElements(this);
409
}
410
411
@Override
412
public final Parameters getRemoteParameters() {
413
return params;
414
}
415
416
protected final Map<String, @Nullable Object> toJson() {
417
return getRemoteParameters().toJson();
418
}
419
}
420
421
private abstract static class PreW3CLocator extends By implements Remotable {
422
private static final Pattern CSS_ESCAPE =
423
Pattern.compile("([\\s'\"\\\\#.:;,!?+<>=~*^$|%&@`{}\\-\\/\\[\\]\\(\\)])");
424
private final Parameters remoteParams;
425
private final ByCssSelector fallback;
426
427
private PreW3CLocator(String using, String value, String formatString) {
428
this.remoteParams = new Remotable.Parameters(using, value);
429
this.fallback = new ByCssSelector(String.format(formatString, cssEscape(value)));
430
}
431
432
@Override
433
public WebElement findElement(SearchContext context) {
434
return context.findElement(fallback);
435
}
436
437
@Override
438
public List<WebElement> findElements(SearchContext context) {
439
return context.findElements(fallback);
440
}
441
442
@Override
443
public final Parameters getRemoteParameters() {
444
return remoteParams;
445
}
446
447
protected final Map<String, @Nullable Object> toJson() {
448
return fallback.toJson();
449
}
450
451
private String cssEscape(String using) {
452
using = CSS_ESCAPE.matcher(using).replaceAll("\\\\$1");
453
if (!using.isEmpty() && Character.isDigit(using.charAt(0))) {
454
using = "\\" + (30 + Integer.parseInt(using.substring(0, 1))) + " " + using.substring(1);
455
}
456
return using;
457
}
458
}
459
}
460
461