Skip to main content
Handbook/Test/Page 65 · Visual

Catching What the Eye Sees

Share

Share this page

Pass it to someone who needs it.

Star on GitHub

Key takeaway: Catch UI changes you didn't mean to make

Your tests all pass, but the page looks broken: a button jumped, a layout collapsed, two lines of text overlap. A normal test checks values, not appearance, so it sails right past a break the eye catches instantly. This chapter gets you tests that catch a change in how things look or render, not just what they return.

8.5.1Assertions miss what the eye catches

A test that checks a value confirms the total says 42 and the email field holds text. It says nothing about whether the total sits off the edge of the screen or the field overlaps the button next to it.

That gap is the whole problem. To catch a visual break you need a test that checks the output itself, the actual thing rendered, not just a number returned from the code behind it.

8.5.2A snapshot test freezes the output

A snapshot test records the exact output a piece of your app produces the first time it runs, saves it, then compares every future run against that saved copy. Any difference turns the test red so you can look at what changed.

For a single component, one self-contained piece of your interface, the output it records is the HTML that component renders. Your agent writes this, using a helper like Testing Library to render the component outside a browser; here is the shape:

test("the price tag renders", () => { const html = render(<PriceTag amount={42} />); expect(html).toMatchSnapshot(); });

The first run saves the HTML. If a later edit changes that markup, the test fails and shows you the exact lines that moved.

8.5.3A visual test compares screenshots

A snapshot of markup misses a break the markup itself does not show: a broken stylesheet, an overlap, a collapsed layout. For that you need to look at the actual pixels.

A visual regression test takes a screenshot of a real, rendered screen and compares it pixel by pixel against an approved baseline image. Playwright does this in one line:

test("the dashboard looks right", async ({ page }) => { await page.goto("/dashboard"); await expect(page).toHaveScreenshot(); });

The first run saves the baseline. Every run after compares against it and fails on any visible drift, catching the button that moved even when the code behind it is unchanged.

Watch out: a baseline captured on your machine fails on another one over font rendering alone. Generate and store baselines from the same environment that will run them, usually Playwright's own container image, or keep the visual suite local until you have.

The same screenshot run can check more than looks. Have your agent add an accessibility check, axe inside a Playwright test, and every run also flags unlabeled fields and unreadable contrast.

8.5.4Review the diff, never rubber-stamp it

These tests turn red on any change, the ones you meant and the ones you did not. The whole value lives in one habit: when a test goes red, you look at the diff and decide.

If the change is one you intended, you update the baseline so the new look becomes the approved one. If it is a break you did not mean, you fix the code. Accepting every new image without looking defeats the test entirely.

Watch out: Blindly re-approving a failed visual test is the same as deleting it. The red is the point; the diff is the thing you are paying for. Always look before you update the baseline.

A visual test goes red on any change; you look at the diff and decide, never rubber-stamp the new image.

This prompt sets your agent up to add both:

Ready prompt
Act as a senior engineer adding visual safety to my app. Put the new tests with my existing test suite and follow its conventions, do not stand up a second setup beside it. Add snapshot tests for my key components so any change to their rendered output turns red. Add visual-regression screenshot tests, using Playwright's screenshot compare, for the few most important screens I list below. Generate the baselines in the same environment that will run them, and add an axe accessibility check to the same screenshot run. Wire the suite into my quality gate, and add one line to my rules file: never update a baseline without showing me the diff and what changed. Tell me which components and screens you covered. If you need the full reasoning behind this step, read https://zalt.me/guides/vibe-coding/test/visual-and-snapshot-tests My most important screens:

Do this now: paste the prompt and list your two or three most important screens. Let your agent add snapshot tests for the key components and one visual-regression test per screen.

Mahmoud Zalt

Mahmoud Zalt

Software engineer, 16+ yrs · built Sistava.com in 3 months, idea to production, using these methods

Resources
Star on GitHubContribute
Donate

Support my work

A small tip keeps the free work coming.

© 2026 Mahmoud Zalt. Free to read, not to republish.
Copyright & license