Testing

Introduction

XPath selects nodes in HTML and XML. Browsers implement XPath 1.0 through document.evaluate(); devtools add the $x() helper.

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.

See: Using XPath in JavaScript

Selectors

Descendant selectors

CSSXPath?
h1//h1?
div p//div//p?
ul > li//ul/li?
ul > li > a//ul/li/a 
div > *//div/* 
:root/html?
:root > body/html/body 

Attribute selectors

CSSXPath?
#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

CSSXPath?
ul > li:first-of-type//ul/li[1]?
ul > li:nth-of-type(2)//ul/li[2] 
ul > li:last-of-type//ul/li[last()] 
li#id:first-of-type//li[1][@id="id"]?
a:first-child//*[1][name()="a"] 
a:last-child//*[last()][name()="a"] 

Siblings

CSSXPath?
h1 ~ ul//h1/following-sibling::ul?
h1 + ul//h1/following-sibling::*[1][self::ul] 
h1 ~ #id//h1/following-sibling::*[@id="id"] 

jQuery

CSSXPath?
$('ul > li').parent()//ul/li/..?
$('li').closest('section')//li/ancestor-or-self::section[1] 
$('a').attr('href')//a/@href?
$('span').text()//span/text() 

Other things

CSSXPath?
h1:not([id])//h1[not(@id)]?
Text match//button[text()="Submit"]?
Text contains//button[contains(., "Go")] 
Numeric compare//product[@price > 2.50] 
Has children//ul[*] 
Has children (specific)//ul[li] 
Or logic//a[@name or @href]?
Union (joins results)//a | //div?

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

PartExampleMeaning
////ulAny descendant
//liDirect child
liliElement step
[1]li[1]Predicate

A path is a series of steps separated by /; // selects descendants at any depth.

See: Location steps

Prefixes

PrefixExampleWhat
////hr[@class='edge']Anywhere
././aRelative
//html/body/divRoot

Begin your expression with any of these.

See: Abbreviated syntax

Separators

SeparatorExampleSelects
///ul/li/aChild
////*[@id="list"]//aDescendant

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

//a[1][@href='/']
//a[@href='/'][1]

Order is significant: these two are different.

See: Predicates

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

string(2)               # => "2"
number('3.14')          # => 3.14
boolean(0)              # => false
boolean('')             # => false
boolean('x')            # => true

See: string(), number(), boolean()

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()/.

See: descendant-or-self axis

Other axes

AxisAbbrevNotes
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

//a | //span

Use | to join two expressions.

See: Node-sets

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.

See: Child axis, descendant-or-self axis

Closest

./ancestor-or-self::*[
  contains(concat(' ', normalize-space(@class), ' '), ' box ')
][1]

Works like jQuery's $().closest('.box').

See: ancestor-or-self axis

Attributes

//item[@price > 2*@discount]

Finds <item> whose price is more than twice its discount.

See: Attribute axis

References

0 Comments for this cheatsheet. Write yours!