Automate Component Screenshots with Playwright
Screenshot every component automatically. Catch visual bugs before they ship, without clicking through pages manually.
What you will need
Version 20 or later. Playwright runs on Node and downloads its own browser engines on first install.
Storybook, a docs site, or any local URL where your components render. The script in step 2 navigates to one URL per component.
Terminal on Mac, Windows Terminal, or the built-in terminal in VS Code.
Most of that is the first install and baseline run. Subsequent runs take seconds.
- 01
Baseline
First run saves golden screenshots of every component
- 02
Change
Someone modifies code, tokens, or styles
- 03
Diff
Playwright compares new screenshots against baselines
- 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
.gitattributeswith Git LFS if your repository grows too big.
Catch a real visual regression in your own component library
-
Scaffold the test and capture baselines for six real components
Run
npm init playwright@latestin your design system or docs repo. Pick six components you actually ship (not the toy examples in this guide). Createtests/components.spec.tswith one test per component pointed at your real preview URL (Storybook, Ladle, a docs site). Runnpx 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 testpasses 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
- Six snapshot PNGs exist in the
-
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. Runnpx playwright show-report. Study the diff image. Decide: was this change intentional? If yes, run--update-snapshotsto 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".
The guides alone saved me a full day of work every sprint.
- All guides, prompts, and templates
- Starter kits and templates
- New content every week
- Priority support