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