Skip to content
Free tool

XPath tester that can see into shadow roots

Paste a fragment or a whole page, type an expression, watch the matches light up as you type. Declarative shadow DOM is parsed and searched, and each hit comes with a composed path that says which host it lives under.

Paste HTML on the left, an expression such as //button[contains(., 'Absenden')] on the right. Nothing is uploaded.

This tool runs in your browser and needs JavaScript.

Observations, not verdicts. What you enter is processed for this result and written to a usage journal described in the privacy policy; nothing else is stored.

Testing an expression

  1. Paste the markup

    A fragment from the developer tools is enough. To reproduce a component that uses shadow DOM, include its template with shadowrootmode set to open, as the server would have sent it.

  2. Write the expression

    XPath 1.0 as browsers evaluate it. Matches are recomputed on every keystroke; a syntax error is shown in place instead of an empty result.

  3. Read the match list

    Each hit shows its tag, its text and a composed path. A match inside a shadow root is marked with the host element it belongs to.

  4. Narrow until exactly one matches

    An expression that matches two elements is ambiguous for an agent. Add a predicate on text, an attribute or position until the count reads one.

How matching works, and the shadow DOM problem

The tester runs entirely in your browser. The markup you paste is parsed into a document that is never attached to the page, and the expression is evaluated with the browser's own XPath engine, the same document.evaluate a Playwright or Selenium locator ends up calling. Nothing you paste leaves the tab; there is no server round trip, and the usage journal records only that the tool was opened.

What XPath 1.0 can and cannot express

Browsers implement XPath 1.0. That gives you axes, predicates, contains(), normalize-space(), starts-with(), text() and positional filters. It does not give you matches(), ends-with() or anything from XPath 2.0 and later, so an expression that works in an XML toolkit can fail in a browser without being wrong. The tester reports the engine's error message where that happens.

Why shadow roots need special handling

An XPath expression stops at a shadow boundary. A web component that renders its button inside a shadow root is invisible to //button evaluated from the document, which is why a locator that works on one site fails on a portal built from components. Declarative shadow DOM, the template element with shadowrootmode, makes this common on server-rendered pages too.

The tester handles it the way Browserberg's perception layer does: the expression is evaluated in the document and then again inside every shadow root, and a match inside a root is reported with a composed path, the path to the host element followed by the path inside its tree. That is the address an agent uses when it needs to click something a plain XPath cannot reach.

The ambiguity rule

In the product, an XPath is one rung on a five-rung ladder of element identity. When a cached plan is replayed, each rung is tried in turn, the rung that matched is recorded, and an expression that matches more than one element is refused as ambiguous rather than resolved to the first hit. The tester shows the match count for that reason. One is the number you want.

What the tester does not judge

It matches against the markup you pasted, not against a live page, so anything a script adds after load is absent unless you paste the rendered DOM. It says nothing about whether a matched element is visible, enabled or actually clickable; that is the agent view's job, where clickability is four signals with a recorded reason and reachability is a hit test rather than rectangle geometry. And it does not tell you whether the expression will survive the next deploy: a path built from generated class names matches today and nothing tomorrow. Prefer text, roles and stable attributes.

Tip · Text predicates survive redesigns better than positions

//button[normalize-space()='Weiter'] keeps matching when a column is added; //div[3]/button[2] does not. If the label changes with the locale, match on an aria-label, a name attribute or a data-testid instead.

Questions about XPath in browsers

How do I test an XPath expression?

In any browser, open the developer tools console and call $x('//your/expression'), or paste the markup into this tester to see matches highlighted as you type without touching the live page.

Why does my XPath find nothing inside a web component?

The element sits in a shadow root, and XPath does not cross shadow boundaries. Evaluate inside the root or use a tool that composes paths across hosts, which is what this tester does.

Does the tester support XPath 2.0 functions?

No, because browsers do not. matches(), ends-with() and regular expressions are unavailable; contains(), starts-with() and normalize-space() are.

Is XPath better than CSS selectors?

Neither is better. XPath can match on text content and walk up to a parent, which CSS cannot; CSS is shorter and faster for attribute and class matches. Agents use both, chosen per element.

Is my HTML sent to a server?

No. Parsing and evaluation happen in your browser tab. The page works offline once loaded.

Stop writing selectors by hand

Five browser hours, no card. Let observe hand you the candidates and their identities on any page.