XPath Locators Cheat Sheet: Syntax and Examples
A practical XPath reference for Selenium: paths, predicates, text matching, axes, indexing, runnable examples, and tips for choosing reliable locators.
XPath locators identify elements by navigating a document tree and filtering matches with predicates. In Selenium, use a stable unique ID when available, then a clear CSS selector; choose XPath when you need text matching or to navigate between related elements such as a label and its input. This cheat sheet covers the XPath 1.0 patterns commonly used in browser automation. Example matches depend on the page’s DOM and XPath implementation.
1. XPath basics
An XPath location step has an axis, a node test, and optional predicates. A slash separates steps. An omitted axis means child, and @ abbreviates the attribute axis. The familiar // abbreviation searches descendants.
| Expression | Meaning |
|---|---|
/html/body/main |
Follow child steps from the document root. |
//button |
Find button descendants through the document tree. |
.//button |
Find button descendants relative to the current context node. |
//input/@name |
Select the name attribute of matching input elements. |
* |
Match any element node on the selected axis. |
Use absolute paths such as /html/body/div[2]/form sparingly: a small structural change can invalidate them. Relative paths based on stable attributes or meaningful relationships are generally easier to maintain.
2. Attributes and predicates
Predicates in square brackets filter the nodes selected by a step. Attribute predicates use @attribute; conditions can be combined with and or or.
| Need | XPath | What it selects |
|---|---|---|
| Exact attribute value | //input[@name='email'] |
Inputs whose name is exactly email. |
| Several conditions | //input[@type='text' and @name='email'] |
Inputs meeting both conditions. |
| Either condition | //button[@type='submit' or @aria-label='Save'] |
Buttons meeting at least one condition. |
| Attribute exists | //a[@href] |
Links with an href attribute. |
| Attribute contains text | //button[contains(@class, 'primary')] |
Buttons whose class string contains primary. |
contains(@class, 'primary') checks for a substring, not a class token. It can also match values such as not-primary. For class-token matching in XPath 1.0, use a whitespace-aware expression:
//button[contains(concat(' ', normalize-space(@class), ' '), ' primary ') ]
Where possible, use a stable ID, a test attribute, or CSS class selector instead of a complicated class expression.
3. Text matching and XPath functions
XPath’s string functions help locate elements by their string value. For HTML, an element’s string value can include text from descendant nodes; exact behavior should be checked against the target DOM and browser engine.
| Function or pattern | Example | Use |
|---|---|---|
text() |
//button[text()='Save'] |
Match a direct text node. Can miss nested markup or extra whitespace. |
normalize-space() |
//button[normalize-space()='Save'] |
Trim leading/trailing whitespace and collapse whitespace runs before comparing. |
contains() |
//a[contains(., 'Documentation')] |
Find elements whose string value contains a fragment. |
starts-with() |
//a[starts-with(@href, '/docs')] |
Match attribute values with a prefix. |
position() |
//li[position()=2] |
Test a node’s position in the current step’s context. |
last() |
//li[last()] |
Match the last item in the current context. |
Use . when matching an element’s full string value, including descendant text, as in contains(., 'Documentation'). Use text() when you specifically mean a direct text child. Neither expression automatically means “visible text”; hidden descendants and whitespace can affect the result.
4. Axes and relationships
XPath defines thirteen axes. These are the ones most useful in everyday locators. An axis describes the direction to navigate from the current context node.
| Axis | Abbreviated form | Example |
|---|---|---|
child (default) |
/ |
//form/input |
parent |
.. |
//input[@name='email']/.. |
self |
. |
//button[self::button] |
descendant |
// |
//section/descendant::button |
ancestor |
None | //span[normalize-space()='Total']/ancestor::tr[1] |
following-sibling |
None | //label[normalize-space()='Email']/following-sibling::input |
preceding-sibling |
None | //input/preceding-sibling::label |
following |
None | //h2[.='Details']/following::button[1] |
preceding |
None | //button[@id='save']/preceding::input[1] |
attribute |
@ |
//input/@name |
Axes make XPath useful when the target is best found through a nearby label, row, or ancestor. Keep the relationship narrow and meaningful. A positional ancestor such as ancestor::tr[1] means the nearest matching ancestor in that axis context.
5. Indexing and predicate context
XPath positions start at 1, unlike many programming language indexes. Predicate position depends on the step and its context, so parentheses can change the result.
//button[1]
(//button)[1]
(//button[@type='submit'])[1]
//li[position() <= 3]
//li[last()]
//button[1] selects button elements that are first in the relevant child-step context; it does not necessarily mean the first button in the entire document. (//button)[1] groups the complete result and then selects its first node. The same context distinction explains why preceding::foo[1] and (preceding::foo)[1] can select different nodes.
6. XPath with Selenium
XPath is a WebDriver locator strategy. Here are runnable examples using Selenium 4 with Python and JavaScript. Install the language binding with python -m pip install selenium or npm install selenium-webdriver. A compatible browser and driver must also be available to Selenium; consult the official Selenium setup documentation for your environment.
Python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = webdriver.ChromeOptions()
# Uncomment to run without opening a visible browser window.
# options.add_argument('--headless=new')
driver = webdriver.Chrome(options=options)
try:
driver.get('https://example.com')
heading = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//h1'))
)
print(heading.text)
finally:
driver.quit()
JavaScript
const { Builder, By, until } = require('selenium-webdriver');
(async function main() {
const driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('https://example.com');
const heading = await driver.wait(
until.elementLocated(By.xpath('//h1')),
10000
);
console.log(await heading.getText());
} finally {
await driver.quit();
}
})();
For a locator scoped to a known container, find the container first, then search within it. This can make intent clearer and avoid matching similarly named elements elsewhere:
form = driver.find_element(By.CSS_SELECTOR, 'form#signup')
email = form.find_element(By.XPATH, ".//input[@name='email']")
7. Choosing a locator that lasts
| Question | Prefer |
|---|---|
| Is there a unique, predictable ID? | Use the ID. |
| No ID, but a clear stable attribute or structure? | Use a compact CSS selector where it expresses the target clearly. |
| Must locate through text or move from a related node? | Use XPath, keeping the expression short and readable. |
| Does the expression depend on several levels of layout or a numeric position? | Look for a stable ID, test attribute, accessible name, or narrower container. |
Selenium’s guidance prefers HTML IDs when they are unique and consistently predictable, then a good CSS selector when IDs are absent. It also cautions that XPath can be harder to debug and that complicated DOM traversals may be slow. This is practical Selenium guidance, not a universal speed ranking. Keep locators compact, readable, and scoped. See Selenium’s locator guidance and WebDriver locator strategies.
8. Troubleshooting XPath locators
| Symptom | Likely cause | Fix |
|---|---|---|
| No such element | The DOM has not rendered yet, the expression is wrong, or the element is inside a frame or shadow root. | Wait for the condition, inspect the live DOM, and switch into the relevant frame. Shadow DOM needs the appropriate shadow-root access; XPath does not cross into it automatically. |
| More than one match | The expression is too broad, such as //button. |
Add a stable attribute predicate or scope the search to a unique container. |
| Text locator misses | Whitespace, nested markup, hidden text, or direct-text versus full-string differences. | Inspect the element’s DOM string value and try normalize-space(.) when appropriate. |
| Class match selects wrong element | contains(@class, 'x') matches substrings. |
Use the whitespace-aware token pattern or a CSS class selector. |
Wrong item selected with [1] |
Position applies in a different step or axis context than expected. | Group the result with parentheses when you mean the first node in the complete result; verify with a DOM inspector. |
| Invalid selector syntax | Unbalanced brackets or quotes, unsupported function, or malformed string literal. | Check quoting and brackets, and confirm that the target browser’s XPath engine supports the expression. |
| Works locally, fails in CI | Timing, browser differences, stale state, or different page content. | Wait for a meaningful state instead of sleeping a fixed duration, and inspect the CI DOM and browser logs. |
9. Performance, reliability, and maintenance
- Prefer meaning over traversal: a stable ID or concise CSS selector is easier for a team to maintain than a long chain of ancestors and indexes.
- Scope searches: identify a stable parent, then search within it.
- Wait for state: use explicit waits for the target condition; fixed delays add time and remain unreliable when load time varies.
- Avoid brittle positions: adding a sibling can change what an indexed locator selects.
- Keep the XPath version in mind: these examples use common XPath 1.0 constructs. Do not assume functions from later XPath versions are supported by browser automation engines.
- Performance has context: XPath flexibility does not imply that every XPath is slow. Selenium’s warning concerns complex DOM traversals; avoid unsupported universal speed claims and optimize only after observing a real bottleneck.
10. Capture a page while investigating a locator
A screenshot can help document what a page looked like when a locator failed, though it does not replace inspecting the DOM. For a visual record, ScreenshotNeo provides a website screenshot API and MCP server. It accepts URL parameters for image or PDF capture, and its options include full-page capture, selector-based element capture, custom waits, and custom CSS or JavaScript. See the ScreenshotNeo API documentation.
Or skip the browser setup
Make one GET request to capture a page. This cURL example saves a WebP image:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Cookie banners are accepted like a visitor and removed along with 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. The MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Options include PNG, JPEG, or WebP output, PDF, full-page or element capture, viewport and device presets, waits, and custom headers. Read the API docs for parameters and setup.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month, with no card required.
11. FAQ
Is XPath the same thing as a CSS selector?
No. Both can locate DOM elements, but XPath navigates document axes and offers predicates and functions for text and relationships. CSS selectors express matching through CSS selector syntax.
Are XPath indexes zero-based?
No. XPath positions are one-based: the first matching position is 1.
Can XPath select elements by visible text?
It can match string values with expressions such as normalize-space(.), but XPath matching does not itself guarantee that the matched text is visible.
Where should I look for a fuller reference?
MDN’s XPath overview links to axes, functions, and guides. For Selenium-specific usage, see its official locator practices.


