ZeroDOM

Parses a live web page into a token-optimized interaction graph for AI browser agents — 71% fewer tokens than Playwright's ARIA snapshot mode. Every CSS selector is checked for actual document-wide uniqueness before being returned, not assumed from an attribute.

Documentation

Every tool here answers the same question: what can this page do, addressed precisely enough that an agent can act on it without guessing. An agent working from raw HTML either burns its context window re-reading a 30,000-token page every turn, or gets a hand-rolled subset of the DOM that misses the one button it needed. ZeroDOM sits between the two: a deterministic pass over the page that keeps everything an agent can click or fill, drops everything it can't, and hands back a graph small enough to reread on every single action without it mattering.

Nothing about that pass touches a model. Parsing is lxml (Python) or linkedom (TypeScript) over HTML you already have — from a Playwright page, from a raw string, from a file — and it runs the same way every time. The only thing that ever reaches an LLM is the text you choose to send it: graph.to_compact_text(), a few hundred tokens instead of the page. Start with the quickstart if you already know Playwright, or install first if you're setting up from scratch.

Measured across 111 live sites — static pages, SPAs, web components, shadow DOM, iframes, dashboards, commerce, and login walls — 10,756 audited nodes resolved to exactly one live element 99.00% of the time, with 0.03% ambiguous and zero invalid selectors. Median token savings against raw HTML: 98.9%.

How it fits together

The selector never leaves your process. A model only ever sees a node id like [03] — the CSS path that actually addresses the element lives in selector_map(), on your side, and gets resolved right before the click. On real pages that selector is often longer than the label describing it; keeping it out of the prompt is most of where the token savings above come from.

1

Live page

a Playwright Page, or HTML you already have

2

ZeroDOMParser

one DFS pass — prune, collect, label

3

InteractionGraph

compact text or JSON, sent to the model

4

Model picks a node

e.g. click 03 — never a selector

5

selector_map()[node]

resolved on your side, not the model's

6

Act

click/fill via Playwright, then re-read