ScreenshotNeo

BlogHow-to

How to Use Playwright’s Area Snapshot for Visual Testing

Use Playwright locators to test one region’s accessible structure with ARIA snapshots or its rendered pixels with focused screenshots.

By the ScreenshotNeo team1 October 20267 min read

“Area snapshot” can mean two different Playwright checks. Use an ARIA snapshot when you want to verify the accessible structure inside one region. Use a locator screenshot when you want to verify that region’s rendered pixels. They are complementary: one protects roles, names, text, and hierarchy; the other protects layout and styling.

In both cases, scope the check with a locator instead of asserting against the whole page. The locator defines the area under test.

1. What “area snapshot” means in Playwright

Playwright’s locator.ariaSnapshot() returns the accessible tree for the matched element as YAML. The test-runner matcher expect(locator).toMatchAriaSnapshot() compares that tree with a snapshot template and supports partial matching when volatile names or attributes should be omitted. See the locator API and ARIA snapshots guide.

A locator screenshot takes a clipped image of the element’s bounds. Use locator.screenshot() to save an image or expect(locator).toHaveScreenshot() for a visual regression assertion. See locator screenshots and visual comparisons.

2. Set up a small, scoped test

  1. Install Playwright Test in your project.
  2. Choose a stable locator such as a role, test id, or component root.
  3. Navigate to a deterministic test URL and wait for the state your assertion needs.
  4. Run either the ARIA assertion, the visual assertion, or both.
npm init playwright@latest
npx playwright test

Keep the installed Playwright version in mind. Newer locator snapshot options are versioned APIs; check the API page for the version your project actually uses.

3. Test accessible structure with an ARIA snapshot

This checks what assistive technology can discover, not colors or pixel placement. The locator limits the accessible tree to the selected region.

import { test, expect } from '@playwright/test';

test('product region exposes the expected structure', async ({ page }) => {
  await page.goto('http://localhost:3000/products');

  await expect(page.getByRole('main')).toMatchAriaSnapshot(`
    - heading "Products" [level=1]
    - list:
      - listitem:
        - link "View details"
  `);
});

Use role, accessible name, and relevant attributes in the template. Partial matching is useful when a name, count, or state is intentionally dynamic. Keep the template focused on behavior the component promises to expose.

To inspect the current YAML while designing a test, call ariaSnapshot() directly:

const snapshot = await page.getByTestId('product-card').ariaSnapshot();
console.log(snapshot);

An ARIA snapshot will not tell you whether a card is aligned, whether a margin changed, or whether an image is clipped. Add a screenshot assertion for those concerns.

4. Test rendered pixels with a locator screenshot

locator.screenshot() captures only the selected element. The test-runner matcher stores and compares an expected image.

import { test, expect } from '@playwright/test';

test('product card keeps its visual layout', async ({ page }) => {
  await page.goto('http://localhost:3000/products');
  const card = page.getByTestId('product-card');

  await expect(card).toHaveScreenshot();
});

test('save one area for inspection', async ({ page }) => {
  await page.goto('http://localhost:3000/products');
  await page.getByTestId('product-card').screenshot({
    path: 'artifacts/product-card.png',
    animations: 'disabled',
  });
});

Screenshot assertions wait for two consecutive locator screenshots to be identical before comparing them. This helps when layout settles over a short period, but it does not make external data, fonts, or cross-platform rendering deterministic by itself.

5. Make visual area checks repeatable

Use the documented screenshot controls to remove variation that is unrelated to the behavior under test:

Control Use it for Example
Disable animations Transitions or looping motion that should not affect the baseline animations: 'disabled'
Mask locators Clocks, rotating avatars, random IDs, or other volatile regions mask: [page.getByTestId('timestamp')]
Stylesheet Hide carets or freeze a known dynamic decoration for this capture stylePath: 'visual-test.css' (where supported by your installed version)
Stable target Prevent unrelated page changes from changing the image Capture the component locator, not page
await expect(page.getByTestId('checkout-summary')).toHaveScreenshot({
  animations: 'disabled',
  mask: [page.getByTestId('live-price')],
});

These controls reduce irrelevant variation; they cannot guarantee identical rendering across operating systems, browser versions, fonts, or GPU paths. Pin the browser/runtime used by CI and review every changed baseline in UI Mode or the generated image diff. A changed expected image is a test update that still needs human review.

6. ARIA snapshot or screenshot? Choose by intent

Testing goal Use What it observes
Roles, names, hierarchy, accessible text toMatchAriaSnapshot() on a locator Accessible-tree YAML
Spacing, color, typography, alignment, clipping Locator screenshot or toHaveScreenshot() Rendered pixels
Whole-page visual regression Page screenshot assertion and UI Mode diff Full rendered page

For a component with both accessibility and visual requirements, keep two focused assertions. A visual match can pass while a button loses its accessible name; an ARIA match can pass while CSS breaks the layout.

7. A complete component workflow

import { test, expect } from '@playwright/test';

test.describe('cart panel', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000/cart');
    await page.getByTestId('cart-panel').waitFor();
  });

  test('has the expected accessible structure', async ({ page }) => {
    await expect(page.getByTestId('cart-panel')).toMatchAriaSnapshot(`
      - heading "Your cart" [level=2]
      - list:
        - listitem:
          - button "Remove"
      - button "Checkout"
    `);
  });

  test('keeps the panel appearance', async ({ page }) => {
    const panel = page.getByTestId('cart-panel');
    await expect(panel).toHaveScreenshot({
      animations: 'disabled',
      mask: [page.getByTestId('shipping-estimate')],
    });
  });
});

Generate the first visual baseline with the Playwright update command only after reviewing the page:

npx playwright test --update-snapshots

When a test fails, inspect the actual, expected, and diff images in UI Mode. Update a baseline only when the product change is intentional.

8. Troubleshooting

Symptom Likely cause Fix
toMatchAriaSnapshot is undefined Old Playwright Test version or a non-test-runner assertion import Upgrade to a version that supports the matcher and import expect from @playwright/test; verify the installed API.
ARIA output is empty or missing a node The locator matches the wrong element, content is not rendered, or the node is hidden Inspect ariaSnapshot(), use a stable locator, and wait for the component state before asserting.
Screenshot includes only part of the component The locator’s bounds exclude overflowing or portal-rendered content Choose a wrapper that owns the visible content, or capture the related locator separately.
Snapshots differ on every run Animation, timestamps, random data, late fonts, or changing network content Disable animations, mask volatile locators, freeze test data, wait for the relevant state, and use a screenshot stylesheet where supported.
Diff appears only in CI Different browser, OS fonts, scale factor, or rendering stack Use a consistent CI image and browser version; avoid relying on platform-specific font rasterization.
Expected image changed after a harmless refactor Baseline includes pixels outside the behavior under test Narrow the locator and mask dynamic regions instead of accepting a broad new baseline.
ARIA test passes but the UI is broken Accessible structure and pixels are separate contracts Add a locator screenshot assertion for the affected visual behavior.
Screenshot passes but keyboard/screen-reader behavior regressed Pixels do not encode semantics Add or update the ARIA snapshot and interaction tests.

9. Performance, reliability, and cost

  • Scope saves time: a locator screenshot is smaller and less sensitive to unrelated page changes than a full-page capture.
  • Wait for state, not arbitrary sleeps: prefer a locator wait, a deterministic response, or an assertion that reflects readiness. Use a short delay only when the UI contract genuinely requires it.
  • Keep baselines reviewable: one component per test makes diffs easier to understand and reduces churn.
  • Control data: fixture content, fixed locale/timezone, stable fonts, and consistent browser versions make failures actionable.
  • CI cost: Playwright runs a real browser for every capture. Parallelize independent tests carefully, but avoid sharing state that makes screenshots nondeterministic.

10. Or skip the browser setup

If you need rendered images in a pipeline instead of maintaining browser workers, ScreenshotNeo exposes one GET request for a URL and returns PNG, JPEG, WebP, or PDF. Its capture options include element selection by CSS selector, full-page capture with lazy images loaded, device and viewport controls, dark mode, custom CSS/JavaScript, waits, request blocking, headers/cookies, geolocation, resizing, caching, async jobs, and bulk capture. Read the ScreenshotNeo API docs for the option names supported by your request.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and whether it was billed. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and start with the 1,000 included screenshots.

11. FAQ

Is an ARIA snapshot an image?

No. It is YAML representing the matched element’s accessible tree. Use a locator screenshot for pixels.

Can I snapshot only one component?

Yes. Build a locator for that component and call either ariaSnapshot(), toMatchAriaSnapshot(), screenshot(), or toHaveScreenshot() on it.

Should every component have both checks?

Add both when the component has independent semantic and visual contracts. Use only the check that matches the regression risk you need to catch.

Why did a tiny text change produce a large image diff?

Text reflow can change line breaks and element heights. Inspect the diff, then decide whether the visual change is intentional; do not widen the mask just to hide a meaningful layout change.

Are screenshot baselines portable across machines?

They are most reliable with a pinned browser, OS image, fonts, and scale factor. Treat cross-platform differences as configuration to control, not as proof that the UI changed.