Testland
Browse all skills & agents

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-testing
View source

storybook-visual-regression-testing

Overview

Storybook ships two officially-supported paths to visual regression testing:

  1. Hosted (Chromatic addon). Install @chromatic-com/storybook; every story becomes a Chromatic snapshot automatically. Review happens in the Chromatic UI. This is the default recommendation in the Storybook visual-testing docs (opens in new window).
  2. Self-hosted (test-runner + Playwright snapshots). Install @storybook/test-runner and add a postVisit hook that calls expect(page).toHaveScreenshot(). Baselines live in the repo, no external service.

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

  • The repo has .storybook/ with a working Storybook config.
  • The team wants per-story visual coverage (atom-level), not page-level coverage.
  • Either: the team is already paying for / using Chromatic (path 1), or the team prefers committed-to-repo baselines (path 2).

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)):

HookWhenUse for
setup()Once, before all testsOne-time setup (e.g. starting a mock server).
preVisit(page, context)Before each story renders in the browserSet viewport, inject auth, mock APIs.
`postVisit(page, context)After each story has fully renderedVisual 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-storybook

For 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_PID

The wait-on step is important - test-storybook will fail unhelpfully if the server is not up before it starts.

Choosing between paths

QuestionPath 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: 14

concurrently -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.