Free tools Windows power users keep installed
One-click scans. No signup required.
Use WebdriverIO’s $ command to locate one element and $$ to locate multiple elements. CSS is the default selector strategy; WebdriverIO also supports text selectors, XPath, accessible-name selectors, and custom locator strategies. Prefer a selector that identifies the control’s purpose and is likely to survive changes to styling or page structure.
Start with $ and $$
WebdriverIO’s $ and $$ are element-query commands, not jQuery or Sizzle. Use $ when you expect one target and $$ when you want a collection of matches.
// Locate one element with a CSS selector (CSS is the default strategy).
const submit = await $('[data-testid="submit"]');
// Locate all matching elements.
const listItems = await $$('.results li');
Use a selector that distinguishes the intended element. A generic tag such as button can match several controls, while a styling class may change when the design changes. A dedicated test ID, accessible name, or suitable user-facing text can make the target clearer.
Choose a selector that fits the element
| Strategy | Example | When it helps | Trade-off to consider |
|---|---|---|---|
| CSS | $('[data-testid="submit"]') |
Use the default strategy for IDs, attributes, classes, and structural relationships. | Styling classes can be fragile; generic selectors may not uniquely identify the target. |
| Exact link text | $('=WebdriverIO') |
Find a link by its exact text. | Visible wording can change or be localized. |
| Partial link text | $('*=driver') |
Find a link whose text includes the supplied string. | Partial text may match more than one link. |
| Accessible name | $('aria/Submit') |
Target a control by the name exposed to assistive technology. | Behavior differs between BiDi-capable and Classic sessions; see the compatibility notes below. |
| XPath | $('//ul/li[2]') |
Express a relationship in the document tree, such as selecting the second list item. | Tree-dependent expressions can be harder to maintain if the structure changes. |
| Custom strategy | browser.custom$('myStrategy', args) |
Apply an application-specific lookup rule that ordinary selectors do not express. | Requires a registered strategy and a web environment where execute can run. |
For a user-facing control, the official selector example recommends a specific button text such as button=Submit as its strongest example, and marks aria/Submit and a dedicated data-testid as good choices. It rates generic button and styling-based .btn.btn-large poorly in that example. These are context-dependent recommendations, not a guarantee that visible text is always stable: consider whether wording changes with localization, and use translation files when translated text could change.
#1 Best Overall
Scope a query or combine strategies by chaining
Do not mix multiple selector strategies in a single selector string. If the target is inside a component, first locate the component, then query within it. Chaining can also combine strategies deliberately:
const select = await $('custom-datepicker').$('#calendar').$('aria/Select');
Every $ or $$ query attempts to locate elements. When one combined selector can identify the target clearly, prefer it over repeated lookups. Chain when it narrows the search to a meaningful parent or when moving from one selector strategy to another makes the query easier to understand.
Rank #2
Register a custom locator strategy when needed
For an application-specific rule, register a strategy with browser.addLocatorStrategy(name, function), then call browser.custom$ for one result or browser.custom$$ for multiple results. This example strategy uses document.querySelectorAll:
browser.addLocatorStrategy('myStrategy', (selector) => {
return document.querySelectorAll(selector);
});
const oneMatch = await browser.custom$('myStrategy', '[data-testid="submit"]');
const allMatches = await browser.custom$$('myStrategy', '.result');
Custom strategies are for cases where the application’s lookup rule is not adequately expressed by ordinary selectors. They require a web context in which WebdriverIO can run execute.
Account for WebdriverIO version and session type
Shadow DOM in v9
WebdriverIO v9 automatically pierces Shadow DOM. The current selectors guide says the special >>> deep-selector prefix is no longer required, and recommends removing it when migrating.
aria/ selectors in BiDi and Classic sessions
In BiDi-capable browser sessions, WebdriverIO first uses browsingContext.locateNodes with an accessibility locator against the browser accessibility tree. If it finds no match, it falls back to a Classic XPath heuristic so existing queries can still match. Classic sessions use that XPath approximation directly; the guide warns this can be slower on large pages. Do not assume the same lookup path or performance across session types.
Rank #4
Common selector problems and fixes
- A selector matches the wrong element or several elements: make it more specific with a stable attribute, an accessible name, or an appropriate text selector; use
$when targeting one element and$$when collecting matches. - A selector breaks after a visual redesign: replace classes used only for styling with a selector based on purpose, such as a test ID or accessible name, if the application provides one.
- A text selector stops matching after translation or copy edits: check the rendered link text and consider whether localization is expected. Use an appropriate stable attribute instead when text is not a dependable identifier.
- A mixed selector string does not work: WebdriverIO does not combine multiple strategies in one selector string; locate a parent and chain a child query using the second strategy.
- An old deep selector is redundant in v9: remove the
>>>prefix because v9 pierces Shadow DOM automatically. aria/lookup behaves differently across environments: confirm whether the session is BiDi-capable or Classic; the Classic XPath approximation may be slower on large pages.- A custom strategy cannot run: confirm it was registered with
browser.addLocatorStrategyand that the lookup is being performed in a web environment whereexecutecan run.
Keep queries clear and economical
WebdriverIO documentation recommends resilient selectors, targeting a single element where possible, and minimizing repeated queries. That does not mean every selector form has a universal speed ranking. The documented distinction is narrower: BiDi accessibility-tree lookup is typically faster than its Classic XPath approximation, while actual test behavior depends on the page and environment.
Or skip the browser setup
If your goal is a screenshot rather than an element interaction in a WebdriverIO test, ScreenshotNeo can return an image or PDF from one GET request. Its service removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are not billed. An MCP server lets AI agents use screenshot tools, and the free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000.
Recommended Free Tools
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Learn more at ScreenshotNeo, or sign up for 1,000 free screenshots a month with no card.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




