Docs
Storybook Docs

Storyshots migration guide

We're actively integrating community feedback to improve the tooling and documentation for snapshot testing with Storybook. If you're interested in participating in this process and helping us improve it. Please fill out this form to share your feedback.

Migrating tests from Storyshots

Prerequisites

Before you begin the migration process, ensure that you have:

  • A fully functional Storybook configured with one of the supported frameworks running the latest stable version (i.e., 7.6 or higher).
  • Familiarity with your current Storybook and its testing setup.

With the test-runner

Storybook test-runner turns all of your stories into executable tests. Powered by Jest and Playwright. It's a standalone, framework-agnostic utility that runs parallel to your Storybook. It enables you to run multiple testing patterns in a multi-browser environment, including interaction testing with the play function, DOM snapshot, and accessibility testing.

Setup

To get started with the migration process from the Storyshots addon to the test-runner, we recommend that you remove the Storyshots addon and similar packages (i.e., storybook/addon-storyshots-puppeteer ) from your project, including any related configuration files. Then, follow the test-runner's setup instructions to install, configure and run it.

Extend your test coverage

The Storyshots addon offered a highly customizable testing solution, allowing users to extend testing coverage in various ways. However, the test-runner provides a similar experience but with a different API. Below, you will find additional examples of using the test-runner to achieve results similar to those you achieved with Storyshots.

Enable DOM snapshot testing with the test-runner

To enable DOM snapshot testing with the test-runner, you can extend the test-runner's configuration file and use the available hooks and combine them with Playwright's built-in APIs to generate DOM snapshots for each story in your project. For example:

.storybook/test-runner.js
module.exports = {
  async postVisit(page, context) {
    // the #storybook-root element wraps the story. In Storybook 6.x, the selector is #root
    const elementHandler = await page.$('#storybook-root');
    const innerHTML = await elementHandler.innerHTML();
    expect(innerHTML).toMatchSnapshot();
  },
};

If you've set up DOM snapshot tests in your project with the test-runner and enabled the index.json mode via CLI flag, tests are generated in a temporary folder outside your project, and snapshots get stored alongside them. You'll need to extend the test-runner's configuration and provide a custom snapshot resolver to allow a different location for the snapshots. See the Troubleshooting section for more information.

Run image snapshot tests with the test-runner

By default, the test-runner provides you with the option to run multiple testing patterns (e.g., DOM snapshot testing, accessibility) with minimal configuration. However, if you want, you can extend it to run visual regression testing alongside your other tests. For example:

.storybook/test-runner.js
const { waitForPageReady } = require('@storybook/test-runner');
 
const { toMatchImageSnapshot } = require('jest-image-snapshot');
 
const customSnapshotsDir = `${process.cwd()}/__snapshots__`;
 
/** @type { import('@storybook/test-runner').TestRunnerConfig } */
module.exports = {
  setup() {
    expect.extend({ toMatchImageSnapshot });
  },
  async postVisit(page, context) {
    // Waits for the page to be ready before taking a screenshot to ensure consistent results
    await waitForPageReady(page);
 
    // To capture a screenshot for different browsers, add page.context().browser().browserType().name() to get the browser name to prefix the file name
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotsDir,
      customSnapshotIdentifier: context.id,
    });
  },
};

Troubleshooting

As running snapshot tests with Storybook and the test-runner can lead to some technical limitations that may prevent you from setting up or running your tests successfully, we've prepared a set of instructions to help you troubleshoot any issues you may encounter.

The test-runner reports an error when running snapshot tests

If you're experiencing intermittent test failures with the test-runner, uncaught errors may occur when your tests run in the browser. These errors might not have been caught if you were using the Storyshots addons previously. The test-runner will, by default, consider these uncaught errors as failed tests. However, if these errors are expected, you can ignore them by enabling custom story tags in your stories and test-runner configuration files. For more information, please refer to the test-runner documentation.

The test-runner does not generate snapshot files in the expected directory

If you've configured the test-runner to run snapshot tests, you may notice that the paths and names of the snapshot files differ from those previously generated by the Storyshots addon. This is because the test-runner uses a different naming convention for snapshot files. Using a custom snapshot resolver, you can configure the test-runner to use the same naming convention you used previously.

Run the following command to generate a custom configuration file for the test-runner that you can use to configure Jest:

npm run test-storybook -- --eject

Update the file and enable the snapshotResolver option to use a custom snapshot resolver:

./test-runner-jest.config.js
import { getJestConfig } from '@storybook/test-runner';
 
const defaultConfig = getJestConfig();
 
const config = {
  // The default Jest configuration comes from @storybook/test-runner
  ...defaultConfig,
  snapshotResolver: './snapshot-resolver.js',
};
 
export default config;

Finally, create a snapshot-resolver.js file to implement a custom snapshot resolver:

./snapshot-resolver.js
import path from 'path';
 
export default {
  resolveSnapshotPath: (testPath) => {
    const fileName = path.basename(testPath);
    const fileNameWithoutExtension = fileName.replace(/\.[^/.]+$/, '');
    const modifiedFileName = `${fileNameWithoutExtension}.storyshot`;
 
    // Configure Jest to generate snapshot files using the following naming convention (__snapshots__/Button.storyshot)
    return path.join(path.dirname(testPath), '__snapshots__', modifiedFileName);
  },
  resolveTestPath: (snapshotFilePath, snapshotExtension) =>
    path.basename(snapshotFilePath, snapshotExtension),
  testPathForConsistencyCheck: 'example.storyshot',
};

The format of the snapshot files is different from the ones generated by the Storyshots addon

By default, the test-runner uses jest-serializer-html to serialize HTML snapshots. This may cause differences in formatting compared to your existing snapshots, even if you're using specific CSS-in-JS libraries like Emotion, Angular's ng attributes, or other similar libraries that generate hash-based identifiers for CSS classes. However, you can configure the test-runner to use a custom snapshot serializer to solve this issue by overriding the random class names with a static one that will be the same for each test run.

Run the following command to generate a custom configuration file for the test-runner that you can use to provide additional configuration options.

npm run test-storybook -- --eject

Update the file and enable the snapshotSerializers option to use a custom snapshot resolver. For example:

./test-runner-jest.config.js
import { getJestConfig } from '@storybook/test-runner';
 
const defaultConfig = getJestConfig();
 
const config = {
  ...defaultConfig,
  snapshotSerializers: [
    // Sets up the custom serializer to preprocess the HTML before it's passed onto the test-runner
    './snapshot-serializer.js',
    ...defaultConfig.snapshotSerializers,
  ],
};
 
export default config;

Finally, create a snapshot-serializer.js file to implement a custom snapshot serializer:

./snapshot-serializer.js
// The jest-serializer-html package is available as a dependency of the test-runner
const jestSerializerHtml = require('jest-serializer-html');
 
const DYNAMIC_ID_PATTERN = /"react-aria-\d+(\.\d+)?"/g;
 
module.exports = {
  /*
   * The test-runner calls the serialize function when the test reaches the expect(SomeHTMLElement).toMatchSnapshot().
   * It will replace all dynamic IDs with a static ID so that the snapshot is consistent.
   * For instance, from <label id="react-aria970235672-:rl:" for="react-aria970235672-:rk:">Favorite color</label> to <label id="react-mocked_id" for="react-mocked_id">Favorite color</label>
   */
  serialize(val) {
    const withFixedIds = val.replace(DYNAMIC_ID_PATTERN, 'mocked_id');
    return jestSerializerHtml.print(withFixedIds);
  },
  test(val) {
    return jestSerializerHtml.test(val);
  },
};

When the test-runner executes your tests, it will introspect the resulting HTML and replace any dynamically generated attributes with the static ones provided by the regex expression before snapshotting the component.