storycap

repository·master·Indexed 20 days ago

https://github.com/reg-viz/storycap

A Storybook Addon that uses Puppeteer to crawl Storybook and capture screenshot images of stories, primarily used for visual regression testing. It includes 'storycrawler', a utility for building Storybook crawling tools, and supports both simple mode (via URL) and managed mode (via Storybook configuration and the withScreenshot decorator).

Tokens
13.6K
Snippets
47
Records
58
Agent score
73%

What's inside storycap

  1. Parallelize story capture across multiple computers using sharding

    master

    To speed up large screenshot suites, you can distribute stories across multiple machines using the --shard CLI argument.

    Format: <shardNumber>/<totalShards>

    • <shardNumber>: The index of the current machine (1-based).
    • <totalShards>: The total number of machines participating.

    Stories are distributed in a round-robin fashion based on their ID. For example, if you have two computers, run them with --shard 1/2 and --shard 2/2 respectively.

    # On machine 1
    storycap --shard 1/2 http://localhost:9009
    
    # On machine 2
    storycap --shard 2/2 http://localhost:9009
  2. Compose variants using the `extends` field

    master

    You can create complex screenshot scenarios by composing variants using the extends field. A variant can inherit properties from another variant, allowing you to build combinations of viewports and element states.

    For example, a hovered variant can extend a small viewport variant to produce a screenshot that is both small and in a hovered state.

    export const Normal = {
      parameters: {
        screenshot: {
          variants: {
            small: {
              viewport: 'iPhone 5',
            },
            hovered: {
              extends: 'small',
              hover: 'button.my-button',
            },
          },
        },
      },
    };
  3. Generate multiple PNGs from one story using variants

    master

    By default, Storycap generates one screenshot per story. To generate multiple images for a single story (e.g., different viewports or element states), use the variants property within the story's parameters.screenshot object.

    The variant key (e.g., hovered) is appended as a suffix to the generated filename.

    Note: variants and viewports cannot be used as keys inside a variant object.

    import React from 'react';
    import MyButton from './MyButton';
    
    export default {
      title: 'MyButton',
      component: MyButton,
    };
    
    export const Normal = {
      parameters: {
        screenshot: {
          variants: {
            hovered: {
              hover: 'button.my-button',
            },
          },
        },
      },
    };
  4. Setup Storycap Managed Mode

    master

    Managed mode allows you to control screenshot timing, size, and other parameters via Storybook configuration.

    1. Add the addon to Storybook

    Add storycap to your .storybook/main.js file:

    /* .storybook/main.js */
    
    module.exports = {
      stories: ['../src/**/*.stories.@(js|mdx)'],
      addons: [
        '@storybook/addon-actions',
        '@storybook/addon-links',
        'storycap', // <-- Add storycap
      ],
    };

    2. Register the withScreenshot decorator

    In your .storybook/preview.js, you must register the withScreenshot decorator to enable capture control:

    /* .storybook/preview.js */
    
    import { withScreenshot } from 'storycap';
    
    export const decorators = [
      withScreenshot, // Registration of the decorator is required
    ];
    
    export const parameters = {
      // Global parameter is optional.
      screenshot: {
        // Put global screenshot parameters (e.g. viewport) here
      },
    };

    3. Run the command

    Start your Storybook and then run storycap against that port:

    $ npx start-storybook -p 9009
    $ npx storycap http://localhost:9009
    /* .storybook/main.js */
    
    module.exports = {
      stories: ['../src/**/*.stories.@(js|mdx)'],
      addons: [
        '@storybook/addon-actions',
        '@storybook/addon-links',
        'storycap',
      ],
    };
  5. Install Storycap

    master

    You can install storycap via npm. While puppeteer is optional, installing it is recommended for better control over the Chromium version used for screenshots.

    $ npm install storycap

    Or with puppeteer:

    $ npm install storycap puppeteer
  6. Control screenshot timing with `waitFor`

    master

    If stories require time to render (e.g., lazy loading or animations), you can use the waitFor option in parameters.screenshot. This parameter accepts:

    1. An async function that returns a Promise.
    2. The name of a global function (string) that returns a Promise.

    Example 1: Waiting for an element using @storybook/test

    import { screen } from '@storybook/test';
    
    export const MyStory = {
      screenshot: {
        waitFor: async () => {
          await screen.findByRole('link');
        },
      },
    };

    Example 2: Waiting for a global function (e.g., font loading)

    If you have a global function fontLoading defined in your Storybook preview (e.g., in preview-head.html), you can reference it by name:

    /* .storybook/preview.js */
    import { withScreenshot } from 'storycap';
    
    export const decorators = [withScreenshot];
    
    export const parameters = {
      screenshot: {
        waitFor: 'fontLoading',
      },
    };
  7. Update withScreenshot decorator and global options

    master

    When migrating to storycap, replace the deprecated setScreenshotOptions and initScreenshot with the withScreenshot decorator. Configuration that was previously global should now be passed as an argument to withScreenshot within your Storybook configuration.

    Note for Storybook v5.0+ users: It is recommended to use Storybook's addParameters with the screenshot key instead of decorators for configuration.

    /* .storybook/config.js */
    import { addDecorator, addParameters } from '@storybook/react';
    import { withScreenshot } from 'storycap';
    
    // Using Decorator pattern
    addDecorator(withScreenshot({
      viewport: {
        width: 768,
        height: 400,
        deviceScaleFactor: 2,
      },
    }));
    
    // Recommended pattern for Storybook v5.0+
    addParameters({
      screenshot: {
        viewport: {
          width: 768,
          height: 400,
          deviceScaleFactor: 2,
        },
      },
    });
  8. How to crawl Storybook stories with storycrawler

    master

    To crawl a Storybook instance, you follow a workflow of connecting to the server, fetching the list of stories, and then using workers to visit each story's preview window.

    1. Connect: Use StorybookConnection to establish a connection to your Storybook URL.
    2. Fetch Stories: Use StoriesBrowser to boot a Puppeteer process that retrieves the list of available stories (containing name, kind, and id).
    3. Setup Workers: Initialize multiple StoryPreviewBrowser instances to act as parallel workers.
    4. Execute Tasks: Use createExecutionService to manage a queue of tasks. Each task receives a worker and a story. Inside the task, you can use worker.setCurrentStory(story) to navigate and worker.page to access the underlying Puppeteer Page object for custom logic (like metrics extraction or screenshots).
    5. Wait for Stability: Use MetricsWatcher on the worker's page to ensure the UI framework has finished updating the DOM before performing actions.
    import {
      StorybookConnection,
      StoriesBrowser,
      StoryPreviewBrowser,
      MetricsWatcher,
      createExecutionService,
    } from 'storycrawler';
    
    (async function () {
      // 1. Connect to the target Storybook server.
      const storybookUrl = 'https://storybookjs.netlify.app/vue-kitchen-sink';
      const connection = await new StorybookConnection({ storybookUrl }).connect();
    
      // 2. Launch Puppeteer process to fetch stories info.
      const storiesBrowser = await new StoriesBrowser(connection).boot();
      const stories = await storiesBrowser.getStories();
    
      // 3. Launch Puppeteer browsers to visit each story's preview window
      const workers = await Promise.all([0, 1, 2, 3].map(i => new StoryPreviewBrowser(connection, i).boot()));
    
      try {
        // 4. Create an execution service to manage the task queue
        const service = createExecutionService(workers, stories, story => async worker => {
          // Display story in the worker's preview window
          await worker.setCurrentStory(story);
    
          // Wait for UI framework updating DOM
          await new MetricsWatcher(worker.page).waitForStable();
    
          // 5. Extract information using the Puppeteer page instance
          const m = await worker.page.metrics();
          return { story, nodesCount: m.Nodes };
        });
    
        // Execute the queued tasks
        const results = await service.execute();
    
        results.forEach(({ story, nodesCount }) => console.log(`${story.id}: ${nodesCount}`));
      } finally {
        // Cleanup
        await storiesBrowser.close();
        await Promise.all(workers.map(worker => worker.close()));
        await connection.disconnect();
      }
    })();
  9. Manage the v7-simple-react example project scripts

    master

    This example project is bootstrapped with Create React App. You can use the following npm scripts to manage the development lifecycle:

    • Development: Run npm start to launch the app in development mode at http://localhost:3000. The page reloads on edits.
    • Testing: Run npm test to launch the test runner in interactive watch mode.
    • Production Build: Run npm run build to create an optimized, minified production build in the build folder.
    • Ejecting: Run npm run eject to remove the single build dependency and copy all configuration files (Webpack, Babel, ESLint, etc.) directly into your project for full customization. Warning: This is a one-way operation.
    npm start
    npm test
    npm run build
    npm run eject
  10. Run Storycap in Simple Mode

    master

    In simple mode, you do not need to configure Storybook. You only need to provide the URL of your running Storybook instance.

    To run against a local server:

    $ npx storycap http://localhost:9001

    To launch the Storybook server automatically using the --serverCmd option:

    $ storycap --serverCmd "start-storybook -p 9001" http://localhost:9001

    To run against a pre-built Storybook (e.g., using http-server):

    $ build-storybook -o dist-storybook
    $ storycap --serverCmd "npx http-server dist-storybook -p 9001" http://localhost:9001

    Storycap can also crawl hosted Storybook pages:

    $ storycap https://next--storybookjs.netlify.app/vue-kitchen-sink/
  11. Migrate from zisui 1.x to storycap

    master

    To migrate from zisui to storycap:

    1. Replace the dependency:

      npm uninstall zisui
      npm install storycap
    2. Simple Mode: If you were using the zisui CLI, simply use the storycap command instead. All zisui CLI options are supported by storycap.

      storycap http://your.storybook.com
    3. Managed Mode (React):

      • Replace import 'zisui/register'; with import 'storycap/register'; in .storybook/addons.js.
      • Update the withScreenshot import in .storybook/config.js from zisui to storycap.
      • Recommendation: For Storybook v5.0 or later, use addParameters with the screenshot key instead of the withScreenshot decorator.
    $ npm uninstall zisui
    $ npm install storycap
  12. Manage the v8-simple-react example project with npm scripts

    master

    The v8-simple-react example is a React application bootstrapped with Create React App. You can manage the development lifecycle using the following standard npm scripts:

    • Development: Run npm start to launch the app in development mode at http://localhost:3000. The page reloads on edits and displays lint errors in the console.
    • Testing: Run npm test to launch the test runner in interactive watch mode.
    • Production Build: Run npm run build to create an optimized, minified production build in the build folder.
    • Ejecting: Run npm run eject to expose the underlying build configuration (Webpack, Babel, etc.). Warning: This is a one-way operation and cannot be undone.
    npm start
    npm test
    npm run build
    npm run eject