A Playwright aria snapshot of a bank statement page fails every run because the balance figures change, so how do you stabilize it?
answer
- The template asserts more than intended
- Slashes relax a value to a pattern
- Drop the name, keep the role
- Scope beats patching lines
- Backslashes vanish inside template literals
basics
~20 sRelax 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.
solid answer
~40 sThe template is asserting content the test never meant to defend. Replace the volatile values with regular expressions, such as `- text: /Closing balance: \$[0-9,.]+/`, or drop the value so only the node's role survives. Then scope the locator: `page.getByRole('region', { name: 'Balance' })` instead of `page.locator('body')`, so header, banner and footer edits stop rewriting a statement test. Where exact content genuinely matters, make the data deterministic through seeding or a stubbed response instead of loosening the template. Watch the escaping: in an inline template literal a backslash is eaten by JavaScript, so `\d` must be doubled or replaced by `[0-9]`, which is a non-issue in a file-backed `.yml`. Regenerating on every failure is not a fix; the test would just fail again tomorrow.
code
typescript · 6 linesawait expect(page.getByRole('region', { name: 'Balance' }))
.toMatchAriaSnapshot(`
- heading "Balance" [level=2]
- text: /Closing balance: \\$[0-9,.]+/
- text: /Statement period: .+/
`);go deeper
Know that a template value can be a pattern written between slashes, and that a node written with only its role matches any name. Those two moves handle most changing text.
Explain why the flapping happens: the generated template recorded volatile content as an expectation. Show both the regex fix and the tighter locator, and mention the backslash escaping trap inline.
Decide what the test defends before touching syntax, then pick scope, patterns or deterministic data deliberately, and justify how much relaxation still leaves a check worth running.
Weigh the maintenance cost across a suite: unstable templates train people to regenerate blindly, so set expectations for scope and for where deterministic data is worth its setup.
## Why the template is flapping A statement page is mostly volatile content: a closing balance that moves, dates that roll, a transaction table whose row count depends on the account. If the template was generated from a live page and committed as-is, it recorded all of that as an expectation, and every run now compares today's figures with the day the template was written. Nothing is wrong with the matcher; the template is asserting things the test never meant to assert. Separate the lines before touching any of them: 1. **Genuinely volatile**, such as the closing balance or the statement period, which differ on every run and must never be asserted literally. 2. **Stable but incidental**, such as a marketing strapline in the page header, which changes rarely but is nothing this test defends. 3. **Contractual**, such as the export control's name and the transactions table, which is the reason the assertion exists. Three tactics fix the first two groups, and a good answer uses them together. ## 1. Relax volatile values into regular expressions A name or a text node can be a regular expression written between slashes, so the shape of the value is asserted and its content is not: ```yaml - region "Balance": - heading "Balance" [level=2] - text: /Closing balance: \$[0-9,.]+/ - table "Transactions" ``` Two further relaxations are available when even the shape moves: - **Drop the value entirely.** A bare `- table` keeps the control and its position while letting the caption be reworded freely. - **Drop the node.** Anything the test does not defend can simply be absent from the template, since the expectation asserts what it names and nothing more. ## 2. Scope the locator to the region under test Pointing the assertion at `page.locator('body')` puts the header, navigation, cookie banner and footer inside the expectation, so every unrelated edit anywhere on the site rewrites the statement test's template. Scope it to what the test is about: - `page.getByRole('main')` for a page-level structural check. - `page.getByRole('region', { name: 'Balance' })` for a widget-level one. Narrow scope is the single highest-leverage change, because it removes whole classes of churn rather than patching individual lines. ## 3. Make the data deterministic where it should be Some volatility is a test-data problem rather than a template problem. A statement seeded with a fixed set of transactions, or served from a stubbed response, gives an exact template that genuinely defends the row structure. That is a heavier setup, so reserve it for the surface where exact content matters and use regular expressions elsewhere. ## Escaping, the gotcha that wastes an afternoon Inline templates are JavaScript template literals, and a backslash is consumed by the string literal before the matcher ever sees it: `\d` inside backticks becomes a plain `d`, so the regex silently stops meaning "a digit". Either double it, `\\d`, or sidestep the problem by writing character classes without backslashes, `[0-9,.]+`, or by moving the template into a `.yml` file where no JavaScript escaping applies. ## Choosing between the tactics | Tactic | Keeps defending | Costs | | --- | --- | --- | | Regex value | The shape of the text, e.g. a currency figure | Slightly weaker assertion, easy to over-relax | | Bare role, no name | The control's existence and position | Renames stop failing | | Tighter locator | Everything, on a smaller surface | Content outside the region is unwatched | | Deterministic data | Exact names and row structure | Seeding or stubbing setup to maintain | ## The trap to avoid The tempting fix is to regenerate the template on every failure, which is not a fix at all: the test becomes a record of the last run and fails again tomorrow. The second tempting fix is deleting the assertion, which trades a noisy check for no check. The right instinct is to decide what the test is actually defending on this page, usually "the balance widget, the transactions table and the export control are all present, named and correctly nested", and to write a template that says exactly that.
- How far should you relax a template before it stops being worth running?Until it still fails on the things the test exists to defend. If every node has lost its name and its state, the snapshot only proves that some roles appear somewhere, which almost nothing can break. Keep exact names on the controls whose labels are a contract, and relax only the values that legitimately move.
- Would you rather scope the locator tighter or write more regular expressions?Scope first. A tighter locator removes an entire class of churn, since content outside the region can no longer touch the template, while regexes patch one line each. Use regular expressions inside the chosen region for values that genuinely vary, such as a balance or a statement period.
- Why does a regex that works in a .yml file break when pasted inline?An inline template is a JavaScript template literal, so the string literal consumes the backslash before Playwright parses the YAML, turning a digit class into a literal letter. Double the backslash, use a bracket class with no backslash, or keep the template in the file where no JavaScript escaping is applied.
You are checking that the statement still has a balance box, a transaction table and an export control, not memorizing this month's numbers.
saying these in an interview costs you the question
- Regenerates the template on every run to stay green
- Deletes the assertion because it is noisy
- Snapshots the whole body and accepts the churn
- Thinks names and text cannot be patterns
- Forgets that template literals eat backslashes
- Relaxes every node until nothing can fail