npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

story-diff

v1.0.0

Published

Test-framework-agnostic visual regression testing for Storybook components using Puppeteer or Playwright

Downloads

158

Readme

Story Diff

The Completely Free, Open-Source Chromatic Alternative

npm version License: MIT

Test-framework-agnostic visual regression and snapshot testing for Storybook components using Puppeteer or Playwright. Story Diff enables you to capture screenshots of your Storybook stories and compare them against baselines, regardless of whether you use Vitest, Jest, Playwright Test, or any other test runner. It uses either puppeteer or playwright to load the stories, and a combination of pixelmatch & pngjs for the image comparison.

Installation

npm install --save-dev story-diff puppeteer
npm install --save-dev story-diff playwright @playwright/test
npx playwright install chromium

Requirements:

  • Node.js 20 or higher
  • Storybook 10.0.0 or higher
  • Puppeteer 24.41.0 or higher
  • Playwright 1.59.1 or higher when using the Playwright provider

Quick Start

Create a test file in your preferred testing framework (e.g., Vitest, Jest):

// visual.test.ts
import { StoryDiff } from 'story-diff';

describe('Storybook Visual Regression', () => {
  let diff: StoryDiff;

  beforeAll(async () => {
    // 1. Initialize the StoryDiff instance
    diff = new StoryDiff({
      storybookUrl: 'http://localhost:6006',
      snapshotsDir: './visual-snapshots',
      browser: {
        provider: 'puppeteer',
      },
    });

    // 2. Launch browser and ensure Storybook is ready
    await diff.setup();
  }, 60000);

  afterAll(async () => {
    // 3. Clean up browser instances
    await diff.teardown();
  });

  // 4. Capture and assert single story
  it('matches Button primary baseline', async () => {
    const result = await diff.assertMatchesBaseline('components-button--primary', {
      snapshotName: 'button-primary-desktop',
      viewport: 'desktop',
    });

    expect(result.match).toBe(true);
  });

  // 5. Test globals/themes
  it('matches dark mode', async () => {
    const result = await diff.assertMatchesBaseline('components-button--primary', {
      snapshotName: 'button-primary-dark',
      globals: { theme: 'dark' },
    });
    expect(result.match).toBe(true);
  });

  // 6. Test multiple stories declaratively in batch
  it('supports declarative batch execution', async () => {
    const results = await diff.runAll([
      {
        componentName: 'Button',
        storyPath: 'components-button',
        stories: ['primary', 'secondary'],
        viewports: ['mobile', 'desktop'],
      },
    ]);

    results.forEach((r) => {
      expect(r.result.match).toBe(true);
    });
  }, 60000);
});

Playwright Test Example

import { expect, test } from '@playwright/test';
import { StoryDiff } from 'story-diff';

test.describe('Storybook Visual Regression', () => {
  let diff: StoryDiff;

  test.beforeAll(async () => {
    diff = new StoryDiff({
      storybookUrl: 'http://localhost:6006',
      snapshotsDir: './visual-snapshots',
      browser: {
        provider: 'playwright',
        browserName: 'chromium',
      },
    });

    await diff.setup();
  });

  test.afterAll(async () => {
    await diff.teardown();
  });

  test('matches Button primary baseline', async () => {
    const result = await diff.assertMatchesBaseline('components-button--primary', {
      snapshotName: 'button-primary-desktop',
      viewport: 'desktop',
    });

    expect(result.match).toBe(true);
  });
});

Running Tests

  1. Ensure your Storybook server is running:

    npm run storybook

    Tip: To prevent Storybook from opening a browser window, use the --no-open flag:

    storybook dev -p 6006 --no-open
  2. In a separate terminal, run your test framework:

    # Vitest
    vitest run visual.test.ts
    
    # Jest
    NODE_OPTIONS=--experimental-vm-modules jest visual.test.ts
    
    # Playwright Test
    playwright test visual.spec.ts

Tip: For CI, use wait-on and concurrently to automate this. See CI/CD Integration.

Core Concepts

Story IDs

Story IDs follow Storybook's naming convention: {path}--{story-name}. For example:

  • components-button--primary
  • pages-login--default
  • ui-card--with-image

You can find story IDs in your Storybook's URL or in the index.json file.

Snapshot Naming

Snapshot names are used to identify baseline images on disk. They should be:

  • Descriptive and unique
  • Lowercase with hyphens
  • Include viewport if testing multiple sizes

Examples:

  • button-primary-desktop
  • card-with-image-mobile
  • login-form-dark-mode

Viewports

Story Diff includes three default viewports:

  • mobile: 393×852px
  • tablet: 768×1024px
  • desktop: 1440×900px

You can reference them by name or define custom viewports. See Custom Viewports.

Globals

Globals are Storybook's way of passing configuration to stories (e.g., theme, locale). Pass them via the globals option:

await diff.assertMatchesBaseline('button--primary', {
  snapshotName: 'button-dark',
  globals: { theme: 'dark', locale: 'en-US' },
});

Automatic Lifecycle Management

Story Diff can automatically handle the beforeAll and afterAll hooks to manage the browser lifecycle.

Using the Constructor

Pass autoLifecycle: true to the constructor. This will attempt to detect beforeAll and afterAll in the global scope (supported by Vitest and Jest).

const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  autoLifecycle: true,
});

Using the hookLifecycle Helper

If you prefer explicit registration or need to support environments without global hooks (like Playwright Test), use the hookLifecycle helper:

import { test } from '@playwright/test';
import { StoryDiff, hookLifecycle } from 'story-diff';

const diff = new StoryDiff();

// Explicitly pass hooks for Playwright Test
hookLifecycle(diff, {
  enabled: true,
  beforeAll: test.beforeAll,
  afterAll: test.afterAll,
  timeout: 60000,
});

Configuration Options

type LifecycleConfig = {
  enabled?: boolean; // Enable automatic management
  beforeAll?: HookFn; // Custom beforeAll implementation
  afterAll?: HookFn; // Custom afterAll implementation
  timeout?: number; // Timeout for hooks (default: 60000ms)
};

API Reference

StoryDiff Class

The main class for visual regression testing.

Constructor

const diff = new StoryDiff(config?: StoryDiffConfig);

If you omit config, Story Diff searches upward from process.cwd() for one of these files and loads it automatically:

  • story-diff.config.mjs
  • story-diff.config.js
  • story-diff.config.cjs
  • story-diff.config.json
  • story-diff.mjs
  • story-diff.js
  • story-diff.cjs
  • story-diff.json

Lifecycle Methods

setup()

Launches the browser and verifies Storybook is ready.

await diff.setup();

Must be called before any capture or assertion methods.

teardown()

Closes the browser and cleans up resources.

await diff.teardown();

Should be called in test cleanup (e.g., afterAll).

Configuration

StoryDiffConfig

type StoryDiffConfig = {
  // Required: URL where Storybook is hosted
  storybookUrl: string;

  // Required: Directory path for baseline images
  snapshotsDir: string;

  // Optional: Custom viewport definitions
  viewports?: Record<string, Viewport>;

  // Optional: Browser automation configuration
  browser?: BrowserConfig;

  // Optional: Image comparison settings
  comparison?: ComparisonConfig;

  // Optional: Update mode (overwrites baselines)
  update?: boolean;

  // Optional: Fail when baseline is missing
  failOnMissingBaseline?: boolean;

  // Optional: Logger configuration
  logger?: LoggerConfig;

  // Optional: Default capture options applied to every call
  defaults?: CaptureOptions;

  // Optional: Batch definitions used when runAll() is called without arguments
  tests?: readonly StoryVisualTest[];
};

When loaded from a config file, snapshotsDir is resolved relative to that file.

BrowserConfig

type BrowserConfig = {
  // Browser automation provider. Default: 'puppeteer'
  provider?: 'puppeteer' | 'playwright';

  // Playwright-only browser engine. Default: 'chromium'
  browserName?: 'chromium' | 'firefox' | 'webkit';

  // Playwright-only browser channel, e.g. 'chromium' or 'chrome'
  channel?: string;

  // Run browser in headless mode. Default: true
  headless?: boolean;

  // Extra browser launch args
  args?: readonly string[];

  // Launch timeout in ms
  timeout?: number;

  // Custom browser executable path
  executablePath?: string;
};

LoggerConfig

type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';

type LoggerConfig = {
  // Logging level. Default: 'silent'
  level?: LogLevel;
  // Custom logger function. If not provided, uses console
  customLogger?: (level: LogLevel, message: string, ...args: unknown[]) => void;
};

ComparisonConfig

type ComparisonConfig = {
  // Pixelmatch threshold (0-1). Lower = stricter. Default: 0.1
  threshold?: number;

  // Acceptable diff as percentage (0-100) or pixel count. Default: 0
  failureThreshold?: number;

  // Whether failureThreshold is 'percent' or 'pixel'. Default: 'percent'
  failureThresholdType?: 'percent' | 'pixel';

  // Allow size mismatches between actual and baseline. Default: false
  allowSizeMismatch?: boolean;
};

Methods

captureStory()

Captures a screenshot of a story without comparison.

const screenshot: Buffer = await diff.captureStory(
  storyId: string,
  options?: CaptureOptions
);

Options:

type CaptureOptions = {
  viewport?: string | Viewport;
  globals?: Record<string, string>;
  waitForSelector?: string;
  waitForTimeout?: number;
};

compareWithBaseline()

Compares a screenshot buffer against a baseline.

const result: ComparisonResult = await diff.compareWithBaseline(
  screenshot: Buffer,
  snapshotName: string,
  comparisonOverride?: ComparisonConfig
);

assertMatchesBaseline()

Captures a story and asserts it matches the baseline. Throws on mismatch.

const result: ComparisonResult = await diff.assertMatchesBaseline(
  storyId: string,
  options: AssertOptions
);

Options:

type AssertOptions = CaptureOptions & {
  snapshotName: string;
  comparison?: ComparisonConfig;
};

Throws:

  • BaselineMissingError - When baseline doesn't exist and failOnMissingBaseline is true
  • VisualRegressionError - When images differ beyond threshold
  • SizeMismatchError - When dimensions differ and allowSizeMismatch is false

updateBaseline()

Saves a screenshot as a new baseline.

const path: string = await diff.updateBaseline(
  screenshot: Buffer,
  snapshotName: string
);

runAll()

Batch processes multiple stories declaratively.

const results: BatchResult[] = await diff.runAll(
  tests?: StoryVisualTest[]
);

Input:

type StoryVisualTest = {
  componentName: string;
  storyPath: string;
  stories: readonly string[];
  viewports?: readonly string[];
  globals?: Record<string, string>;
};

Output:

type BatchResult = {
  storyId: string;
  snapshotName: string;
  viewport: string;
  result: ComparisonResult;
};

Type Definitions

ComparisonResult

type ComparisonResult = {
  match: boolean;
  diffPixels: number;
  diffPercentage: number;
  diffImage: Buffer | null;
  baselineCreated: boolean;
  baselineMissing: boolean;
  snapshotPath: string;
  diffPath: string | null;
};

Viewport

type Viewport = {
  name: string;
  width: number;
  height: number;
};

Advanced Usage

Logging Configuration

Control output verbosity with the logger configuration:

// Default - silent mode (no output)
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  // logger is silent by default, no need to configure
});

// Info mode - recommended for development
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  logger: {
    level: 'info',
  },
});

// Error only
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  logger: {
    level: 'error',
  },
});

// Debug mode - verbose output
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  logger: {
    level: 'debug',
  },
});

// Custom logger (e.g., integrate with your logging framework)
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  logger: {
    level: 'info',
    customLogger: (level, message, ...args) => {
      // Send to your logging service
      myLogger.log({ level, message, args });
    },
  },
});

Log Levels:

  • silent: No output (default)
  • error: Only errors
  • warn: Errors and warnings
  • info: Errors, warnings, and informational messages
  • debug: All messages including detailed debugging information

Headless vs Headed Mode

By default, Story Diff runs the browser in headless mode. For debugging or E2E testing, you can run in headed mode:

// Headed mode - see the browser window
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  browser: {
    headless: false,
  },
});

// Headless mode (default)
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  browser: {
    headless: true, // or omit this line
  },
});

When to use headed mode:

  • Debugging test failures
  • Developing new tests
  • Demonstrating visual tests
  • Internal E2E testing

Note: Headed mode is slower and requires a display. Always use headless mode in CI/CD environments.

Environment Variable Control:

You can control headless mode via environment variables:

# Run in headed mode for debugging
HEADLESS=false npm test

# Run in headless mode (default)
npm test
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  browser: {
    headless: process.env.HEADLESS !== 'false',
  },
  logger: {
    level: (process.env.LOG_LEVEL as LogLevel) || 'silent',
  },
});

This allows you to easily switch between modes without changing code:

# Debug with headed browser and verbose logging
HEADLESS=false LOG_LEVEL=debug npm test

# CI mode with default silent logging
npm test

# Enable info logging for CI troubleshooting
LOG_LEVEL=info npm test

Batch Testing

Test multiple stories and viewports efficiently:

const results = await diff.runAll([
  {
    componentName: 'Button',
    storyPath: 'components-button',
    stories: ['primary', 'secondary', 'disabled'],
    viewports: ['mobile', 'tablet', 'desktop'],
  },
  {
    componentName: 'Card',
    storyPath: 'components-card',
    stories: ['default', 'with-image'],
    viewports: ['desktop'],
    globals: { theme: 'dark' },
  },
]);

// Generates snapshots like:
// - button-primary-mobile.png
// - button-primary-tablet.png
// - button-primary-desktop.png
// - button-secondary-mobile.png
// ... etc

Custom Viewports

Define custom viewport sizes:

const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  viewports: {
    mobile: { name: 'mobile', width: 375, height: 667 },
    tablet: { name: 'tablet', width: 768, height: 1024 },
    desktop: { name: 'desktop', width: 1920, height: 1080 },
    '4k': { name: '4k', width: 3840, height: 2160 },
  },
});

Or pass inline viewports:

await diff.assertMatchesBaseline('button--primary', {
  snapshotName: 'button-custom',
  viewport: { name: 'custom', width: 1024, height: 768 },
});

Root Config File

If you want one global config object for the whole project, create a root config file and instantiate StoryDiff without passing config in your test hooks:

// story-diff.config.mjs
export default {
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './visual-snapshots',
  failOnMissingBaseline: false,
  defaults: {
    viewport: 'desktop',
    globals: {
      theme: 'dark',
      locale: 'en-AU',
    },
    waitForSelector: '#storybook-root',
    waitForTimeout: 300,
  },
  tests: [
    {
      componentName: 'Button',
      storyPath: 'components-button',
      stories: ['primary', 'secondary'],
      viewports: ['mobile', 'desktop'],
    },
  ],
};
import { StoryDiff } from 'story-diff';

const diff = new StoryDiff();

beforeAll(async () => {
  await diff.setup();
});

afterAll(async () => {
  await diff.teardown();
});

it('uses discovered defaults automatically', async () => {
  const result = await diff.assertMatchesBaseline('components-button--primary', {
    snapshotName: 'button-primary-dark',
  });

  expect(result.match).toBe(true);
});

it('can use configured batch tests without redefining them', async () => {
  const results = await diff.runAll();
  expect(results).toHaveLength(4);
});

Comparison Thresholds

Fine-tune sensitivity to visual changes:

// Global configuration
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  comparison: {
    threshold: 0.05, // Stricter pixel matching
    failureThreshold: 0.5, // Allow 0.5% difference
    failureThresholdType: 'percent',
  },
});

// Per-assertion override
await diff.assertMatchesBaseline('button--primary', {
  snapshotName: 'button',
  comparison: {
    threshold: 0.2, // More lenient for this test
    failureThreshold: 100, // Allow 100 pixels difference
    failureThresholdType: 'pixel',
  },
});

Async Components

Wait for async content to load before capturing:

// Wait for a specific element
await diff.assertMatchesBaseline('async-component--default', {
  snapshotName: 'async-loaded',
  waitForSelector: '#data-loaded',
});

// Wait for a fixed duration
await diff.assertMatchesBaseline('animated-component--default', {
  snapshotName: 'animation-complete',
  waitForTimeout: 2000, // 2 seconds
});

CI/CD Integration

Using concurrently and wait-on

For local development and CI, use these tools to automate Storybook startup:

{
  "scripts": {
    "test:visual": "concurrently -k -s first \"npm run storybook -- --ci\" \"wait-on http://localhost:6006 && vitest run visual.test.ts\""
  }
}

GitHub Actions Example

name: Visual Regression Tests

on: [push, pull_request]

jobs:
  visual-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build Storybook
        run: npm run build-storybook

      - name: Run visual tests
        run: npm run test:visual

      - name: Upload diff images
        if: failure()
        uses: actions/upload-artifact@v7
        with:
          name: visual-diffs
          path: ./visual-snapshots/*-diff.png

Error Handling

Story Diff provides custom error classes for robust error handling:

import {
  VisualRegressionError,
  BaselineMissingError,
  SizeMismatchError,
  NotInitializedError,
  StorybookConnectionError,
  ViewportNotFoundError,
} from 'story-diff';

try {
  await diff.assertMatchesBaseline('button--primary', {
    snapshotName: 'btn',
  });
} catch (e) {
  if (e instanceof VisualRegressionError) {
    console.log(`Visual diff detected: ${e.diffPercentage}%`);
    console.log(`Diff image: ${e.diffPath}`);
  } else if (e instanceof BaselineMissingError) {
    console.log(`Baseline missing: ${e.snapshotPath}`);
  } else if (e instanceof SizeMismatchError) {
    console.error('Snapshot dimensions have changed!');
  } else if (e instanceof NotInitializedError) {
    console.error('Call setup() before using StoryDiff');
  } else if (e instanceof StorybookConnectionError) {
    console.error('Cannot connect to Storybook');
  } else if (e instanceof ViewportNotFoundError) {
    console.error('Unknown viewport name');
  }
}

Error Types

| Error | When Thrown | Properties | | -------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------- | | VisualRegressionError | Images differ beyond threshold | snapshotName, diffPercentage, diffPixels, snapshotPath, diffPath | | BaselineMissingError | Baseline doesn't exist (when failOnMissingBaseline: true) | snapshotName, snapshotPath | | SizeMismatchError | Image dimensions differ (when allowSizeMismatch: false) | - | | NotInitializedError | Methods called before setup() | - | | StorybookConnectionError | Cannot reach Storybook server | - | | ViewportNotFoundError | Unknown viewport name used | - |

Baseline Management

Creating Baselines

On first run, baselines must be created. There are two approaches:

1. Explicit Update Mode (Recommended)

UPDATE_SNAPSHOTS=true npm test
const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  update: process.env.UPDATE_SNAPSHOTS === 'true',
});

2. Silent Creation Mode

const diff = new StoryDiff({
  storybookUrl: 'http://localhost:6006',
  snapshotsDir: './snapshots',
  failOnMissingBaseline: false, // Creates baselines silently
});

Updating Baselines

When components change intentionally, update baselines:

UPDATE_SNAPSHOTS=true npm test

Or update specific tests:

UPDATE_SNAPSHOTS=true npm test -- button.test.ts

Baseline Storage

Baselines are stored as PNG files in your snapshotsDir:

visual-snapshots/
├── button-primary-desktop.png
├── button-primary-mobile.png
├── button-secondary-desktop.png
└── card-default-desktop.png

Diff images (on failure) are saved with -diff suffix:

visual-snapshots/
├── button-primary-desktop.png
└── button-primary-desktop-diff.png  ← Generated on mismatch

Commit baselines to version control to track visual changes over time.

Testing Frameworks

Vitest

Vitest is the recommended framework for its speed and ESM support.

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    testTimeout: 60000,
    hookTimeout: 60000,
  },
});
// visual.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { StoryDiff } from 'story-diff';

describe('Visual Regression', () => {
  let diff: StoryDiff;

  beforeAll(async () => {
    diff = new StoryDiff({
      storybookUrl: 'http://localhost:6006',
      snapshotsDir: './snapshots',
    });
    await diff.setup();
  }, 60000);

  afterAll(async () => {
    await diff.teardown();
  });

  it('matches baseline', async () => {
    const result = await diff.assertMatchesBaseline('button--primary', {
      snapshotName: 'button-primary',
    });
    expect(result.match).toBe(true);
  });
});

Jest

Jest requires experimental VM modules for ESM support.

// jest.config.ts
export default {
  preset: 'ts-jest/presets/default-esm',
  extensionsToTreatAsEsm: ['.ts'],
  testTimeout: 60000,
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
};

Run with:

NODE_OPTIONS=--experimental-vm-modules jest

Other Frameworks

Story Diff works with any test framework that supports async/await:

  • Mocha
  • Ava
  • Tape
  • Node's built-in test runner

Troubleshooting

Storybook Connection Errors

Problem: StorybookConnectionError: Failed to connect to Storybook

Solutions:

  • Ensure Storybook is running: npm run storybook
  • Verify the URL is correct (default: http://localhost:6006)
  • Check firewall/network settings
  • Increase browser timeout:
    browser: {
      timeout: 60000;
    }

Flaky Tests

Problem: Tests pass sometimes, fail other times

Solutions:

  • Use waitForSelector for async content
  • Increase waitForTimeout for animations
  • Disable animations in Storybook preview:
    // .storybook/preview.ts
    export const parameters = {
      chromatic: { disableSnapshot: false },
      // Disable animations
      layout: 'fullscreen',
    };
  • Adjust failureThreshold to allow minor differences

Size Mismatch Errors

Problem: SizeMismatchError: Image size mismatch

Solutions:

  • Component dimensions changed - update baseline
  • Viewport changed - ensure consistent viewport usage
  • Allow size mismatches temporarily:
    comparison: {
      allowSizeMismatch: true;
    }

Memory Issues

Problem: Tests crash with out-of-memory errors

Solutions:

  • Close browser between test suites
  • Reduce concurrent tests
  • Increase Node memory:
    NODE_OPTIONS=--max-old-space-size=4096 npm test

Puppeteer Installation Issues

Problem: Puppeteer fails to install or launch

Solutions:

  • Install system dependencies (Linux):
    sudo apt-get install -y \
      libnss3 libatk1.0-0 libatk-bridge2.0-0 \
      libcups2 libdrm2 libxkbcommon0 libxcomposite1 \
      libxdamage1 libxrandr2 libgbm1 libasound2
  • Use custom Chrome path:
    browser: {
      executablePath: '/usr/bin/google-chrome';
    }

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make your changes with tests
  4. Run tests: npm test
  5. Run E2E tests: npm run test:e2e
  6. Commit with conventional commits: feat: add new feature
  7. Push and create a pull request

Development Setup

# Clone the repository
git clone https://github.com/yourusername/story-diff.git
cd story-diff

# Install dependencies
npm install

# Build the library
npm run build

# Run unit tests
npm test

# Run E2E tests (requires Storybook)
npm run test:e2e

# Type checking
npm run typecheck

License

MIT License - Copyright (c) 2026 Shubham Sharma

See LICENSE for full details.