Capture the current accessibility tree in Puppeteer with await page.accessibility.snapshot(). The promise resolves to the page’s root serialized accessibility node or null. Navigate first, synchronize on the state your test needs, then take the snapshot; an arbitrary sleep is not a reliable substitute for a real navigation, selector, or application-state condition.
Capture the accessibility tree
A minimal, runnable example launches Chromium, loads a page, waits for a meaningful element, and writes the tree as readable JSON:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('h1');
const snapshot = await page.accessibility.snapshot();
if (snapshot === null) {
throw new Error('Puppeteer returned no accessibility root');
}
console.log(JSON.stringify(snapshot, null, 2));
} finally {
await browser.close();
}
})();
The API captures the current state of the accessibility tree and returns its root accessible node. Because the call is asynchronous, use await. A null result is valid and should be handled explicitly rather than passed to code that assumes an object.
Synchronize the page before taking a snapshot
A snapshot is a point-in-time observation. For a single-page application, complete the interaction or state transition that the test is meant to inspect before calling the API.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problems#1 Best Overall
Navigation-driven pages
await page.goto('https://example.com/account', {
waitUntil: 'networkidle0'
});
const snapshot = await page.accessibility.snapshot();
Use a navigation event that matches the application. networkidle0 can be unsuitable for pages with long-lived connections or analytics requests; domcontentloaded plus a specific readiness check is often more deterministic.
Selector-driven readiness
await page.goto('https://example.com/dashboard', {
waitUntil: 'domcontentloaded'
});
await page.waitForSelector('[data-testid="dashboard-ready"]', {
visible: true
});
const snapshot = await page.accessibility.snapshot();
State-driven readiness
await page.click('button[aria-label="Open menu"]');
await page.waitForFunction(() => {
const menu = document.querySelector('[role="menu"]');
return menu && menu.getAttribute('aria-hidden') !== 'true';
});
const snapshot = await page.accessibility.snapshot();
Prefer an application condition that proves the intended state. A fixed setTimeout may pass on one machine and capture an incomplete tree on another.
Control what Puppeteer returns
The snapshot options let you trade compact output for diagnostic completeness and choose the part of the document to inspect.
| Option | Default | Effect | Use it when |
|---|---|---|---|
interestingOnly |
true |
Prunes nodes Puppeteer considers uninteresting. | You want a compact, user-focused tree; set false while diagnosing missing or structural nodes. |
includeIframes |
false |
Includes accessibility trees for each iframe in the frame subtree. | The test depends on embedded documents or widgets. |
root |
Entire page | Scopes the capture to an ElementHandle<Node>. |
You are inspecting one component instead of the whole document. |
Option names and behavior should be checked against the documentation for the Puppeteer release installed in your project. The snapshot documentation surfaced as version 25.10.0 and the options documentation as 25.12.0 on September 29, 2026; those labels can change as Puppeteer releases.
Get the default filtered tree
const snapshot = await page.accessibility.snapshot();
interestingOnly: true is the default. It is usually the most readable representation for assertions about headings, buttons, links, names, and roles.
Request an unpruned tree
const fullTree = await page.accessibility.snapshot({
interestingOnly: false,
});
The larger result can expose structural nodes that the default filter omits. Use it when an expected item appears absent, when examining layout-generated semantics, or when comparing a component before and after an interaction.
Include iframe content
const withFrames = await page.accessibility.snapshot({
includeIframes: true,
});
Leave this off for pages where embedded content is irrelevant. Turning it on can make output considerably larger, and cross-origin frames may still have their own loading or readiness behavior.
Scope the snapshot to an element
const dialogHandle = await page.$('[role="dialog"]');
if (!dialogHandle) {
throw new Error('Dialog was not rendered');
}
const dialogTree = await page.accessibility.snapshot({
root: dialogHandle,
});
await dialogHandle.dispose();
console.log(JSON.stringify(dialogTree, null, 2));
The root value is an element handle, so acquire it after the component is rendered and dispose of it when finished. A scoped tree is easier to assert and log for a large application.
Combine options for diagnosis
const diagnosticTree = await page.accessibility.snapshot({
root: dialogHandle,
interestingOnly: false,
includeIframes: true,
});
Combining every option is not automatically better: it produces the most data, not necessarily the clearest signal. Start with a scoped, filtered tree and expand only the dimension that explains the failure.
Understand the returned node
The result is a serialized accessibility node. A typical node can contain properties such as role, name, value, description, focused, and a children array. Property presence varies by node and state, so assertions should not assume every field exists.
function findFocused(node) {
if (!node) return null;
if (node.focused === true) return node;
for (const child of node.children || []) {
const match = findFocused(child);
if (match) return match;
}
return null;
}
const snapshot = await page.accessibility.snapshot();
const focused = findFocused(snapshot);
console.log(focused
? { role: focused.role, name: focused.name, value: focused.value }
: 'No focused node');
The recursive loop must inspect every child. Returning immediately after the first unsuccessful branch can miss a focused node in a later subtree.
Write stable assertions
function collectRoles(node, roles = []) {
if (!node) return roles;
if (node.role) roles.push(node.role);
for (const child of node.children || []) collectRoles(child, roles);
return roles;
}
const tree = await page.accessibility.snapshot();
const roles = collectRoles(tree);
if (!roles.includes('button')) {
throw new Error('Expected a button in the accessibility tree');
}
Prefer assertions on semantic roles and accessible names over DOM order. If your test intentionally checks hidden or presentational structure, capture with interestingOnly: false and document why.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #4
What a Puppeteer snapshot proves—and what it does not
Puppeteer exposes Blink’s computed accessibility tree. With the default filter, it approximates the pruning applied when accessibility data is translated to platform-specific trees or consumed by assistive technology. Chrome’s tree also contains nodes unused on most platforms and by many screen readers; Puppeteer discards those nodes unless you request the unpruned form.
Consequently, a snapshot is excellent for repeatable inspection of browser semantics, regression tests, accessible names, roles, focus, and state. It does not prove that every screen reader on every operating system will expose exactly the same experience. Platform accessibility layers, browser versions, assistive-technology behavior, and user settings can differ. Include real assistive-technology testing when that level of assurance is required.
Cross-check the tree in Chrome DevTools
- Open the page in Chrome and press F12 or choose More tools → Developer tools.
- In Elements, select the DOM node you want to inspect.
- Open the Accessibility tab. It shows the accessibility tree, ARIA attributes, and computed accessibility properties for the selected node.
- Toggle Show accessibility tree to replace the DOM tree with the full-page accessibility tree.
DevTools is interactive: it is useful for discovering why a name, role, state, or relationship is computed a certain way. Puppeteer is scriptable and repeatable, so use both when a test failure needs a visual explanation.
Common failures and fixes
The result is null
- Cause: the page has not produced an accessibility root, navigation is incomplete, or the document is effectively empty.
- Fix: wait for the real readiness condition, verify the URL and rendered content, and branch explicitly on
nullbefore traversing.
An expected node is missing
- Cause: the default
interestingOnlyfilter removed a structural node, the component is not rendered yet, or the accessible name is different from the visible text. - Fix: capture with
interestingOnly: false, wait for the component’s state, and inspect the computed name in DevTools.
Iframe content does not appear
- Cause: iframe trees are excluded by default.
- Fix: pass
includeIframes: trueand wait for the frame’s own content to load. If the frame is cross-origin, treat it as a separate document with separate readiness conditions.
The snapshot is unexpectedly large
- Cause:
interestingOnly: false,includeIframes: true, or an unscoped page snapshot. - Fix: return to the defaults, scope with
root, and enable one diagnostic option at a time.
Focus cannot be found
- Cause: focus moved during an asynchronous update, the active element is not represented as expected, or traversal stopped too early.
- Fix: take the snapshot immediately after the focus-inducing action, inspect every child recursively, and compare the result with DevTools.
Assertions are flaky
- Cause: a fixed delay is racing the application, or assertions depend on unstable ordering.
- Fix: synchronize on navigation, a selector, or a state predicate; assert semantic properties rather than incidental tree order.
Performance, output, and test design
- Use the filtered tree for routine checks and reserve unpruned captures for diagnosis; this reduces serialization and log volume.
- Scope a component with
rootwhen the page contains a large application shell. - Keep snapshots out of normal console output in continuous integration unless a test fails; save failure artifacts instead.
- For reproducibility, record the Puppeteer version, Chromium revision, URL, viewport, locale, and application state alongside a failing tree.
- Redact secrets and personal data before persisting snapshots. Accessible names and values can contain account information.
- Do not treat a successful snapshot as a complete accessibility audit. It checks the browser’s computed tree, not keyboard operation, contrast, motion, or every assistive-technology combination.
Or skip the browser setup
If you need a rendered screenshot rather than the semantic tree, ScreenshotNeo provides a single HTTP request. It is separate from Puppeteer accessibility inspection: it captures pixels (PNG, JPEG, WebP, or PDF), while the snapshot API returns semantic nodes.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Best Value
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo documentation for authentication and options. Before capture, it can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account to try it with 1,000 screenshots a month and no card.
FAQ
Does the snapshot include the DOM?
No. It returns serialized accessibility nodes derived from Blink’s accessibility tree, not the complete HTML or CSS DOM.
Can I use a snapshot as a screen-reader compatibility guarantee?
No. It is a browser-level inspection aid. Validate important workflows with the target platforms, browsers, and assistive technologies as well.
When should I keep interestingOnly enabled?
Keep the default for readable, user-focused checks. Disable it when the question specifically concerns omitted structural or otherwise uninteresting nodes.
Frequently Asked Questions
Does the snapshot include the DOM?
No. It returns serialized accessibility nodes derived from Blink’s accessibility tree, not the complete HTML or CSS DOM.
Can I use a snapshot as a screen-reader compatibility guarantee?
No. It is a browser-level inspection aid. Validate important workflows with the target platforms, browsers, and assistive technologies as well.
When should I keep interestingOnly enabled?
Keep the default for readable, user-focused checks. Disable it when the question specifically concerns omitted structural or otherwise uninteresting nodes.
Recommended Free Tools
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

