How WebdriverIO Uses Selenium Locators
WebdriverIO uses `$` and `$$` to query elements with CSS, XPath, text, accessibility, and other selectors. Learn how these relate to WebDriver locators and how to choose reliable ones.
WebdriverIO uses $ and $$ to query elements: $ returns one matching element and $$ returns a collection. CSS is the default query form. These commands provide WebdriverIO’s convenient interface over WebDriver element-finding behavior, while some selector forms add framework-level syntax or behavior. “Selenium locators” is a useful shorthand for the underlying WebDriver element-location strategies, but not every WebdriverIO selector form is a separate protocol strategy.
This guide explains the common selector forms, how to find elements by ID, how to choose maintainable locators, and what can vary by session, version, or mobile driver. Examples use WebdriverIO’s JavaScript API.
1. What is a WebdriverIO selector?
A selector is an expression WebdriverIO evaluates to locate an element or elements. In ordinary tests, use the query commands:
const submitButton = await $('button=Submit');
const rows = await $$('table tbody tr');
The WebDriver protocol defines element-finding commands that accept a locator strategy and a value. WebdriverIO exposes element queries through $ and $$; its selector guide also documents convenient forms such as exact text and accessible-name queries. The forms are not all interchangeable protocol-level locator strategies. See the [WebdriverIO selector guide](https://webdriver.io/docs/selectors/) and [WebDriver protocol reference](https://webdriver.io/docs/api/webdriver/).
The names $ and $$ do not mean WebdriverIO is using jQuery or the Sizzle Selector Engine. They are WebdriverIO query commands.
2. Runnable example: locate and interact with elements
In a WebdriverIO test, query the element, then use its element commands. This example assumes your project is configured to run WebdriverIO tests and that the page contains the example form.
describe('form submission', () => {
it('fills and submits the form', async () => {
await browser.url('https://webdriver.io');
const email = await $('[data-testid="email"]');
const submit = await $('button=Submit');
await email.setValue('dev@example.com');
await submit.click();
await expect($('[role="status"]')).toHaveText('Submitted');
});
});
Replace the URL and selectors with elements from your application. A test ID is appropriate when the application exposes a stable test attribute; a user-facing text or accessible-name query can better express the interaction being tested.
3. Common WebdriverIO selector forms
| Form | Example | Use and caveat |
|---|---|---|
| CSS (default) | $('#checkout'), $('form input[name="email"]') |
Use ordinary CSS selector syntax. WebdriverIO uses CSS when no other strategy is indicated. |
| XPath | $('//button[@type="submit"]') |
Useful when the relationship or attributes are awkward to express in CSS. XPath syntax and context matter; use a valid XPath expression. |
| ID via CSS | $('#checkout') |
An HTML ID is normally queried with the CSS ID selector. Escape special characters as required by CSS selector syntax. |
| ID via XPath | $("//*[@id='checkout']") |
Another way to match an element by its HTML id attribute. |
| Exact text | $('button=Submit') |
WebdriverIO text selector syntax for a button with exact text. Text can change with translation or copy edits. |
| Partial link text | $('*=driver') |
WebdriverIO selector syntax for a link containing the given text. A broad substring can match the wrong link. |
| Accessible name | $('aria/Submit') |
Queries by accessible name. Current WebdriverIO documentation describes BiDi accessibility-tree querying for BiDi sessions and a Classic XPath heuristic fallback. |
| Mobile selector forms | Platform-specific forms in the selector guide | Some forms depend on Appium, the OS, and the selected driver. Do not assume a mobile strategy is a standard browser WebDriver strategy. |
The WebDriver protocol does not provide a general id locator strategy. Some drivers, including some Appium drivers, may support an ID strategy. For browser tests, prefer $('#someid') or an XPath query for the HTML attribute rather than assuming id=someid is portable.
4. How to choose a reliable locator
- Prefer an intentional test attribute when the test needs a stable hook. For example,
[data-testid="submit"]makes the test contract explicit. - Use accessible names or visible text when the test is about the user-visible control. Examples include
aria/Submitandbutton=Submit. The selector guide highlights these as useful choices. - Use CSS or XPath for structural details that are part of the scenario. Keep the expression specific enough to identify the intended element.
- Avoid generic tags and styling-only classes as durable hooks.
$('button')can match several controls;.btn.btn-largecan break when styling changes. WebdriverIO’s guide marks such choices as poor examples. - Account for localization. A translated button label changes an exact text selector. Use the application’s translation files or a stable test attribute when the same test runs across locales.
No selector is universally most reliable. Match the locator to what the test intends to guarantee: a stable implementation hook, a user-visible label, or a structural relationship. WebdriverIO’s [best-practices guide](https://webdriver.io/docs/bestpractices/) also recommends resilient selectors and limiting repeated element queries where possible.
5. Session, version, and platform behavior
WebDriver BiDi and accessibility queries
According to the current WebdriverIO selector guide, aria/ queries use an accessibility locator against the browser accessibility tree in WebDriver BiDi sessions. In Classic sessions, WebdriverIO uses an XPath heuristic fallback. That implementation difference means you should consider your session type and driver support when diagnosing a query that behaves differently across configurations.
Shadow DOM in WebdriverIO v9
The current guide says WebdriverIO v9 automatically pierces shadow DOM. The older >>> deep-selector workaround is therefore unnecessary in v9. If a selector example from an older project uses that syntax, check the project’s WebdriverIO version before carrying it forward.
Mobile tests
Mobile selectors may rely on Appium or a compatible platform driver. Confirm the selector form against the chosen iOS or Android driver documentation; do not assume a browser CSS or XPath example applies to native contexts.
6. Troubleshooting locator failures
| Symptom | Likely cause | Fix |
|---|---|---|
| No element found | The page or component has not rendered yet, the selector is wrong, or the test is in the wrong browsing context. | Confirm the current URL and context, inspect the rendered DOM, and wait for the intended element using the appropriate WebdriverIO wait or assertion. |
| Several elements match | The selector is too broad, such as button or a repeated class. |
Scope the query to a meaningful container or use a unique test attribute, accessible name, or more specific CSS/XPath expression. |
id=... does not work |
The active WebDriver driver may not implement a general ID locator strategy. | Use CSS ID syntax such as $('#someid') or XPath such as $("//*[@id='someid']"). |
| Text query fails in another locale | The visible label is translated or has changed. | Use locale-aware expected text, consult translation resources, or use a stable test attribute if text is not the behavior under test. |
aria/ behavior differs between runs |
The session may use BiDi versus Classic behavior, or driver/browser support may differ. | Check the session configuration and current WebdriverIO documentation; verify the accessible name in the accessibility tree and test the same supported session type. |
| Shadow-root query stopped matching | An older deep-selector workaround may be used with a different WebdriverIO version or stale syntax. | For v9, follow the automatic shadow DOM piercing behavior documented by WebdriverIO; check version-specific guidance for older projects. |
| Mobile locator works on one platform only | The selector strategy may be driver- or OS-specific. | Use the strategy supported by the active Appium/compatible driver and confirm the current native or web context. |
7. Performance, reliability, and cost
Locator performance is usually less useful as a selection criterion than correctness and stability. Avoid repeated DOM queries when you can keep and reuse an element reference within a step, and avoid selectors that scan or match a broad part of the page when a narrow query will do. WebdriverIO’s best-practices guide advises limiting repeated $ and $$ queries. Do not label CSS, XPath, or accessibility selectors universally fastest: session type, driver implementation, page structure, and selector semantics differ.
Reliable tests wait for the state they need instead of relying on arbitrary timing. A stable locator reduces failures caused by markup or styling changes, while a user-facing locator can intentionally make a test fail when visible wording changes. There is no per-locator price; practical cost comes from test execution time and maintenance effort, so measure changes in your own CI environment.
8. Inspect the page separately from test automation
Sometimes the question is not how to automate an interaction, but what the rendered page looks like or whether a target element is present in a capture. ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. It can capture a page as an image or PDF and supports CSS element capture, custom JavaScript, waits, and other capture options; consult the API documentation for configuration.
9. Or skip the browser setup
If your goal is a rendered screenshot rather than an interactive WebdriverIO test, make one request to ScreenshotNeo. This JavaScript example runs in Node.js 18 or later, which provides global fetch.
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
For other clients, the same one-request capture is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
ScreenshotNeo removes cookie or consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. See the docs and sign up for free.
10. FAQ
How does WebdriverIO use Selenium locators?
WebdriverIO’s $ and $$ commands query elements using CSS by default or other documented selector forms. They expose element-finding behavior through WebdriverIO’s API, with additional convenience syntax.
What is the difference between a WebdriverIO selector and a Selenium locator?
“Selenium locator” often refers to a WebDriver element-finding strategy and its value. WebdriverIO selectors include those familiar forms plus framework-level query syntax and behavior, so the terms overlap without being identical.
How do I find an element by ID in WebdriverIO?
Use CSS ID syntax, such as $('#login'), or XPath, such as $("//*[@id='login']"). A general id locator strategy is not part of the WebDriver protocol.
Should I use CSS or XPath in WebdriverIO?
Use CSS for straightforward attributes and structure; use XPath when its relationship or attribute expression fits the test better. Choose the selector that is clear, specific, and stable for the scenario.
Which locator is most reliable?
There is no universal answer. A purposeful test attribute is often stable for automation, while accessible names and exact text are useful when the test should follow the user-visible interface.


