You are writing a Selenium test and the element selector is fragile — three CSS classes deep, breaks on every UI tweak. Or you inherited an XSLT stylesheet that picks the wrong product node and you need to learn why. Or you are scraping an RSS feed and want a single expression that grabs every guid regardless of nesting depth. An XPath tester lets you iterate in seconds — paste XML, type an expression, watch matches appear, refine until the result set matches your intent.
This tool runs XPath 1.0 via the xpath npm library on top of @xmldom/xmldom. Every expression is evaluated against the DOM tree built from your input. Results are tagged with their node type — element, attribute, text, string, number, boolean — so you know exactly what kind of value you matched. Everything runs in the browser; nothing uploads.
How to use
- Paste XML into the left pane.
- Enter an XPath expression on the right, or pick one of the example chips to prefill.
- Click Evaluate. Matching nodes appear below, each labeled with its node type and serialized form.
- Refine and re-evaluate. Most real XPath work is iterative — start with a permissive descendant query, then add predicates to narrow.
XPath axis cheat sheet
Every XPath step is built from an axis plus a node test plus optional predicates. The 13 axes give you full directional control over the tree.
| Axis | Shorthand | What it selects |
|---|
| child | book (default) | Direct children of the context node |
| descendant | .//book | All descendants (children, grandchildren...) |
| descendant-or-self | //book | Context node + all descendants |
| parent | .. | Parent of context node |
| ancestor | ancestor::book | All ancestors up to root |
| following-sibling | following-sibling::* | Siblings appearing after the context node |
| preceding-sibling | preceding-sibling::* | Siblings appearing before the context node |
| attribute | @id | Attributes of the context node |
| self | . | The context node itself |
The shorthand axes (/, //, @, .,..) cover 90% of practical XPath. Reach for the long form (following-sibling::, ancestor::) when you need non-descendant navigation.
Predicates: filters in square brackets
A predicate is a boolean or positional expression applied to a node-set. The matched nodes are kept; everything else drops. Common forms:
//book[@genre='fiction'] — attribute equality//book[price > 10] — numeric comparison on child element//book[contains(title, 'XML')] — substring match//book[starts-with(@id, 'SKU-')] — string-function predicate//book[1] — first match (1-indexed!)//book[last()] — last match//book[position() <= 3] — first three//book[@id and @genre] — has both attributes//book[not(@discontinued)] — does NOT have the attribute
Predicates can chain — //book[@genre='fiction'][price > 10] applies both filters. Each successive bracket runs against the result of the previous one.
XPath 1.0 vs 2.0 / 3.0
This tool implements XPath 1.0 — the version baked into XSLT 1.0, DOM, and most browser document.evaluate() implementations. XPath 2.0 (2007) and 3.0 (2014) added rich data types and language features that this tool does not support:
| Feature | XPath 1.0 (this tool) | XPath 2.0 / 3.0 |
|---|
| Data types | node-set, string, number, boolean | + sequence, date, duration, decimal, etc. |
if/then/else | Not supported | Supported |
for $x in ... return | Not supported | Supported |
Regex (matches()) | No — use contains | Yes |
| String functions | substring, contains, normalize-space | + tokenize, replace, upper-case |
| Where to test | This tool, browsers, Selenium | Saxon, BaseX, eXist-db |
XPath 1.0 is what runs in browser DevTools ($x('...') in the console), Selenium WebDriver, and most XSLT 1.0 pipelines. If you need 2.0+ for sequences or regex, evaluate with Saxon-HE locally instead.
Examples
Example 1: Grab every title regardless of depth
The // descendant axis is the fastest way to pull elements out of a deep tree when you do not care about position.
Input
<library>
<book id="1">
<title>XML Pro</title>
</book>
<book id="2">
<title>Go Basics</title>
</book>
</library>
XPath: //book/title/text()Output
[text] "XML Pro"
[text] "Go Basics"
Example 2: Predicate-filtered selection
Combine attribute predicates with a child step to narrow before drilling down.
Input
<catalog>
<book genre="fiction">
<title>Dune</title>
</book>
<book genre="tech">
<title>DDIA</title>
</book>
<book genre="fiction">
<title>1984</title>
</book>
</catalog>
XPath:
//book[@genre='fiction']/titleOutput
[element] <title>Dune</title>
[element] <title>1984</title>
Example 3: Scalar functions
count(), sum(), string(), boolean() all return a scalar (not a node-set). The result panel labels them by type.
Input
<orders>
<order total="120"/>
<order total="45"/>
<order total="89"/>
</orders>
XPath: sum(//order/@total)
XML specification reference
Quick reference from W3C XML 1.0. The converter enforces these rules on input; anything outside them is rejected or normalized.
| Element | Meaning | Example |
|---|
| Prolog | Optional XML declaration | <?xml version="1.0"?> |
| Element | Opening + closing tag wrapping content | <p>hi</p> |
| Attribute | name="value" on an opening tag | <a href="x"> |
| CDATA | Literal text including markup characters | <![CDATA[<x/>]]> |
| Entity | Five predefined: < > & " ' | & |
Common invalid forms
<b><i>bold italic</b></i> // overlapping elements
<tag> // missing closing tag
<a href=x> // attribute value not quoted
Common pitfalls
- Default namespaces silently break paths.
<root xmlns="http://x"> binds every element to that namespace. A path like //book matches nothing because book in http://x is not the same as namespace-lessbook. Workaround in XPath 1.0: //*[local-name()='book']. - Predicates are 1-indexed.
//book[1] is the first book, not the second. Coming from programming languages, this trips everyone exactly once. //book[1] ≠ (//book)[1]. The first means "every book that is the first child of its parent". The second means "the first book in document order". Parentheses change the context that the predicate filters.- String comparison is not type-aware. Numeric attributes get compared as strings unless you coerce.
//book[@year > 2000] compares numerically; //book[@year = '2000'] compares as a string. - Text node vs string value.
//title/text() returns text nodes; string(//title)returns the concatenated string of the element's descendants. For mixed content, the difference matters. - Empty result is not always wrong. XPath 1.0 returns an empty node-set instead of an error when nothing matches. Use
count(//book) to confirm the document has the elements you expect before debugging the predicate.
This tester vs browser DevTools and CLI tools
- Browser DevTools console (
$x('...')): evaluates against the live HTML DOM. Great for scraping a rendered page, weak for standalone XML. XPath 1.0 only. xmllint --xpath: CLI, XPath 1.0, available everywhere. Good for shell pipelines and CI. Less interactive.- Saxon-HE (
java -jar saxon-he.jar): XPath 2.0 and 3.0+, sequences, regex, dates. The standard if you need modern XPath features. Heavy install. - This tool: XPath 1.0 in a tab, no install, fast iteration on specific documents. Best for prototyping selectors before pasting into Selenium, XSLT, or a CMS rule engine.
When NOT to use this tool
- JSON data. Use JSONPath Tester or JMESPath instead — XPath is for XML.
- HTML scraping at scale. Use Playwright/Puppeteer with CSS selectors or page DevTools console. CSS selectors are usually more readable than XPath for HTML.
- You need XPath 2.0 or 3.0 features. Sequences, regex, if/then/else, date arithmetic — install Saxon-HE locally.
- Very large XML files (>10MB). The parser loads the whole tree into memory. Stream with
xmllint --stream instead. - You want to transform XML, not query it. XPath selects; XSLT transforms. Use an XSLT processor (xsltproc, Saxon) for transformation pipelines.
Troubleshooting
- 0 matches but the element is clearly there. Check for default namespaces.
//*[local-name()='yourElement'] is the universal workaround in XPath 1.0. - Selenium XPath works in DevTools but fails in the test. The DOM mutates after page load. Add explicit waits before the
find_element call. The XPath itself is probably fine. - Predicate filters everything out. Check value typing — string vs number. Try
//book[@year='2020'] (string) and//book[@year=2020] (numeric) and see which one matches. - The XPath returns text nodes you cannot serialize.
text() returns DOM text nodes. To get a plain string, wrap withstring() or call .textContent in JS. The result panel here shows the value directly so you can confirm. - Need a wildcard for namespace prefixes. XPath 1.0 has
* for any element name but no prefix wildcard. Use local-name() to match across namespaces.
Frequently asked questions
Which XPath version is supported?
XPath 1.0 via the xpath npm library running over @xmldom/xmldom. XPath 2.0+ constructs like if/then/else, sequences, tokenize(), matches() regex, and date arithmetic are not supported here. For XPath 2.0/3.0, use Saxon-HE locally.
Does it handle namespaces?
Basic namespace-aware queries work — namespaced elements appear in the result tagged with their prefix. Custom prefix-to-URL mapping is not exposed in the UI yet. If your XML declares a default namespace (xmlns="..."), the easiest workaround is local-name()='yourElement' in a predicate.
Is my XML uploaded?
No. Parsing and XPath evaluation happen entirely in your browser. There is no server round-trip — confirm by opening DevTools Network panel before you click Evaluate. The tab works offline.
Why do I see 0 matches?
The two most common causes: (1) the path uses absolute addressing from a root that does not exist in the document — try //element instead of/element; (2) the XML declares a default namespace, so unprefixed element names in the XPath do not match the namespaced elements in the document — work around with //*[local-name()='element'].
Can I select attributes and text nodes?
Yes. Use @attr for attributes (e.g. //book/@id) and text() for text nodes (e.g. //title/text()). The result panel labels each match with its node type — element, attribute, text, string, number, or boolean. To convert a node-set into a single string, wrap with string().
Are XPath positions 0-indexed or 1-indexed?
1-indexed. //book[1] is the first matching book. //book[0] matches nothing. last()returns the count of the current step's context, so //book[last()] is the last book in each parent.
Related guides
JSON vs YAML: When to Use What — A Developer's Guide
JSON wins on APIs; YAML wins on configs. Side-by-side syntax, parser behaviour, and where each fits across Kubernetes manifests, REST payloads, and GitHub Actions.
XML vs JSON: Differences, Use Cases, and Migration Guide
XML still runs banking, SOAP, EPUB, and OOXML; JSON owns everything else. Syntax differences, tooling, and a sober migration playbook for teams stuck on legacy XML.
YAML Configuration Files: Syntax, Best Practices, and Common Pitfalls
YAML whitespace is unforgiving and the Norway problem (no:) still bites. Types, anchors, aliases, and the rules that keep Kubernetes manifests, GitHub Actions, and Helm values readable.
Related tools
Related Tools
Timestamp Converter
Convert Unix timestamps to human-readable dates and vice versa.
Color Converter
Convert colors between HEX, RGB, HSV, CMYK, and HSL formats.
Aspect Ratio Calculator
Calculate and convert aspect ratios for images and videos.
PX / REM / EM Converter
Convert between PX, REM, and EM CSS units instantly with customizable base font size.
DPI / PPI Calculator
Calculate screen pixel density from resolution and size.
WCAG Contrast Checker
Test color combinations against WCAG 2.1 AA and AAA standards with live preview.
Date Diff Calculator
Find years, months, days, hours, and business days between any two dates and times.