Automate Component Screenshots with Playwright

Screenshot every component automatically. Catch visual bugs before they ship, without clicking through pages manually.

What you will need

Node.js

Version 20 or later. Playwright runs on Node and downloads its own browser engines on first install.

Open
A component preview URL

Storybook, a docs site, or any local URL where your components render. The script in step 2 navigates to one URL per component.

A terminal

Terminal on Mac, Windows Terminal, or the built-in terminal in VS Code.

30 minutes

Most of that is the first install and baseline run. Subsequent runs take seconds.

Visual regression flow
  1. 01

    Baseline

    First run saves golden screenshots of every component

  2. 02

    Change

    Someone modifies code, tokens, or styles

  3. 03

    Diff

    Playwright compares new screenshots against baselines

  4. 04

    Approve

    Accept the new baseline or revert the regression

Why This Matters for Your Design System

You do not need a QA team to catch visual bugs. You need a script that takes a screenshot of every component state, every time someone pushes code, and diffs it against yesterday’s version. That is what Playwright does.

Most designers think browser automation is an engineering tool. It is not. It is the fastest way to prove your design system actually looks the way Figma says it should. This workflow gets you from zero to automated screenshots in 20 minutes.


Step 1: Install Playwright

Open your terminal and navigate to your project folder (or create a new one for testing). Run the Playwright installer:

npm init playwright@latest

If Playwright is new to you, start with Playwright for Designers first. If you only need the definition, read Playwright in the dictionary.

The installer will ask you a few questions:

  • Language: Choose TypeScript or JavaScript (TypeScript is recommended but not required)
  • Tests folder: Accept the default tests/ or choose your own
  • GitHub Actions: Say yes if you want to run tests in CI later
  • Install browsers: Say yes

This downloads Chromium, Firefox, and WebKit browsers. Playwright runs real browsers, not simulations, so your screenshots match what users actually see.

Why this step matters: Playwright installs actual browser engines. This means screenshots are rendered exactly as they would be in production. Browser rendering differences (the subtle spacing or antialiasing variations between Chrome and Safari) are captured and testable.


Step 2: Write Your First Screenshot Test

Create a test file at tests/components.spec.ts (or .js if you chose JavaScript):

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

const components = [
  { name: 'button-primary', url: '/story/button--primary' },
  { name: 'button-secondary', url: '/story/button--secondary' },
  { name: 'card-default', url: '/story/card--default' },
  { name: 'input-text', url: '/story/input--text' },
  { name: 'alert-success', url: '/story/alert--success' },
  { name: 'alert-error', url: '/story/alert--error' },
];

const BASE_URL = 'http://localhost:6006/iframe.html?id=';

for (const component of components) {
  test(`screenshot: ${component.name}`, async ({ page }) => {
    await page.goto(`${BASE_URL}${component.url}`);
    await page.waitForLoadState('networkidle');
    await expect(page).toHaveScreenshot(`${component.name}.png`);
  });
}

This script loops through a list of components, navigates to each one in Storybook, waits for everything to load, and takes a screenshot. If you are not using Storybook, replace BASE_URL with whatever URL serves your component previews.

Why this step matters: The toHaveScreenshot assertion does more than take a picture. On the first run, it saves a baseline screenshot. On every subsequent run, it compares the new screenshot against the baseline and fails if there is a visual difference. This is called visual regression testing, and it catches the kinds of subtle bugs that human eyes miss during quick reviews.


Step 3: Generate Baselines

Run the tests for the first time to establish your baseline screenshots:

npx playwright test --update-snapshots

This creates a folder structure like:

tests/
  components.spec.ts-snapshots/
    button-primary-chromium-darwin.png
    button-secondary-chromium-darwin.png
    card-default-chromium-darwin.png
    ...

Open these images and verify they look correct. These are your “golden” reference images. Every future test run will compare against them.

Commit the baseline images to your repository. They are part of your test suite.

Why this step matters: Baselines define “correct.” If a button currently has an 8px border-radius and someone changes it to 4px, the test will detect the difference because the screenshot no longer matches the baseline. Without baselines, there is nothing to compare against.


Step 4: Run Tests and Review Diffs

After establishing baselines, run the tests normally:

npx playwright test

If all components match their baselines, every test passes. If something changed, Playwright fails the test and generates a diff image showing exactly what is different.

To see the results in a visual report:

npx playwright show-report

This opens an HTML report in your browser. For failed tests, you see three images side by side:

  • Expected: The baseline (what it should look like)
  • Actual: The current screenshot (what it looks like now)
  • Diff: A highlighted overlay showing exactly which pixels changed
Test Results:
  ✓ screenshot: button-primary        (1.2s)
  ✓ screenshot: button-secondary      (0.9s)
  ✗ screenshot: card-default          (1.1s)
    → Screenshot comparison failed
  ✓ screenshot: input-text            (0.8s)

Why this step matters: The diff image is the most valuable output. Instead of telling an engineer “something looks off with the card component,” you can share a precise, pixel-level comparison showing the exact visual change. This turns vague feedback into actionable information.


Step 5: Integrate into Your Sprint Workflow

Now that screenshot testing works locally, integrate it into your team’s process:

Before merging a PR: Add a step to your CI pipeline that runs Playwright tests on every pull request. If a visual change is intentional (a design update), the engineer updates the baselines. If it is unintentional (a regression), they fix the code.

For design reviews: Run the tests locally before and after making design changes. Share the diff report with your team to show exactly what changed.

Expanding coverage: Add new components to the test list as your system grows. The pattern is simple: one entry in the components array per variant you want to track.

// Add new components as your system grows
const components = [
  // Existing
  { name: 'button-primary', url: '/story/button--primary' },
  // New additions
  { name: 'button-loading', url: '/story/button--loading' },
  { name: 'button-disabled', url: '/story/button--disabled' },
  { name: 'modal-default', url: '/story/modal--default' },
];

Why this step matters: The real value of visual testing comes from consistency. Running it once is interesting. Running it on every pull request catches bugs before they ship. Over time, you build confidence that your component library looks exactly as intended across every change.


What You Get

  • Automated screenshots of every component variant, taken in real browsers.
  • Pixel-level diff detection that catches changes human eyes might miss.
  • An HTML report with side-by-side comparisons for easy review.
  • A visual changelog that documents how your components evolve over time.
  • Cross-browser testing. Playwright runs on Chromium, Firefox, and WebKit by default, so you can verify rendering consistency.

Common Pitfalls

  • Flaky screenshots from animations. If your components have CSS transitions or animations, they can cause inconsistent screenshots. Use await page.waitForTimeout(500) or disable animations in your test configuration.
  • Font rendering differences. Different operating systems render fonts differently. Screenshots taken on Mac will differ from those taken on Linux. Run your CI tests on the same OS consistently.
  • Dynamic content. If components show timestamps, random data, or user avatars, mock that data in your test environment so screenshots are deterministic.
  • Large baseline files. PNG screenshots can be large. Use .gitattributes with Git LFS if your repository grows too big.

Exercise

Catch a real visual regression in your own component library

45 min
  1. Scaffold the test and capture baselines for six real components

    Run npm init playwright@latest in your design system or docs repo. Pick six components you actually ship (not the toy examples in this guide). Create tests/components.spec.ts with one test per component pointed at your real preview URL (Storybook, Ladle, a docs site). Run npx playwright test --update-snapshots. Commit the snapshot PNGs alongside the test file.

    • Six snapshot PNGs exist in the *-snapshots/ folder, each showing a real component
    • npx playwright test passes cleanly on the second run
    • The PNGs are committed to the repo (visual tests are worthless without version control)
    • Your CI config was updated or queued to run these tests on every PR
  2. Break it on purpose, review the diff, accept or reject

    Change one thing in a component you test: a border radius, a color token, a padding value. Run npx playwright test (no --update-snapshots). Watch the test fail. Run npx playwright show-report. Study the diff image. Decide: was this change intentional? If yes, run --update-snapshots to accept the new baseline. If no, revert the code. This is the exact workflow you will use on every PR forever.

    • The HTML report shows the three panels (expected, actual, diff) for the failing test
    • The diff image highlights exactly the pixels that changed
    • You made an explicit accept or reject decision, not “I guess it is fine”
    • You understand how you would review this diff if a teammate sent it in a PR

Finished this lesson?

Mark it complete to track your progress through "Automation for DS Teams".

Lesson
9 / 13
Progress
69%
Read time
30 min
Free to try Cancel anytime
The guides alone saved me a full day of work every sprint.
Senior Design Systems Lead
Enterprise SaaS
Pro
Full access to everything.
$39 /month
  • All guides, prompts, and templates
  • Starter kits and templates
  • New content every week
  • Priority support