In Playwright, what happens the first time expect(page).toHaveScreenshot() runs and no reference image exists?
answer
- Not a create-and-pass matcher
- Actual capture written to the baseline path
- The first run still fails
- Default update mode is missing
- A retry compares against the new file
basics
~20 sPlaywright 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.
solid answer
~40 s`toHaveScreenshot()` is not a create-and-pass matcher. On a run where the expected PNG is missing, the runner writes the actual capture to the baseline path and fails the test with `A snapshot doesn't exist at <path>, writing actual.` The failure is deliberate: an image the run authored moments ago has been reviewed by nobody, so it must not turn a suite green on its own. The write itself is governed by the `updateSnapshots` setting, whose default `'missing'` permits exactly this; `'none'` makes a missing reference a plain failure with nothing written. Because the file now exists, the next attempt compares against it and passes, which is how a first run can end up reported as flaky-but-green with an unreviewed baseline underneath it.
code
typescript · 8 linesimport { test, expect } from '@playwright/test';
test('statement balance widget', async ({ page }) => {
await page.goto('/statements/2026-08');
// Run 1: writes statement-balance-widget-1.png and FAILS.
// Run 2: compares the fresh capture against that file.
await expect(page).toHaveScreenshot();
});go deeper
Remember both halves: the reference file is written and the test still fails. Look at the PNG the run created before you commit it.
Explain the mechanics: the write follows the missing update mode, and a retry in the same run compares against the file attempt one wrote, which is how a first run reports as flaky rather than failed.
Show that you pin the update mode for automated runs so a baseline can never be authored where nobody reviews it, and that you can diagnose a repeating first-run failure as a discarded workspace or a moved path.
Own the rule for how a baseline enters the repository at all, so the write-and-fail signal stays meaningful instead of being routinely cleared by rerunning with the update flag.
## The rule in one line `expect(page).toHaveScreenshot()` compares the current capture with a **reference image** stored on disk (also called a baseline or an expectation). When that file is absent, Playwright does two things in the same assertion: it **writes** the capture to the reference path, and it **fails** the test with a message of the form `A snapshot doesn't exist at <path>, writing actual.` Candidates routinely guess one half of this and miss the other. "It passes because there is nothing to compare" and "it fails and writes nothing until you pass `-u`" are both wrong. ## Why failing is the correct behaviour A screenshot baseline is a claim about what the UI is supposed to look like. The run that produced it has no idea whether the bank statement page rendered correctly, half-loaded, or with a broken stylesheet. - A passing first run would mean any state at all — including a visibly broken page — silently becomes the definition of correct. - Failing forces a human decision: look at the written PNG, then commit it or fix the page. - The message names the exact path it wrote, so the file is easy to find and inspect. - The written file shows up as a new untracked file in the working tree, which is the signal that a baseline was created rather than verified. ## What governs the write The behaviour is not hard-coded; it follows the runner's snapshot-update setting, configurable as `updateSnapshots` in `playwright.config.ts` or as `--update-snapshots` on the command line. | Setting | Effect on a missing reference | |---|---| | `'missing'` (the default) | The reference is created from the actual capture, and the test still fails | | `'none'` | Nothing is written; the missing reference is simply a failure | | `'all'` / `'changed'` | Regeneration modes you pass deliberately when refreshing existing references | Pinning `updateSnapshots: 'none'` for continuous integration is the usual move: on a machine where nobody will look at the file, creating a baseline has no value, and the failure is the whole point. ## How a first run still ends up green This is the part interviewers probe, because it explains a confusing pipeline result: 1. Attempt one finds no reference, writes the capture to the baseline path, and fails. 2. If retries are configured, attempt two runs against a workspace where the file now exists. 3. The second capture matches the file the first attempt wrote, so the assertion passes. 4. The runner reports the test as passed on retry — flaky, not failed — and the overall run can be green. Nothing was actually verified. The suite compared the application against itself. That is why the missing-reference failure is worth understanding rather than working around. ## The naming detail that surprises people When the matcher is called with no name argument, the reference file name is derived from the test title plus an index, so a test titled `statement balance widget` produces `statement-balance-widget-1.png`, and a second call in the same test produces `-2`. Passing an explicit name — `toHaveScreenshot('balance.png')` — decouples the file from the title, which matters because renaming the test otherwise orphans the old PNG and triggers the write-and-fail path all over again. ## Reading the failure correctly When this failure appears, the useful questions are mechanical: - Is the reference genuinely new, or did the path change because the test was renamed, or because a new project or platform was added to the run? - Did the run happen somewhere the written file will be thrown away, such as a container image, in which case the same failure repeats forever? - Is `updateSnapshots` set to something other than the default, which changes whether a file appeared at all? Answering those three separates "a new baseline exists and needs a look" from "the path this suite expects has quietly moved".
- How would you stop a continuous integration run from ever creating a baseline it then compares against?Set `updateSnapshots: 'none'` for that run, in the config or as `--update-snapshots=none`. A missing reference then fails outright with nothing written, so a baseline can only enter the repository from a machine where someone looked at it.
- The same missing-reference failure repeats on every run even though the file is being written. What would you check?Whether the written file survives the run. A container or ephemeral workspace discards it, so every run is a first run. Also check whether the path changed: a renamed test, a new project name, or a different platform all produce a different reference file name.
Like a spell checker with an empty dictionary: the first unknown word gets added to it, but you are still told the word was unknown.
saying these in an interview costs you the question
- Says the first run passes because there is nothing to compare against
- Thinks a missing baseline is skipped rather than failed
- Assumes nothing is written unless you pass the update flag
- Treats a first-run file as a reviewed baseline
- Believes the write-and-fail behaviour is a bug in the runner