🎯 A customizable, anti-detection cloud browser powered by self-developed Chromium designed for web crawlers and AI Agents.👉Try Now
Back to Blog

The Accessibility Tree Is Not a Cheaper Page: Measuring What Agents Read

Michael Lee
Michael Lee

Expert Network Defense Engineer

07-Aug-2026

TL;DR:

  • The accessibility tree is what most browser agents feed their model instead of raw HTML, retrieved over the Chrome DevTools Protocol with Accessibility.getFullAXTree.
  • It is not a compression of the page. On a live catalogue page: raw HTML 9,824 tokens, innerText 582, full accessibility tree 5,951 — the tree costs 10.2× plain text.
  • The reason is visible in the node roles: of 1,401 nodes, 267 are StaticText and 391 are InlineTextBox, so most strings are stored twice.
  • Filtering to interactive roles gives 742 tokens across 114 nodes — 1.3× innerText — while keeping everything an agent needs to click.
  • Measured through local Chromium and through the Scrapeless Scraping Browser, every number was identical, so an agent's page representation does not drift between environments.
  • The Scrapeless free plan covers the cloud-browser run in this guide.

Every browser agent has to answer one question before it can do anything: what do you hand the model? A 51,004-character HTML document does not fit a sensible prompt budget, and a screenshot costs image tokens and loses exact strings. The usual third answer is the accessibility tree.

That answer is right about the shape and wrong about the price. The tree carries roles and names — link, button, heading — which is exactly what an agent needs to decide where to click. It is also, unfiltered, an order of magnitude more expensive than the page's plain text.

This guide pulls the tree over CDP, measures it against the alternatives on the same live page, and shows the filter that makes it worth sending.

What the Accessibility Tree Is

The browser builds a second tree alongside the DOM, for screen readers. Each node carries a role — one of the control types defined by the WAI-ARIA specification — and a name, the string a screen reader announces, derived by the algorithm in the Accessible Name and Description Computation. Presentational wrappers collapse, aria-* attributes are resolved, and interactive elements are labelled.

That structure is why agents like it. link: Books to Scrape is directly actionable in a way that a <div class="col-sm-8 h1"><a href="..."> is not. The tree is exposed by the Accessibility domain of the Chrome DevTools Protocol, and it is the same data Chrome renders in its own accessibility pane. If CDP itself is new, the protocol primer covers the transport this article rides on.

Install

bash Copy
pip install playwright tiktoken
playwright install chromium

tiktoken is only here to count tokens; the extraction needs Playwright alone. The verification run used Playwright 1.59.0 and tiktoken 0.12.0.

Pull the Tree

Playwright exposes a raw CDP session, which is how you reach domains it does not wrap:

python Copy
    cdp = page.context.new_cdp_session(page)
    nodes = cdp.send("Accessibility.getFullAXTree")["nodes"]

Every node is a dict whose role and name are themselves objects with a value key. Most nodes have no name at all — containers, ignored nodes, and layout boxes — so the useful projection is role plus name for the named ones:

python Copy
def ax_lines(nodes, roles=None):
    lines = []
    for node in nodes:
        role = (node.get("role") or {}).get("value", "")
        name = ((node.get("name") or {}).get("value") or "").strip()
        if not name:
            continue
        if roles is not None and role not in roles:
            continue
        lines.append(f"{role}: {name}")
    return lines

On a live book catalogue that yields lines like:

text Copy
RootWebArea: All products | Books to Scrape - Sandbox
heading: All products
link: Books to Scrape
StaticText: We love being scraped!
link: Home
StaticText: /
InlineTextBox: /

Two of those seven lines are the same slash. That duplication is the whole cost story.

Measure It Against the Alternatives

Count tokens for the three representations of the identical page:

python Copy
def measure(page, label):
    html = page.content()
    text = page.evaluate("document.body.innerText")
    cdp = page.context.new_cdp_session(page)
    nodes = cdp.send("Accessibility.getFullAXTree")["nodes"]

    full = "\n".join(ax_lines(nodes))
    acts = "\n".join(ax_lines(nodes, INTERACTIVE))
    roles = [(n.get("role") or {}).get("value", "") for n in nodes]
text Copy
  raw html            tokens=  9824  chars=51004
  innerText           tokens=   582  chars=2029
  AX full             tokens=  5951  named_nodes=864
  AX interactive only tokens=   742  nodes=114
  AX total nodes      1401
  StaticText nodes    267
  InlineTextBox nodes 391
  price in html/text/ax: True/True/True

The full accessibility tree costs 5,951 tokens against innerText's 582. It is 10.2× the plain text of the same page, and about 60% of the raw HTML it was supposed to replace.

The role counts explain it. Of 1,401 nodes, 267 are StaticText and 391 are InlineTextBox — 658 nodes, nearly half the tree, devoted to text the page already contains once. InlineTextBox nodes are layout fragments: a single sentence broken across two rendered lines becomes two of them. Sending the full tree means paying for every string at least twice.

Worth stating plainly: nothing was lost. The price £51.77 is present in the HTML, in innerText, and in the accessibility tree. On this page the tree is more expensive, not less complete.

Filter to What an Agent Can Act On

An agent reading a page needs text. An agent operating a page needs the things it can click and type into. Those are a small set of roles:

python Copy
INTERACTIVE = {"link", "button", "textbox", "combobox", "checkbox", "radio", "menuitem", "tab"}

Passing that set to the same function collapses the tree to 114 nodes and 742 tokens — 1.3× innerText, and 8× cheaper than the unfiltered version. Every link and control keeps its accessible name, which is what a click instruction refers to.

That suggests a division rather than a choice. Use innerText when the model has to read and extract, use the role-filtered tree when it has to decide what to operate, and send both when the task needs both — together they are 1,324 tokens, still an eighth of the raw HTML. The agent loop guide covers what happens after that decision is made.

Run It on the Scraping Browser

Nothing above needs a local browser. The Scrapeless Scraping Browser speaks the same protocol, so the CDP session and the accessibility call are unchanged — only the connection differs:

python Copy
    endpoint = (
        "wss://browser.scrapeless.com/api/v2/browser"
        f"?token={os.environ['SCRAPELESS_API_KEY']}&sessionTTL=180&proxyCountry=ANY"
    )
    with sync_playwright() as p:
        browser = p.chromium.connect_over_cdp(endpoint, timeout=90000)
        page = browser.new_page()
        page.goto(URL, wait_until="domcontentloaded")
        measure(page, "Scrapeless Scraping Browser")
        browser.close()

The cloud run returned identical figures on every metric: 9,824 / 582 / 5,951 / 742 tokens, 1,401 nodes, the same StaticText and InlineTextBox counts. That has a practical consequence. A page representation that shifts between your laptop and production would make agent behaviour irreproducible; this one does not. Keep the key in the environment as SCRAPELESS_API_KEY, and see the Scraping Browser introduction for the remaining session parameters.

Getting started takes a minute — create a free Scrapeless account and the free plan covers this run.

Run It

bash Copy
export SCRAPELESS_API_KEY="your-api-key"
python3 ax_demo.py

The complete output from the verification run:

text Copy
playwright 1.59.0 | tiktoken 0.12.0
[local chromium]
  raw html            tokens=  9824  chars=51004
  innerText           tokens=   582  chars=2029
  AX full             tokens=  5951  named_nodes=864
  AX interactive only tokens=   742  nodes=114
  AX total nodes      1401
  StaticText nodes    267
  InlineTextBox nodes 391
  price in html/text/ax: True/True/True

[Scrapeless Scraping Browser]
  raw html            tokens=  9824  chars=51004
  innerText           tokens=   582  chars=2029
  AX full             tokens=  5951  named_nodes=864
  AX interactive only tokens=   742  nodes=114
  AX total nodes      1401
  StaticText nodes    267
  InlineTextBox nodes 391
  price in html/text/ax: True/True/True

ratios vs innerText: html=16.9x  ax_full=10.2x  ax_interactive=1.3x

Troubleshooting

Accessibility.getFullAXTree returns very few nodes. The tree is built lazily. Navigate and wait for content before requesting it, and call Accessibility.enable first if your client does not do it implicitly.

Every node has an empty name. You are reading node["name"] directly. Both role and name are objects — the string is at node["name"]["value"].

Node counts differ between two runs of the same page. InlineTextBox nodes follow line wrapping, so a different viewport width changes how many there are. Set an explicit viewport when the count itself matters.

The tree omits something visible on screen. Content marked aria-hidden, and text generated purely by CSS ::before/::after, is intentionally absent. Read those from the DOM instead; the accessibility tree is not a substitute for it.

Roles look unfamiliar. StaticText, InlineTextBox, and RootWebArea are internal Chrome roles rather than ARIA ones, which is why filtering on an ARIA-only allowlist drops most of the tree. The Core Accessibility API Mappings define which roles a browser is required to expose; anything beyond that set is engine-specific.

Conclusion

The accessibility tree is a good answer to what an agent should be given, and a bad default in its raw form. On the page measured here it costs 5,951 tokens — ten times the plain text it is often assumed to compress — because nearly half its nodes exist to describe text the page already stated once.

Filtered to the roles an agent can act on, the same tree is 742 tokens and still names every control. That is the version to put in a prompt, paired with innerText when the task also requires reading. Measure both on your own target before choosing: the numbers here come from one catalogue page, and the ratio depends entirely on how much of the page is prose and how much is controls.

Ready to try it? Start with the Scrapeless free plan and see the current pricing for higher volumes.

FAQ

Q: Should I send the accessibility tree instead of the HTML?

Send a filtered version of it. Unfiltered it measured 5,951 tokens against 9,824 for the raw HTML — a saving, but far less than expected. Restricted to interactive roles it drops to 742, which is where the real reduction is.

Q: Is the accessibility tree cheaper than plain text?

No, and this is the common misconception. On the page measured here it cost 10.2× innerText. The tree's value is the role labels it adds, not a smaller payload.

Q: Why are there so many InlineTextBox nodes?

They are layout fragments — one per rendered line of text. A sentence that wraps across two lines produces two of them, on top of the StaticText node holding the same string. That is why 658 of 1,401 nodes on the test page were text duplication.

Q: Does the tree contain everything on the page?

Not quite. aria-hidden content and text generated by CSS pseudo-elements are deliberately excluded. On the page measured here nothing needed was missing — the price appeared in all three representations — but confirm that on your own target rather than assuming it.

Q: Do I need a local Chrome to read the accessibility tree?

No. The cloud run in this guide produced byte-identical numbers to local Chromium, because both speak the same protocol. Only the connection line changes.

At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.

Most Popular Articles

Catalogue