Skip to main content

Storybook Testing

Storybook 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:

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 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. See testing a local Storybook below.

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

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 suite that reads index.json, generates one test per story and compares a screenshot of each story against a golden image with toHaveScreenshot.

Create a file called storybook.spec.mjs:

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:

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:

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:

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 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.

Every session appears in your TestingBot dashboard with video, screenshots and logs.

Storybook Test Runner

If you already use the Storybook 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:

npx test-storybook --eject

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

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:

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 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 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. The screenshot suite from the previous section 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:

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 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.

Testing a local Storybook

During development, Storybook runs on localhost:6006, which the TestingBot cloud cannot reach directly. Start a TestingBot Tunnel to make it available:

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:

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. 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, 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.

Was this page helpful?
Last updated