Testing
Introduction
XPath selects nodes in HTML and XML. Browsers implement XPath 1.0 through document.evaluate(); devtools add the $x() helper.
- XPath reference (developer.mozilla.org)
- XPath 1.0 specification (w3.org)
Browser console
$x('//div') // array of elements
$x('//li', ul) // search within a node
Works in Firefox and Chromium browsers.
See: $x(), Firefox console helpers
document.evaluate()
const result = document.evaluate(
'//ul/li', document, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
)
result.snapshotLength // => 3
result.snapshotItem(0) // first match
Use document.evaluate() outside devtools; $x() is a console helper.
Selectors
Descendant selectors
Attribute selectors
| CSS | XPath | ? |
|---|---|---|
#id | //*[@id="id"] | ? |
.class | //*[@class="class"] ...kinda | |
input[type="submit"] | //input[@type="submit"] | |
a#abc[for="xyz"] | //a[@id="abc"][@for="xyz"] | ? |
a[rel] | //a[@rel] | |
a[href^='/'] | //a[starts-with(@href, '/')] | ? |
a[href$='pdf'] | //a[substring(@href, string-length(@href) - 2) = 'pdf'] | ? |
a[href*='://'] | //a[contains(@href, '://')] | |
a[rel~='help'] | //a[contains(@rel, 'help')] ...kinda |
Order selectors
Siblings
| CSS | XPath | ? |
|---|---|---|
h1 ~ ul | //h1/following-sibling::ul | ? |
h1 + ul | //h1/following-sibling::*[1][self::ul] | |
h1 ~ #id | //h1/following-sibling::*[@id="id"] |
jQuery
Other things
Class check
//div[contains(concat(' ',normalize-space(@class),' '),' foobar ')]
XPath has no space-separated token operator. Wrap the attribute in spaces and search for the token; this also works for rel.
See: contains()
Expressions
Steps and axes
//ul/li[1]/a[@href]
Syntax
| Part | Example | Meaning |
|---|---|---|
// | //ul | Any descendant |
/ | /li | Direct child |
li | li | Element step |
[1] | li[1] | Predicate |
A path is a series of steps separated by /; // selects descendants at any depth.
See: Location steps
Prefixes
| Prefix | Example | What |
|---|---|---|
// | //hr[@class='edge'] | Anywhere |
./ | ./a | Relative |
/ | /html/body/div | Root |
Begin your expression with any of these.
See: Abbreviated syntax
Separators
| Separator | Example | Selects |
|---|---|---|
/ | //ul/li/a | Child |
// | //*[@id="list"]//a | Descendant |
Separate your steps with /. Use two (//) if you don't want to select direct children.
See: Axes
Steps
//div
//div[@name='box']
//*[@id='link']
A step may have an element name (div) and predicates ([...]). Both are optional.
They can also be these other things:
//a/text() #=> text nodes
//a/@href #=> href attributes
//a/* #=> child elements
See: Location steps
Predicates
Predicates
//div[true()]
//div[@class="head"]
//div[@class="head"][@id="top"]
Keeps only the nodes for which the condition is true. Predicates can be chained.
See: Predicates
Operators
# Comparison
//a[@id = "xyz"]
//a[@id != "xyz"]
//a[@price > 25]
# Logic (and/or)
//div[@id="head" and position()=2]
//div[(h1 or p) and not(@hidden)]
# Arithmetic (div and mod, not / and %)
//item[@price > 2*@discount]
//item[@price div 2 > 4]
//item[position() mod 2 = 1]
Use comparison and logic operators to make conditionals.
See: Booleans
Using nodes
# Use them inside functions
//ul[count(li) > 2]
//ul[count(li[@class='hide']) > 0]
# This returns `<ul>` that has a `<li>` child
//ul[li]
You can use nodes inside predicates.
See: Predicates
Indexing
//a[1] # first <a> of each parent
(//a)[1] # first <a> in the document
//a[last()] # last <a> of each parent
(//a)[last()] # last <a> in the document
//ol/li[2] # second <li>
//ol/li[position()=2] # same as above
//ol/li[position()>1] # all but the first
Predicates apply to each parent's matches. Wrap the path in () to index the whole result.
See: Predicates
Chaining order
Nesting predicates
//section[.//h1[@id='hi']]
This returns <section> if it has an <h1> descendant with id='hi'.
See: Predicates
Functions
Node-set functions
name() # //*[starts-with(name(), 'h')]
local-name() # //*[local-name()='item']
namespace-uri()
id('item1') # element with id="item1"
count() # //ul[count(li) > 2]
position() # //ol/li[position()=2]
last() # //ul/li[last()]
text() and @attr are node tests, not functions (steps).
See: Node-set functions
Boolean functions
not(expr) # //button[not(@disabled)]
boolean(expr) # boolean('') => false
true()
false()
lang('en') # //p[lang('en')]
See: Boolean functions
String functions
contains() # //font[contains(@class, 'head')]
starts-with() # //font[starts-with(@class, 'head')]
substring() # substring('12345', 2, 3) => '234'
substring-before() # substring-before('01/02', '/') => '01'
substring-after() # substring-after('01/02', '/') => '02'
concat() # concat('a', 'b') => 'ab'
string-length() # //p[string-length() > 80]
normalize-space() # //p[normalize-space() = 'Hi']
translate() # translate('ABC', 'ABC', 'abc') => 'abc'
string() # string(//a/@href) => '/home'
# Case-insensitive text match
//button[contains(
translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz'),
'go'
)]
# href ends with .pdf
//a[substring(@href, string-length(@href) - 2) = 'pdf']
XPath 1.0 has no ends-with(); use substring(). XPath 2.0 adds ends-with() and matches().
See: String functions
Number functions
sum(//item/@price) # total of all price attributes
floor(3.7) # => 3
ceiling(3.2) # => 4
round(3.5) # => 4
See: Number functions
Type conversion
Axes
Using axes
//ul/li # ul > li
//ul/child::li # ul > li (same)
//ul/following-sibling::li # ul ~ li
//ul/descendant-or-self::li # ul li
//ul/ancestor-or-self::li[1] # $('ul').closest('li')
Steps are separated by /, usually picking child nodes. Use :: to pick a different axis.
See: Axes
Child axis
# both the same
//ul/li/a
//child::ul/child::li/child::a
child:: is the default axis. This makes //a/b/c work.
# both the same
# this works because `child::li` is truthy, so the predicate succeeds
//ul[li]
//ul[child::li]
# both the same
//ul[count(li) > 2]
//ul[count(child::li) > 2]
See: Child axis
Descendant-or-self axis
# both the same
//div//h4
//div/descendant-or-self::node()/child::h4
// is short for descendant-or-self::node()/.
Other axes
| Axis | Abbrev | Notes |
|---|---|---|
ancestor | ||
ancestor-or-self | ||
attribute | @ | @href is short for attribute::href |
child | div is short for child::div | |
descendant | ||
descendant-or-self | // | // is short for /descendant-or-self::node()/ |
namespace | Not supported by browsers | |
self | . | . is short for self::node() |
parent | .. | .. is short for parent::node() |
following | ||
following-sibling | ||
preceding | ||
preceding-sibling |
There are other axes you can use.
See: Axes
Unions
More examples
Examples
//* # all elements
count(//*) # count all elements
(//h1)[1]/text() # text of the first h1 heading
//li[span] # find a <li> with a direct <span> child
# ...expands to //li[child::span]
//ul/li/.. # use .. to select a parent
See: XPath
Find a parent
//section[h1[@id='section-name']]
Finds a <section> that directly contains h1#section-name.
//section[.//h1[@id='section-name']]
Finds a <section> that contains h1#section-name anywhere.
The leading . makes the inner path relative to the section.
Closest
./ancestor-or-self::*[
contains(concat(' ', normalize-space(@class), ' '), ' box ')
][1]
Works like jQuery's $().closest('.box').
Attributes
//item[@price > 2*@discount]
Finds <item> whose price is more than twice its discount.
See: Attribute axis
References
- XPath 1.0 specification (w3.org)
- XPath reference (MDN)
- CSS selectors compared to XPath (MDN)
- XPath 3.1 (w3.org)
0 Comments for this cheatsheet. Write yours!