XPath Cheat Sheet

Quick reference for XPath syntax and patterns in test automation.

Robustness Rules

Follow these rules for selectors that survive DOM changes:

DO

  • Use data-testid attributes
  • Use relative paths (//
  • Target by unique attributes
  • Use aria-* and role attributes
  • Use contains() for classes
  • Anchor to stable parent elements

DON'T

  • Use auto-generated classes
  • Use absolute paths from /html
  • Use position indexes [1], [2]
  • Rely on element order
  • Match exact multi-class strings
  • Use deep nested paths

Basic Selectors

//tagname

Select all elements with this tag name

//*

Select all elements (wildcard)

//div | //span

Select multiple element types (union)

.

Current node

..

Parent node

Attribute Selectors

//*[@id='value']

Select by ID

//*[@class='value']

Select by exact class (use contains for partial)

//input[@type='text']

Select by attribute value

//*[@data-testid]

Select elements that have this attribute

//*[not(@disabled)]

Select elements WITHOUT an attribute

//*[@data-testid='login-btn']

Select by data-testid (recommended)

//*[@aria-label='Close']

Select by ARIA label (accessible)

Partial Matching

//*[contains(@class, 'btn')]

Class contains "btn"

//*[starts-with(@id, 'user-')]

ID starts with "user-"

//a[contains(@href, '/login')]

Href contains "/login"

//*[contains(@class, 'btn') and contains(@class, 'primary')]

Multiple class conditions

Text Content

//button[text()='Submit']

Exact text match

//button[contains(text(), 'Add')]

Text contains

//button[normalize-space()='Submit']

Normalized text (handles whitespace)

//button[starts-with(text(), 'Save')]

Text starts with

//*[.='Full Text Here']

Full string value including descendants

Position & Indexing

(//button)[1]

First button in document

(//ul/li)[last()]

Last li in document

(//tr)[position() > 1]

All rows except first (skip header)

//ul/li[1]

First li child of each ul

//ul/li[last()-1]

Second-to-last li child

(//div)[position() <= 3]

First three divs

Axes Navigation

//div/child::span

Direct children (same as //div/span)

//form//input

Descendants at any level (same as descendant::)

//input/parent::div

Direct parent (same as ..)

//input/ancestor::form

Any ancestor (parent, grandparent, etc.)

//label/following-sibling::input

Sibling after current element

//input/preceding-sibling::label

Sibling before current element

//h2/following::p

All elements after in document order

Tables

//table/tbody/tr

All body rows

//tr[th]/following-sibling::tr

Data rows (skip header)

//tr[.//td[text()='John']]

Row containing specific cell text

//tr[.//td[text()='John']]//button

Button in row with specific text

//td[2]

Second column cells (fixed index)

count(//th[text()='Name']/preceding-sibling::th)+1

Dynamic column index by header

Form Elements

//input[@type='email']

Email input

//input[@type='password']

Password input

//button[@type='submit']

Submit button

//select[@name='country']

Select dropdown by name

//input[@required]

Required fields

//input[not(@disabled)]

Enabled inputs

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

Input by its label text

Conditions & Logic

//input[@type='text' and @required]

AND condition

//button[@type='submit' or @type='button']

OR condition

//div[not(@hidden)]

NOT condition

//div[@class and @id]

Has both attributes

//input[@type='text'][@name='email']

Chained predicates (same as AND)

XPath Functions

count(//li)

Count matching elements

string(//title)

Get string value of element

concat(@first, " ", @last)

Concatenate strings

translate(text(), 'ABC', 'abc')

Replace characters (case conversion)

substring(@id, 1, 4)

Substring extraction

string-length(text())

Length of text content

sum(//price)

Sum of numeric values

XPath Axes Diagram

ancestor::

preceding:: ← [ current node ] → following::

descendant::

self:: = current node | child:: = direct descendants | parent:: = direct ancestor

following-sibling:: = siblings after | preceding-sibling:: = siblings before

Attribute Preference Order

1

data-testid, data-test, data-cy

Specifically for testing, most stable

2

id (if static, not auto-generated)

Unique identifiers are reliable

3

name (for form elements)

Form element names are typically stable

4

aria-label, role

Accessibility attributes, usually stable

5

Text content

May change with copy updates

6

Class names (use semantic only)

Avoid auto-generated or utility classes

7

Position indexes [1], [2], etc.

Last resort, breaks easily