Selectors are the addresses your tests use to find elements on the page.
seleniumbase-rs accepts several formats and converts them into thirtyfour::By
locators automatically. Choosing the right selector is one of the most important
things you can do for test maintainability.
What you will learn
- The selector formats supported by
BaseCase. - How to pierce open Shadow DOM trees.
- How to convert between CSS and XPath.
- Best practices for stable, readable selectors.
Supported formats#
| Prefix | Meaning | Example |
|---|---|---|
# | ID | #username |
. | Class (first match) | .btn-primary |
/, //, ./, ../, ( | XPath | //div[@class='hero'] |
| an XPath axisThe direction an XPath step walks from the current element, as in parent:: or following-sibling::. | XPath | parent::div, following-sibling::td |
link=, link_text=, text= | Exact link text | link=Log in |
partial_link=, partial_link_text=, partial_text= | Partial link text | partial_text=Privacy |
p_link=, p_link_text=, p_text= | Partial link text (short forms) | p_text=Privacy |
| (none) | CSS selector | input[name='email'] |
Most BaseCase methods accept these strings directly. The library inspects the
selector to decide which By variant to use, so the same method takes CSS,
XPath, or a prefixed link-text selector without you having to say which it is.
Any of the thirteen XPath axes may lead an expression, so parent::div and
ancestor-or-self::form are recognized as XPath. CSS pseudo-elements are not
confused with them, because a pseudo-element never begins with an axis name:
div::before and ::after stay CSS.
The prefixes are matched exactly and everything after one is the text, so
LINK=Home (wrong case) and link=Home (leading space) are CSS, and
text=//div is a link whose text is //div. An attribute selector such as
[name=q] or input[name='q'] is CSS: there is no name= prefix.
If you need to force a particular strategy, construct the Selector variant
yourself instead of relying on detection:
Examples#
Using Selector directly#
If you prefer an explicit type, use the Selector enum or the selector! macro:
Shadow DOM#
Web Components often hide markup inside Shadow DOM trees. seleniumbase-rs can
pierce through open shadow roots with the ::shadow combinator.
Each ::shadow fragment moves the search context into the shadow root of the
preceding element. You can chain them for nested Web Components:
Limitations#
- Only open shadow roots are accessible. Closed shadow roots cannot be pierced by WebDriver or CDP.
- CSS selectors inside a shadow root must match the local DOM; they do not leak out to the parent tree.
For full control, fetch a shadow root as a WebElement and search inside it:
XPath to CSS#
A simple XPath expression can be turned into the equivalent CSS selector:
Only expressions with an exact CSS equivalent are converted: a tag (or *)
anywhere in the document, narrowed by attribute tests (@id='x', @href)
joined with and. Anything else (or, contains(), text(), positions,
paths with several steps) is an InvalidSelector error, because a CSS selector
that quietly left part of the XPath out would match different elements. The
other direction is BaseCase::convert_css_to_xpath.
Best practices#
- Prefer stable attributes:
data-testid,name,id, or ARIA roles are usually more stable than positional XPath or generated class names. - Avoid generated class names: frameworks such as styled-components or CSS modules generate class names that change between builds.
- Use link text for navigation:
link=andpartial_link=match the text users see, which makes tests readable and resilient to markup changes. - Keep selectors short: a selector with many descendants is brittle. Add stable attributes to the application under test when needed.
- Document unusual selectors: if a selector is non-obvious, add a comment explaining what it targets.
Selector troubleshooting#
ElementNotFound
Likely cause: Selector is wrong or element not yet rendered.
Fix: Check the selector; add an explicit wait.
InvalidSelector
Likely cause: Malformed XPath or CSS.
Fix: Validate syntax; escape quotes properly.
Stale element
Likely cause: DOM was replaced after the element was found.
Fix: Re-find the element before each action.
Shadow DOM not pierced
Likely cause: Shadow root is closed or selector format is wrong.
Fix: Verify the component uses open shadow DOM; use ::shadow.