Build This Now
Build This Now
Qu'est-ce que le code Claude ?Installer Claude CodeL'installateur natif de Claude CodeTon premier projet Claude Code
Subscription BillingMulti-Tenant SaaSAI Chat FeatureRate LimitingStripe WebhooksSemantic SearchRealtime UpdatesDrip Email Sequencesv2.1.122 Release NotesClaude Code Dynamic Workflows : comment orchestrer 1 000 sous-agents sur une vraie codebaseBonnes pratiques Claude CodeMeilleures pratiques pour Claude Opus 4.7Claude Code sur un VPSIntégration GitRevue de code avec Claude CodeLes Worktrees avec Claude CodeClaude Code à distanceClaude Code ChannelsChannels, Routines, Teleport, DispatchTâches planifiées avec Claude CodePermissions Claude CodeLe mode auto de Claude CodeAjouter les paiements Stripe avec Claude CodeFeedback LoopsWorkflows TodoGestion des tâches dans Claude CodeTemplates de projetTarification et utilisation des tokens Claude CodeTarifs de Claude Code : ce que tu vas vraiment payerClaude Code Ultra ReviewConstruire une app Next.js avec Claude CodeSupabase DatabaseVercel DeepsecTest-Driven DevelopmentConstruire un MVP SaaS avec Claude CodeAjouter l'authentification avec Claude Code (Supabase Auth)Ajouter les emails transactionnels avec Claude Code (Resend + React Email)Construire une API type-safe avec Claude Code (oRPC + Zod)File UploadsBackground Jobs (Inngest)Admin DashboardFull-Text SearchClaude Agent SDKKiro Migration GuideClaude Research AgentLeave Grok BuildRoute Subagent ModelsClaude Monorepo SetupCI Repair AgentVisual Regression TestsAgent Cost DashboardCursor Migration GuideCommerce agentique : comment construire une app que les agents IA peuvent payer1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingCombien coûte la création d'un SaaS avec Claude Code en 2026Parallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/Visual Regression Tests

Visual Regression Tests

Build visual regression tests with Claude Code, Next.js 16, and Playwright. Create stable screenshot baselines, review diffs, and block accidental UI changes in CI.

Arrête de tout configurer. Place à la construction.

Des templates SaaS avec orchestration IA.

Découvrez ce que nous construisons pour les entreprises →
speedy_devvWritten by speedy_devvPublished Jul 27, 202611 min readHandbook hubWorkflow index

Visual regression tests compare a rendered page with an approved screenshot and fail when pixels change beyond a defined threshold. In a Next.js 16 project, Claude Code can build and maintain that loop, while Playwright supplies the browser, stable screenshot capture, comparison engine, and failure artifacts.

This is most useful for changes that remain valid to the DOM but damage the interface: a clipped price, a shifted card, an invisible focus ring, or a mobile layout that wraps one line too early. The goal is not to reject every pixel difference. It is to turn meaningful visual changes into a reviewable engineering decision.

What visual regression testing covers

A visual test is one layer of a healthy test suite. It does not replace component assertions or user-flow tests, because a screenshot can look correct while a button is impossible to activate.

Test typeBest at detectingTypical blind spot
Unit or component testLogic, props, states, and accessibility rolesCross-component layout
End-to-end testNavigation, forms, and real user flowsSubtle visual damage
Screenshot comparisonSpacing, typography, color, clipping, and responsive layoutBehavior hidden behind a correct image

Playwright's toHaveScreenshot assertion captures the page repeatedly until two consecutive screenshots match, then compares the stable result with the stored baseline. That built-in stabilization helps, but it cannot make a live clock, random avatar, changing advertisement, or late-loading third-party widget deterministic. Those elements still need to be controlled or masked.

If the project has no browser coverage yet, read the Claude Code browser automation guide before expanding this setup. Visual checks work best after the core navigation path is already testable.

Build a deterministic test page

Start with one route and one desktop viewport. A narrow first target makes failures understandable and establishes a baseline-review habit before the suite expands.

The example below creates a small pricing preview with one intentionally dynamic value. Add it as app/visual-test/page.tsx; the test will mask the changing timestamp but verify the rest of the page.

import styles from "./page.module.css";

const features = [
  "Unlimited private projects",
  "Preview deployments",
  "Visual review history",
];

export default function VisualTestPage() {
  return (
    <main className={styles.shell}>
      <section className={styles.hero}>
        <p className={styles.eyebrow}>Developer workspace</p>
        <h1>Ship interface changes with confidence.</h1>
        <p className={styles.intro}>
          Review the rendered result before a visual mistake reaches production.
        </p>

        <article className={styles.card}>
          <div>
            <p className={styles.plan}>Team</p>
            <p className={styles.price}>
              $24 <span>per member</span>
            </p>
          </div>
          <ul>
            {features.map((feature) => (
              <li key={feature}>{feature}</li>
            ))}
          </ul>
          <button type="button">Start a workspace</button>
        </article>

        <p className={styles.build}>
          Preview generated at{" "}
          <span data-testid="build-time">{new Date().toISOString()}</span>
        </p>
      </section>
    </main>
  );
}

The CSS creates enough spacing, color, typography, and responsive behavior to make a visual check useful. Save it as app/visual-test/page.module.css; CSS Modules keep the fixture independent of whether the application uses Tailwind.

.shell {
  min-height: 100vh;
  padding: 72px 24px;
  color: #172033;
  background:
    radial-gradient(circle at 50% 0%, #e0e7ff 0, transparent 38%),
    #f8fafc;
}

.hero {
  width: min(100%, 720px);
  margin: 0 auto;
  text-align: center;
}

.eyebrow {
  margin: 0 0 12px;
  color: #4f46e5;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.hero h1 {
  max-width: 650px;
  margin: 0 auto;
  font-size: clamp(40px, 7vw, 68px);
  line-height: 0.98;
  letter-spacing: -0.045em;
}

.intro {
  max-width: 540px;
  margin: 24px auto 40px;
  color: #526078;
  font-size: 19px;
  line-height: 1.6;
}

.card {
  display: grid;
  gap: 28px;
  width: min(100%, 460px);
  margin: 0 auto;
  padding: 32px;
  text-align: left;
  background: white;
  border: 1px solid #dbe2ee;
  border-radius: 24px;
  box-shadow: 0 24px 70px rgb(43 55 83 / 12%);
}

.plan {
  margin: 0 0 8px;
  font-weight: 700;
}

.price {
  margin: 0;
  font-size: 42px;
  font-weight: 800;
  letter-spacing: -0.04em;
}

.price span {
  color: #64748b;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
}

.card ul {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.card li::before {
  margin-right: 10px;
  color: #4f46e5;
  content: "✓";
}

.card button {
  min-height: 48px;
  color: white;
  font: inherit;
  font-weight: 700;
  background: #312e81;
  border: 0;
  border-radius: 12px;
}

.build {
  margin-top: 22px;
  color: #64748b;
  font-size: 12px;
}

@media (max-width: 520px) {
  .shell {
    padding: 48px 16px;
  }

  .card {
    padding: 24px;
  }
}

The timestamp is realistic dynamic content, but it is not the subject of the test. Masking only that value keeps the surrounding sentence, typography, and position under visual coverage.

Install and configure Playwright

Install the Playwright test runner and the Chromium browser it controls. The browser installation is separate because Playwright pins compatible browser binaries for reliable local and CI execution.

Run these commands from the Next.js project root. The final command adds reusable scripts without asking you to edit package.json by hand.

npm install --save-dev @playwright/test
npx playwright install chromium
npm pkg set scripts.test:visual="playwright test"
npm pkg set scripts.test:visual:update="playwright test --update-snapshots"

A visual suite should control the environment more tightly than a general end-to-end suite. The configuration below fixes the browser, viewport, color scheme, locale, and time zone, disables animations for screenshot assertions, and keeps CI workers at one to reduce machine-level variance.

Save this complete configuration as playwright.config.ts. The webServer option starts Next.js before the tests and reuses an existing local server outside CI.

import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests/visual",
  fullyParallel: false,
  forbidOnly: Boolean(process.env.CI),
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: process.env.CI
    ? [["line"], ["html", { open: "never" }]]
    : [["list"], ["html", { open: "never" }]],
  expect: {
    toHaveScreenshot: {
      animations: "disabled",
      caret: "hide",
      scale: "css",
      maxDiffPixelRatio: 0.005,
      pathTemplate:
        "{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}",
    },
  },
  use: {
    baseURL: "http://127.0.0.1:3000",
    colorScheme: "light",
    locale: "en-US",
    timezoneId: "UTC",
    trace: "on-first-retry",
    screenshot: "only-on-failure",
  },
  projects: [
    {
      name: "chromium-desktop",
      use: {
        ...devices["Desktop Chrome"],
        viewport: { width: 1440, height: 1000 },
      },
    },
  ],
  webServer: {
    command: "npm run dev",
    url: "http://127.0.0.1:3000",
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

The ratio allows a very small amount of pixel variation without accepting a visibly broken layout. Tune it from observed, reviewed failures rather than choosing a large tolerance up front. Playwright documents the complete screenshot options in its toHaveScreenshot API.

Write the first screenshot test

Wait for web fonts before capturing the page. A screenshot taken while the fallback font is visible can shift every line even though the application code is correct.

Create tests/visual/visual-test.spec.ts with the following test. The assertion verifies the complete scrollable page and replaces the changing timestamp with Playwright's standard mask color.

import { expect, test } from "@playwright/test";

test("pricing preview matches its approved design", async ({ page }) => {
  await page.goto("/visual-test");
  await page.evaluate(() => document.fonts.ready);

  await expect(page).toHaveScreenshot("pricing-preview.png", {
    fullPage: true,
    mask: [page.getByTestId("build-time")],
  });
});

Generate the first approved image locally, inspect it, and commit it with the test. The update command is an approval operation, not a generic repair command.

npm run test:visual:update
npm run test:visual
git status --short

After a later UI change, run the normal test command first. If it fails, open playwright-report/index.html with npx playwright show-report, compare the expected, actual, and diff views, then either fix the regression or deliberately regenerate the baseline.

Add visual tests to CI

CI should use the same package lock, browser family, and command as local development. Playwright's CI documentation recommends installing browser dependencies in the workflow, while the HTML report gives reviewers the evidence needed to diagnose a failure.

The following GitHub Actions workflow runs on pull requests and uploads the report only when the visual command fails. Save it as .github/workflows/visual-regression.yml.

name: Visual regression

on:
  pull_request:

jobs:
  screenshots:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Install Chromium
        run: npx playwright install --with-deps chromium

      - name: Compare screenshots
        run: npm run test:visual

      - name: Upload Playwright report
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

Linux and macOS can render fonts differently. Generate the committed baseline in the same Linux environment used by CI, or use Playwright's official container for both baseline generation and comparison. Do not compensate for operating-system drift with a broad threshold.

For a broader delivery pipeline, pair this job with the checks in the Claude Code GitHub Actions guide. Screenshot approval should remain visible in the pull request instead of becoming an invisible automation side effect.

Give Claude Code review rules

Claude Code is effective at adding page coverage because it can inspect the component, identify dynamic regions, run the test, and explain the diff. It still needs a firm rule against blindly replacing evidence.

Add a focused section to the repository's CLAUDE.md. These instructions make the expected behavior repeatable across sessions and keep snapshot updates under human review.

## Visual regression rules

- Run `npm run test:visual` after changing shared layout, typography, or pricing UI.
- Never run `npm run test:visual:update` only to make a failure disappear.
- Inspect the expected, actual, and diff images before proposing a baseline update.
- Fix unstable data at its source. Mask only content that must change between runs.
- Keep one intentional assertion per screenshot and use descriptive baseline names.
- Report whether each failure is a regression, an intended design change, or instability.

A useful Claude Code prompt names the route, viewport, dynamic regions, and acceptance rule. For example: “Add visual coverage for the account settings route at the existing desktop project. Mask only the signed-in user's avatar, run the test, and explain every diff before changing a baseline.”

This process fits naturally beside test-driven development with Claude Code. The behavioral test defines what the feature does; the screenshot defines the approved rendered state.

Grow the suite without making it noisy

Expand by user risk, not by page count. Checkout, authentication, pricing, navigation, and shared responsive shells usually deserve coverage before low-traffic informational pages.

Use a separate Playwright project when a second viewport represents a real design contract. A mobile project at a fixed device profile is clearer than changing viewport dimensions inside one test, and its baseline path remains separate through the project-name token.

Keep these boundaries as the suite grows:

  • Mock or seed API data so the same entities render every time.
  • Freeze clocks when dates affect layout, and mask only when the exact date is irrelevant.
  • Host fonts locally or ensure they finish loading before capture.
  • Disable animations through Playwright rather than adding test-only CSS throughout the product.
  • Review each updated PNG in the pull request.
  • Delete obsolete baselines when routes or projects disappear.

Avoid pixel snapshots for highly personalized dashboards, live maps, videos, and advertising surfaces unless those regions can be made deterministic. A role-based assertion may provide more signal for those areas.

The practical success measure is not the number of images. It is whether a failed comparison points to one understandable decision and gives the reviewer enough evidence to make it quickly.

Frequently asked questions

How do I run visual regression tests with Claude Code?

Give Claude Code one deterministic Playwright command, a small set of screenshot tests, and an explicit rule that baselines change only after a human reviews the diff. Generate and commit the approved baseline, then run the comparison in CI on every relevant pull request.

Should Playwright screenshots be committed to Git?

Yes. Approved baseline PNGs are test inputs and belong in Git beside the test that uses them. Failure artifacts such as the actual image and pixel diff belong in the CI artifact, where reviewers can inspect them without adding them to repository history.

How do I prevent flaky Playwright screenshot tests?

Use the same browser and operating system for baseline creation and CI. Fix the viewport, locale, time zone, color scheme, fonts, and test data; disable animations; wait for font loading; and mask only the smallest region that must change. A large pixel threshold usually hides instability instead of fixing it.

Code Kit is a $29 one-time harness that runs on top of Claude Code. It helps plan, build, evaluate, and test changes with quality gates for zero type errors, zero lint errors, and a clean production build; Claude Code itself requires a paid Anthropic plan.

Posted by @speedy_devv

Continue in Workflow

  • Commerce agentique : comment construire une app que les agents IA peuvent payer
    Un guide en français simple du commerce agentique en 2026 : ce que font x402, ACP et le Machine Payments Protocol, plus un pas-à-pas d'un week-end pour livrer une API payante que les agents IA peuvent acheter.
  • Bonnes pratiques Claude Code
    Cinq habitudes séparent les ingénieurs qui livrent avec Claude Code : les PRDs, les règles CLAUDE.md modulaires, les slash commands personnalisés, les resets /clear, et un état d'esprit d'évolution du système.
  • Le mode auto de Claude Code
    Un second modèle Sonnet examine chaque appel d'outil Claude Code avant qu'il s'exécute. Ce que le mode auto bloque, ce qu'il autorise, et les règles d'autorisation qu'il place dans tes paramètres.
  • Channels, Routines, Teleport, Dispatch
    Les quatre fonctionnalités Claude Code livrées par Anthropic en mars et avril 2026 qui transforment le CLI en une couche de coordination orientée événements, entre téléphone, web et desktop.
  • Claude Code 1M Context in Practice: When Bigger Isn't Better
    The 1M-token context window is GA at flat pricing, but bigger isn't always better. A decision framework, token-cost math, and when to use /compact, subagents, and dynamic workflows instead.
  • How to Build an Admin Dashboard With Claude Code
    Ship an internal admin panel with Claude Code: role-gated routes, a searchable users and orders table with pagination, impersonation-safe RLS, and metrics tiles pulled through a type-safe API.

More from Handbook

  • Principes de base de l'agent
    Cinq façons de construire des agents spécialisés dans le code Claude : Sous-agents de tâches, .claude/agents YAML, commandes slash personnalisées, personas CLAUDE.md, et invites de perspective.
  • L'ingénierie du harness agent
    Le harness, c'est toutes les couches autour de ton agent IA sauf le modèle lui-même. Découvre les cinq leviers de contrôle, le paradoxe des contraintes, et pourquoi le design du harness détermine les performances de l'agent bien plus que le modèle.
  • Patterns d'agents
    Orchestrateur, fan-out, chaîne de validation, routage par spécialiste, raffinement progressif, et watchdog. Six formes d'orchestration pour câbler des sub-agents Claude Code.
  • Meilleures pratiques des équipes d'agents
    Patterns éprouvés pour les équipes d'agents Claude Code. Prompts de création riches en contexte, tâches bien calibrées, propriété des fichiers, mode délégué, et correctifs v2.1.33-v2.1.45.

Arrête de tout configurer. Place à la construction.

Des templates SaaS avec orchestration IA.

Découvrez ce que nous construisons pour les entreprises →

CI Repair Agent

Build a guarded Claude Code CI repair agent that reads failed GitHub Actions logs, proposes a bounded patch, and leaves verification and merge control to CI.

Agent Cost Dashboard

Build a Claude Agent SDK cost dashboard in Next.js 16. Record per-run estimates, token usage, cache activity, and model mix without confusing estimates with billing.

On this page

What visual regression testing covers
Build a deterministic test page
Install and configure Playwright
Write the first screenshot test
Add visual tests to CI
Give Claude Code review rules
Grow the suite without making it noisy
Frequently asked questions
How do I run visual regression tests with Claude Code?
Should Playwright screenshots be committed to Git?
How do I prevent flaky Playwright screenshot tests?

Arrête de tout configurer. Place à la construction.

Des templates SaaS avec orchestration IA.

Découvrez ce que nous construisons pour les entreprises →