Predicates: Filtering with [ ]
Predicates are the square-bracket filters that turn a broad selection into exactly the elements you want. Learn how they evaluate, how they chain, and the classic index trap.
Predicates: Filtering with [ ]
A predicate is anything inside square brackets. It filters the nodes selected by the step before it, keeping only the ones for which the predicate is true.
//button all buttons
//button[@type='submit'] only buttons whose type attribute equals "submit"
Almost every useful XPath is a location step plus a predicate. Getting predicates right is most of the job.
How a predicate is evaluated
For every node selected by the step, XPath evaluates the predicate with that node as the context node. If the result is true, the node stays.
Three kinds of expressions are common inside predicates:
| Expression | Example | Evaluates to |
|---|---|---|
| Comparison | [@type='submit'] | boolean |
| Existence | [@disabled] | true if the attribute exists |
| Number | [3] | shorthand for [position()=3] |
The existence form trips people up. //input[@required] selects inputs that have a required attribute, regardless of its value. It does not test for required="true".
Chaining predicates
You can stack predicates. Each one filters the result of the previous one, and each is evaluated in turn:
//input[@type='text'][@name='email']
For attribute conditions, chaining is the same as using and:
//input[@type='text' and @name='email']
The two forms differ only when a numeric predicate is involved, because position() is recalculated after each filter:
//li[@class='item'][1] the first li among those with class="item"
//li[1][@class='item'] the first li child of each parent, only if it has class="item"
Read chained predicates left to right and ask “what is the set at this point?”
The index trap
//div[1] does not mean “the first div in the document”. It means “every div that is the first div child of its parent”. If ten parents each contain a div, you get ten results.
To pick the first result of a whole expression, wrap it in parentheses first:
(//div)[1] the first div in document order
(//div)[last()] the last div in document order
This is one of the most common mistakes in test suites, and it silently returns the wrong element instead of failing.
Predicates on any step
Predicates are not limited to the final step. They can sit on any step in the path:
//form[@id='checkout']//input[@name='zip']
//table[@data-testid='orders']/tbody/tr[td[normalize-space()='Shipped']]/td[1]
The second example has a predicate that itself contains a path (td[...]). A nested path inside a predicate is true if it selects at least one node, so tr[td[...]] reads as “rows that have a td matching this”.
Boolean predicates you will use daily
//button[not(@disabled)] enabled buttons
//a[@href and normalize-space()] links with an href and visible text
//input[@type='checkbox' and @checked] checked checkboxes (attribute present)
//div[@role='alert' or @aria-live='polite'] either condition
//li[position() > 1] skip the first item
//li[position() mod 2 = 0] even items
Robustness notes
Stable: attribute and text predicates that describe what the element is. //button[@data-testid=‘save’]
Fragile: numeric predicates that describe where the element is. //div[3]/button[2] breaks the moment someone adds a banner.
If you must use an index, keep it as close to the target as possible and anchor the path to a stable container first.
Try It Yourself
Open in Playground →
Then change the predicate to [1] and to (...)[1] and compare the counts.
Next Steps
- Node Types and Wildcards - What
*,node()andtext()actually select - Position and Indexing -
position(),last()and the reverse-axis surprise - Logic: and, or, not - Combining conditions cleanly