Turning Storybook into a visual testing platform
Using Playwright and Percy for automated UI regression testing
Turning Storybook into a visual testing platform
Hello! I’m Alistair Bancroft, a Product Engineer based in London, currently working on the Checkout team at John Lewis Partnership.
In modern front-end engineering, we already write unit tests, story-level examples, and integration tests. However, it is still surprisingly easy for visual regressions to slip through. These issues often pass unnoticed and only surface after a production deployment.
While working on component-driven features and payment journeys at John Lewis, we were already using Percy in our CI pipelines to catch full-page visual regressions. This gave us a valuable safety net and helped prevent major layout issues from reaching production.
As our applications and component library grew, however, this approach began to show its limitations. Page-level snapshots were increasingly affected by layout shifts, loading states, and asynchronous content. This introduced noise into the results and often led to false negatives, where smaller, component-level changes went undetected.
This experience highlighted the importance of visual quality assurance (QA). It is not just about validating application behaviour through automated tests, but about asserting visual correctness. Tools like Percy allow us to automate this by capturing image snapshots and highlighting differences across every change.
Storybook already plays an important role in areas such as documentation, component testing, and accessibility validation. However, when combined with Percy and Playwright, it can also be extended into a reliable visual testing platform.
In this article, I will show how we have done exactly that by automatically looping through every Storybook story, capturing visual snapshots with Playwright, and validating them with Percy in CI.
Why visual QA matters
Storybook gives us a comprehensive catalogue of component variations, from simple buttons and form states to complex edge-case error screens. It provides an invaluable reference for development and design. However, on its own, it does not tell us whether those components have changed visually over time.
Without automated visual regression testing, subtle UI issues can still make their way into production. Minor spacing changes, misaligned elements, or unintended style regressions are often only noticed after deployment, leading to reactive fixes and unnecessary discussion around visual consistency.
A visual QA system helps close this gap by verifying not only application behaviour, but also the rendered output of every story.
A real-world Percy test for Storybook
To turn our Storybook build into a reliable source of visual regression tests, we use a Playwright-based test that automatically iterates through every published story and captures a Percy snapshot for each one. This runs as part of our CI pipeline against the static Storybook build, ensuring that visual changes are detected early in the development process.
The core of this setup is a single test file that dynamically discovers stories and generates snapshots without any manual configuration.
Here’s the test we currently use to snapshot every story in our Storybook build:
// Percy integration for Playwright,
// used to capture and upload visual snapshots
import percySnapshot from "@percy/playwright";
// Playwright test runner and utilities
import { test } from "@playwright/test";
// Import Storybook’s generated metadata file,
// which contains information about every published story
import * as storiesData from "../storybook-static/index.json";
// Run tests in parallel to improve execution time
test.describe.configure({ mode: "parallel" });
// Lock the viewport size to ensure consistent snapshots
test.use({
viewport: { width: 1280, height: 800 },
});
test.describe("Component Library: Visual Regression Tests", () => {
// Extract the story entries from Storybook metadata
const { entries } = storiesData;
// Filter out anything that is not a story
const stories = Object.values(entries).filter(
(entry: any) =>
entry?.type === "story"
);
// Generate one Playwright test per story
stories.forEach(({ id, title }) => {
test(`${title} | ${id}`, async ({ page }) => {
// Navigate directly to the isolated story iframe
await page.goto(`/iframe.html?id=${id}`);
// Disable animations and transitions
// to prevent unstable visual diffs
await page.addStyleTag({
content: `
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
`,
});
// Wait for network activity to settle
// before capturing a snapshot
await page.waitForLoadState("networkidle");
// Capture and upload a Percy snapshot
// The name includes the component title and story ID
// to make snapshots easy to locate in the Percy dashboard
await percySnapshot(page, `Component Library: ${title} | ${id}`);
});
});
});
At first glance, this test appears relatively straightforward. However, there are several deliberate design choices that make it both scalable and maintainable over time.
We begin by importing Storybook’s generated index.json file, which allows the test to automatically discover every published story. This removes the need to manually maintain a list of URLs and ensures that new stories are included in visual testing by default.
Each test navigates directly to the iframe.html endpoint, which renders the story in isolation without the surrounding Storybook interface. This helps produce cleaner, more consistent snapshots that focus solely on the component under test.
Finally, percySnapshot is used to capture and track visual changes for each story, providing a clear history of regressions and approvals across successive deployments.

Percy review dashboard showing an approved build with no visual differences detected across Storybook stories.
Making Visual QA Reliable
In practice, visual testing can be sensitive to small environmental differences, particularly in interfaces that rely on animations, lazy loading, or asynchronous rendering. Without additional stabilisation, these factors can lead to inconsistent results and unnecessary noise in snapshot comparisons.
To improve reliability, we introduced a small number of practical refinements.
First, we lock the viewport size to ensure that snapshots are captured consistently across different environments and CI runners:
test.use({ viewport: { width: 1280, height: 800 } });
We also disable CSS animations and transitions, which can otherwise introduce subtle visual differences between runs:
await page.addStyleTag({
content: `
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
`,
});
We initially experimented with waiting for specific DOM selectors to indicate when a story had finished rendering. In practice, this proved unreliable in our setup. The Storybook root container is often populated before all rendering and styling has completed, which led to snapshots being captured too early.
Instead, we rely on waiting for the page to reach a network idle state before capturing each snapshot:
await page.waitForLoadState("networkidle");
With this setup, every Storybook story is continuously validated against its visual baseline, providing clear diffs in pull requests and ensuring that UI regressions are identified and addressed before they reach production.
Essential tooling and configuration
To support this workflow, only a small number of additional tools are required, all of which can be integrated into an existing development and CI setup with minimal overhead.
At the core of the solution is Playwright, which provides the browser automation layer used to render and interact with Storybook stories. This is paired with the official @percy/playwright package, which captures and uploads visual snapshots as part of the test run. Both packages are installed as development dependencies and run exclusively within the test environment.
In addition to these local dependencies, the CI environment must be configured with a single piece of sensitive configuration, the Percy access token. This token is generated within the Percy dashboard and is injected into the pipeline as a secure environment variable, typically exposed as PERCY_TOKEN.

Terminal output showing Percy wrapping the Playwright test runner to capture and upload visual snapshots
With these dependencies and credentials in place, the project is able to generate and authenticate visual snapshots as part of its automated test suite.
Running visual regression tests in CI
Once the tooling is in place, visual regression testing is integrated directly into the delivery pipeline. On each pull request and deployment, the pipeline builds the static Storybook output and executes the Percy-powered Playwright test suite against it.

Baseline vs Changes — visual testing in action
During this process, visual snapshots are captured and uploaded automatically, with any unexpected differences surfaced as part of the CI results. These diffs are visible alongside the corresponding pull request, allowing teams to review and address regressions before code is merged.
This workflow ensures that Storybook remains a continuously validated source of truth for UI behaviour and appearance, and that visual changes are reviewed with the same discipline as functional changes.
Conclusion
Automated visual quality assurance is not simply about generating snapshots. It is about building confidence in the changes we ship. By validating every Storybook story on each pull request, teams can identify visual regressions early, reduce rework, and focus more of their time on delivering new functionality.
With the right tooling and a small amount of additional discipline, Storybook can evolve from a documentation tool into a continuously validated source of truth for your user interface.
메타데이터
- post_id
- ff1f7db24c00
- slug
- turning-storybook-into-a-visual-testing-platform-ff1f7db24c00
- url
- https://medium.com/john-lewis-software-engineering/turning-storybook-into-a-visual-testing-platform-ff1f7db24c00
- canonical_url
- https://medium.com/john-lewis-software-engineering/turning-storybook-into-a-visual-testing-platform-ff1f7db24c00
- author_url
- https://medium.com/@0_7734
- status
- ok
- fetched_at
- 2026-06-15 20:49:13