Skip to content

QA & Testing

Playwright Visual Regression Testing for Marketing Sites

Screenshot-Based QA That Actually Stops Regressions

📸 🔄 🚨

Unit tests pass. TypeScript is happy. Your Tailwind compiles. Then the PR merges, preview builds, and your hero image is crushed to 12px height on mobile. Welcome to why marketing sites need visual regression testing — the kind that doesn't rely on developers noticing the break.

Playwright screenshots at 6 viewports (360, 390, 768, 1024, 1280, 1440) catch these moments before production. Each PR runs a diff against the baseline. If pixels move beyond a threshold, the action fails and blocks the merge. No surprises after shipping.

Why Visual Regression Beats Unit Tests for Marketing Sites

Marketing sites live and die by rendering. A button tests green but doesn't visually align with its label? Users won't use it. A font swap ships before the web font loads? Looks broken. CSS grid collapses wrong on tablets? Dead layout.

Unit tests can't see pixels. They test logic — "this component renders a button element". Visual regression testing asks: "Does this button look right at every breakpoint a human might use?" That's the question marketing teams care about. Velocity X runs Playwright before every merge. Miss it, and CSS regressions fail the PR automatically, no human judgment needed.

Architecture: 6 Viewports, Baseline Diffs, Threshold

Setup is straightforward. Create a Playwright config that captures full-page screenshots at common viewport widths: 360 (small phone), 390 (standard phone), 768 (tablet portrait), 1024 (tablet landscape), 1280 (laptop), 1440 (desktop). Store these as baseline images. On every PR, re-run the same screenshots, diff them pixel-by-pixel against the baseline, and fail if the difference exceeds a threshold (usually 0.1–0.5%).

{`// playwright.config.ts
export default {
  use: {
    screenshot: 'only-on-failure',
  },
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: false,
  },
};

// src/tests/visual.spec.ts
import { test, expect } from '@playwright/test';

const viewports = [
  { name: '360', width: 360, height: 800 },
  { name: '390', width: 390, height: 844 },
  { name: '768', width: 768, height: 1024 },
  { name: '1024', width: 1024, height: 768 },
  { name: '1280', width: 1280, height: 720 },
  { name: '1440', width: 1440, height: 900 },
];

viewports.forEach(({ name, width, height }) => {
  test(\`homepage renders correctly at \${name}px\`, async ({ browser }) => {
    const context = await browser.newContext({ viewport: { width, height } });
    const page = await context.newPage();
    await page.goto('http://localhost:3000');
    await expect(page).toHaveScreenshot(\`homepage-\${name}.png\`);
  });
});`}

On first run, Playwright saves screenshots as baselines (stored in tests/__screenshots__/). On the next PR, the same test re-takes screenshots and compares. If pixels differ by more than the threshold, the test fails. CI/CD blocks the merge. A human reviews the diff — either it's intentional (update the baseline) or accidental (fix the CSS). Either way, nothing ships blind.

CI Integration: GitHub Actions + Netlify Preview

Netlify builds every PR to a preview URL. Before merging, run a GitHub Action that spins up Playwright, points it at the preview URL, takes screenshots, and diffs against the repository baseline. Fail the PR if differences exceed your threshold. The action runs in parallel with code review. By the time a human reads the PR, Playwright has already screamed if rendering is broken.

Store baseline screenshots in git. When they need updating (intentional design changes, new font, refactored layout), approve the update in the PR review, merge the baseline changes, and the next PR starts with the new ground truth.

Six Questions Teams Ask

Won't this flag every tiny design tweak?

Thresholds exist for this. Set diff tolerance to 0.1–0.5% — small anti-alias shifts and subpixel rendering differences pass. Anything that moves a major visual element gets caught.

How do I update baselines when design changes intentionally?

Most test runners let you pass a flag like --update-snapshots. The action re-captures and commits the new baseline as part of the PR. Code review the diff, approve, merge.

Does this slow down CI?

Playwright test runs usually complete in 30–60 seconds per viewport. For 6 viewports, ~5 minutes total. Parallel test runners cut that to 2–3 minutes. Netlify builds take longer anyway. This doesn't add meaningful delay.

What about dynamic content or animations?

Wait for elements to be stable before screenshotting. Disable animations in tests with CSS (animation: none). Scroll to fixed positions (hero, fold, footer). Freeze time-dependent content. Playwright includes helpers for all of this.

If Playwright fails a PR, can I override it?

Yes — the action isn't a force gate. You can merge anyway if the diff is intentional. But forcing means the baseline isn't updated, so the next PR will fail the same check. Good practice: always update baselines in the same PR as visual changes.

Do I need to screenshot every page?

Start with critical paths: homepage, pricing, blog index, key landing pages. You can expand later. Even 3–4 key pages catch 90% of layout regressions.

The Bottom Line

Marketing sites break visually all the time — font swaps, CSS typos, responsive logic edge cases, third-party script slowdowns. Unit tests won't catch them. Code review misses them. Playwright visual regression testing stops them before production. Screenshot 6 viewports, diff against baseline, fail the PR if rendering breaks. It's the safety net that turns AI-assisted edits and rapid iteration from scary to standard practice.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.