🔩

Storybook Test-Runner の Play 関数で Visual Test と Snapshot Test をする

2024/01/07に公開

Visual Test と Snapshot Test はフロントエンドで大事なことです。Storybook の Test-Runner が コンポーネントの Story をテストケースとして、作動正確性を確認できるものです。我々は Test-Runner を使って、コンポーネントの状態に基づいて視覚と DOM の変更範囲を確保しています。この文は Storybook Test-Runner で、Play Function の中で Visual Test と Snapshot Test の使い方について紹介しいたします。

問題点

Storybook の文書が Visual Test (Image Snapshot) と Snapshot Test (HTML Snapshot) を触れたが、postVisit の時点は作動終わった時ですから、状態に基づけませんでした。

解決策

コードがこのリポジトリにあります。

preVisit で、Window オブジェクトに Snapshot 関数を追加します。

// .storybook/test-runner.ts
import { type TestRunnerConfig } from "@storybook/test-runner";
import { toMatchImageSnapshot } from "jest-image-snapshot";

const config: TestRunnerConfig = {
  setup() {
    expect.extend({ toMatchImageSnapshot });
  },
  async preVisit(page) {
    if (await page.evaluate(() => !("takeSnapshot" in window))) {
      await page.exposeBinding("takeSnapshot", async ({ page }) => {
        const elementHandler = await page.$("#storybook-root");
        const innerHTML = await elementHandler?.innerHTML();
        expect(innerHTML).toMatchSnapshot();
      });
    }

    if (await page.evaluate(() => !("takeScreenshot" in window))) {
      await page.exposeBinding("takeScreenshot", async ({ page }) => {
        const image = await page.locator("#storybook-root").screenshot();
        expect(image).toMatchImageSnapshot();
      });
    }
  },
};

export default config;

そして、Play 関数で Snapshot 関数を呼び出します。

import type { Meta, StoryObj } from "@storybook/react";
import { expect, fn, userEvent, within } from "@storybook/test";

import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  title: "Button",
  component: Button,
};

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

export const Primary: Story = {
  args: {
    onClick: fn(),
  },
  play: async () => {
    await window.takeSnapshot?.();
    await window.takeScreenshot?.();
    // 何回でも呼び出せます
  },
};

Prepare で追加しない理由

Prepare では expect はまだ注入しませんので、呼び出せません。

...
const config: TestRunnerConfig = {
  async prepare({ page, browserContext, testRunnerConfig }) {
    ...
    // default prepare
    ...
    // expose takeSnapshot
    await page.exposeBinding("takeSnapshot", async ({ page }) => {
      const elementHandler = await page.$("#storybook-root");
      const innerHTML = await elementHandler?.innerHTML();
      expect(innerHTML).toMatchSnapshot();
    });
  },
  ...

expect 関数は setup から使えます。だか、setup は引数がない関数です。

終わりに

テスト環境を設定していたとき、コンポーネントの最終状態だけではなく、中間状態も確保してほしいでした。どうやってコンポーネントの中間状態をテストするかを探して、この方法を見つけました。

Discussion