Skip to main content

Integration with Storybook

This is your go‑to page for integrating Eyes with Storybook. It covers common usage first, then best practices, and finally advanced/less‑common options. (See Advanced Usage for edge cases.)

Usage options (from most common to specialized)

A) Let Eyes start Storybook (default)

npx eyes-storybook
  • Eyes finds your .storybook config, starts Storybook on an open port, crawls stories, takes snapshots, and sends them to the Ultrafast Grid.

B) Point to an existing Storybook URL

npx eyes-storybook -u http://localhost:6006
npx eyes-storybook -u https://storybook.yourcompany.com

Use this when Storybook is already running locally or remotely.

C) Static build

Serve your built Storybook and pass the URL:

# for example
npx http-server storybook-static -p 6006
npx eyes-storybook -u http://localhost:6006

Project configuration

Create or adjust applitools.config.js:

// applitools.config.js
/**
* @type {import('@applitools/eyes-storybook').ApplitoolsConfig}
**/
module.exports = {
apiKey: process.env.APPLITOOLS_API_KEY,
serverUrl: 'https://eyes.applitools.com',
appName: 'My Storybook App',
batchName: 'My Storybook',
showLogs: false,
};

NOTE: applitools.config.js should be located in the root directory of the project by default. Otherwise, use --conf CLI flag to point the CLI to the right place. Read more here.

Learn more about the different settings here.

Common CLI flags

  • -u, --storybook-url <url> — Reuse an existing Storybook server.
  • -p, --storybook-port <n> / -h, --storybook-host <host> — Target dev server.
  • -c, --storybook-config-dir <path> — Non‑standard config location.
  • -s, --storybook-static-dir <path[,path]> — Extra static dirs.
  • --include <title|/regex/> — Only visually check matching stories.
  • --navigation-wait-until <event> — See navigationWaitUntil above.
  • --read-stories-timeout <ms> — Fail if stories take too long to load (default: 60,000ms).
  • -e, --exitcode — Exit non‑zero on diffs/failures (default: true).

Read more here

Best practices

  • Name stories clearly (be descriptive & stable) so testName is meaningful in results.

    Why: Story titles become Eyes test names → better search & triage.

    Do

    // Button.stories.tsx
    export default { title: 'Forms/Button' };
    export const Primary = { args: { variant: 'primary', label: 'Save' } };
    export const PrimaryDisabled = {
    args: { variant: 'primary', disabled: true },
    };

    Resulting test names

    • Forms/Button: Primary
    • Forms/Button: PrimaryDisabled

    Tips

    • Encode the state in the story name (e.g., WithError, Loading, RTL).
    • Avoid generic Default in large libraries—prefer Default (Light) if you must keep it.
  • Use parameters.eyes sparingly (override only what you must).

    Why: Keep global policy in applitools.config.js. Override per story only when required.

    Global policy (config)

    // applitools.config.js
    module.exports = {
    matchLevel: 'Strict',
    browser: [
    { name: 'chrome', width: 1440, height: 900 },
    { name: 'firefox', width: 1024, height: 768 },
    ],
    ignoreCaret: true,
    };

    Per-story override (only where needed)

    // Card.stories.tsx
    export const WithToast = {
    args: { variant: 'info' },
    parameters: {
    eyes: {
    // override ONLY what differs for this story
    floatingRegions: [
    {
    selector: '.toast',
    maxUpOffset: 10,
    maxDownOffset: 10,
    maxLeftOffset: 30,
    maxRightOffset: 30,
    },
    ],
    },
    },
    };

    Don’t copy the entire browser matrix into every story. Do let the global matrix apply unless a story truly needs a different target.

  • Freeze dynamic data when possible (see Deterministic data below).

    Why: Randomness creates noisy diffs. Make stories deterministic under Eyes.

    Switch on the Eyes query flag

    // In story code (runs in the iframe)
    const qp = new URL(location.href).searchParams;
    const isEyes = qp.has('eyes-storybook');
    if (isEyes) {
    // freeze time & randomness
    // @ts-ignore
    Date.now = () => new Date('2025-01-01T00:00:00Z').getTime();
    Math.random = () => 0.42;
    }

    Disable animations just before capture

    // applitools.config.js
    module.exports = {
    scriptHooks: {
    beforeCaptureScreenshot:
    "document.documentElement.style.setProperty('animation','none','important');" +
    "document.documentElement.style.setProperty('transition','none','important');",
    },
    };

    Mock network (example with MSW)

    // in story setup
    import { http, HttpResponse } from 'msw';
    export const parameters = {
    msw: {
    handlers: [
    http.get('/api/price', () => HttpResponse.json({ price: 9.99 })),
    ],
    },
    };
  • Group runs by batch (e.g., by PR or sprint) for easier review.

    Why: Batches organize review by PR/branch/sprint and keep history meaningful.

    Set batch name with PR number

    // applitools.config.js
    module.exports = {
    batch: {
    name: process.env.CI ? `PR #${process.env.PR_NUMBER} — UI` : 'Local run',
    },
    properties: [{ name: 'team', value: 'design-systems' }],
    };

    Monorepo tip: set a unique appName per package so baselines don’t collide.

    module.exports = { appName: '@org/ui-kit' };
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';

const meta: Meta = {
title: 'Components/Button',
component: Button,
parameters: {
eyes: {
waitBeforeCapture: '#ready',
ignoreRegions: [{ selector: '.anim' }],
layoutBreakpoints: [480, 1200],
},
},
};
export default meta;

export const Primary: StoryObj = {
args: { label: 'Buy' },
parameters: {
eyes: {
browser: [
{ name: 'chrome', width: 1280, height: 800 },
{ name: 'firefox', width: 1280, height: 800 },
],
},
},
};

Supported story parameters (parameters.eyes)

  • include: boolean — Skip when false.
  • waitBeforeCapture: number | string — Milliseconds or CSS selector.
  • properties: Array<{name,value}> — Custom metadata (shows in Dashboard).
  • browser — Override Ultrafast Grid targets per story.
  • ignoreRegions: Array<CSSSelector | {left,top,width,height}>
  • floatingRegions: Array<{left,top,width,height,maxUp,maxDown,maxLeft,maxRight}>
  • layoutBreakpoints: true | number[] — Auto or explicit responsive points.

Classic stories API

storiesOf('Components/Button', module).add(
'Primary',
() => <Button label="Buy" />,
{ eyes: { waitBeforeCapture: 250 } }
);

Browsers & devices (Ultrafast Grid)

Define browser in applitools.config.js.

module.exports = {
browser: [
{ name: 'chrome', width: 1024, height: 768 },
{ name: 'firefox', width: 800, height: 600 },
{ name: 'edgechromium-one-version-back', width: 1200, height: 800 },
],
};

Chrome device emulation

module.exports = {
browser: {
name: 'chrome',
deviceName: 'Galaxy Note 8',
screenOrientation: 'portrait',
},
};

You can find the list of all supported emulated devices here.

iOS devices (real‑device rendering on UFG)

module.exports = {
browser: {
iosDeviceInfo: {
deviceName: 'iPhone XR',
screenOrientation: 'portrait',
iosVersion: 'latest', //the latest iOS version that's supported by the UFG. Use latest-1 for one version prior to the latest.
},
},
};

You can find the list of all supported iOS devices here.

Filtering which stories to check

  • CLI: --include "Button: with text" or --include /Button:.*/
  • Config: supply an include function (see above).

Deterministic data in stories

Eyes appends ?eyes-storybook=true to the story iframe URL. Use it to switch mocks/fixtures:

const isEyes = new URL(window.location).searchParams.get('eyes-storybook');
const FIXED = 1_600_000_000_000;
const date = new Date(isEyes ? FIXED : undefined);

Making stories deterministic keeps snapshots stable and reproducible across local and CI runs. It prevents “false diffs” caused only by time/random seeds, so reviewers focus on meaningful UI changes instead of noise.

Compared to masking areas with ignoreRegions, stabilizing the data preserves full visual coverage of the component. Regions silence noise by hiding parts of the UI and can accidentally mask real regressions inside those areas. The trade-off is a tiny amount of story/setup code (or telling your mock layer to honor ?eyes-storybook=true).

Rule of thumb: prefer deterministic data by default; use regions for truly irrelevant or inherently moving UI (ads, live tickers, transient toasts).

Baseline identity (how snapshots are keyed)

  • appName (defaults to package.json#name, override via config)
  • testName (derived from story path/title)
  • browser/viewport/OS (Ultrafast Grid target)
  • branchName (you can optionally influence lookup/compare with baselineBranchName / parentBranchName)

Running the example project

  1. Clone or download one of the repository examples below and navigate to that folder

    • React example
      git clone https://github.com/applitools/tutorial-storybook-react.git
      cd tutorial-storybook-react
    • Angular example
      git clone https://github.com/applitools/tutorial-storybook-angular.git
      cd tutorial-storybook-angular
    • Vue example
      git clone https://github.com/applitools/tutorial-storybook-vue.git
      cd tutorial-storybook-vue
  2. Install the dependencies

    npm install
  3. Set your API key (explained here)

  4. Run the example tests with the command below. The will create a test baseline of every story in the Storybook.

    npx eyes-storybook

  5. After the example tests complete. Visit your Applitools Eyes dashboard to view the results.