XPath Recipes
56 expressions for the situations that come up every week, each with a robustness rating and a one-click playground link. Copy it, adjust the text or attribute, ship it.
Test Attributes
By data-testid
Target an element by its test id attribute
Any test attribute
Match data-testid, data-test or data-cy in one expression
Test id inside a container
Scope a test id to a specific component
Text Matching
Button by Text
Find a button containing specific text
Exact visible label
Match the trimmed visible text of a button, tolerant of whitespace and nesting
Text in any text node
Match when the text is split across several text nodes
Case-insensitive text
Lowercase the text with translate() before comparing (XPath 1.0 has no lower-case())
//button[translate(normalize-space(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')='sign in'] Try it Text containing an apostrophe
Use double quotes, or concat() when both quote types appear
Element's own text only
Match the direct text node, ignoring nested children
Form Elements
Input by Label
Find the input that follows a label
Input via label for/id
Resolve the label's for attribute to the input's id, wherever the input sits
Checkbox inside a wrapping label
Find a checkbox by the label text that wraps it
Checkbox next to its label
When the label follows the checkbox as a sibling, walk backwards from the label
Option in a select
Locate a select by name and an option by visible text
Radio button by value
Pick one radio from a group by its value
Submit button in a form
Scope the submit button to its form
Validation message for a field
Error text rendered as a sibling of the input
Tables
Table Cell by Header
Find a cell by computing the column index from its header
Row containing a cell
The row that has a cell with specific text
Button in a specific row
Filter the row by content, then descend to the action
Cell by row name and column header
Survives column reordering
//tr[td[normalize-space()='Keyboard']]/td[count(//th[normalize-space()='Stock']/preceding-sibling::th)+1] Try it Data rows, skipping the header
Rows that have td cells rather than th cells
Last row in a table body
Parentheses make the index global rather than per-parent
Lists & Cards
Button in the card with a title
Container-by-content, then the control inside it
//article[.//h2[normalize-space()='Smart Watch Series 5']]//button[normalize-space()='Add to Cart'] Try it Card that shows a badge
Select the container by something nested in it
List item by status attribute
Filter repeated items on a data attribute
Nth Item in List
Select a specific item from a list by position
Lists with more than N items
Count children inside a predicate
Dialogs & Overlays
Button inside an open dialog
Anchor to the dialog landmark to avoid the page-level duplicate
Dialog with a given title
Identify the dialog by its heading when several can be open
Close button by aria-label
Icon buttons usually carry an accessible name
Toast or status message
Live regions announce their content
Attributes
Contains Class
Match partial class names
Exact class token
Match the whole class token so 'btn' does not match 'btn-danger'
ID prefix
Match the stable stem of a generated id
Attribute ends with (XPath 1.0)
ends-with() does not exist in 1.0; use substring and string-length
Attribute present
Existence test, regardless of value
Attribute absent
not() tests for absence; HTML boolean attributes are true when present
Structural
Axes
Sibling Navigation
Find an element relative to its sibling
Nearest ancestor of a type
ancestor::x[1] is the closest one, because ancestor is a reverse axis
First input after a heading
following:: crosses containers; [1] keeps the nearest
Cell to the left
preceding-sibling::td[1] is the immediately preceding cell
SVG & Icons
Inline SVG element
//svg fails because of the namespace; local-name() works
Button containing an icon
Match the icon by its data attribute, then the button around it
Filled rating stars
Class attributes on SVG elements are plain strings; count the matches to get the rating
Bar in an SVG chart
Data attributes inside a chart
Position
First match on the page
(//x)[1] is global; //x[1] is per parent
All but the first
Skip a header row or a placeholder option
Even rows
Arithmetic on position()
Need it in your framework's syntax?
The code generator turns any of these into Selenium, Playwright, Cypress, Puppeteer, WebdriverIO or Robot Framework calls with quoting handled.
Open the code generator →