skip to content

Snapshot Matching

Comparing what actually rendered against a stored file - an image or an accessibility tree - rather than a value you typed. Interviewers probe where that file lands and what updating it means.

on this pageshow

explore

questions

11

In Playwright, what does expect(locator).toMatchAriaSnapshot() compare, and what does it ignore?

level: juniorimportance: must knowfreq 45%

answer

  1. Structure, not appearance
  2. Serialised to YAML, line per node
  3. Roles, accessible names, a few states
  4. Hidden nodes never appear
  5. Retries like other web-first assertions

basics

~20 s

It compares an element's accessibility tree, meaning the roles, accessible names and a few ARIA states of its non-hidden nodes, against a YAML template. It ignores pixels, CSS, tag names, class names and hidden nodes.

solid answer

~40 s

`expect(locator).toMatchAriaSnapshot()` serialises the accessibility tree under a locator into YAML and compares it with an expected template, either inline or stored in a file such as `main.aria.yml`. Each line carries a node's role and accessible name, plus optional state such as `[level=1]`, nested by indentation. Because the tree holds roles and names rather than markup or pixels, a restyled or relocated export button passes, while a renamed one, a demoted heading, or a balance widget that stopped rendering fails. Nodes hidden from assistive technology never appear. It is an auto-retrying assertion, so it re-reads the tree until it matches or the expect timeout expires. A pass means the structure matches what you recorded, not that the page is accessible.

code

typescript · 13 lines
typescript
import { expect, test } from '@playwright/test';

test('statement page keeps its structure', async ({ page }) => {
  await page.goto('/accounts/12345/statement');

  await expect(page.getByRole('main')).toMatchAriaSnapshot(`
    - heading "October statement" [level=1]
    - region "Balance":
      - text: /Closing balance/
    - table "Transactions"
    - button "Export CSV"
  `);
});

go deeper

for a junior

Remember the one-line version: it compares roles and accessible names from the accessibility tree against a YAML template, not pixels or markup. Know that the template can be inline or in a file.

for a middle

Be able to explain why a restyle passes and a rename fails, what an accessible name is, and why hidden nodes are absent. Mention that the assertion retries until the expect timeout.

for a senior

Show judgment about scope: the wider the locator, the more unrelated edits rewrite the template. Explain what a snapshot buys over a handful of element assertions and where it costs you review time.

for a principal

Frame it as a cheap, broad structural net whose real cost is churn and review fatigue, and set the policy for which surfaces earn one and who reviews regenerated templates.

## What the matcher compares `expect(locator).toMatchAriaSnapshot(template)` builds the **accessibility tree** of the element the locator points at, serialises it to YAML, and compares that YAML against the template you supply. The accessibility tree is the structure the browser exposes to assistive technology: every node that is not hidden, described by its **role** (`heading`, `button`, `table`, `link`, `region`), its **accessible name** (the text that identifies it), and a small set of ARIA states such as a heading `level` or a checkbox being checked. Aria snapshots landed in Playwright 1.49 and the matcher is one of the auto-retrying assertions, so it re-reads the tree until it matches or the `expect` timeout expires. A template for a bank statement page reads like the page's outline: ```yaml - heading "October statement" [level=1] - region "Balance": - text: /Closing balance/ - table "Transactions" - button "Export CSV" ``` ## What it deliberately ignores - **Pixels, colours and layout.** Restyling the export button, moving it, or changing its font does not move the tree, so the assertion stays green. - **Tag names, classes, ids and data attributes.** The tree records that a node *is* a button, not that it is a `<button class="btn-primary">`. - **Hidden nodes.** Anything removed from the accessibility tree, such as a container marked `aria-hidden`, never appears in the snapshot at all. - **Properties you did not write.** The template asserts what it names and nothing else: a bare `- button` line matches a button whatever its accessible name happens to be. ## What that makes it good at catching 1. A control renamed from "Export CSV" to "Download", which no visual or CSS check treats as a regression but which every user of the label notices. 2. A heading demoted from `[level=1]` to `[level=2]`, flattening the document outline. 3. A whole region that stopped rendering, for example the balance widget vanishing above the transaction table. 4. A control that lost its label entirely and now exposes an empty name. ## How it differs from asserting one element | Change on the statement page | Aria snapshot of `main` | A single assertion on the export button | | --- | --- | --- | | Export button renamed | Fails | Fails | | Balance widget removed | Fails | Passes, nothing looked at it | | Heading level changed | Fails | Passes | | Button restyled or moved | Passes | Passes | | A new unrelated banner added | Depends on the template's scope | Passes | One snapshot therefore acts as a broad structural net over a surface, where element assertions are narrow and deliberate. That breadth is the whole point and also the cost: the wider the element you point it at, the more unrelated edits will rewrite it. ## Writing and storing the expectation Two storage forms exist, and both compare the same way: - **Inline**, as a template literal passed straight to the matcher, which keeps the expectation next to the test that owns it. - **File-backed**, via `toMatchAriaSnapshot({ name: 'main.aria.yml' })`, which stores the YAML in the test file's snapshot directory. Hand-writing a template is the common beginner mistake, because indentation and nesting must reflect the real tree. Generate it instead: `await page.getByRole('main').ariaSnapshot()` returns the YAML for the live page, and you paste in the part you actually want to assert, deleting the volatile rest. ## What a pass does not prove A green snapshot means the tree still matches what you recorded. It says nothing about whether that tree is a *good* one; judging whether the page is usable by assistive technology is a separate discipline with its own criteria and its own manual checks. Recording a page whose structure is already wrong simply freezes the wrongness.

  • How would you produce the template in the first place instead of typing it by hand?
    Call `ariaSnapshot()` on the locator you intend to assert, for example `await page.getByRole('main').ariaSnapshot()`, and print or log the YAML it returns. Paste the part you care about into the test and delete the volatile lines. Hand-written templates usually fail on nesting, because the indentation has to reflect the real tree.
  • Does a green aria snapshot mean the statement page is accessible?
    No. It means the tree matches the one you recorded. If the recorded tree already had an unlabelled export button, the snapshot preserves that defect happily. Judging whether the structure serves assistive technology is a separate review with its own criteria, and this matcher only guards against unnoticed change.
  • Why does the matcher retry rather than compare once?
    It is one of Playwright's auto-retrying assertions, so it re-reads the accessibility tree until it matches or the expect timeout runs out. That absorbs a statement page whose transaction table renders a moment after the balance widget, without a hand-written wait before the assertion.

It checks a floor plan rather than a photograph of the lobby: the rooms and their labels must match, but repainting the walls changes nothing.

saying these in an interview costs you the question

  • Calls it a screenshot comparison stored as text
  • Thinks it asserts CSS classes, ids or tag names
  • Expects aria-hidden content to appear in the snapshot
  • Believes a passing snapshot proves the page is accessible
  • Assumes it compares once with no retry
  • Says every property of every node must be listed
open as a page

In Playwright, what happens the first time expect(page).toHaveScreenshot() runs and no reference image exists?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Playwright captures the page, writes that capture to the snapshots folder as the new reference image, and still fails the test. A later run, or an automatic retry, compares against the file just written and passes.

open as a page

In Playwright, what path does toHaveScreenshot() use for its reference image, and what controls it?

level: middleimportance: must knowfreq 61%

basics

~20 s

By default the reference lands beside the test in a folder named after the spec file, with the project name and platform in the file name. The snapshotPathTemplate config option, built from tokens, changes that layout.

open as a page

In Playwright, what does running the tests with -u actually rewrite, and what does it leave untouched?

level: seniorimportance: must knowfreq 57%

basics

~20 s

It rewrites reference images only for the tests that command actually executed, and only for the projects and the platform that ran. Everything filtered out, every other project, and every orphaned file stays exactly as it was.

open as a page

A Playwright aria snapshot fails after you intentionally rename the Export button; how do you get it green without weakening the check?

level: middleimportance: should knowfreq 41%

basics

~20 s

Read the YAML diff in the failure, confirm every changed line is intended, then re-run that spec with --update-snapshots so the stored template is regenerated. Commit the regenerated template with the code change and review it as a normal text diff.

open as a page

In a Playwright aria snapshot template, how do you express a node's role, accessible name and heading level?

level: middleimportance: should knowfreq 32%

basics

~20 s

Each node is one YAML list item: the role, then the accessible name in double quotes, then state in square brackets, as in a heading line reading role heading, name October statement, level=1. Indentation nests children.

open as a page

In Playwright, what does the mask option of toHaveScreenshot() accept, and what does it do to the image?

level: middleimportance: should knowfreq 52%

basics

~20 s

It accepts an array of locators. Each matched element is painted over with a solid box covering its bounding box, bright pink by default, changeable with maskColor. The box is applied while capturing, so both the reference and the actual carry it.

open as a page

Which capture defaults does Playwright's toHaveScreenshot() apply that page.screenshot() does not?

level: middleimportance: should knowfreq 45%

basics

~20 s

The matcher defaults to animations disabled and scale css, where a plain page screenshot allows animations and captures at device resolution. Both hide the text caret. The matcher also re-captures until the image matches or the assertion times out.

open as a page

A Playwright aria snapshot of a bank statement page fails every run because the balance figures change, so how do you stabilize it?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Relax the volatile nodes instead of regenerating the template: write their text or name as a regular expression between slashes, or drop the value entirely, and scope the assertion to the region the test is about rather than the whole page body.

open as a page

How would you frame a Playwright screenshot baseline for a bank statement page that scrolls for thousands of rows?

level: seniorimportance: should knowfreq 38%

basics

~10 s

Assert on a locator rather than the page: expect(page.getByTestId('balance-widget')).toHaveScreenshot() scrolls that element into view and captures only its box. Full page and clip are the alternatives when a region cannot be located.

open as a page

Across a large Playwright suite, how do you decide which pages get an aria snapshot without creating constant template churn?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Put snapshots on high-value, stable surfaces whose structure is a contract, scope each one to the region the test is about, and set an update policy: regeneration scoped to the changed spec, templates reviewed as code, shrink any template that churns.

open as a page