ScreenshotNeo

BlogGuides

Visual Regression Testing in Figma: A Complete Workflow with Storybook and Chromatic

Learn how to connect Figma designs to Storybook visual tests, establish pixel baselines, review diffs, and automate regression checks in CI.

By the ScreenshotNeo team29 September 202610 min read

Visual Regression Testing in Figma: A Complete Workflow with Storybook and Chromatic

Direct answer: Figma is usually the design reference for visual regression testing, while a browser-based runner captures the implemented interface. The practical workflow is to turn reusable UI components and their states into Storybook stories, publish Storybook to Chromatic, connect those stories to matching Figma components with the official Figma Storybook plugin, and run pixel comparisons in CI. Figma helps everyone identify the intended design; Storybook and Chromatic render the code and compare screenshots against approved baselines.

This distinction matters. A markup snapshot checks serialized HTML-like output. A visual test checks rendered pixels, which is what exposes a wrong spacing value, font fallback, contrast change, clipped content, or unexpected responsive layout. Storybook describes visual tests as comparisons of rendered pixels against known baselines. Chromatic captures each story, compares it with the previous baseline, and highlights changed pixels for review.

What visual regression testing in Figma actually means

There is no single “Figma screenshot test” that runs inside a design file and proves the production page is correct. Figma supplies the reference: components, variants, states, spacing decisions, colors, typography, and interaction intent. Your application supplies the implementation. The regression system captures that implementation in a controlled browser environment and records images that can be compared over time.

Figma supplies the reference while Storybook and Chromatic compare rendered component pixels.
Figma supplies the reference while Storybook and Chromatic compare rendered component pixels.

The Figma Storybook plugin provides the connection between those two systems. With a Storybook project published on Chromatic, Figma edit permission, and Chromatic collaboration access, a team can link a published story to a Figma component, variant, or instance. During handoff and review, a designer can move from a Figma object to the corresponding rendered story instead of searching through a separate test dashboard.

Layer Responsibility Typical output
Figma Design reference and component relationships Components, variants, states, layout intent
Storybook Reproducible implementation states Stories for default, loading, error, empty, hover, focus, and other states
Chromatic Cloud rendering, screenshot capture, pixel diffing, and review Baselines, changed-pixel overlays, pull-request checks
CI Runs the visual test on code changes Pass, review-required, or failed build status

1. Define the component inventory

Start with the components that carry the most visual risk or appear across many screens: buttons, navigation, cards, forms, tables, dialogs, and layout primitives. Map Figma components and variants to implementation components. Record states that are easy to miss in a static design handoff:

  • Default, hover, focus-visible, pressed, disabled, and loading states.
  • Validation errors, helper text, empty states, and long-content states.
  • Light and dark themes.
  • Mobile, tablet, and desktop widths.
  • Long labels, translated strings, and missing or slow-loading assets.

2. Build deterministic Storybook stories

A story should render one meaningful, repeatable state. Avoid stories that depend on the current time, random data, a live API, or the logged-in user. Mock those inputs. Give each story stable content so a text change is a deliberate review rather than noise.

import type { Meta, StoryObj } from '@storybook/react';
import { PricingCard } from './PricingCard';

const meta = {
  title: 'Billing/PricingCard',
  component: PricingCard,
  parameters: { layout: 'centered' },
} satisfies Meta<typeof PricingCard>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: {
    name: 'Growth',
    price: '$15',
    description: 'For teams shipping production interfaces.',
    highlighted: false,
  },
};

export const Highlighted: Story = {
  args: { ...Default.args, highlighted: true },
};

export const LongContent: Story = {
  args: {
    ...Default.args,
    description: 'A deliberately long description that checks wrapping and card height.',
  },
};

Keep stories focused enough that a reviewer can understand a diff quickly. A full-page story is still useful for navigation and composition, but it should complement component stories rather than replace them.

3. Publish Storybook to Chromatic

Connect the repository to Chromatic and publish a build from CI or a local command. Install the official @chromatic-com/storybook addon in the Storybook project. The first visual-test build creates baseline snapshots. Later builds capture the same stories and compare them with those baselines.

npm install --save-dev chromatic @chromatic-com/storybook
npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN

Store the project token in your CI secret manager. Do not commit it to the repository. The exact CI command can be adapted to your package manager and pipeline, but the important property is that every pull request renders the same story set under controlled conditions.

Install the official Figma Storybook plugin. In Figma, select the component, variant, or instance that represents the implementation state, then attach the corresponding published Chromatic story. A useful naming convention is to keep the Storybook title close to the Figma component hierarchy, for example Forms/TextField/Error. This makes links understandable during design review and reduces mistaken mappings.

5. Run visual tests in CI

Run the Chromatic build on pull requests and the branches that feed production. A visual test should produce one of three outcomes:

  1. Unchanged: the captured pixels match the approved baseline.
  2. Changed and intentional: a reviewer confirms the design or implementation change and accepts a new baseline.
  3. Changed and unexpected: the author fixes the code before merging.

Never auto-accept every diff. An accepted baseline becomes the new reference, so an accidental change can hide future regressions.

Choose a comparison matrix deliberately

The same component can render differently when its environment changes. Define the matrix that matters to your product instead of creating every possible combination.

Axis Examples Why it matters
Browser Chromium, Firefox, WebKit Font metrics, form controls, and layout behavior differ.
Viewport 375px, 768px, 1440px Breakpoints can create wrapping, overflow, or hidden controls.
Device scale Standard and retina Raster assets and antialiasing can change.
Theme Light, dark, high contrast Colors, shadows, borders, and contrast requirements change.
State Loading, error, empty, populated Conditional UI often regresses outside the default path.
Content Short, long, translated Text expansion exposes clipping and layout assumptions.

Chromatic documents browser, viewport, device, theme, and component-state variations as supported snapshot dimensions. Keep the set stable. Adding a new viewport creates new baselines that need review; changing an existing viewport can make many historical diffs hard to interpret.

What to compare: component or full page?

Component-level coverage is fast and precise. It is the best first layer for reusable controls and all their states. A changed button style can be reviewed once instead of rediscovered across many pages.

Full-page coverage catches composition problems: a broken grid, a missing route-level font, an incorrect header offset, or a modal that overlays the wrong region. Use it for critical journeys and representative pages, but keep the number of pages manageable so reviewers can investigate every change.

End-to-end visual flows are useful when appearance depends on navigation or user actions. Capture after login, filtering, opening a dialog, or submitting a form. These tests need especially careful data control because a changed account or API response can create noisy diffs.

Reduce false positives

  • Use fixed fonts and wait for fonts to finish loading before capture.
  • Mock network responses and freeze clocks when timestamps appear in the UI.
  • Use stable seed data and deterministic ordering.
  • Hide blinking carets, animated cursors, carousels, and video during capture.
  • Wait for images and layout-critical data before taking a snapshot.
  • Review anti-aliasing differences separately from meaningful layout changes.

When a diff is intentional, include the reason in the pull request. The review record should answer what changed, which Figma decision authorized it, and whether other themes or viewports were checked.

Or skip the browser setup

If your requirement is to capture a reference page, an implemented route, or a Figma-exported prototype without maintaining a browser harness, ScreenshotNeo provides a website screenshot API. The request below returns an image for a URL; see the ScreenshotNeo API documentation for the complete parameter set.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo accepts options for full-page capture with lazy images loaded, CSS-element capture, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, clicks, selector waits, delays, network idle, blocked ads and trackers, custom headers and cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching with a chosen TTL, signed public image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, PDFs, HTML/CSS-to-image, and usage reporting.

It removes cookie-consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and whether the request was billed. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account to capture up to 1,000 screenshots each month without a card.

Automate baseline checks in a pull request

A practical CI job has four stages:

A clean capture removes common overlays before the screenshot is returned.
A clean capture removes common overlays before the screenshot is returned.
  1. Install dependencies from the lockfile.
  2. Build Storybook with the same environment variables used for review.
  3. Run Chromatic and wait for its result.
  4. Require human review for changed snapshots before merge.
name: visual-regression
on: [pull_request]
jobs:
  chromatic:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npx chromatic --project-token=${{ secrets.CHROMATIC_PROJECT_TOKEN }} --exit-zero-on-changes

Whether your pipeline fails immediately on changes or posts a review status depends on your team’s policy. A strict gate is appropriate for a small, stable component library. A review-required status can work better while a product is establishing its first reliable baselines.

Troubleshooting

Symptom Likely cause Fix
Every story changed after a dependency update Browser, font, or rendering environment changed. Inspect the build environment and font loading first. Rebaseline only after confirming the differences are expected.
Text wraps differently in CI The intended webfont was not loaded or the viewport differs. Wait for fonts, verify the font files are available, and make viewport settings explicit.
Only animated stories fail The capture occurs at a different animation frame. Disable animation for visual tests or freeze the animation at a deterministic state.
Images are blank Assets load after the snapshot or a remote request is unavailable. Mock the asset, preload it, and wait for the relevant selector or network idle.
Local and cloud results disagree Different browser versions, operating systems, device scales, or fonts. Treat the cloud build as the review authority and reproduce its configuration locally where possible.
Figma link opens the wrong state The story mapping points to the component but not the correct variant. Link each Figma variant or instance to its exact Storybook story and use matching names.
Diffs include timestamps or random IDs Non-deterministic data appears in the rendered output. Freeze the clock and replace random or live values with fixed fixtures.
Pull requests become too slow to review Too many redundant pages or environment combinations. Keep broad state coverage at component level and reserve full-page tests for critical routes.

Performance, reliability, and cost notes

Visual testing cost is mostly review time and rendered snapshots. Component stories usually provide more defect coverage per snapshot than duplicating the same component across many pages. Start with a small matrix, then add browsers, themes, or pages when a real defect or product requirement justifies them.

Reliability improves when the test input is deterministic: fixed data, stable fonts, controlled network responses, explicit viewports, and disabled motion. Treat baseline changes as code-review events. A green build only means the pixels match the approved reference; it does not prove that the design itself is correct.

For external page screenshots, ScreenshotNeo’s billing model can reduce wasted capture cost: only clean shots are billed, while bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Its cache TTL, asynchronous jobs, signed webhooks, and bulk capture can also help when a comparison pipeline needs many URLs.

Checklist for a Figma-connected visual test system

  • Every important Figma component maps to a Storybook component.
  • Stories cover variants, interaction states, themes, and representative content lengths.
  • Stories use deterministic data, fonts, assets, and timing.
  • Storybook is published to Chromatic and linked from Figma.
  • Browser, viewport, device, and theme dimensions are intentional.
  • Pull requests run visual tests automatically.
  • Reviewers distinguish intentional design changes from regressions.
  • New baselines include a reason and, when relevant, a Figma reference.
  • Full-page flows cover critical composition and navigation risks.

FAQ

Can Figma itself run pixel regression tests?

Figma is the design reference and linking surface. The rendered pixel comparison normally runs against the implemented UI in a browser-based system such as Storybook with Chromatic.

Are visual tests the same as DOM snapshot tests?

No. DOM or markup snapshots compare serialized structure. Visual tests compare rendered pixels and therefore catch user-visible differences such as spacing, color, typography, size, and contrast.

Should every Figma variant become a Storybook story?

Create a story for every state that can change independently or fail independently. Combine purely cosmetic combinations when the additional snapshot would not improve review coverage.

When should I use a full-page screenshot?

Use one for critical routes and composition risks. Use isolated component stories for faster, more focused coverage of reusable UI states.

Can ScreenshotNeo replace Chromatic?

ScreenshotNeo is a URL screenshot API and MCP server. It is useful for capturing deployed pages or reference URLs without browser setup. Chromatic remains the workflow described here for Storybook baselines, pixel diffs, and pull-request review.