· testing · playwright · ci · astro

A shared component broke a page nobody touched

This site is about as boring as web technology gets: static Astro output, zero JavaScript files shipped to the client. It still runs 53 automated tests on every push — 51 Playwright and 2 Vitest — including axe accessibility audits against WCAG 2.2 AA on 7 routes and 32 full-page visual regression baselines. A reasonable person might ask why a personal portfolio needs any of that. Commit ad76792 is why.

The change that looked local

At the end of July I was integrating real project photos into the site. One task in the plan: add thumbnails to the cards on /projects. The edit went into ProjectCard.astro — when a project has an image field, render a thumbnail above the text. I regenerated the visual baselines for the projects page, watched everything go green locally, and moved on.

Here is what I did not think about. The home page has a “Featured work” section, and it renders the same ProjectCard component. The featured projects that had just gained images now rendered thumbnails on the home page too. The home page’s layout changed — and nobody had touched the home page.

The failing run

The visual suite screenshots 8 pages at 4 viewports — 320, 768, 1024 and 1440 px — and compares each full-page shot against a committed baseline with a maxDiffPixelRatio of 0.02. On the next CI run, GitHub Actions run 30642437223 reported:

4 failed: home-mobile/tablet/laptop/desktop, 37 passed

That failure shape is the diagnosis. All four home viewports failing while every other page stays green is exactly what a legitimate layout change to one page looks like. Before touching anything I confirmed the failures reproduced identically on ubuntu-latest in CI, on the same commit, not just on my machine — as the commit message puts it, “a real regression, not environment noise”. The baselines for the home page still showed the pre-thumbnail render; the page itself had legitimately moved on.

Targeted regeneration, not a blanket reset

The tempting fix when visual tests fail is to regenerate everything and let CI go green. That is also the way to make a visual suite worthless: a blanket reset blesses whatever the current render happens to be, bugs included. If I had done that here without looking, I would have learned nothing — and the next unintended change would sail through the same way.

Instead I treated it like any other regression. Confirm the cause (the shared component), confirm the new render is actually the intended one (it was — the thumbnails belong on the featured cards), then regenerate only what is stale: a single --update-snapshots run scoped with --grep to the home visual tests. Four PNG baselines changed, nothing else in the diff. The commit message records the whole chain — root cause, the CI run that confirmed it, and the exact regeneration command — so the fix is auditable later, by me or anyone reading the public repo.

What every other test said

Nothing. That is the interesting part. Every functional assertion on the home page kept passing: the heading is visible, the links resolve, the cards render their titles. The accessibility audit was equally happy — a thumbnail with proper alt text is not a WCAG violation. By every behavioural measure the home page was fine. The only kind of test that can notice “this page looks different now” is one that actually looks at the page.

Shared components are the multiplier here. You edit one file in the mental context of one page, and every page importing that component changes with it. That is precisely what components are for — and precisely why the blast radius of a “small” edit is larger than the diff suggests. Full-page visual regression turns those invisible side effects into pixel diffs you are forced to look at.

Takeaway

A component-based architecture means no change is as local as it looks. Functional tests verify what a page does; only visual tests verify what a page is. For the cost of 32 PNG files in a repo and a few seconds of CI, I get a guarantee no assertion-based suite can offer: if any of the 8 pages shifts by more than 2% of its pixels, at any of 4 viewports, a robot tells me before a human ever sees it. On a site that exists to be looked at, that is the one test category I would keep if I could only keep one.

← All posts