All articles

Locator Strategies: XPath, and When You Actually Need It

This is Part 4 of the locator strategies series. It follows Part 3, on CSS selectors.

XPath has a bad reputation in test automation, and a lot of it is earned by one specific thing: the absolute path copied straight out of dev tools. That kind breaks the moment a front-end change lands. A locator nobody touched suddenly stops finding its element, someone loses the morning patching paths, and it is easy to decide XPath is the problem and reach for anything else.

The absolute path is the problem, not XPath itself. Kept relative, XPath stops breaking like that, and what you have left is a tool that does two things nothing else in the kit can do.

Most days you will not need it, and that is fine. CSS, a stable attribute, and a data-testid cover the large majority of what you automate. But for the handful of jobs CSS cannot do, relative XPath is the right tool, and it is worth knowing which jobs those are.

The XPath to never write

Right-click an element in dev tools, choose Copy, and you may be offered “Copy full XPath.” It hands you something like this:

'/html/body/div[2]/div/div[3]/main/section/ul/li[2]/button'

Never put that in a test. It is a turn-by-turn route through the entire page, and it depends on every single one of those elements staying exactly where it is. Add one wrapping <div> anywhere above your target, which front-end teams do constantly, and the route now points at the wrong element or at nothing. This single pattern, absolute XPath copied from dev tools, is responsible for most of the reputation XPath has for being brittle. The brittleness is not XPath. It is the absolute path.

Everything good about XPath comes from writing it relatively, starting with // to mean “anywhere on the page,” and anchoring to something meaningful.

What XPath does that CSS cannot

There are two real powers here, and they are the reason to keep XPath in your toolkit at all.

The first is selecting by the text a user actually reads. CSS can match an attribute, but it cannot see text content. XPath can:

'//button[text()="Save"]'                  // a button whose text is exactly Save
'//button[contains(., "Save")]'            // a button containing the word Save
'//h2[normalize-space()="Your cart"]'      // text, ignoring stray whitespace

The second is walking up and across the tree. A CSS selector can only travel downward, from a parent into its children. XPath has axes, which let you step from an element to its parent, its ancestor, or its sibling. That is how you find a field by the label sitting next to it, or a row by a name inside one of its cells.

form row (parent)CSS: parent to childlabel: EmailinputXPath: across to a siblingCSS only walks down, parent to child. XPath also steps across to a sibling and up to an ancestor.

That sibling hop looks like this, and it is genuinely useful when a form field has no good attribute of its own but does have a clear label beside it:

'//label[text()="Email"]/following-sibling::input'

And the row example is one you will reach for often. Say you have a users table and you want the Delete button in Louise Bennett’s row, without guessing which row number she is in:

'//tr[td[normalize-space()="Louise Bennett"]]//button[text()="Delete"]'

That reads almost like a sentence: find the row that has a cell saying “Louise Bennett,” then the Delete button inside it. No counting, no position, and it keeps working when the rows reorder.

CSS can’t, XPath can

The table below is where the extra tool earns its place, at a glance. The CSS marks are cannot, ~ only in a limited way.

What you needCSSXPath
Select an element by its exact visible text//button[text()="Save"]
Select an element that contains some text//button[contains(., "Save")]
Walk up to an ancestor~//td/ancestor::tr
Find a field by its label’s text//label[text()="Email"]/following-sibling::input
Find a row by the text in one of its cells//tr[td[normalize-space()="Louise Bennett"]]

CSS gets a ~ on walking up because the modern :has() selector can pick a parent by a descendant it contains, which covers some of the same ground. What CSS cannot do at all is match on the text a person reads, and that is XPath’s real home.

Use it sparingly, and relatively

XPath is a sharp tool, so a few rules keep it from cutting you:

  • Stay relative. Start with // and anchor to text or an attribute. Never paste an absolute /html/body/... path.
  • Avoid position. //div[2] and [last()] are the XPath version of :nth-child. They break the moment order or count changes. Anchor to something true about the element, not where it sits.
  • Reach for it second, not first. Modern test tools have quietly absorbed a lot of what people used XPath for. Selecting by text is now getByText in Playwright or cy.contains in Cypress, and selecting a row by its content is a filter({ hasText }). The capstone covers those locators in full. When one of them fits, prefer it; keep raw XPath for the cases it does not reach.

Where this leaves you

XPath is not the everyday tool, and it should not be the first thing you reach for. But when you need to find an element by the words a user reads, or step up the tree from a child to find a parent, nothing else does the job as directly. Write it relatively, keep position out of it, and it is a reliable last resort rather than a flaky one. You can practise the text and relationship patterns, in both CSS and XPath, in the playground on the locator cheat sheet.

You now have every individual strategy: id, name, and class; CSS selectors; and XPath. The question that ties them together is the one that matters most in practice. Out of all of these, which should you reach for first? The answer has changed since 2020, and that is the capstone: choosing the right locator, a priority order for 2026.

Found it useful? Share it.
Julia Pottinger

Written by

Julia Pottinger

Hi, I'm Julia. I've been in QA for over a decade. I spend my days testing software and my own time building apps and games, and I write here to share what I learn, the practical, honest lessons you can actually use.

Comments 0

Share your thoughts, ask questions, or add to the conversation.

Be kind and constructive. Stay on topic. No spam or self-promotion.
Loading comments…