storybook-visual-regression-testing
Sets up visual regression coverage for a Storybook project - either via the official @chromatic-com/storybook addon (hosted) or via @storybook/test-runner with a postVisit hook that calls Playwright's toHaveScreenshot (self-hosted). Covers test-runner install, lifecycle hooks (setup / preVisit / postVisit), and CI integration. Use when a repo already has a working `.storybook/` config and the team wants per-story visual coverage rather than page-level snapshots.
Install with skills.sh (any agent)
npx skills add testland/qa --skill storybook-visual-regression-testingstorybook-visual-regression-testing
Overview
Storybook ships two officially-supported paths to visual regression testing:
This skill covers both. For full Chromatic CLI behavior (TurboSnap, exit codes, config file), pair with chromatic-visual-regression-testing; for toHaveScreenshot option syntax, pair with playwright-snapshots.
When to use
Path 1 - Hosted via Chromatic addon
Install
npm install --save-dev @chromatic-com/storybook(Per storybook-visual-testing (opens in new window).)
Activate
The addon registers itself in Storybook on next start. Sign into Chromatic from the Storybook UI, link the project, then click "Catch a UI change" to capture the first baseline (storybook-visual-testing (opens in new window)).
After the first baseline, every story functions as a visual test automatically - no per-story parameters block, no custom assertion code.
CI integration
Chromatic CI invocation is identical to running it standalone - see chromatic-visual-regression-testing for the npx chromatic CLI flag set, exit codes, TurboSnap, and chromatic.config.json schema.
Path 2 - Self-hosted via test-runner
Install
npm install --save-dev @storybook/test-runner(Per storybook-test-runner (opens in new window).)
Add the script to package.json:
{
"scripts": {
"test-storybook": "test-storybook"
}
}How it works
@storybook/test-runner turns every story into an executable test powered by Jest + Playwright under the hood (storybook-test-runner (opens in new window)). It requires a Storybook running on a discoverable URL (default http://localhost:6006) or a published Storybook static build.
Lifecycle hooks
Configure in .storybook/test-runner.ts - three hooks (storybook-test-runner (opens in new window)):
| Hook | When | Use for |
|---|---|---|
setup() | Once, before all tests | One-time setup (e.g. starting a mock server). |
preVisit(page, context) | Before each story renders in the browser | Set viewport, inject auth, mock APIs. |
| `postVisit(page, context) | After each story has fully rendered | Visual snapshot, accessibility audit, custom assertion. |
context carries id, title, and name for the current story.
Visual regression via postVisit + Playwright snapshots
.storybook/test-runner.ts:
import type { TestRunnerConfig } from '@storybook/test-runner';
import { expect } from '@playwright/test';
import { getStoryContext } from '@storybook/test-runner';
const config: TestRunnerConfig = {
async postVisit(page, context) {
// Skip stories explicitly opted out
const storyContext = await getStoryContext(page, context);
if (storyContext.parameters?.visualRegression?.disable) return;
// Locate the story's root element and snapshot it
const root = page.locator('#storybook-root');
await expect(root).toHaveScreenshot(
`${context.id}.png`,
{ animations: 'disabled', mask: [page.locator('[data-mask]')] }
);
},
};
export default config;This captures one PNG per story under tests/__snapshots__/<context.id>.png (or the path your playwright.config.ts snapshotPathTemplate resolves to). Per-story opt-out via a story parameters.visualRegression.disable = true.
For the full toHaveScreenshot option set (mask, threshold, maxDiffPixels, etc.) and the per-OS / per-browser baseline structure, see playwright-snapshots.
Running
Two-step: start Storybook, then run the test-runner against it (storybook-test-runner (opens in new window)):
# Terminal A - long-running
npm run storybook
# Terminal B
npm run test-storybookFor CI, build a static Storybook and serve it locally during the test run:
npx storybook build && \
npx http-server storybook-static --port 6006 --silent &
SERVER_PID=$!
npx wait-on http://localhost:6006
npm run test-storybook
kill $SERVER_PIDThe wait-on step is important - test-storybook will fail unhelpfully if the server is not up before it starts.
Choosing between paths
| Question | Path 1 (Chromatic) | Path 2 (self-hosted) |
|---|---|---|
| Want a hosted UI for diff review? | ✅ | ❌ |
| Need TurboSnap (story-aware change detection)? | ✅ | ❌ |
| Want baselines committed to the repo? | ❌ | ✅ |
| Acceptable to add a paid SaaS dependency? | ✅ | ❌ (free) |
| Cross-browser baselines? | ✅ built-in | ⚠️ via Playwright projects, locally |
| Per-story opt-out granularity? | ✅ via parameters.chromatic.disableSnapshot | ✅ via parameters.visualRegression.disable |
A common mid-size setup: Path 1 in CI, with Path 2 for local authoring so engineers can iterate without consuming Chromatic snapshot quota.
CI integration
For Path 2, the workflow is the standard test-runner pattern with Playwright browsers installed:
# .github/workflows/storybook-visual.yml
name: storybook-visual
on:
pull_request:
push:
branches: [main]
jobs:
test-storybook:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Build Storybook
run: npx storybook build
- name: Serve + test
run: |
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --port 6006 --silent" \
"npx wait-on http://localhost:6006 && npm run test-storybook"
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: storybook-test-runner-report
path: |
test-results/
playwright-report/
retention-days: 14concurrently -k -s first ensures the server is killed when the test process exits - without -k, the workflow hangs.
References
Related skills
chromatic-visual-regression-testing
Authors and runs Chromatic visual tests on Storybook, Playwright, or Cypress projects via the `chromatic` CLI; configures baselines, TurboSnap, UI Review, and CI gating; reads exit codes for change-vs-error classification. Use when the project ships visual regression coverage to Chromatic Cloud.
percy-visual-regression-testing
Authors Percy visual snapshot tests via the @percy/cli + framework SDK (Playwright, Cypress, Selenium, Storybook), runs them with `percy exec -- {test command}`, configures viewports / masking / ignored regions, and reviews diffs in the Percy build UI. Use when the project ships visual regression coverage to BrowserStack Percy.
playwright-snapshots
Authors Playwright `expect(page).toHaveScreenshot()` assertions, configures masks / clips / threshold / maxDiffPixels per test, manages the per-OS / per-browser snapshot directory, and runs the update flow with `--update-snapshots`. Use when the project ships self-hosted visual regression coverage in Playwright (no external snapshot service).
responsive-breakpoint-runner
Produces a single breakpoint-matrix report (rows = pages/stories, columns = viewports) across Percy, Chromatic, Playwright snapshots, or Storybook test-runner. Routes per-engine viewport syntax, runs each, and aggregates the results into one cross-breakpoint view. Use when the team needs one unified pass/fail view across three or more viewport widths instead of separate per-engine or per-breakpoint reports. The matrix-view output is the distinguishing trait: it dispatches to playwright-snapshots (and the other engines) rather than replacing any single-engine skill.
visual-baseline-conventions
Reference catalog for visual regression coverage decisions - which Storybook stories or pages get baselines, how to choose breakpoints, when to mask vs adjust threshold, when to add or remove a baseline, and a decision matrix for picking among Percy / Chromatic / Playwright / Storybook test-runner. Use when designing visual coverage for a new project or auditing an existing baseline set.
visual-baseline-gate
Consumes pre-classified visual-diff JSON and a reviewer-signed acceptance log to produce a single go/no-go CI verdict for visual regression. Blocks when intentional baseline changes lack a non-author reviewer sign-off or when regressions are present, and emits a markdown + JSON artifact for the CI step. Use this skill when the gate's input is pre-classified diff data and the enforcement concern is reviewer approval, not when the goal is fanning out to multiple engines (use a multi-engine CI orchestrator for that).
visual-diff-summarizer
Triages a PR's visual regression diffs when there are too many changed screenshots to review one by one. Clusters snapshots (from Percy, Chromatic, Playwright `toHaveScreenshot`, Storybook, Loki) by component / route, separates changes that match PR intent from cascade / regression suspects, recommends which baselines to update, and emits one PR comment pointing the reviewer at the screenshots that need actual eyes. Use when a PR has 20+ visual diffs / changed screenshots and the reviewer needs help deciding which to open.