Test and evaluate XPath expressions against HTML or XML online. Free XPath tester with live results, match highlighting, and support for XPath 1.0 axes, predicates, and functions.
An XPath expression that works in one document structure will silently return zero nodes in another. And when you are building a scraper, writing a test, or extracting data from an XML feed, silent failure is the most expensive kind. You write the expression, run the pipeline, get empty results, and have no idea whether the problem is the path, the namespace, the document shape, or a subtle difference in how the parser handles whitespace.
This XPath tester lets you paste HTML or XML, write an expression, and see exactly which nodes match before you commit the expression to your code. It runs entirely in your browser using the native DOMParser and document.evaluate() APIs. No data leaves your machine, no backend is involved, and the results are instant.
The tool has a straightforward two-step workflow that matches how most developers think about XPath.
Quick-fill preset buttons below the expression field cover common patterns: selecting all links, all images, headings, elements with a specific class, and meta tags. Click a preset to populate the expression, then modify it to match your use case.
For each matched node, you can expand the row to see the full serialized output, the actual HTML or XML that the expression selected. This is useful for debugging: it tells you not just how many nodes matched, but exactly what they contain.
The XPath evaluator also handles non-node results. Expressions that return strings (like string(//title)), numbers (count(//div)), or booleans (boolean(//meta[@name='robots'])) display the scalar result directly instead of a node list.
XPath (XML Path Language) is a query language for selecting nodes from an XML or HTML document. It uses a path-based syntax that navigates the document tree, similar to how file system paths navigate directories. Unlike CSS selectors, XPath can traverse the tree in any direction: up to parents, sideways to siblings, and down to descendants. That makes it strictly more powerful for complex selection tasks.
Developers use XPath in three main contexts:
lxml, scrapy, and JavaScript's document.evaluate() accept XPath expressions to locate and extract content from web pages.The version used by modern browsers and most libraries is XPath 1.0, which is the version this XPath tester evaluates. XPath 2.0 and 3.0 exist but are not natively supported by browser engines.
XPath syntax combines path expressions, axes, predicates, and functions. Here is a reference covering the most useful constructs you will encounter.
| Expression | Selects |
|---|---|
| /html/body/div | Direct child <div> of <body> (absolute path) |
| //div | All <div> elements anywhere in the document |
| //div/p | All <p> that are direct children of a <div> |
| //div//p | All <p> that are descendants of a <div> (any depth) |
| //* | All elements in the document |
| //div/@class | The class attribute of every <div> |
| //text() | All text nodes in the document |
Axes define the direction of traversal relative to the current node. They are what make XPath more expressive than CSS selectors.
| Axis | Direction | Example |
|---|---|---|
| child:: | Direct children | child::div (same as ./div) |
| parent:: | Immediate parent | parent::div (or ..) |
| ancestor:: | All ancestors up to root | ancestor::form |
| descendant:: | All descendants at any depth | descendant::span |
| following-sibling:: | Siblings after the current node | following-sibling::li |
| preceding-sibling:: | Siblings before the current node | preceding-sibling::li |
| self:: | The current node itself | self::div |
| attribute:: | Attributes of the node | attribute::href (or @href) |
Predicates filter the node set by adding conditions inside square brackets.
| Expression | Selects |
|---|---|
| //a[@href] | Links that have an href attribute |
| //div[@class='main'] | Divs where class is exactly "main" |
| //li[position()=1] | First <li> child of each parent (same as //li[1]) |
| //li[last()] | Last <li> child of each parent |
| //p[contains(text(), 'error')] | Paragraphs containing the text "error" |
| //input[@type='text' and @name] | Text inputs that also have a name attribute |
| //div[not(@class)] | Divs without a class attribute |
XPath 1.0 includes string, numeric, and boolean functions that operate on node values and attributes.
| Function | Returns | Example |
|---|---|---|
| contains(str, sub) | Boolean | //div[contains(@class, 'nav')] |
| starts-with(str, prefix) | Boolean | //a[starts-with(@href, '/api')] |
| normalize-space(str) | String | //p[normalize-space()='Hello'] |
| string-length(str) | Number | //p[string-length() > 100] |
| count(nodeset) | Number | count(//li) |
| text() | Text nodes | //h1[text()='Welcome'] |
| not(expr) | Boolean | //img[not(@alt)] |
| concat(str1, str2, ...) | String | concat(//title, ' - ', //meta[@name='author']/@content) |
If you are writing a scraper, these are the expressions you will reach for repeatedly. Test each one in this XPath tester against your target page's HTML before adding it to your scraping code.
| Task | XPath Expression |
|---|---|
| Get page title | string(//title) |
| All external links | //a[starts-with(@href, 'http')] |
| Images without alt text | //img[not(@alt) or @alt=''] |
| Meta description | //meta[@name='description']/@content |
| All headings (h1-h6) | //h1 | //h2 | //h3 | //h4 | //h5 | //h6 |
| Table rows | //table//tr[position() > 1] |
| Form inputs | //form//input[@type!='hidden'] |
| Element by data attribute | //*[@data-testid='submit-button'] |
| Nth item in a list | //ul[@class='results']/li[3] |
| Sibling after a label | //label[text()='Email']/following-sibling::input |
Each of these expressions can be tested directly in this tool. Paste the target page's HTML, run the expression, and verify it selects the correct nodes before committing it to your Python, JavaScript, or Ruby scraping code.
Both XPath and CSS selectors address nodes in a document tree. CSS selectors are more familiar to frontend developers and slightly faster in browser engines. XPath is more powerful and handles cases CSS cannot express.
| Capability | CSS Selectors | XPath |
|---|---|---|
| Select by tag, class, id | Yes | Yes |
| Select by attribute value | Yes | Yes |
| Navigate to parent/ancestor | No | Yes |
| Select by text content | No | Yes |
| Select preceding siblings | No | Yes |
| Boolean conditions (and/or/not) | Limited | Yes |
| Return attribute values directly | No | Yes |
| Count, string, math functions | No | Yes |
| XML namespace support | No | Yes |
| Browser performance | Faster | Slightly slower |
Use CSS selectors when you are selecting elements by tag, class, id, or attribute and do not need to navigate upward or sideways. They are the default choice for Playwright, Puppeteer, and DOM manipulation in JavaScript.
Use XPath when you need to select by text content (//a[contains(text(), 'Next')]), navigate to ancestors (ancestor::form), select preceding siblings, or return attribute values directly. XPath is also the only option for processing pure XML documents and when working with Selenium locators or Python's lxml library.
When an XPath expression returns zero matches or the wrong nodes, the problem is usually one of these issues. Use this XPath tester to experiment with corrections until the expression matches what you expect.
/div/p selects <p> children directly under root <div>. //div//p selects any <p> that is a descendant of any <div> at any depth. Most of the time you want //.//DIV will not match <div> in HTML mode (where the browser lowercases tag names). In XML mode, tag case must match exactly.//p[text()='Hello'] requires an exact match including whitespace. Use normalize-space() to trim: //p[normalize-space()='Hello']. Or use contains() for partial matches.//div[@class='nav'] only matches if the class attribute is exactly "nav". For elements with multiple classes like class="nav main sidebar", use contains(@class, 'nav'). Be aware this will also match "navigation", so add spaces if needed: contains(concat(' ', @class, ' '), ' nav ').//li[1] selects the first item. //li[0] selects nothing.Paste your HTML or XML into this XPath tester, enter the expression, and click Evaluate. The tool shows every matching node instantly, without any code or setup. It runs entirely in your browser.
No. All parsing and evaluation happens locally in your browser using the native DOMParser and document.evaluate() APIs. No data leaves your machine at any point.
Both select elements in a document tree, but XPath can navigate upward (to parents and ancestors), select by text content, use boolean logic, and return non-element values like attributes and counts. CSS selectors are faster in browsers and sufficient for most forward/downward selection. Use XPath when CSS cannot express the query.
Common causes: case sensitivity (XPath is case-sensitive), exact vs partial attribute matching (@class='nav' vs contains(@class, 'nav')), whitespace in text content (use normalize-space()), and position indexing starting at 1 instead of 0. Try this tool to test variations of your expression until you find the correct one.
This tool uses the browser's native document.evaluate() which implements XPath 1.0. XPath 2.0 and 3.0 add features like regular expressions, date functions, and quantified expressions, but they are not supported by browser engines. For XPath 2.0/3.0 evaluation, you need server-side tools like Saxon.
HTML mode uses the browser's forgiving HTML parser, which auto-corrects malformed markup (adding missing tags, fixing nesting). XML mode uses a strict XML parser that requires well-formed input and reports parse errors. Use HTML mode for web page content and XML mode for API responses, feeds, and structured data files.
Yes. Paste the page's HTML source into this tester, write your expression, and verify it matches the correct elements. Once the expression works here, use it directly in Selenium with driver.find_element(By.XPATH, "your_expression"). The browser's XPath engine is the same one Selenium uses.
Use the text() function or contains(). For exact match: //a[text()='Click here']. For partial match: //a[contains(text(), 'Click')]. For case-insensitive match, use translate(): //a[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'click')].
Structured page data instead of raw HTML. Your agent processes less, decides faster, and costs less to run.