ホーム自己紹介ブログ
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
フロントエンド
テスト

-

読者になる

|

シェアする

|

silverbirders

silverbirder

Webソフトウェアエンジニア
個人ブログキュレーション こぶりー 運営

ブログを応援する

この記事がよかったら、お布施という形で応援してもらえるとうれしいです。

おふせぼたん

※ ログイン不要で投稿できます。

※ 同じブラウザから投稿を削除できます。

0

読み込み中...

次の記事へ前の記事へ

関連する記事

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

Webmention 使ってみた

Webmention というのを最近知りました。 名前の通り、Web 上でメンションすることができます。 受信側、送信側、どちらも対応したので感想を残そうと思います。 Webmention ざっくりと Web AサイトにBサイトのリンクをつ

2026年09月08日

フロントエンド
個人開発
モジュールアップデートでやっていること

ITヲタクな AI くんにピッタリな仕事の一つは、モジュールアップデート。 特に、メジャーアップデートが超助かる。 昔だったら 数ヶ月掛かった移行も、数日たらずに対応できるようになったので、 さすが ITヲタクくん。 対応してよかったフロー

2026年07月24日

AI
フロントエンド
写経テストコードを全部消した

以下で書いた通り、プロダクトコードを写経したテストコードを削除しました。 "こぶりー" ( https://kobliy.vercel.app/ ) という個人ブログを読むアプリのコードです。 https://silverbirder.gi

2026年06月08日

AI
フロントエンド
テスト

タグ「テスト」の記事

Testcontainersで開発PR単位にE2Eテストなど

Testcontainers + Playwright + Cucumber.js による受け入れテストを、公私ともに活用しています。 運用していて気づいた点について、また書き残そうと思います。 開発PR単位でE2Eテスト これはTestc

2026年09月07日

テスト
ブローガスト25日目: ミューテーションテストのキルゼロテスト

テストコードをテストする、ミューテーションテスト。 プロダクションコードを突然変異でコードを書き換えたら(ミューテーション)、テストが失敗してくれるかどうか。 失敗するということは、不具合を検知できたということです。 ミューテーションする箇

2026年08月25日

テスト
Testcontainers運用経験から分かった、良い点・困る点

お仕事先のWebアプリケーションに、Testcontainersを導入運用して数ヶ月経過しました。 良い点と困る点について、書き残しておきます。 前提 簡単な前提として、バックエンドDB・APIとフロントエンドの3層構成のよくあるWebアプ

2026年07月08日

テスト
← ブログ一覧へ