Screenshots or it didn't happen
Build passing is not done. Our definition of done is a full-resolution capture of the deployed page, triaged by a vision model, signed off by a human.
The most expensive bugs we have shipped were invisible to every automated check. The build was green. The tests passed. And the page was wrong: a headline wrapped into nonsense on mobile, a section rendered behind another, a form that looked fine and submitted nothing.
So we adopted a rule that sounds primitive and works: no change is done until someone has looked at a screenshot of the deployed page. Not the dev server. The real thing, both viewports, at full resolution.
Making evidence cheap
for (const vp of [{ w: 1440, h: 900 }, { w: 390, h: 844 }]) {
const page = await browser.newPage({ viewport: vp });
page.on('console', (m) => errors.push(m)); // console is part of the picture
await page.goto(URL, { waitUntil: 'networkidle' });
await page.screenshot({ path: shot(vp), fullPage: true });
}
Capture is one command per project, so there is no excuse to skip it. Timestamped captures accumulate into the cheapest regression archive ever built.
The machine reads first
This year we put a vision model in front of the human. Every capture pair goes to a model with a deliberately narrow brief: report text overflow, truncation, overlapping elements, broken alignment and contrast failures, with pixel regions, and answer NONE if the page is clean. No taste, no compliments, defects only. It catches roughly 70% of mechanical failures before a person looks, and it never gets bored on capture number forty.
The human pass still decides, and it has rules of its own: open captures at 1:1 because overflow hides at fit-to-window zoom, read the rendered copy aloud because screenshots find the typos your editor did not, and treat a clean-looking page with console errors as dirty.
The generalization
Database features are done when the row is queried back. Emails are done when the message is opened in a real client. Model pipelines are done when the output is read at the destination, not at the API response. The pattern is one sentence: verify at the layer the user lives in, not the layer you happened to write.