> **Version 9** — **React** / **TypeScript**
> Also available:
- `?renderer=angular` for angular
- `?language=js` for JavaScript, json
- `?codeOnly=true` for code snippets only
- other versions: Version 10.3 (latest) (`/docs/writing-tests/integrations/test-runner.md`), Version 8 (`/docs/8/writing-tests/integrations/test-runner.md`)

# Test runner

The test runner has been superseded by the [Vitest addon](./vitest-addon.mdx), which offers the same functionality, powered by the faster and more modern [Vitest](https://vitest.dev/) browser mode. It also enables the full Storybook Test experience, allowing you to run interaction, accessibility, and visual tests from your Storybook app.

If you are using a Vite-powered Storybook framework, we recommend using the Vitest addon instead of the test runner.

Storybook test runner turns all of your stories into executable tests. It is powered by [Jest](https://jestjs.io/) and [Playwright](https://playwright.dev/).

* For those [without a play function](../../writing-stories/index.mdx): it verifies whether the story renders without any errors.
* For those [with a play function](../../writing-stories/play-function.mdx): it also checks for errors in the play function and that all assertions passed.

These tests run in a live browser and can be executed via the [command line](#cli-options) or your [CI server](#set-up-ci-to-run-tests).

## Setup

The test-runner is a standalone, framework-agnostic utility that runs parallel to your Storybook. You will need to take some additional steps to set it up properly. Detailed below is our recommendation to configure and execute it.

Run the following command to install it.

```shell
npm install @storybook/test-runner --save-dev
```

```shell
pnpm add --save-dev @storybook/test-runner
```

```shell
yarn add --dev @storybook/test-runner
```

Update your `package.json` scripts and enable the test runner.

```json title="package.json"
{
  "scripts": {
    "test-storybook": "test-storybook"
  }
}
```

Start your Storybook with:

```shell
npm run storybook
```

```shell
pnpm run storybook
```

```shell
yarn storybook
```

  Storybook's test runner requires either a locally running Storybook instance or a published Storybook to run all the existing tests.

Finally, open a new terminal window and run the test-runner with:

```shell
npm run test-storybook
```

```shell
pnpm run test-storybook
```

```shell
yarn test-storybook
```

## Configure

Test runner offers zero-config support for Storybook. However, you can run `test-storybook --eject` for more fine-grained control. It generates a `test-runner-jest.config.js` file at the root of your project, which you can modify. Additionally, you can extend the generated configuration file and provide [testEnvironmentOptions](https://github.com/playwright-community/jest-playwright#configuration) as the test runner also uses [jest-playwright](https://github.com/playwright-community/jest-playwright) under the hood.

### CLI Options

The test-runner is powered by [Jest](https://jestjs.io/) and accepts a subset of its [CLI options](https://jestjs.io/docs/cli) (for example, `--watch`, `--maxWorkers`).
If you're already using any of those flags in your project, you should be able to migrate them into Storybook's test-runner without any issues. Listed below are all the available flags and examples of using them.

| Options                         | Description                                                                                                                                                                                             |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--help`                        | Output usage information <br />`test-storybook --help`                                                                                                                                                   |
| `-s`, `--index-json`            | Run in index json mode. Automatically detected (requires a compatible Storybook) <br />`test-storybook --index-json`                                                                                     |
| `--no-index-json`               | Disables index json mode <br />`test-storybook --no-index-json`                                                                                                                                          |
| `-c`, `--config-dir [dir-name]` | Directory where to load Storybook configurations from <br />`test-storybook -c .storybook`                                                                                                               |
| `--watch`                       | Run in watch mode <br />`test-storybook --watch`                                                                                                                                                         |
| `--watchAll`                    | Watch files for changes and rerun all tests when something changes.<br />`test-storybook --watchAll`                                                                                                     |
| `--coverage`                    | Runs [coverage tests](#generate-code-coverage) on your stories and components <br /> `test-storybook --coverage`                                                                                              |
| `--coverageDirectory`           | Directory where to write coverage report output <br />`test-storybook --coverage --coverageDirectory coverage/ui/storybook`                                                                              |
| `--url`                         | Define the URL to run tests in. Useful for custom Storybook URLs <br />`test-storybook --url http://the-storybook-url-here.com`                                                                          |
| `--browsers`                    | Define browsers to run tests in. One or multiple of: chromium, firefox, webkit <br />`test-storybook --browsers firefox chromium`                                                                        |
| `--maxWorkers [amount]`         | Specifies the maximum number of workers the worker-pool will spawn for running tests <br />`test-storybook --maxWorkers=2`                                                                               |
| `--testTimeout [amount]`        | Defines the maximum time in milliseconds that a test can run before it is automatically marked as failed. Useful for long-running tests <br /> `test-storybook --testTimeout=60000`                      |
| `--no-cache`                    | Disable the cache <br />`test-storybook --no-cache`                                                                                                                                                      |
| `--clearCache`                  | Deletes the Jest cache directory and then exits without running tests <br />`test-storybook --clearCache`                                                                                                |
| `--verbose`                     | Display individual test results with the test suite hierarchy <br />`test-storybook --verbose`                                                                                                           |
| `-u`, `--updateSnapshot`        | Use this flag to re-record every snapshot that fails during this test run <br />`test-storybook -u`                                                                                                      |
| `--eject`                       | Creates a local configuration file to override defaults of the test-runner <br />`test-storybook --eject`                                                                                                |
| `--json`                        | Prints the test results in JSON. This mode will send all other test output and user messages to stderr. <br />`test-storybook --json`                                                                    |
| `--outputFile`                  | Write test results to a file when the --json option is also specified. <br />`test-storybook --json --outputFile results.json`                                                                           |
| `--junit`                       | Indicates that test information should be reported in a junit file. <br />`test-storybook --**junit**`                                                                                                   |
| `--ci`                          | Instead of the regular behavior of storing a new snapshot automatically, it will fail the test and require Jest to be run with `--updateSnapshot`. <br />`test-storybook --ci`                           |
| `--shard [index/count]`         | Requires CI. Splits the test suite execution into multiple machines <br /> `test-storybook --shard=1/8`                                                                                                  |
| `--failOnConsole`               | Makes tests fail on browser console errors<br />`test-storybook --failOnConsole`                                                                                                                         |
| `--includeTags`                 | Experimental feature <br />Defines a subset of stories to be tested if they match the enabled [tags](#experimental-filter-tests). <br />`test-storybook --includeTags="test-only, pages"`                 |
| `--excludeTags`                 | Experimental feature <br />Prevents stories from being tested if they match the provided [tags](#experimental-filter-tests). <br />`test-storybook --excludeTags="no-tests, tokens"`                      |
| `--skipTags`                    | Experimental feature <br />Configures the test runner to skip running tests for stories that match the provided [tags](#experimental-filter-tests). <br />`test-storybook --skipTags="skip-test, layout"` |

```shell
npm run test-storybook -- --watch
```

```shell
pnpm run test-storybook --watch
```

```shell
yarn test-storybook --watch
```

### Run tests against a deployed Storybook

By default, the test-runner assumes that you're running it against a locally served Storybook on port `6006`. If you want to define a target URL to run against deployed Storybooks, you can use the `--url` flag:

```shell
npm run test-storybook -- --url https://the-storybook-url-here.com
```

```shell
pnpm run test-storybook  --url https://the-storybook-url-here.com
```

```shell
yarn test-storybook --url https://the-storybook-url-here.com
```

Alternatively, you can set the `TARGET_URL` environment variable and run the test-runner:

```sh
TARGET_URL=https://the-storybook-url-here.com yarn test-storybook
```

## Run accessibility tests

When you have the [Accessibility addon](https://storybook.js.org/addons/@storybook/addon-a11y) installed, you can run accessibility tests alongside your interaction tests, using the test-runner.

For more details, including configuration options, see the [Accessibility testing documentation](../accessibility-testing.mdx).

## Run snapshot tests

[Snapshot testing](../snapshot-testing.mdx) is a helpful tool for verifying that edge cases like errors are handled correctly. It can also be used to verify that the rendered output of a component is consistent across different test runs.

### Set up

To enable snapshot testing with the test-runner, you'll need to take additional steps to set it up properly.

Add a new [configuration file](#test-hook-api) inside your Storybook directory with the following inside:

```ts
// .storybook/test-runner.ts

const config: TestRunnerConfig = {
  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();
  },
};

export default config;
```

  The `postVisit` hook allows you to extend the test runner's default configuration. Read more about them [here](#test-hook-api).

When you execute the test-runner (for example, with `yarn test-storybook`), it will run through all of your stories and run the snapshot tests, generating a snapshot file for each story in your project located in the `__snapshots__` directory.

### Configure

Out of the box, the test-runner provides an inbuilt snapshot testing configuration covering most use cases. You can also fine-tune the configuration to fit your needs via `test-storybook --eject` or by creating a `test-runner-jest.config.js` file at the root of your project.

#### Override the default snapshot directory

The test-runner uses a specific naming convention and path for the generated snapshot files by default. If you need to customize the snapshot directory, you can define a custom snapshot resolver to specify the directory where the snapshots are stored.

Create a `snapshot-resolver.js` file to implement a custom snapshot resolver:

```js
// ./snapshot-resolver.js

export default {
  resolveSnapshotPath: (testPath) => {
    const fileName = path.basename(testPath);
    const fileNameWithoutExtension = fileName.replace(/\.[^/.]+$/, '');
    // Defines the file extension for the snapshot file
    const modifiedFileName = `${fileNameWithoutExtension}.snap`;

    // Configure Jest to generate snapshot files using the following convention (./src/test/__snapshots__/Button.stories.snap)
    return path.join('./src/test/__snapshots__', modifiedFileName);
  },
  resolveTestPath: (snapshotFilePath, snapshotExtension) =>
    path.basename(snapshotFilePath, snapshotExtension),
  testPathForConsistencyCheck: 'example',
};
```

Update the `test-runner-jest.config.js` file and enable the `snapshotResolver` option to use the custom snapshot resolver:

```js
// ./test-runner-jest.config.js

const defaultConfig = getJestConfig();

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

export default config;
```

When the test-runner is executed, it will cycle through all of your stories and run the snapshot tests, generating a snapshot file for each story in your project located in the custom directory you specified.

#### Customize snapshot serialization

By default, the test-runner uses [`jest-serializer-html`](https://github.com/algolia/jest-serializer-html) to serialize HTML snapshots. This may cause issues if you use specific CSS-in-JS libraries like [Emotion](https://emotion.sh/docs/introduction), Angular's `ng` attributes, or similar libraries that generate hash-based identifiers for CSS classes. If you need to customize the serialization of your snapshots, you can define a custom snapshot serializer to specify how the snapshots are serialized.

Create a `snapshot-serializer.js` file to implement a custom snapshot serializer:

```js
// ./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);
  },
};
```

Update the `test-runner-jest.config.js` file and enable the `snapshotSerializers` option to use the custom snapshot resolver:

```js
// ./test-runner-jest.config.js

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;
```

When the test-runner executes your tests, it will introspect the resulting HTML, replacing the dynamically generated attributes with the static ones provided by the regular expression in the custom serializer file before snapshotting the component. This ensures that the snapshots are consistent across different test runs.

## Generate code coverage

Storybook also provides a [coverage addon](https://storybook.js.org/addons/@storybook/addon-coverage). It is powered by [Istanbul](https://istanbul.js.org/), which allows out-of-the-box code instrumentation for the most commonly used frameworks and builders in the JavaScript ecosystem.

### Set up

Engineered to work alongside modern testing tools (e.g., [Playwright](https://playwright.dev/)), the coverage addon automatically instruments your code and generates code coverage data. For an optimal experience, we recommend using the test-runner alongside the coverage addon to run your tests.

Run the following command to install the addon.

```shell
npx storybook@latest add @storybook/addon-coverage
```

```shell
pnpm dlx storybook@latest add @storybook/addon-coverage
```

```shell
yarn dlx storybook@latest add @storybook/addon-coverage
```

The CLI's [`add`](../../api/cli-options.mdx#add) command automates the addon's installation and setup. To install it manually, see our [documentation](../../addons/install-addons.mdx#manual-installation) on how to install addons.

Start your Storybook with:

```shell
npm run storybook
```

```shell
pnpm run storybook
```

```shell
yarn storybook
```

Finally, open a new terminal window and run the test-runner with:

```shell
npm run test-storybook -- --coverage
```

```shell
pnpm run test-storybook --coverage
```

```shell
yarn test-storybook --coverage
```

![Coverage test output](../../_assets/writing-tests/integrations/test-runner-coverage-result.png)

### Configure

By default, the [`@storybook/addon-coverage`](https://storybook.js.org/addons/@storybook/addon-coverage) offers zero-config support for Storybook and instruments your code via [`istanbul-lib-instrument`](https://www.npmjs.com/package/istanbul-lib-instrument) for [Webpack](https://webpack.js.org/), or [`vite-plugin-istanbul`](https://github.com/iFaxity/vite-plugin-istanbul) for [Vite](https://vitejs.dev/). However, you can extend your Storybook configuration file (i.e., `.storybook/main.js|ts`) and provide additional options to the addon. Listed below are the available options divided by builder and examples of how to use them.

```ts
// .storybook/main.ts
// For Vite support add the following import
// import type { AddonOptionsVite } from '@storybook/addon-coverage';

// Replace your-framework with the framework and builder you are using (e.g., react-webpack5, vue3-webpack5)

const coverageConfig: AddonOptionsWebpack = {
  istanbul: {
    include: ['**/stories/**'],
    exclude: ['**/exampleDirectory/**'],
  },
};

const config: StorybookConfig = {
  stories: [],
  addons: [
    // Other Storybook addons
    {
      name: '@storybook/addon-coverage',
      options: coverageConfig,
    },
  ],
};

export default config;
```

<details>
<summary>Vite options</summary>

| Options           | Description                                                                                                                                                                                                                                        | Type                        |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `checkProd`            | Configures the plugin to skip instrumentation in production environments<br />`options: { istanbul: { checkProd: true,}}`                                                                                                                           | `boolean`                   |
| `cwd`                  | Configures the working directory for the coverage tests.<br />Defaults to `process.cwd()`<br />`options: { istanbul: { cwd: process.cwd(),}}`                                                                                                        | `string`                    |
| `cypress`              | Replaces the `VITE_COVERAGE` environment variable with `CYPRESS_COVERAGE`.<br />Requires Cypress's [code coverage](https://docs.cypress.io/guides/tooling/code-coverage)<br />`options: { istanbul: { cypress: true,}}`                              | `boolean`                   |
| `exclude`              | Overrides the [default exclude list](https://github.com/storybookjs/addon-coverage/blob/main/src/constants.ts) with the provided list of files or directories to exclude from coverage<br />`options: { istanbul: { exclude: ['**/stories/**'],}}`  | `Array<String>` or `string` |
| `extension`            | Extends the [default extension list](https://github.com/storybookjs/addon-coverage/blob/main/src/constants.ts) with the provided list of file extensions to include in coverage<br />`options: { istanbul: { extension: ['.js', '.cjs', '.mjs'],}}` | `Array<String>` or `string` |
| `forceBuildInstrument` | Configures the plugin to add instrumentation in build mode <br />`options: { istanbul: { forceBuildInstrument: true,}}`                                                                                                                             | `boolean`                   |
| `include`              | Select the files to collect coverage<br />`options: { istanbul: { include: ['**/stories/**'],}}`                                                                                                                                                    | `Array<String>` or `string` |
| `nycrcPath`            | Defines the relative path for the existing nyc [configuration file](https://github.com/istanbuljs/nyc?tab=readme-ov-file#configuration-files)<br />`options: { istanbul: { nycrcPath: '../nyc.config.js',}}`                                        | `string`                    |
| `requireEnv`           | Overrides the `VITE_COVERAGE` environment variable's value by granting access to the `env` variables<br />`options: { istanbul: { requireEnv: true,}}`                                                                                              | `boolean`                   |

</details>

<details>
<summary>Webpack 5 options</summary>

| Options      | Description                                                                                                                                                                                                                                        | Type                        |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `autoWrap`             | Provides support for top-level return statements by wrapping the program code in a function<br />`options: { istanbul: { autoWrap: true,}}`                                                                                                         | `boolean`                   |
| `compact`              | Condenses the output of the instrumented code. Useful for debugging<br />`options: { istanbul: { compact: false,}}`                                                                                                                                 | `boolean`                   |
| `coverageVariable`     | Defines the global variable name that Istanbul will use to store coverage results<br />`options: { istanbul: { coverageVariable: '__coverage__',}}`                                                                                                 | `string`                    |
| `cwd`                  | Configures the working directory for the coverage tests.<br />Defaults to `process.cwd()`<br />`options: { istanbul: { cwd: process.cwd(),}}`                                                                                                        | `string`                    |
| `debug`                | Enables the debug mode for additional logging information during the instrumentation process<br />`options: { istanbul: { debug: true,}}`                                                                                                           | `boolean`                   |
| `esModules`            | Enables support for ES Module syntax<br />`options: { istanbul: { esModules: true,}}`                                                                                                                                                               | `boolean`                   |
| `exclude`              | Overrides the [default exclude list](https://github.com/storybookjs/addon-coverage/blob/main/src/constants.ts) with the provided list of files or directories to exclude from coverage<br />`options: { istanbul: { exclude: ['**/stories/**'],}}`  | `Array<String>` or `string` |
| `extension`            | Extends the [default extension list](https://github.com/storybookjs/addon-coverage/blob/main/src/constants.ts) with the provided list of file extensions to include in coverage<br />`options: { istanbul: { extension: ['.js', '.cjs', '.mjs'],}}` | `Array<String>` or `string` |
| `include`              | Select the files to collect coverage<br />`options: { istanbul: { include: ['**/stories/**'],}}`                                                                                                                                                    | `Array<String>` or `string` |
| `nycrcPath`            | Defines the relative path for the existing nyc [configuration file](https://github.com/istanbuljs/nyc?tab=readme-ov-file#configuration-files)<br />`options: { istanbul: { nycrcPath: '../nyc.config.js',}}`                                        | `string`                    |
| `preserveComments`     | Includes comments in the instrumented code<br />`options: { istanbul: { preserveComments: true,}}`                                                                                                                                                  | `boolean`                   |
| `produceSourceMap`     | Configures Instanbul to generate a source map for the instrumented code<br />`options: { istanbul: { produceSourceMap: true,}}`                                                                                                                     | `boolean`                   |
| `sourceMapUrlCallback` | Defines a callback function invoked with the filename and the source map URL when a source map is generated<br />`options: { istanbul: { sourceMapUrlCallback: (filename, url) => {},}}`                                                            | `function`                  |

</details>

### What about other coverage reporting tools?

Out of the box, code coverage tests work seamlessly with Storybook's test-runner and the [`@storybook/addon-coverage`](https://storybook.js.org/addons/@storybook/addon-coverage). However, that doesn't mean you can't use additional reporting tools (e.g., [Codecov](https://about.codecov.io/)). For instance, if you're working with [LCOV](https://wiki.documentfoundation.org/Development/Lcov), you can use the generated output (in `coverage/storybook/coverage-storybook.json`) and create your own report with:

```shell
npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook
```

## Set up CI to run tests

You can also configure the test-runner to run tests on a CI environment. Documented below are some recipes to help you get started.

### Run against deployed Storybooks via Github Actions deployment

If you're publishing your Storybook with services such as [Vercel](https://vercel.com/) or [Netlify](https://docs.netlify.com/site-deploys/notifications/#github-commit-statuses), they emit a `deployment_status` event in GitHub Actions. You can use it and set the `deployment_status.target_url` as the `TARGET_URL` environment variable. Here's how:

```yml
// .github/workflows/storybook-tests.yml — yml
name: Storybook Tests

on: deployment_status

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    if: github.event.deployment_status.state == 'success'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
      - name: Install dependencies
        run: yarn
      - name: Install Playwright
        run: npx playwright install --with-deps
      - name: Run Storybook tests
        run: yarn test-storybook
        env:
          TARGET_URL: '${{ github.event.deployment_status.target_url }}'
```

  The published Storybook must be publicly available for this example to work. We recommend running the test server using the recipe [below](#run-against-non-deployed-storybooks) if it requires authentication.

### Run against non-deployed Storybooks

You can use your CI provider (for example, [GitHub Actions](https://github.com/features/actions), [GitLab Pipelines](https://docs.gitlab.com/ee/ci/pipelines/), [CircleCI](https://circleci.com/)) to build and run the test runner against your built Storybook. Here's a recipe that relies on third-party libraries, that is to say, [concurrently](https://www.npmjs.com/package/concurrently), [http-server](https://www.npmjs.com/package/http-server), and [wait-on](https://www.npmjs.com/package/wait-on) to build Storybook and run tests with the test-runner.

```yml
// .github/workflows/storybook-tests.yml — yml
name: 'Storybook Tests'

on: push

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
      - name: Install dependencies
        run: yarn
      - name: Install Playwright
        run: npx playwright install --with-deps
      - name: Build Storybook
        run: yarn build-storybook --quiet
      - name: Serve Storybook and run tests
        run: |
          npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
            "npx http-server storybook-static --port 6006 --silent" \
            "npx wait-on tcp:127.0.0.1:6006 && yarn test-storybook"
```

  By default, Storybook outputs the [build](../../sharing/publish-storybook.mdx#build-storybook-as-a-static-web-application) to the `storybook-static` directory. If you're using a different build directory, you'll need to adjust the recipe accordingly.

## Advanced configuration

### Test hook API

The test-runner renders a story and executes its [play function](../../writing-stories/play-function.mdx) if one exists. However, certain behaviors are impossible to achieve via the play function, which executes in the browser. For example, if you want the test-runner to take visual snapshots for you, this is possible via Playwright/Jest but must be executed in Node.

The test-runner exports test hooks that can be overridden globally to enable use cases like visual or DOM snapshots. These hooks give you access to the test lifecycle *before* and *after* the story is rendered.
Listed below are the available hooks and an overview of how to use them.

| Hook        | Description                                                                                                     |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| `prepare`   | Prepares the browser for tests<br />`async prepare({ page, browserContext, testRunnerConfig }) {}`               |
| `setup`     | Executes once before all the tests run<br />`setup() {}`                                                         |
| `preVisit`  | Executes before a story is initially visited and rendered in the browser<br />`async preVisit(page, context) {}` |
| `postVisit` | Executes after the story is visited and fully rendered<br />`async postVisit(page, context) {}`                  |

  These test hooks are experimental and may be subject to breaking changes. We encourage you to test as much as possible within the story's [play function](../../writing-stories/play-function.mdx).

To enable the hooks API, you'll need to add a new configuration file inside your Storybook directory and set them up as follows:

```ts
// .storybook/test-runner.ts

const config: TestRunnerConfig = {
  // Hook that is executed before the test runner starts running tests
  setup() {
    // Add your configuration here.
  },
  /* Hook to execute before a story is initially visited before being rendered in the browser.
   * The page argument is the Playwright's page object for the story.
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async preVisit(page, context) {
    // Add your configuration here.
  },
  /* Hook to execute after a story is visited and fully rendered.
   * The page argument is the Playwright's page object for the story
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async postVisit(page, context) {
    // Add your configuration here.
  },
};

export default config;
```

  Except for the `setup` function, all other functions run asynchronously. Both `preVisit` and `postVisit` functions include two additional arguments, a [Playwright page](https://playwright.dev/docs/pages) and a context object which contains the `id`, `title`, and the `name` of the story.

When the test-runner executes, your existing tests will go through the following lifecycle:

* The `setup` function is executed before all the tests run.
* The context object is generated containing the required information.
* Playwright navigates to the story's page.
* The `preVisit` function is executed.
* The story is rendered, and any existing `play` functions are executed.
* The `postVisit` function is executed.

### (Experimental) Filter tests

When you run the test-runner on Storybook, it tests every story by default. However, if you want to filter the tests, you can use the `tags` configuration option. Storybook originally introduced this feature to generate [automatic documentation](../../writing-docs/autodocs.mdx) for stories. But it can be further extended to configure the test-runner to run tests according to the provided tags using a similar configuration option or via CLI flags (e.g., `--includeTags`, `--excludeTags`, `--skipTags`), only available with the latest stable release (`0.15` or higher). Listed below are the available options and an overview of how to use them.

| Option    | Description                                                                   |
| --------- | ----------------------------------------------------------------------------- |
| `exclude` | Prevents stories if they match the provided tags from being tested.           |
| `include` | Defines a subset of stories only to be tested if they match the enabled tags. |
| `skip`    | Skips testing on stories if they match the provided tags.                     |

```ts
// .storybook/test-runner.ts

const config: TestRunnerConfig = {
  tags: {
    include: ['test-only', 'pages'],
    exclude: ['no-tests', 'tokens'],
    skip: ['skip-test', 'layout'],
  },
};

export default config;
```

  Running tests with the CLI flags takes precedence over the options provided in the configuration file and will override the available options in the configuration file.

#### Disabling tests

If you want to prevent specific stories from being tested by the test-runner, you can configure your story with a custom tag, enable it to the test-runner configuration file or run the test-runner with the `--excludeTags` [CLI](#cli-options) flag and exclude them from testing. This is helpful when you want to exclude stories that are not yet ready for testing or are irrelevant to your tests. For example:

```ts
// MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework

const meta = {
  component: MyComponent,
  //👇 Provides the `no-tests` tag to all stories in this file
  tags: ['no-tests'],
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const ExcludeStory: Story = {
  //👇 Adds the `no-tests` tag to this story to exclude it from the tests when enabled in the test-runner configuration
  tags: ['no-tests'],
};
```

#### Run tests for a subset of stories

To allow the test-runner only to run tests on a specific story or subset of stories, you can configure the story with a custom tag, enable it in the test-runner configuration file or run the test-runner with the `--includeTags` [CLI](#cli-options) flag and include them in your tests. For example, if you wanted to run tests based on the `test-only` tag, you can adjust your configuration as follows:

```ts
// MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework

const meta = {
  component: MyComponent,
  //👇 Provides the `test-only` tag to all stories in this file
  tags: ['test-only'],
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const IncludeStory: Story = {
  //👇 Adds the `test-only` tag to this story to be included in the tests when enabled in the test-runner configuration
  tags: ['test-only'],
};
```

  Applying tags for the component's stories should either be done at the component level (using `meta`) or at the story level. Importing tags across stories is not supported in Storybook and won't work as intended.

#### Skip tests

If you want to skip running tests on a particular story or subset of stories, you can configure your story with a custom tag, enable it in the test-runner configuration file, or run the test-runner with the `--skipTags` [CLI](#cli-options) flag. Running tests with this option will cause the test-runner to ignore and flag them accordingly in the test results, indicating that the tests are temporarily disabled. For example:

```ts
// MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework

const meta = {
  component: MyComponent,
  //👇 Provides the `skip-test` tag to all stories in this file
  tags: ['skip-test'],
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const SkipStory: Story = {
  //👇 Adds the `skip-test` tag to this story to allow it to be skipped in the tests when enabled in the test-runner configuration
  tags: ['skip-test'],
};
```

### Authentication for deployed Storybooks

If you use a secure hosting provider that requires authentication to host your Storybook, you may need to set HTTP headers. This is mainly because of how the test runner checks the status of the instance and the index of its stories through fetch requests and Playwright. To do this, you can modify the test-runner configuration file to include the `getHttpHeaders` function. This function takes the URL of the fetch calls and page visits as input and returns an object containing the headers that need to be set.

```ts
// .storybook/test-runner.ts

const config: TestRunnerConfig = {
  getHttpHeaders: async (url) => {
    const token = url.includes('prod') ? 'prod-token' : 'dev-token';
    return {
      Authorization: `Bearer ${token}`,
    };
  },
};

export default config;
```

### Helpers

The test-runner exports a few helpers that can be used to make your tests more readable and maintainable by accessing Storybook's internals (e.g., `args`, `parameters`). Listed below are the available helpers and an overview of how to use them.

```ts
// .storybook/test-runner.ts

const config: TestRunnerConfig = {
  // Hook that is executed before the test runner starts running tests
  setup() {
    // Add your configuration here.
  },
  /* Hook to execute before a story is initially visited before being rendered in the browser.
   * The page argument is the Playwright's page object for the story.
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async preVisit(page, context) {
    // Add your configuration here.
  },
  /* Hook to execute after a story is visited and fully rendered.
   * The page argument is the Playwright's page object for the story
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async postVisit(page, context) {
    // Get the entire context of a story, including parameters, args, argTypes, etc.
    const storyContext = await getStoryContext(page, context);

    // This utility function is designed for image snapshot testing. It will wait for the page to be fully loaded, including all the async items (e.g., images, fonts, etc.).
    await waitForPageReady(page);

    // Add your configuration here.
  },
};

export default config;
```

#### Accessing story information with the test-runner

If you need to access information about the story, such as its parameters, the test-runner includes a helper function named `getStoryContext` that you can use to retrieve it. You can then use it to customize your tests further as needed. For example, if you need to configure Playwright's page [viewport size](https://playwright.dev/docs/api/class-page#page-set-viewport-size) to use the viewport size defined in the story's parameters, you can do so as follows:

```ts
// .storybook/test-runner.ts

const { MINIMAL_VIEWPORTS } = require('storybook/viewport');

const DEFAULT_VIEWPORT_SIZE = { width: 1280, height: 720 };

const config: TestRunnerConfig = {
  async preVisit(page, story) {
    // Accesses the story's parameters and retrieves the viewport used to render it
    const context = await getStoryContext(page, story);
    const viewportName = context.parameters?.viewport?.defaultViewport;
    const viewportParameter = MINIMAL_VIEWPORTS[viewportName];

    if (viewportParameter) {
      const viewportSize = Object.entries(viewportParameter.styles).reduce(
        (acc, [screen, size]) => ({
          ...acc,
          // Converts the viewport size from percentages to numbers
          [screen]: parseInt(size),
        }),
        {},
      );
      // Configures the Playwright page to use the viewport size
      page.setViewportSize(viewportSize);
    } else {
      page.setViewportSize(DEFAULT_VIEWPORT_SIZE);
    }
  },
};

export default config;
```

#### Working with assets

If you're running a specific set of tests (e.g., image snapshot testing), the test-runner provides a helper function named `waitForPageReady` that you can use to ensure the page is fully loaded and ready before running the test. For example:

```ts
// .storybook/test-runner.ts

const customSnapshotsDir = `${process.cwd()}/__snapshots__`;

const config: TestRunnerConfig = {
  setup() {
    expect.extend({ toMatchImageSnapshot });
  },
  async postVisit(page, context) {
    // Awaits for the page to be loaded and available including assets (e.g., fonts)
    await waitForPageReady(page);

    // Generates a snapshot file based on the story identifier
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotsDir,
      customSnapshotIdentifier: context.id,
    });
  },
};

export default config;
```

### Index.json mode

The test-runner transforms your story files into tests when testing a local Storybook. For a remote Storybook, it uses the Storybook's [index.json](../../configure/index.mdx#feature-flags) (formerly `stories.json`) file (a static index of all the stories) to run the tests.

#### Why?

Suppose you run into a situation where the local and remote Storybooks appear out of sync, or you might not even have access to the code. In that case, the `index.json` file is guaranteed to be the most accurate representation of the deployed Storybook you are testing. To test a local Storybook using this feature, use the `--index-json` flag as follows:

```shell
npm run test-storybook -- --index-json
```

```shell
pnpm run test-storybook --index-json
```

```shell
yarn test-storybook --index-json
```

  The `index.json` mode is not compatible with the watch mode.

If you need to disable it, use the `--no-index-json` flag:

```shell
npm run test-storybook -- --no-index-json
```

```shell
pnpm run test-storybook --no-index-json
```

```shell
yarn test-storybook --no-index-json
```

#### How do I check if my Storybook has a `index.json` file?

Index.json mode requires a `index.json` file. Open a browser window and navigate to your deployed Storybook instance (for example, `https://your-storybook-url-here.com/index.json`). You should see a JSON file that starts with a `"v": 3` key, immediately followed by another key called "stories", which contains a map of story IDs to JSON objects. If that is the case, your Storybook supports [index.json mode](../../configure/index.mdx#feature-flags).

## What's the difference between Chromatic and Test runner?

The test-runner is a generic testing tool that can run locally or on CI and be configured or extended to run all kinds of tests.

[Chromatic](https://www.chromatic.com/?utm_source=storybook_website\&utm_medium=link\&utm_campaign=storybook) is a cloud-based service that runs [visual](../visual-testing.mdx) and [interaction tests](../interaction-testing.mdx) (and soon [accessibility tests](../accessibility-testing.mdx)) without setting up the test runner. It also syncs with your git provider and manages access control for private projects.

However, you might want to pair the test runner and Chromatic in some cases.

* Use it locally and Chromatic on your CI.
* Use Chromatic for visual and component tests and run other custom tests using the test runner.

## Troubleshooting

### The test runner seems flaky and keeps timing out

If your tests time out with the following message:

```shell
Timeout - Async callback was not invoked within the 15000 ms timeout specified by jest.setTimeout
```

It might be that Playwright couldn't handle testing the number of stories you have in your project. Perhaps you have a large number of stories, or your CI environment has a really low RAM configuration. In such cases, you should limit the number of workers that run in parallel by adjusting your command as follows:

```json title="package.json"
{
  "scripts": {
    "test-storybook:ci": "yarn test-storybook --maxWorkers=2"
  }
}
```

### The error output in the CLI is too short

By default, the test runner truncates error outputs at 1000 characters, and you can check the full output directly in Storybook in the browser. However, if you want to change that limit, you can do so by setting the `DEBUG_PRINT_LIMIT` environment variable to a number of your choosing, for example, `DEBUG_PRINT_LIMIT=5000 yarn test-storybook`.

### Run the test runner in other CI environments

As the test runner is based on Playwright, you might need to use specific docker images or other configurations depending on your CI setup. In that case, you can refer to the [Playwright CI docs](https://playwright.dev/docs/ci) for more information.

### Tests filtered by tags are incorrectly executed

If you've enabled filtering tests with tags and provided similar tags to the `include` and `exclude` lists, the test-runner will execute the tests based on the `exclude` list and ignore the `include` list. To avoid this, make sure the tags provided to the `include` and `exclude` lists differ.

### The test runner doesn't support Yarn PnP out of the box

If you've enabled the test-runner in a project running on a newer version of Yarn with Plug'n'Play (PnP) enabled, the test-runner might not work as expected and may generate the following error when running tests:

```shell
PlaywrightError: jest-playwright-preset: Cannot find playwright package to use chromium
```

This is due to the test-runner using the community-maintained package [jest-playwright-preset](https://github.com/playwright-community/jest-playwright) that still needs to support this feature. To solve this, you can either switch the [`nodeLinker`](https://yarnpkg.com/features/linkers) setting to `node-modules` or install Playwright as a direct dependency in your project, followed by adding the browser binaries via the [`install`](https://playwright.dev/docs/browsers#install-browsers) command.

### Run test coverage in other frameworks

If you intend on running coverage tests in frameworks with special files like Vue 3 or Svelte, you'll need to adjust your configuration and enable the required file extensions. For example, if you're using Vue, you'll need to add the following to your nyc configuration file (i.e., `.nycrc.json` or `nyc.config.js`):

```js
// .nyc.config.js — JavaScript configuration
export default {
  // Other configuration options
  extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.vue'],
};
```

```json
// .nycrc.json — JSON configuration
{
  "extension": [".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx", ".vue"]
}
```

### The coverage addon doesn't support optimized builds

If you generated a production build optimized for performance with the [`--test`](../../sharing/publish-storybook.mdx#customizing-the-build-for-performance) flag, and you're using the coverage addon to run tests against your Storybook, you may run into a situation where the coverage addon doesn't instrument your code. This is due to how the flag works, as it removes addons that have an impact on performance (e.g., [`Docs`](../../writing-docs/index.mdx), [coverage addon](https://storybook.js.org/addons/@storybook/addon-coverage)). To resolve this issue, you'll need to adjust your Storybook configuration file (i.e., `.storybook/main.js|ts`) and include the [`disabledAddons`](../../api/main-config/main-config-build.mdx#testdisabledaddons) option to allow the addon to run tests at the expense of a slower build.

```ts
// .storybook/main.ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.

const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: ['@storybook/addon-docs', '@storybook/addon-vitest', '@storybook/addon-coverage'],
  build: {
    test: {
      disabledAddons: ['@storybook/addon-docs'],
    },
  },
};

export default config;
```

### The coverage addon doesn't support instrumented code

As the [coverage addon](https://storybook.js.org/addons/@storybook/addon-coverage) is based on Webpack5 loaders and Vite plugins for code instrumentation, frameworks that don't rely upon these libraries (e.g., Angular configured with Webpack), will require additional configuration to enable code instrumentation. In that case, you can refer to the following [repository](https://github.com/yannbf/storybook-coverage-recipes) for more information.

**More testing resources**

* [Interaction testing](../interaction-testing.mdx) for user behavior simulation
* [Accessibility testing](../accessibility-testing.mdx) for accessibility
* [Visual testing](../visual-testing.mdx) for appearance
* [Snapshot testing](../snapshot-testing.mdx) for rendering errors and warnings
* [Test coverage](../test-coverage.mdx) for measuring code coverage
* [CI](../in-ci.mdx) for running tests in your CI/CD pipeline
* [Vitest addon](./vitest-addon.mdx) for running tests in Storybook
* [End-to-end testing](./stories-in-end-to-end-tests.mdx) for simulating real user scenarios
* [Unit testing](./stories-in-unit-tests.mdx) for functionality