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.
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:
- Raw HTML:
page.content()after load, which includes inline scripts, styles, SVG and every attribute. - Cleaned HTML: the same DOM with
script,style,noscript,svg,iframe,link,metaandtemplateremoved, and every attribute removed excepthref,alt,title,aria-label,role,name,type,valueandplaceholder. - Visible text:
document.body.innerText. - Accessibility tree: Playwright's
locator('body').ariaSnapshot(), the YAML outline of roles and names. - 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:
- data.csv: one row per URL with all token counts, heights and byte sizes.
- measure-tokens.mjs: the script.
- urls.txt: the URL list with groups.
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:
- 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?").
- Inputs. Ask each question with each representation: cleaned HTML, visible text, accessibility tree, one viewport screenshot, tiled screenshots.
- Models. At least one Claude model and one OpenAI model, temperature 0 where supported, 3 runs per question.
- 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.
- 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 reading
- Screenshot API for AI agents: tool definitions for OpenAI, Anthropic and the Vercel AI SDK.
- Chrome DevTools MCP screenshots: what the local DevTools MCP server returns and where it stops.
- Claude tool use with a screenshot tool: return image blocks from a tool call.
Related in this topic
- Screenshot API for AI Agents Give AI agents web vision with one HTTP tool. Tool definitions for OpenAI, Anthropic and the Vercel AI SDK, plus measured token costs per capture.
- Screenshots for AI Agents and Agent Reports Let AI agents observe rendered pages and attach screenshot evidence to their reports. Tool schema, evidence records, capture modes and token cost per mode.
- Website Screenshots for GPT and Claude Vision Pick the viewport, scale and format that vision models read best per token. Capture settings, token math for Claude and GPT, and a tested Python example.
- AI Landing Page Review with Screenshots Capture desktop, mobile and full-page screenshots of a landing page and get a scored AI review as JSON. Rubric prompt, schema and tested Node script.
- Claude Tool Use Screenshot Tool Give Claude a screenshot tool with the Anthropic Messages API. The tool returns image blocks in tool_result. Full Python file, limits and error handling.
- LangChain Screenshot Tool (Python and JS) A LangChain and LangGraph screenshot tool that returns image content blocks to the model and keeps capture metadata as an artifact. Tested in Python and JS.