---
title: Storybook Testing with Playwright | TestingBot
description: Run Storybook component tests on the TestingBot cloud. Cross browser
  screenshot testing of your stories with Playwright and real device testing with
  Appium.
source_url:
  html: https://testingbot.com/support/web-automate/playwright/storybook
  md: https://testingbot.com/support/web-automate/playwright/storybook.md
---

# Storybook Testing

[Storybook](https://storybook.js.org/) is a workshop for building UI components in isolation. Each component state is described as a **story** , and every story is reachable at its own URL:

```bash
https://your-storybook.example.com/iframe.html?id=button--primary&viewMode=story
```

Because stories are plain URLs, you can open them in any browser or device on the TestingBot cloud. This page shows three ways to test your Storybook components with TestingBot:

- **Cross browser screenshot testing** : run a Playwright screenshot test for every story on multiple browsers and browser versions.
- **Storybook Test Runner** : point `@storybook/test-runner` at the TestingBot browser grid.
- **Real device testing** : render stories in Mobile Safari and Chrome on real iPhones and Android devices.

See our [Storybook example repository](https://github.com/testingbot/storybook-testingbot-example) for a runnable version of everything on this page, including a GitHub Actions workflow that builds Storybook, opens a tunnel and screenshots every story on the grid.

## Prerequisites

The browsers and devices in the TestingBot cloud need to be able to reach your Storybook. You have two options:

- **Publish a static build** : run `npm run build-storybook` and host the resulting `storybook-static` directory on any static host (S3, Netlify, Vercel, GitHub Pages or your own server).
- **Use TestingBot Tunnel** : keep Storybook running locally on `localhost:6006` and connect it to the TestingBot cloud with [TestingBot Tunnel](https://testingbot.com/support/tunnel). See [testing a local Storybook](https://testingbot.com#tunnel) below.

Storybook publishes a machine readable list of all your stories at `/index.json`. Verify it is available:

```bash
curl https://your-storybook.example.com/index.json
```

For Storybook versions older than 9, you may need to enable this file with `buildStoriesJson: true` in `.storybook/main.js`.

## Screenshot testing every story

The most flexible approach is a small [Playwright Test](https://testingbot.com/support/web-automate/playwright/playwright-test) suite that reads `index.json`, generates one test per story and compares a screenshot of each story against a golden image with [`toHaveScreenshot`](https://testingbot.com/support/web-automate/playwright/visual-regression-testing).

Create a file called `storybook.spec.mjs`:

```javascript
import { test, expect } from '@playwright/test'

const STORYBOOK_URL = process.env.STORYBOOK_URL || 'https://your-storybook.example.com'

// Fetch the list of stories from Storybook's index.json
const response = await fetch(`${STORYBOOK_URL}/index.json`)
const index = await response.json()
const stories = Object.values(index.entries).filter((entry) => entry.type === 'story')

for (const story of stories) {
  test(`story ${story.id}`, async ({ page }) => {
    await page.goto(`${STORYBOOK_URL}/iframe.html?id=${story.id}&viewMode=story`)

    // Wait until the story has rendered
    await page.waitForSelector('#storybook-root')
    await page.evaluate(() => document.fonts.ready)

    await expect(page).toHaveScreenshot(`${story.id}.png`, {
      animations: 'disabled',
      fullPage: true
    })
  })
}
```

Next, configure Playwright Test to run this suite on the TestingBot grid. Each project in `playwright.config.js` becomes one browser and version combination, so a single run gives you screenshots of every story on every browser you care about:

```javascript
import { defineConfig } from '@playwright/test'
import { getConnectWsEndpoint } from './testingbot.config'

export default defineConfig({
  testDir: './tests',
  timeout: 60 * 1000,
  projects: [
    {
      name: 'chrome@latest:Windows',
      use: {
        connectOptions: {
          wsEndpoint: getConnectWsEndpoint({
            browserName: 'chrome',
            browserVersion: 'latest',
            platform: 'WIN10'
          })
        }
      }
    },
    {
      name: 'firefox@latest:Linux',
      use: {
        connectOptions: {
          wsEndpoint: getConnectWsEndpoint({
            browserName: 'firefox',
            browserVersion: 'latest',
            platform: 'LINUX'
          })
        }
      }
    },
    {
      name: 'safari@latest:macOS',
      use: {
        connectOptions: {
          wsEndpoint: getConnectWsEndpoint({
            browserName: 'safari',
            browserVersion: 'latest',
            platform: 'SONOMA'
          })
        }
      }
    }
  ]
})
```

The `getConnectWsEndpoint` helper is described in our [Playwright Test configuration guide](https://testingbot.com/support/web-automate/playwright/playwright-test#configuration):

```javascript
export function getConnectWsEndpoint(userCapabilities) {
    const defaultCapabilities = {
        'tb:options': {
            key: process.env.TB_KEY,
            secret: process.env.TB_SECRET
        }
    }
    const capabilities = { ...defaultCapabilities, ...userCapabilities }
    return `wss://cloud.testingbot.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`
}
```

Run the suite:

```bash
npx playwright test
```

The first run creates the golden images. Subsequent runs compare each story against its golden image and fail when a component's rendering changes. You can pin specific [browser versions](https://testingbot.com/support/web-automate/playwright/browsers) to catch regressions that only appear on the versions your users run, and use Playwright's built-in parallelism to spread stories over multiple [parallel sessions](https://testingbot.com/support/web-automate/playwright/playwright-parallel-testing).

Every session appears in your [TestingBot dashboard](https://testingbot.com/members) with video, screenshots and logs.

## Storybook Test Runner

If you already use the [Storybook Test Runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner) (`@storybook/test-runner`) to execute your play functions, you can point it at the TestingBot grid. The test runner drives Jest through a bundled `jest-playwright` preset, and that preset connects to an existing browser instead of launching a local one when you give it a `wsEndpoint`.

First, eject the test runner's Jest configuration:

```bash
npx test-storybook --eject
```

That writes a `test-runner-jest.config.js` file. Add a `connectOptions.wsEndpoint` to it:

```javascript
import { getJestConfig } from '@storybook/test-runner'

const testRunnerConfig = getJestConfig()

const capabilities = {
    browserName: 'chrome',
    browserVersion: 'latest',
    platform: 'WIN11',
    'tb:options': {
        key: process.env.TB_KEY,
        secret: process.env.TB_SECRET
    }
}

export default {
    ...testRunnerConfig,
    testEnvironmentOptions: {
        ...testRunnerConfig.testEnvironmentOptions,
        'jest-playwright': {
            ...testRunnerConfig.testEnvironmentOptions['jest-playwright'],
            browsers: ['chromium'],
            connectOptions: {
                wsEndpoint: `wss://cloud.testingbot.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`
            }
        }
    }
}
```

Finally, run the test runner against a Storybook URL that the remote browser can reach:

```bash
npx test-storybook --url https://your-storybook.example.com
```

Each story is visited on the remote TestingBot browser, play functions are executed and assertion failures are reported by Jest as usual.

The connect settings have to go in `testEnvironmentOptions` as shown above. A standalone `jest-playwright.config.js` file is ignored, because the test runner's default Jest config always defines `testEnvironmentOptions['jest-playwright']` and the bundled preset skips its config file whenever that key is present. Older guides that use a `connectBrowserApp` key describe an earlier `jest-playwright` API that current releases no longer read.

The `jest-playwright` project has been deprecated by its maintainers and Storybook is migrating the test runner to `@playwright/test`. Because the configuration above depends on internals of the bundled preset, pin the `@storybook/test-runner` version you tested. For a long-term setup we recommend the [Playwright Test approach](https://testingbot.com#cross-browser) described earlier on this page.

## Stories on real mobile devices

Emulated viewports show you a small screen, but not how Mobile Safari or Chrome on Android actually render your component. Since every story is a URL, you can open it on [real mobile devices](https://testingbot.com/real-device-testing) in the TestingBot cloud.

### Playwright on real Android devices

Playwright tests can run against Chrome on real Android devices in our cloud. Add a `deviceName` and `realDevice: true` to your capabilities, as described in the [Playwright mobile testing guide](https://testingbot.com/support/web-automate/playwright/mobile). The screenshot suite from the [previous section](https://testingbot.com#cross-browser) works unchanged; add a project with real device capabilities to your `playwright.config.js`.

### Mobile Safari and Chrome through Appium

Playwright cannot drive browsers on real iOS devices. To see your stories in Mobile Safari on a real iPhone or iPad, use a WebDriver session, for example with [WebdriverIO](https://testingbot.com/support/web-automate/selenium/webdriverio):

```javascript
const { remote } = require('webdriverio')

const STORYBOOK_URL = 'https://your-storybook.example.com'

;(async () => {
  const browser = await remote({
    hostname: 'hub.testingbot.com',
    capabilities: {
      browserName: 'safari',
      platformName: 'iOS',
      browserVersion: '18.0',
      'appium:deviceName': 'iPhone 15',
      'tb:options': {
        key: process.env.TB_KEY,
        secret: process.env.TB_SECRET,
        realDevice: true,
        name: 'Storybook on real iPhone'
      }
    }
  })

  const response = await fetch(`${STORYBOOK_URL}/index.json`)
  const index = await response.json()
  const stories = Object.values(index.entries).filter((entry) => entry.type === 'story')

  for (const story of stories) {
    await browser.url(`${STORYBOOK_URL}/iframe.html?id=${story.id}&viewMode=story`)
    await browser.$('#storybook-root').waitForExist()
    await browser.saveScreenshot(`./screenshots/${story.id}.png`)
  }

  await browser.deleteSession()
})()
```

Use `browserName: 'chrome'` with `platformName: 'Android'` and an Android `deviceName` to run the same script on a real Android device. See the [device list](https://testingbot.com/support/app-automate/devices) for available devices, and pin both the device name and the version, since the same model is offered on several OS versions.

Real devices cannot resolve the hostname `localhost`, so a tunnelled Storybook needs a real hostname for this tier. Add an entry such as `127.0.0.1 storybook.local` to the `/etc/hosts` file on the machine running the tunnel and use `http://storybook.local:6006` as your Storybook URL. Publishing a static build avoids the problem entirely.

To compare these device screenshots against baselines and review differences in a dashboard, you can feed them into [TestingBot Visual Testing](https://testingbot.com/support/visual-testing).

## Testing a local Storybook

During development, Storybook runs on `localhost:6006`, which the TestingBot cloud cannot reach directly. Start a [TestingBot Tunnel](https://testingbot.com/support/tunnel) to make it available:

```bash
java -jar testingbot-tunnel.jar key secret -i myStorybookTunnel
```

Then add the `tunnelIdentifier` to your capabilities and point your tests at `http://localhost:6006`. Storybook's default port needs one extra capability, `localHttpPorts`, explained below:

```javascript
const capabilities = {
    'tb:options': {
        key: process.env.TB_KEY,
        secret: process.env.TB_SECRET,
        tunnelIdentifier: 'myStorybookTunnel',
        localHttpPorts: [6006]
    },
    browserName: 'chrome',
    browserVersion: 'latest'
}
```

The tunnel forwards the ports `80`, `443`, `3000`, `3001`, `3030`, `3400` and `8080` by default. Storybook's default port `6006` is not one of them, so it has to be requested with [`localHttpPorts`](https://testingbot.com/support/web-automate/selenium/options#localhost). Without it the tunnel starts fine and the session starts fine, but every story fails to load, which looks exactly like a broken tunnel. If you serve Storybook on a port that is already forwarded, such as `3000`, you do not need this capability.

The remote browser will now access your local Storybook through the tunnel.

## Frequently asked questions

How do I run Storybook tests on TestingBot?

Every Storybook story is reachable at a URL: iframe.html?id=&viewMode=story. Enumerate your stories from index.json, then use Playwright's connectOptions to open each story URL on a TestingBot browser and take a screenshot with toHaveScreenshot. Your Storybook needs to be reachable by the remote browser, either as a published static build or through TestingBot Tunnel.

Can I use the Storybook Test Runner with TestingBot?

Yes. The Storybook Test Runner (@storybook/test-runner) is built on Jest and Playwright. Eject its configuration with test-storybook --eject, then add a connectOptions.wsEndpoint pointing to wss://cloud.testingbot.com/playwright inside testEnvironmentOptions['jest-playwright'] in the generated test-runner-jest.config.js. Each story is then visited on a remote TestingBot browser and its play function runs there.

Can I test Storybook components on real mobile devices?

Yes. Because each story is a URL, you can open it in Mobile Safari or Chrome on real iOS and Android devices through an Appium session, or with [Playwright on real Android devices](https://testingbot.com/support/web-automate/playwright/mobile), and capture screenshots of how the component renders on real hardware.

Do I need to install browsers in CI to test Storybook on TestingBot?

No. When you connect to the TestingBot grid, the browsers run in our cloud. You only need playwright-core in your CI image, no npx playwright install step.

### Looking for more help?

Have questions or need more information? Reach out via email or Slack.

[Email us](https://testingbot.com/contact/new) [Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
