A simple CSS change broke the checkout button on mobile. Every functional test passed.
The scary part? Nobody knew. The button was technically in the DOM. It had the correct text. But to an actual user, it was completely invisible, overlapped by a rogue div on mobile screens only. The kind of bug that gets caught in production when a customer tries to check out and can't.
Functional tests are great for verifying behavior. They are not designed to check appearance. They can't tell you if your app actually looks right.
BEFORE : Functional test passes. Button is covered on mobile.
// ❌ BEFORE — Functional test passes, but the button is covered on mobile.
await expect(page.getByRole('button', { name: 'Checkout' })).toBeVisible();
// Passes because the button is in the DOM, even if users can't actually click it.
AFTER : Visual regression catches layout issues across devices.
// ✅ AFTER — Visual regression catches layout issues across devices.
test('cart page looks correct', async ({ page }) => {
await page.goto('/cart');
await expect(page).toHaveScreenshot('cart.png', {
mask: [
page.getByTestId('timestamp'), // changes every run
page.getByTestId('order-id'), // unique per session
],
maxDiffPixelRatio: 0.01,
});
});
// 🧪 Test across viewports in playwright.config.ts
{ name: 'desktop', use: { viewport: { width: 1280, height: 720 } } },
{ name: 'mobile', use: { viewport: { width: 375, height: 667 } } },
Playwright's built-in visual comparisons (toHaveScreenshot) catch these layout breaks instantly. The quick pro-tip that saves every team time: always mask dynamic elements like timestamps or order IDs to avoid flaky false positives.
The distinction most teams miss is this: functional tests verify that something works. Visual tests verify that users can actually see and interact with it. Both questions matter. Most suites only answer one of them.
When you add visual coverage at the viewport level, you stop shipping layout bugs that your test suite is structurally incapable of catching.
Have visual tests ever caught a bug your functional tests completely missed? Drop it in the comments.









![[Tanwydd]](https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3874697%2Faf7553e1-11dc-4b40-b171-7d67491de89f.png)



