はじめに:なぜテスト戦略を見直すのか

AIエージェントがコードを生成する時代が到来しています。Claude Code、GitHub Copilot、Cursor——これらのツールは既に多くの開発現場で活用されていますが、ある根本的な問題が浮上しています。

「AIが生成したコードを、誰が・どうやって検証するのか?」

従来のテスト戦略は人間が書いたコードを人間がレビューし、人間がテストを書く前提で設計されていました。しかし、フロントエンド開発においてAIが1日に数百のコンポーネントを生成できる時代に、この前提は崩壊しつつあります。

本記事では、Storybook 10とVitest 4の組み合わせが、なぜ生成AI時代のフロントエンドテスト戦略として最適解なのかを解説します。

NotebookLMによるサマリ

AIエージェントとの協働基盤としての選定:Storybook 10 + Vitest 4

最初に結論を述べます。

Storybook 10 + Vitest 4を選定する最大の理由は、コンポーネントの仕様を機械可読な形式(CSF)で記述し、AIが生成・検証・修正のサイクルを自律的に回せる基盤を提供する点にあります。「ドキュメント」「テスト」「仕様」を統一し、人間とAIが同じ言語でコンポーネントを理解できる環境を構築するための判断です。

選定理由の要約

  • Storybook 10: フロントエンド単体で実行できるテストの基盤
  • CSF3: AIが読み書きできる機械可読な仕様フォーマット
  • Vitest 4 Browser Mode: 実ブラウザでの正確な検証
  • Portable Stories: テストとドキュメントの統一
  • エコシステム統合: axe、MSWとのシームレスな連携
  • 高速フィードバック: AIエージェントの自律的な修正サイクルを支援

Storybook 10

Storybookは、UIコンポーネントをアプリ本体から切り離して表示・操作できる環境です。 コンポーネントごとの状態(例:Primary / Loading)をStoryとして並べることで、仕様の共有や検証がしやすくなります。

CSF3(Component Story Format 3)とは

CSF3は、コンポーネントの振る舞いを人間にもAIにも同じ意味で読める形(機械可読)で記述する、Storybookの標準フォーマットです。 AIと人間の双方が参照する共通のドキュメントとなり、AIが生成・検証・修正のサイクルを自律的に回せる基盤を提供します。

// Button.stories.tsx
// Button コンポーネントの Story 定義ファイル

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

// play 関数内で使用するテストユーティリティ群
// - userEvent: ユーザー操作(クリック等)のシミュレーション
import { userEvent, within, expect } from '@storybook/test';

// Storybook のメタ情報定義
const meta: Meta = {
  // 対象となるコンポーネント
  component: Button,

  // 全 Story に共通で適用されるデフォルト args
  args: {
    children: 'Click me', // ボタンの表示テキスト
  },

  // Decorator: Story を ThemeProvider でラップする
  // → 実際のアプリケーションと同じテーマコンテキストで描画するため
  decorators: [
    (Story) => (
      
        
      
    ),
  ],
};

export default meta;

// Story 型を Button にバインド
type Story = StoryObj;

/**
 * Primary ボタンの Story
 * - 通常状態の primary variant を表す
 * - play 関数で「クリックでき、フォーカスされ、aria 属性が変化する」ことを検証
 */
export const Primary: Story = {
  args: {
    variant: 'primary', // primary スタイルを指定
  },

  // play: Story 描画後に自動実行されるインタラクションテスト
  play: async ({ canvasElement }) => {
    // Storybook の描画領域(iframe)をクエリスコープとして取得
    const canvas = within(canvasElement);

    // role="button" を持つ要素を取得
    const button = canvas.getByRole('button');

    // ユーザー操作としてクリックを実行
    await userEvent.click(button);

    // クリック後、ボタンがフォーカスされていることを確認
    await expect(button).toHaveFocus();

    // aria-pressed が true になっていることを確認
    // → トグルボタン等のアクセシビリティ要件検証
    await expect(button).toHaveAttribute('aria-pressed', 'true');
  },
};

/**
 * Loading 状態の Story
 * - ボタンが loading 中で操作不可になるケースを表す
 * - progressbar の表示と disabled 状態を検証
 */
export const Loading: Story = {
  args: {
    variant: 'primary',
    loading: true, // ローディング状態を有効化
  },

  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // loading 中はボタンが無効化されていることを確認
    await expect(canvas.getByRole('button')).toBeDisabled();

    // ローディングインジケータ(role="progressbar")が表示されていることを確認
    await expect(canvas.getByRole('progressbar')).toBeVisible();
  },
};

play関数とは

play関数は、Story描画後に自動実行されるスクリプトです。UIを人間が操作する代わりに、コードで操作と検証を記述します。

Storybook の play 関数は「仕様書+自動テスト」を兼ねるイメージです。 UI 状態(Primary / Loadingなど)ごとに操作(userEvent)、アクセシビリティ(role / aria)、状態遷移の結果(expect)をコード上で明示できる点が最大の価値です。

Storybook上の表示

AIエージェントにとっての意味

このフォーマットは、AIエージェントにとって実行可能な仕様書として機能します。

要素 AIが理解できる情報
args コンポーネントが受け取るpropsの具体的な値
play ユーザーインタラクションの期待される振る舞い
decorators コンポーネントが動作する環境の前提条件
parameters テスト実行時の設定(MSW、viewport等)

Claude Codeのようなエージェントは、このStoryを読み取ることで「このコンポーネントは何をすべきか」を正確に把握できます。そして、実装を修正した際に同じplay関数を実行することで、振る舞いが保持されているかを自律的に検証できるのです。

AIエージェントの自律的な修正サイクルを支援
AIエージェントの自律的な修正サイクルを支援

Vitest 4:Browser Modeによる実環境テスト

Vitestは、フロントエンドの開発環境と同じ前提でテストを実行できるテストランナーです。

Browser Modeとは

Browser Modeは、Vitest において「実ブラウザ上でVitestを直接実行できるモード」です。テストを実ブラウザで実行するため、UIコンポーネントを実行環境に近い形で検証できます。 その結果、例えば次のような“実ブラウザ依存”の領域を扱いやすくなります。

  • レイアウトやスタイル適用に起因する不具合(擬似DOMでは再現しづらい領域)
  • 実ブラウザAPI挙動を前提にした検証
  • イベント伝播・フォーカス管理・アクセシビリティといった実環境依存の振る舞い

つまりBrowser Modeは、ユニットテストとE2Eの中間にある 「ブラウザ上のコンポーネントテスト」 を行うための選択肢です。 VRT(Visual Regression Testing)と組み合わせると、AIが生成したコードの「見た目の正しさ」まで自動で検証しやすくなります。

資源再利用:テストを一度書いたら、あらゆる文脈で動く

Portable Storiesによる二重管理の解消

Storybook 10とVitest 4の最も強力な連携機能が、composeStories APIによるStoryの再利用です。この仕組みにより、Storyを書くことがテストを書くことと等価になります。

複数のテストでテスト資源が共有される
// Button.test.tsx
import { composeStories } from '@storybook/react';
import { render } from '@testing-library/react';
import * as stories from './Button.stories';

// Storiesから全ての設定(args, decorators, play)が継承される
const { Primary, Loading, Disabled } = composeStories(stories);

describe('Button', () => {
  it('handles click interaction correctly', async () => {
    const { container } = render();

    // play関数がそのまま実行される
    await Primary.play?.({ canvasElement: container });
  });

  it('displays loading state', async () => {
    const { container } = render();
    await Loading.play?.({ canvasElement: container });
  });

  it('can extend with additional assertions', () => {
    const { getByRole } = render();

    // Story定義にない追加の検証も可能
    expect(getByRole('button')).toHaveAttribute('aria-busy', 'true');
  });
});

再利用される資源の全体像

以下の表は、単一のStory定義からどれだけの資源が再利用されるかを示しています。

資源 定義場所 再利用先
Args(props組み合わせ) *.stories.tsx Unit Test, VRT, E2E, ドキュメント
Play functions *.stories.tsx Interaction Test, Vitest, アクセシビリティ監査
Decorators preview.tsx 全Story, 全Vitest
MSW Handlers mocks/handlers.ts Story, Vitest, E2E

まとめ

生成AI時代のフロントエンド開発において、テスト戦略は「人間が書いたコードを検証する」から「AIが生成したコードを自動検証する」へと進化しています。Storybook 10 + Vitest 4の組み合わせは、この進化に対応するための最適解です。

AIエージェントがコードを生成する割合は、今後さらに増加するでしょう。そのとき「信頼できるコード」と「そうでないコード」を分けるのは、堅牢なテスト基盤の有無です。

参考リンク