ホーム自己紹介ブログ
NO.181
DATE2025. 11. 29

StorybookのcomposeStoryとVitestのtoMatchScreenshotを組み合わせたVRT

Web フロントエンドの UI 開発では、私の中では Storybook は、ほぼ必須なツールです。
UI カタログとして使うだけでなく、ビジュアルリグレッションテスト(VRT) にも活用したいと常々思っています。

VRT が必要になる場面としては、たとえば次のようなケースがあります。

  • 共通コンポーネントのデザイン修正後、依存コンポーネントの崩れを検知したい
  • デザイントークンやテーマ変更の影響範囲を確認したい
  • ライブラリアップデート時にデザイン差分を確認したい

Storybook の以下のドキュメントでは、Visual Tests は Chromatic の利用が紹介されています。

  • Visual tests | Storybook docs

ただ、さまざまな理由から Chromatic に依存したくないこともあります。

Vitest の Browser Mode と toMatchScreenshot

ここで役に立つのが、Vitest に追加された Browser Mode です。
ヘッドレスブラウザ(Chromium など)上での UI テストが可能になりました。

  • Browser Mode | Guide | Vitest

さらに、 toMatchScreenshot により、スクリーンショットを比較する VRT ができるようになりました。

  • Visual Regression Testing | Vitest

Storybook の composeStory と組み合わせる

Storybook には composeStory(および composeStories)というAPIがあり、
Storybook の Story オブジェクトをテストコード内で利用できます。

  • Stories in unit tests | Storybook docs

これを使うことで、

  • Storybook に定義した UI 状態(Story)
  • Vitest のスクリーンショット比較(toMatchScreenshot)

をそのままつなげて、Chromatic なしで Storybook の Story を VRT できるようになります。

実際に対応したコミット

実際にこの方法を試したコミットはこちらです。

  • https://github.com/silverbirder/fequest/commit/13a331b029a406781d500f1ce88a26cf924b3918

上記コミットには、Vitest 側の不具合修正を早めに取り込みたかったため、patch を当てています。

  • 参考: https://github.com/vitest-dev/vitest/issues/8853

使用した環境は次のとおりです。

  • @storybook/react-vite
  • vitest-browser-react

当初は @storybook/nextjs-vite で試していたものの、Next.js の next/navigation 周りの mock エラーに苦戦し、一旦断念しました。
再挑戦すれ解決できる可能性はありますが、今回は @storybook/react-vite を使用しています。

サンプルコンポーネント

// my-input.tsx
import { Input } from "<path to shadcn input component>";
import { ComponentProps } from "react";
 
type Props = ComponentProps<typeof Input>;
 
export const MyInput = ({ ...rest }: Props) => {
  return <Input {...rest} />;
};

Storybook 側のファイル

// my-input.stories.tsx
import type { Meta, StoryObj } from "@storybook/react-vite";
 
import { MyInput } from "./my-input";
 
const meta = {
  component: MyInput,
} satisfies Meta<typeof MyInput>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const Default: Story = {};

Vitest の VRT テストコード

// my-input.spec.tsx
import { composeStories } from "@storybook/react-vite";
import { describe, expect, it } from "vitest";
import { render } from "vitest-browser-react";
 
import * as stories from "./my-input.stories";
 
const { Default } = composeStories(stories);
 
describe("MyInput", () => {
  it("matches Default story screenshot", async () => {
    const { getByTestId } = await render(
      <div data-testid="test">
        <Default />
      </div>,
    );
 
    await expect(getByTestId("test")).toMatchScreenshot();
  });
});

このテストを実行すると、Default Story のスクリーンショットが保存され、
次回以降はその差分が検知されるようになります。

これで Storybook の Story を全部 VRT できる

composeStories と toMatchScreenshot を組み合わせることで、

  • Storybook の Story をそのままテストに読み込む
  • Story 全パターンに対してスクリーンショット比較を行う
  • Chromatic なしで VRT ができる

という、シンプルな構成で VRT ができそうです。
Storybook と Vitest という、どちらもメジャーな OSS だけで実現できるため、外部依存を減らせる点も魅力的です。

追記

@storybook/nextjs-vite でもスクリーンショット VRT が動作することを確認できました。 ポイントは、以下で紹介されている vite-plugin-storybook-nextjs を導入する必要があったことです。

  • Portable stories in Vitest | Storybook docs

私の環境では monorepo を採用しているため、vite.config.ts に次のような設定を追加しました。

// vite.config.ts
import nextjs from "vite-plugin-storybook-nextjs";
 
export default defineConfig({
  plugins: [nextjs({dir: "../../apps/user"})],
});

テストの書き方ですが、上記の紹介した形式だと、以下のエラーが発生しました。

SB_FRAMEWORK_NEXTJS_0002 (NextjsRouterMocksNotAvailable): Tried to access router mocks from "next/router" but they were not created yet. You might be running code in an unsupported environment.

そこで、以下のようなテストの書き方に変更したら動作しました。

// my-input.spec.tsx
import { composeStories } from "@storybook/react-vite";
import { describe, expect, it } from "vitest";
import { render } from "vitest-browser-react";
 
import * as stories from "./my-input.stories";
 
const { Default } = composeStories(stories);
 
describe("MyInput", () => {
  it("matches Default story screenshot", async () => {
    await Default.run();
 
    await expect(document.body).toMatchScreenshot();
  });
});

修正したコミットは以下になります。

  • https://github.com/silverbirder/fequest/commit/d8ef4ddbb52ab78d954f1db2d95b727d6f46d81c
フロントエンド
テスト

シェアする

フォローする

次のページ

oklchとトークン設計

前のページ

具体的なデータで議論しよう

関連する記事

タグ「フロントエンド」の記事

ヒューマンインターフェース ガイドライン という言葉を知りました。

最近、ヒューマンインターフェース ガイドライン(HIG)という言葉を知りました。 「ヒューマンインターフェイスガイドライン」には、どのAppleプラットフォームでも優れた体験を設計できるようにするためのガイドとベストプラクティスが含まれてい

2026-01-24

フロントエンド
AIの利用上限に達した時にすることを残しておく

主にWeb関連の個人開発をしている際に心がけていることを書きます。 月末に近づくにつれ、AIの利用上限に達してしまうことがあります。 その状況になった時、以下のいずれかの選択肢が私の中では残っています。 課金して利用上限を増やす 無料モデル

2026-01-22

フロントエンド
AI
CSSで頑張らなくても、SVGで楽にできるときもある

個人サイトをリニューアルをしています。 ノート風のデザインを目指して、スタイルを調整していました。 ノートの見た目は、現実にあるノートを再現しようとCSSを書いていました。 現在、以下の画像のようなノートになっています。 ノート風デザインの

2026-01-20

フロントエンド

タグ「テスト」の記事

CSS Layout Testing というテスト手法の提案

Web のフロントエンド実装において、次のようなミスによってデザイン崩れを起こしてしまったことはありませんか。 flex-shrink の指定を忘れて、要素が押しつぶされてしまった z-index の指定を間違えて、要素が意図せず前面(また

2026-01-10

フロントエンド
テスト
単体テストを全通り書くんじゃない!

AIの進化によって、プロダクションコードに対するテストコードは、以前と比べて格段に書きやすくなったと感じています。 単体テストに関する基本的なお作法については、以前に以下の記事で整理しました。 興味があれば、参考として読んでもらえると嬉しい

2026-01-09

テスト
Playwright の POM を Storybook 上で確認してから E2E テストを書く

はじめに Playwright で E2E テストを書く際、playwright codegen や、近年では Playwright MCP を利用して、テストコードの雛形を作成することが多いと思います。 ただし、生成したテストコードが正し

2025-12-26

テスト
← ブログ一覧へ