skip to content

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