XPath 1.0 Functions Reference
Every function available in browser XPath, grouped by what it does, with the test-automation use case for each and the XPath 2.0 functions you must not reach for.
XPath 1.0 Functions Reference
Browsers, Selenium, Playwright and Appium all evaluate XPath 1.0. That gives you exactly 27 functions. This page lists all of them with the situations where each one earns its place in a locator.
If a function is not on this page, it will throw an “invalid expression” error in your tests. See the XPath versions guide for what lives in 2.0 and 3.1 and how to work around it.
Node-set functions
| Function | Returns | Test automation use |
|---|---|---|
last() | number of nodes in the context | (//tr)[last()] last row |
position() | index of the context node | //li[position() < 4] first three |
count(node-set) | how many nodes | //ul[count(li) > 5] lists with more than five items |
id(string) | element with that ID | rarely used; //*[@id='x'] is clearer |
local-name(node-set?) | name without namespace prefix | //*[local-name()='svg'] |
namespace-uri(node-set?) | the namespace URI | //*[namespace-uri()='http://www.w3.org/2000/svg'] |
name(node-set?) | full qualified name | //*[name()='svg:path'] in XML |
String functions
| Function | Returns | Test automation use |
|---|---|---|
string(object?) | string value | string(//h1) for assertions |
concat(s1, s2, ...) | joined string | quotes: concat("It", "'", "s") |
starts-with(s, prefix) | boolean | //input[starts-with(@id, 'user-')] dynamic ids |
contains(s, sub) | boolean | //*[contains(@class, 'btn')] |
substring-before(s, sep) | text before separator | substring-before(@href, '?') |
substring-after(s, sep) | text after separator | substring-after(@id, 'row-') |
substring(s, start, len?) | slice, 1-based | substring(@id, 1, 4)='user' |
string-length(s?) | length | //input[string-length(@value) > 0] non-empty |
normalize-space(s?) | trimmed, single-spaced | //button[normalize-space()='Save'] |
translate(s, from, to) | character mapping | case folding, see below |
substring counts from 1, not 0. substring('hello', 1, 2) is he.
translate maps each character in from to the character at the same position in to. Characters in from with no partner in to are deleted. Two idioms:
translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') lower-case
translate(@data-price, '$,', '') strip $ and commas
Boolean functions
| Function | Returns | Test automation use |
|---|---|---|
boolean(object) | boolean conversion | boolean(//div[@role='alert']) “is there an alert” |
not(boolean) | negation | //button[not(@disabled)] |
true() | true | placeholders and generated expressions |
false() | false | same |
lang(string) | matches xml:lang | //p[lang('de')] localisation tests |
not() is a function, not an operator. not @disabled is a syntax error.
Number functions
| Function | Returns | Test automation use |
|---|---|---|
number(object?) | numeric conversion | number(//span[@class='qty']) = 3 |
sum(node-set) | total | sum(//td[@class='price']) cart totals |
floor(n) | round down | maths on positions |
ceiling(n) | round up | same |
round(n) | nearest integer | same |
sum() converts each node’s string value to a number. Any non-numeric value makes the whole result NaN, so strip currency symbols with translate() first or use it only on clean data cells.
Functions that do not exist in XPath 1.0
These are the ones people reach for after reading generic XPath tutorials. None of them work in a browser:
| Missing | Version | XPath 1.0 workaround |
|---|---|---|
ends-with(s, suffix) | 2.0 | substring(s, string-length(s) - string-length('suffix') + 1) = 'suffix' |
lower-case(s), upper-case(s) | 2.0 | translate() with the alphabet |
matches(s, regex) | 2.0 | combine contains, starts-with, translate |
replace(s, regex, with) | 2.0 | translate() for single characters only |
string-join(), tokenize() | 2.0 | not possible; restructure the locator |
exists(), empty() | 2.0 | boolean(), not() |
compare(), codepoints-to-string() | 2.0 | not needed for locators |
The ends-with workaround in full, for an id that ends in -email:
//input[substring(@id, string-length(@id) - string-length('-email') + 1) = '-email']
It is ugly. In practice contains(@id, '-email') is usually good enough.
Where each function goes
Functions can appear in predicates, as the whole expression, or nested:
//tr[normalize-space(td[1]) = 'Widget'] inside a predicate
count(//tr[td[3] > 0]) as the whole expression, returns a number
//td[count(preceding-sibling::td) = 2] nested in arithmetic
concat(normalize-space(//h1), ' | ', string(//title)) building a string
Selenium and Playwright locators must return elements, so an expression that returns a number, string or boolean will error when used as a locator. Use those forms in DevTools or scraping code instead.
Try It Yourself
Open in Playground →
Next Steps
- Position and Indexing -
position(),last()and reverse axes - Case-Insensitive Matching -
translate()in depth - XPath 1.0 vs 2.0 vs 3.1 - Where the other 100+ functions live