Strategy

Locator Strategy in 2026: Where XPath Fits Alongside Role and Test-ID Locators

The industry has moved toward user-facing locators. XPath has not gone away. A pragmatic decision framework for choosing between test ids, ARIA roles, CSS and XPath in a modern suite.

strategytrendsplaywrighttesting-librarybest-practices

What changed

Five years ago the locator conversation was “XPath or CSS”. Today three other ideas dominate:

  1. Test ids (data-testid, data-cy, data-test): attributes that exist only for tests, agreed between developers and testers.
  2. Role-based locators: querying the accessibility tree the way a screen reader would. Popularised by Testing Library, made mainstream by Playwright’s getByRole, and now supported in one form or another by Cypress Testing Library, WebdriverIO and Selenium community libraries.
  3. AI-assisted locator generation and self-healing: tools that pick or repair locators for you. See Self-Healing Locators and AI.

Meanwhile the front-end moved too: web components and shadow DOM are common in design systems, CSS-in-JS generates class names, and single-page frameworks re-render aggressively.

None of this removed the need for XPath. It narrowed the cases where XPath is the first choice.

The decision ladder

Work down the list and stop at the first rung that gives you a unique, readable locator.

1. Test id. If the element has one, use it. getByTestId('save'), //*[@data-testid='save'], [data-testid=save]. Stable by contract.

2. Role plus accessible name. getByRole('button', { name: 'Save' }). Stable because changing it changes what assistive technology announces, which teams notice. Pierces shadow DOM. Not available natively in Selenium; the XPath equivalent is //button[normalize-space()='Save'] or //*[@role='button'][normalize-space()='Save'].

3. Label, placeholder, alt text. getByLabel('Email'). XPath: the forms and labels patterns.

4. Semantic attribute. name, type, href, aria-*. CSS or XPath, whichever your team reads faster.

5. Structure. “The Remove button in the row for USB-C Hub.” This is where XPath earns its keep: lists and repeated components, tables, anchoring. Playwright can express some of this with filter({ hasText }) chains; Selenium cannot without XPath.

6. Index. Last resort, scoped to a container.

Where XPath is still the right first choice

  • Selenium suites. Without role locators built in, XPath’s text matching and axes are the only way to express rungs 2, 3 and 5 in one locator.
  • Any tool needing “up” navigation. ancestor::, parent::, preceding-sibling::. CSS has :has() now, which covers “container that contains X” in modern browsers, but not “the sibling before X” or “the second column of this row”.
  • Cross-tool locator libraries. One XPath string works in Selenium, Playwright, Appium, WebdriverIO, Puppeteer, lxml and DevTools. No other locator language does.
  • Mobile. Appium’s accessibility ids come first, but text-plus-structure queries on the native hierarchy are XPath.
  • Scraping and data extraction. XPath returns strings and numbers; CSS returns elements.
  • Legacy and third-party markup. No test ids, generated classes, table-based layouts. XPath’s anchoring techniques are the only robust option.

Where XPath is the wrong first choice

  • Playwright projects with a role-first convention. getByRole gives you better failure messages, trace-viewer integration, and shadow DOM piercing. Use XPath as the exception and say why in a comment.
  • Anything inside shadow DOM. XPath cannot enter it in any tool.
  • Simple attribute matches. //*[@id='x'] is #x. Readers thank you for the shorter form.
  • Teams that do not know XPath. A locator nobody can read is a maintenance cost regardless of how robust it is.

The convention that works

Most mature teams land on something like:

  1. Ask developers for test ids on interactive elements and containers. Document the naming scheme. See Test ID Conventions.
  2. Use the framework’s user-facing locators (role, label, text) for anything visible.
  3. Use XPath for structural queries and parameterised page-object helpers, kept in one place with descriptive names.
  4. Never commit a “Copy XPath” output or an absolute path.
  5. Rate locators in review: does it survive a wrapper div? A reorder? A restyle?
  • CSS :has() is now supported in all evergreen browsers and closes part of the “container that contains” gap. tr:has(td:nth-child(1):is(:contains)) is still not possible, because CSS has no text matching.
  • Accessibility-tree-driven agents. AI agents that operate browsers (including Playwright’s MCP server) read the accessibility tree, not the DOM. That pushes applications to have good roles and names, which in turn makes role locators more reliable for humans too.
  • Selenium BiDi brings event-driven capabilities but does not change the locator model; XPath remains central for Selenium.
  • Self-healing tools increasingly generate multiple candidate locators per element and fall back between them at runtime. XPath is usually one of the candidates.

Summary

Prefer test ids and roles. Keep XPath for structure, axes, cross-tool libraries, Selenium, mobile and scraping. Learn it well enough that when it is the right tool, your locators are short, anchored and readable. That is what the rest of this site teaches.