テーマを切り替える

Vitest コンポーネントテスト実践:Browser Mode と Playwright 連携

Easton editorial illustration: 组件测试样品, jsdom 模拟舱, Browser 真实渲染舱, CI 覆盖率闸门

正直に言うと、初めて Canvas コンポーネントをテストしたときは大きな落とし穴にハマりました。

テストレポートの緑色の「PASS」を見て、自信満々でコミットしたのです。翌日、同僚が実ブラウザでページを開いたら——Canvas はまったく描画されていませんでした。テストは通ったのに?!

そのとき初めて気づきました。jsdom はあくまで「疑似」ブラウザで、DOM API は再現しても、Canvas の実描画、CSS の計算スタイル、Web Components のライフサイクルまでは検証できないのです。半年かけて整えたユニットテスト設定も、表面だけ触れていたに過ぎませんでした。

だから Vitest 3.0 では Browser Mode が登場しました。実ブラウザ上でテストを走らせます。jsdom のように Node.js 内で DOM を模すのではなく、Chromium / Firefox / Safari を起動してコンポーネントを描画し、Playwright の API で操作します。テストが通れば、本当に通ったと言えます。

本記事では、Browser Mode の設定から React / Vue のコンポーネントテスト、CI のカバレッジゲートまでをゼロから説明します。Vitest テストガイドシリーズの第 3 回で、前 2 回のユニットテスト設定と TDD の流れに、コンポーネントテストのピースをはめ込みます。

なぜ Browser Mode が必要か

「jsdom じゃ足りないの?」と思うかもしれません。

純粋なロジックだけのコンポーネント——電卓やフォームバリデーションなど——なら jsdom で十分です。Node.js 上の DOM シミュレーターで速く、設定も簡単。シリーズ前編のユニットテストも jsdom で、リアクティブデータやイベント発火は問題なく検証できました。

次のような場面では、jsdom は力不足です。

  • Canvas 描画:Canvas API はありますが、実際には描画しません。ctx.fillRect() の呼び出し回数は測れても、描いた図形が正しいかは分かりません。
  • CSS 計算スタイル:jsdom の getComputedStyle() は空オブジェクトです。実ブラウザでは親要素、padding、border が幅に効きますが、jsdom では計算できません。
  • Web ComponentsconnectedCallbackdisconnectedCallback は模倣されますが、発火タイミングは実ブラウザと一致しません。
  • 非同期レンダリング:アニメーションフレーム、requestIdleCallbackIntersectionObserver は未実装か不完全です。

私も同じ轍を踏みました。昨年のプロジェクトで、CSS アニメーションでボタンの開閉を制御するコンポーネントがありました。jsdom では transitionend は決して発火しません。transition そのものがないからです。モックイベントでテストを通しても、実ブラウザでアニメーション時間を変えた瞬間に UI は壊れ、テストだけが「合格」のまま残りました。

Browser Mode はここを埋めます。実ブラウザにコンポーネントを載せ、Chromium(や Firefox / Safari)上で Playwright API によりクリック・入力・待機を行います。ブラウザで起きたことが、そのままテスト対象になります。

参考データとして、Vitest 3.0 の Browser Mode は Chromium コンテキストを共有し、起動は 1 回だけで全テストが同じインスタンスを使います。公式では従来の Playwright E2E より約 30% 速いとされています。50 個のコンポーネントを測っても、ブラウザの起動・終了を繰り返す必要はありません。

テストピラミッドについても議論があります。従来型はユニットが厚く、E2E が薄い形です。一方 Vue 公式ブログや alexop.dev では「逆ピラミッド」——統合 70%、ユニット 20%、E2E 10%——も提案されています。コンポーネントはテンプレート・スタイル・ロジックの統合単位なので、jsdom のユニットだけでは全体挙動は見えません。Browser Mode は jsdom より実態に近く、Playwright E2E より軽い、中間のレイヤーと言えます。

Browser Mode 設定の実践

設定自体は難しくありません。ただし、私が踏んだ穴を先に共有します。

依存関係のインストール

Vitest と Browser Mode プロバイダを入れます。公式は Playwright を推奨しています。

npm install -D vitest @vitest/browser-playwright

Playwright は Chromium、Firefox、WebKit をまとめて入れます。Chromium だけで足りるなら、次のように絞れます。

npx playwright install chromium

Chromium パッケージは約 170MB あり、少し時間がかかります。ダウンロードが終われば、ほぼ準備完了です。

vitest.config.ts の設定

設定はシンプルですが、1 点だけ注意があります。

import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'

export default defineConfig({
  test: {
    browser: {
      provider: playwright(),
      enabled: true,
      instances: [{ browser: 'chromium' }],
    },
  },
})

instances で実行ブラウザを決めます。互換性を広げるなら Firefox と WebKit も追加できます。

instances: [
  { browser: 'chromium' },
  { browser: 'firefox' },
  { browser: 'webkit' },  // Safari
]

私は通常 Chromium のみです。マルチブラウザは遅く、多くのフロントエンド不具合は Chromium で十分見つかります。Safari 固有の問題は、Playwright E2E で重要パスを押さえます。

Headless モードと UI モード

使い分けは次のとおりです。

  • Headless モード:ウィンドウを出さずバックグラウンド実行。CI 向きで速い一方、描画過程は見えません。
  • UI モード:ブラウザウィンドウが開き、描画・クリック・入力を目で追えます。テスト作成時のデバッグに向きます。

開発中は UI モードで、次を実行します。

npx vitest --browser.ui

Vitest のテストワークベンチが開き、左にテスト一覧、右にブラウザが並びます。ファイルを選ぶとコンポーネントが描画され、実行の様子がその場で分かります。ボタンが反応しない?ブラウザでそのまま調べられます。

CI では Headless にします。設定に 1 行足します。

browser: {
  provider: playwright(),
  enabled: true,
  headless: true,  // CI で headless を強制
  instances: [{ browser: 'chromium' }],
}

テストファイルの命名

公式は .browser.test.ts で Browser Mode 用と通常の .test.ts を分けることを推奨しています。メリットは次のとおりです。

  • jsdom のユニットと Browser Mode のコンポーネントテストを同時実行しても混ざらない。
  • CI が落ちたとき、ファイル名でブラウザ系だとすぐ分かる。

Vitest は命名を強制しません。.test.ts のままでも動きます。重要なのは、Browser Mode 対象ディレクトリを設定で指定するか、全体を Browser Mode に載せるかです。私はユニットは jsdom、コンポーネントは Browser Mode と分けています。

React / Vue コンポーネントテストの実践

ここが Browser Mode の本丸です。Testing Library とは少し違いますが、慣れれば手早く書けます。

React コンポーネントテスト

React アダプタを入れます。

npm install -D @vitest/browser-react

Counter コンポーネントで、クリックのたびにカウントが 1 増える例です。

// Counter.browser.test.ts
import { page } from '@vitest/browser/context'
import { userEvent } from '@vitest/browser/context'
import Counter from './Counter'

test('ボタンクリックでカウント', async () => {
  // コンポーネントをブラウザにマウント
  await page.mount(<Counter />)

  // ボタン要素を取得
  const button = page.getByRole('button', { name: 'Count: 0' })

  // クリック
  await userEvent.click(button)

  // テキスト変化を検証
  await expect.element(button).toHaveTextContent('Count: 1')
})

Testing Library との対応は次のとおりです。render()page.mount()screen.getByRole()page.getByRole()。API は近く、page が Browser Mode のコンテキストです。

await expect.element(button) は Vitest の Web Testing API で、要素の状態変化を自動待機します。手動の waitFor() が不要で、Testing Library より短く書けます。

Vue コンポーネントテスト

Vue 用アダプタが必要です。

npm install -D @vitest/browser-vue

Vue の Counter テスト例です。

// Counter.browser.test.ts
import { page } from '@vitest/browser/context'
import { userEvent } from '@vitest/browser/context'
import Counter from './Counter.vue'

test('ボタンクリックでカウント', async () => {
  // Vue コンポーネントをマウント
  await page.mount(Counter)

  const button = page.getByRole('button', { name: 'Count: 0' })
  await userEvent.click(button)
  await expect.element(button).toHaveTextContent('Count: 1')
})

Vue 2 は @vue/test-utils の世界で、Browser Mode は直接サポートしません。Vue 3 なら @vitest/browser-vue で足ります。

実例:ドラッグ並べ替え

プロジェクトに HTML5 Drag & Drop のソートコンポーネントがありました。jsdom では dragstartdrop を本物に近い形で再現できず、モックでは「イベントが飛んだか」しか分からず、並べ替えロジックが正しいかは不明でした。

Browser Mode なら次のように書けます。

test('ドラッグでソート', async () => {
  await page.mount(<SortableList items={['A', 'B', 'C']} />)

  const itemA = page.getByText('A')
  const itemC = page.getByText('C')

  // A を C の後ろへドラッグ
  await userEvent.dragTo(itemA, itemC)

  // 順序を検証
  const items = page.getByRole('listitem')
  await expect.element(items.nth(2)).toHaveTextContent('A')
})

実ブラウザで Drag & Drop が動き、ソートが走り、DOM の順序も変わります。テストが通れば、実利用でも通る可能性が高い——それが Browser Mode の価値です。

Playwright と Browser Mode の使い分け

「どちらもブラウザテストでは?」と混乱しがちです。

一言でいうと、Browser Mode はコンポーネント、Playwright はフローです。

違いの整理

特徴Browser ModePlaywright
テスト範囲単一コンポーネントの隔離テスト複数ページのフローテスト
実行速度約 200ms/テスト2〜5 秒/テスト
起動コストブラウザインスタンスを共有テストごとに独立起動
設定の複雑さ低(Vitest に統合)高(別プロジェクト構成)
向く場面開発中の高速イテレーションリリース前の重要パス検証

Browser Mode はコンポーネントをブラウザに載せ、その部品だけの挙動を見ます。Playwright E2E はアプリ全体を開き、遷移・ログイン・送信といった一連の流れを検証します。

たとえば、Browser Mode は顕微鏡で細胞を、Playwright は健診で臓器系統全体を見るイメージです。役割が違い、置き換えはできません。

組み合わせ方

実プロジェクトでは次のように分けています。

  • Browser Mode:ボタン、フォーム、カード、モーダルなど UI コンポーネント全般。開発と同時に書き、コミット単位で回す。速く、すぐフィードバックが返る。
  • Playwright E2E:ログイン後ホーム、検索結果、注文送信など 3〜5 本の重要パス。リリース前や CI の日次ビルドで実行。

メリットは 3 つです。

  1. 多くの UI 不具合を開発段階で Browser Mode が拾う。
  2. ページをまたぐ統合不具合を Playwright がリリース前に拾う。
  3. コンポーネント 50 本、E2E 5 本程度に抑え、CI を重くしすぎない。

どちらを選ぶか

  • Browser Mode:1 コンポーネントのクリック、入力、描画、スタイル。コードを変えたらすぐ結果が欲しいとき。
  • Playwright:ログイン → 遷移 → 操作 → 検証のような複数ページフロー。フロント + API + DB の横断が必要なとき。

日付ピッカーの範囲制限や無効日、表示形式は Browser Mode。予約ページで日付を選び、注文して決済へ進む流れは Playwright です。

Browser Mode はフロントのみ、Playwright はフルスタックも扱えます。バックエンド API があるなら E2E で前後をつなげられます。Browser Mode ではバックエンドはモックが前提です。

CI 環境のカバレッジゲート

カバレッジは CI の最後の砦です。私も以前は軽視していました。あるリファクタでカバレッジが 80% から 60% に落ち、本番で不具合が出て初めてゲートの意味を実感しました。

カバレッジ設定

vitest.config.ts に閾値を足します。

test: {
  coverage: {
    provider: 'v8',  // または 'istanbul'
    reporter: ['text', 'json', 'html'],
    thresholds: {
      lines: 80,
      functions: 80,
      branches: 75,
      statements: 80
    }
  }
}

閾値の目安は次のとおりです。

  • 新規プロジェクト:50% から始め、徐々に上げる。最初から高すぎると負担が大きい。
  • 成熟プロジェクト:80% が妥当。コアは 90〜95% もあり。
  • 100% は不要:例外分岐など、無理にテストを書いてもコスパが悪いケースがあります。

実行は次のとおりです。

npx vitest run --coverage

閾値を下回ると Vitest が失敗し、CI も止まります。これがゲート——基準未満の変更は main に入れません。

GitHub Actions 連携

ワークフローに「テスト実行」と「カバレッジレポート」を足します。

# .github/workflows/test.yml
name: Test

on: [pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npx playwright install chromium --with-deps

      - name: Run tests with coverage
        run: npx vitest run --coverage

      - name: Report coverage
        uses: davelosert/vitest-coverage-report-action@v2
        with:
          json-summary-path: './coverage/coverage-summary.json'

この Action は PR コメントに変化を載せます。

  • 全体の増減(例:80% → 79% は -1%)
  • カバレッジが下がったファイル
  • 未カバーの新規コード

作者は一目で「新規コードのテストが足りない」と分かります。

CI での Browser Mode 注意点

  1. Playwright インストール--with-deps を付けないと Chromium が起動しないことがあります。
  2. Headlessheadless: true を明示。CI にディスプレイはありません。
  3. タイムアウト:jsdom より遅いので 30 秒程度に延ばすのが無難です。
  4. 並列:ブラウザを共有するため maxWorkers: 4 程度に抑えます。

私の CI 設定例です。

- name: Run browser tests
  run: npx vitest run --coverage --browser.headless
  env:
    CI: true

--browser.headless でウィンドウを出さず、CI: true で Vitest が色付き出力などを調整します。

まとめ

要点はシンプルです。jsdom では実ブラウザの挙動は測れません。Canvas、CSS 計算スタイル、Web Components、ドラッグ、アニメーション——こうした場面では Browser Mode が現実的な選択です。

設定も @vitest/browser-playwright の導入と vitest.config.ts の数行で始められます。React / Vue の API は Testing Library に近く、学習コストは低めです。

万能ではありません。単体 UI は Browser Mode、ユーザーフローは Playwright。開発中は前者で UI を広く押さえ、リリース前に後者で重要パスを確認する組み合わせが、カバレッジと CI 速度のバランスに効きます。

最後にカバレッジゲート。閾値を下回る PR はマージ不可にし、緩やかな低下を防ぎます。vitest-coverage-report-action を入れれば、PR 上で変化が見えるので、抜けもすぐ気づけます。

まだ試していないなら、ボタンや入力のような小さなコンポーネントから始めてください。設定が動くことを確認してから、複雑な部品へ広げれば十分です。つまずきはありますが、一度慣れるとテストが開発の加速装置になります。

Vitest Browser Mode でコンポーネントテストを設定する

ゼロから Browser Mode を設定し、React/Vue のコンポーネントテストを実行し、CI にカバレッジゲートを組み込みます

⏱️ 目安時間: 20 分

  1. 1

    ステップ 1: Playwright プロバイダーをインストール

    npm install -D vitest @vitest/browser-playwright で依存関係を入れ、npx playwright install chromium でブラウザを取得します。
  2. 2

    ステップ 2: vitest.config.ts を設定

    test.browser で provider: playwright()、enabled: true、instances: [{ browser: 'chromium' }] を指定します。CI では headless: true を追加します。
  3. 3

    ステップ 3: コンポーネントテストを書く

    page.mount() でコンポーネントを描画し、page.getByRole() で要素を取得、userEvent.click() で操作し、expect.element() で結果を検証します。
  4. 4

    ステップ 4: カバレッジゲートを設定

    vitest.config.ts の coverage.thresholds に閾値(lines: 80 など)を設定し、GitHub Actions に vitest-coverage-report-action を入れて PR のカバレッジ変化を表示します。

FAQ

Browser Mode と jsdom の違いは?
jsdom は Node.js 上で DOM をシミュレートします。Canvas の描画、CSS 計算スタイル、Web Components のライフサイクルはテストできません。Browser Mode は実ブラウザでコンポーネントを描画し、実際の挙動を検証します。
Browser Mode と Playwright E2E はどう使い分ける?
Browser Mode は単一コンポーネント向け(約 200ms/テスト)で、開発中に即フィードバックが得られます。Playwright は複数ページのフロー向け(2〜5 秒/テスト)で、リリース前に重要パスを確認します。併用を推奨します。
カバレッジ閾値はどのくらいが妥当?
新規プロジェクトは 50% から始め、成熟したプロジェクトは 80% が目安です。コアモジュールは 90% も可能です。100% は不要で、一部の境界ケースはテストしづらいです。
Browser Mode は Vue 2 に対応している?
非対応です。Vue 2 は @vue/test-utils を使います。Vue 3 なら @vitest/browser-vue をそのまま利用できます。
CI で Browser Mode を使うときの注意点は?
Playwright インストール時は --with-deps を付け、headless: true を設定し、タイムアウトを長め(30 秒程度)にし、並列数は抑えます(maxWorkers: 4 など)。

6分で読めます · 公開日: 2026年5月17日 · 更新日: 2026年7月14日

コメント

GitHubアカウントでログインしてコメントできます

Easton BlogEaston Blog