Skip to main content
Handbook/Test/Chapter 74 · Visual

Catching What the Eye Sees

Share

Share this page

Pass it to someone who needs it.

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, the output it records is the HTML that component renders. Your agent writes this; 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.

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. 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. Then explain the baseline-review workflow: on a red test I inspect the diff, update the baseline only for changes I meant, and fix the code for changes I did not. Tell me which components and screens you covered. My most important screens:

Do this now: paste the prompt, list your two or three most important screens, and 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
Contribute
Donate

Support my work

A small tip keeps the free work coming.

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