· Updated · Research · By Simon Chiu

Screenshot vs DOM vs Accessibility Tree: Token Costs

We measured what 26 real web pages cost an LLM agent as raw HTML, cleaned HTML, text, accessibility tree and screenshot. Data, script and method included.

Line illustration of the RenderScreenshot mascot comparing a picture, code blocks and a tree diagram

Short answer: for the first screen of a page, a screenshot is the cheapest representation you can give an LLM agent. A 1280x800 capture costs a fixed 1,334 visual tokens on Claude. Across 26 real pages, raw HTML had a median of 97,744 tokens, cleaned HTML 19,441, the accessibility tree 8,148 and visible text 2,066. The catch: the screenshot shows only one screen, and a full page squeezed into one image becomes unreadable.

This post gives the numbers, the method, the raw CSV and the script, so you can rerun it on your own URLs. It is part of our guide to giving AI agents web vision.

Disclosure. RenderScreenshot sells a screenshot API, so we have an interest in screenshots looking good here. That is why the data and the script are public, and why we report the cases where text or the accessibility tree is the better choice.

Method

Item Value
Date 2026-10-15
Machine macOS, Node 22.18.0, Google Chrome 155, Playwright 1.64.1
Pages 30 public URLs in 7 groups (docs, reference, news, government, SaaS, developer, shop)
Valid results 26 (4 excluded, see below)
Viewport 1280x800, device scale factor 1, locale en-US
Wait load, then up to 10 s for networkidle
Runs 1 per URL (token counts are deterministic for a given page state; pages change daily)
Text tokenizer o200k_base via js-tiktoken 1.0.21
Image tokens Anthropic's published formula (details below)

What we measured for each page:

  1. Raw HTML: page.content() after load, which includes inline scripts, styles, SVG and every attribute.
  2. Cleaned HTML: the same DOM with script, style, noscript, svg, iframe, link, meta and template removed, and every attribute removed except href, alt, title, aria-label, role, name, type, value and placeholder.
  3. Visible text: document.body.innerText.
  4. Accessibility tree: Playwright's locator('body').ariaSnapshot(), the YAML outline of roles and names.
  5. Screenshot: a 1280x800 viewport capture, plus the full scroll height to model a full-page capture.

Excluded rows. npm, Stack Overflow and Etsy returned HTTP 403 to the headless browser. Best Buy returned HTTP 200 with a 2,004-token interstitial page instead of the store. These rows stay in the CSV with their status so you can see them, but they are not in the statistics. Bot walls are a real cost of agent browsing, and we come back to them at the end.

Two limits you should know. First, o200k_base is OpenAI's tokenizer. Claude's text tokenizer is different, so treat the text numbers as relative sizes, not as exact Claude bills. Second, this run measures cost only. It does not measure how well a model answers questions from each representation. The accuracy protocol is at the end of this post, and we will publish it as a separate run.

How image tokens are counted

Anthropic's vision documentation (checked 2026-10-15) says Claude reads an image in 28x28 pixel patches, so an image costs ceil(width / 28) × ceil(height / 28) visual tokens. Images larger than the model's limit are scaled down first:

Tier Models Max long edge Max visual tokens
Standard All other Claude models 1568 px 1568
High-resolution Claude 4.7 and later 2576 px 4784

Our script reproduces the downscaled sizes in Anthropic's own example table within one pixel. OpenAI's current models use a similar idea with 32x32 patches and a per-model multiplier, so the shape of the results holds, but the exact numbers differ.

Results

Medians across 26 pages

Representation Median tokens Smallest Largest
Raw HTML 97,744 641 774,066
Cleaned HTML 19,441 269 229,354
Accessibility tree 8,148 245 186,777
Visible text 2,066 209 53,854
Screenshot, 1280x800 viewport 1,334 1,334 1,334
Screenshots tiled over the full page 11,339 1,334 258,796

Four findings stand out.

1. Raw HTML is not a serious option. Half of the pages were over 100,000 tokens and 7 were over 200,000. The largest home page in the set (a furniture retailer) was 774,066 tokens, which does not fit in most context windows. Almost all of that is framework markup, inline JSON and class names that carry no meaning for the task.

2. Cleaning HTML helps less than you expect. Removing scripts, styles and most attributes cut the median by a factor of about 5.5. The result is still about 9 times the visible text, because the remaining tags and href values are verbose.

3. The accessibility tree is a middle ground, not a small one. The median snapshot was 3.8 times the size of the visible text. It earns that cost when the agent must act: it names each link, button and field with its role, so the model can say "click the button named Sign in" instead of guessing coordinates. For reading, plain text is cheaper.

4. A viewport screenshot has a fixed price. 1,334 tokens for every page, whether it is example.com or a 155,000 px tall API reference. It was cheaper than the accessibility tree on 25 of 26 pages and cheaper than the visible text on 17 of 26. It is also the only representation that shows layout, images, charts, color and what actually rendered after JavaScript ran.

Where the screenshot loses

The fixed price buys one screen. Only 1 of the 26 pages fit in 800 px. The median page was 6,525 px tall, so the first screen is often the hero banner and the navigation, not the answer.

If you send the full page as one image, the model scales it down to fit its limits:

Full page, 1280 px wide Standard tier: arrives as High-res tier: arrives as
4,000 px tall 501x1567 px (39% of original width) 824x2575 px (64%)
8,000 px tall 250x1567 px (20%) 412x2575 px (32%)

Body text at 16 px becomes about 3 px tall at 20% scale. In our sample, 15 of 26 full-page captures would arrive narrower than 400 px on the standard tier, and 11 of 26 on the high-resolution tier. The token count looks low because the model sees a thumbnail. For the Node.js fs reference page, the single full-page image costs 56 tokens and is 12 px wide. It shows nothing.

The honest way to see the whole page is to slice it into viewport-sized images. That cost a median of 11,339 tokens. Tiling cost more than the cleaned HTML on 16 of 26 pages and more than the visible text on all 26.

Bytes do not change the bill

The median viewport capture was 170 KB as PNG and 95 KB as JPEG at quality 80. Both cost the same 1,334 tokens. Format choice changes upload size and latency, not token count. Pick JPEG or WebP for agents unless you need lossless pixels for a diff.

What to send, by task

Agent task Best input Why
"What is this page about?" Visible text, plus one viewport screenshot Text is cheap. The screenshot catches what text misses (hero image, banners, error states).
"Did the deploy render correctly?" Viewport screenshots at desktop and mobile widths Only pixels show broken CSS, missing images or a blank SPA shell.
"Click Sign in and fill the form" Accessibility tree (with element IDs) The model needs roles and names to act. A screenshot alone forces coordinate guessing.
"Extract every price in this table" Visible text, or an element screenshot of the table Text is exact. An element crop keeps the token cost at the size of the table.
"Compare our pricing page with last week" Two viewport or element screenshots Visual diffs need images. Store them as evidence.
"Read this long documentation page" Visible text A full-page screenshot is a thumbnail. Slicing costs more than the text.

The pattern most production agents end up with: text or the accessibility tree for reading and acting, screenshots for verification and evidence. We describe how to wire the screenshot half into an agent on the AI agents use case page, and how to tune capture size for vision models in screenshots for GPT and Claude vision.

Reproduce it

Download the files and run them on your own URLs:

mkdir token-study && cd token-study
npm i [email protected] [email protected]
# save measure-tokens.mjs and urls.txt here, then:
node measure-tokens.mjs urls.txt > data.csv

The script launches your installed Chrome (channel: 'chrome'). To use Playwright's bundled Chromium, run npx playwright install chromium and remove the channel option. The core of the image calculation:

// Claude visual tokens after downscaling to the model's limits.
function claudeTokens(w, h, maxEdge, maxTokens) {
  const cost = (a, b) => Math.ceil(a / 28) * Math.ceil(b / 28);
  if (Math.max(w, h) <= maxEdge && cost(w, h) <= maxTokens) return { w, h, tokens: cost(w, h) };
  let lo = 0, hi = Math.min(1, maxEdge / Math.max(w, h));
  for (let i = 0; i < 40; i++) {
    const mid = (lo + hi) / 2;
    if (cost(Math.floor(w * mid), Math.floor(h * mid)) <= maxTokens) lo = mid; else hi = mid;
  }
  const W = Math.floor(w * lo), H = Math.floor(h * lo);
  return { w: W, h: H, tokens: cost(W, H) };
}

claudeTokens(1280, 800, 1568, 1568);  // { w: 1280, h: 800, tokens: 1334 }
claudeTokens(1280, 8000, 1568, 1568); // { w: 250, h: 1567, tokens: 504 }

The accuracy half: protocol for the next run

Token cost is only useful next to accuracy. This is the protocol we will use, published now so you can criticize it before we run it:

  1. Labelled set. For each URL, write 3 questions with a single checkable answer: one about content ("What is the starter price?"), one about layout or state ("Is a cookie banner covering the page?"), and one about an action ("Which link leads to the API reference?").
  2. Inputs. Ask each question with each representation: cleaned HTML, visible text, accessibility tree, one viewport screenshot, tiled screenshots.
  3. Models. At least one Claude model and one OpenAI model, temperature 0 where supported, 3 runs per question.
  4. Scoring. Exact match against the label, scored by a script, not by a model. Report accuracy per representation with the token cost next to it.
  5. Publication. The questions, answers, raw model outputs and the scoring script go in the same folder as this CSV.

Until that run exists, do not read this post as "screenshots are more accurate". It shows that they are cheaper per screen and that they carry information the text does not.

Bot walls are part of the cost

4 of 30 URLs blocked or redirected our headless browser. An agent that browses with a local headless Chrome will hit the same walls, and it will often not notice: Best Buy returned HTTP 200 with an interstitial page. Check the status code and the page title before you trust any representation. A screenshot makes this failure obvious to a human reviewer, which is one more reason to keep one as evidence.

If you want screenshots without running Chrome yourself, the RenderScreenshot API returns a 1280x800 capture from one HTTP call, with options such as block.cookie_banners and wait.until: "networkidle" (see the wait options). Get a free API key and run your own comparison.

Related in this topic