OSSのテストフレームワーク比較:Vitest vs Jest vs Playwright でフロントエンドテストを高速化する
オープンソースラボ編集部 ・ 2026年6月13日
OSSのテストフレームワーク比較:Vitest vs Jest vs Playwright でフロントエンドテストを高速化する
Cypress(月$75/チーム〜)に対して、Vitest(最速のVite Native Unit Test)・Jest(最もポピュラーなJavaScriptテストランナー)・Playwright(Microsoftが開発したE2Eブラウザテスト)はOSSのJavaScript/TypeScriptテストフレームワークです。
テストの種類とカバレッジ
Unit Test → Vitest / Jest
Integration → Vitest + Testing Library
E2E Test → Playwright / Cypress
API Test → Vitest + msw / Supertest
Visual Test → Playwright / Storybook
主要ツールの概要
Vitest
2022年に公開されたVite専用の高速テストランナーです。GitHubスター13k+。Jestと互換性のあるAPIでViteプロジェクトにネイティブ統合し、起動時間がJestの1/10〜1/100程度です。TypeScript/ESM/CJSをトランスフォームなしで実行でき、vi.mock()・スナップショット・カバレッジ(v8/istanbul)・ブラウザモード(Vitest Browser Mode)・UIダッシュボードを提供します。
# Next.js プロジェクトにVitestをセットアップ
npm install -D vitest @vitejs/plugin-react @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
// vitest.config.ts - Vitest設定
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import { resolve } from "path";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
globals: true, // expect, describe, it を import なしで使う
setupFiles: ["./src/test/setup.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["node_modules", "src/test"],
},
exclude: ["tests/e2e/**"], // PlaywrightのE2Eは除外
},
resolve: {
alias: { "@": resolve(__dirname, "./src") },
},
});
// src/test/setup.ts
import "@testing-library/jest-dom";
import { vi } from "vitest";
// fetch のモック(テスト環境にはfetchがない)
global.fetch = vi.fn();
// src/components/PostCard.test.tsx - React Componentのテスト
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { PostCard } from "./PostCard";
const mockPost = {
id: "1",
title: "OSSのテスト戦略",
slug: "oss-testing-strategy",
excerpt: "Vitest・Playwright・Jestの使い分けを解説",
author: { name: "山田太郎" },
publishedAt: "2026-01-01",
};
describe("PostCard", () => {
it("タイトルと著者が表示される", () => {
render(<PostCard post={mockPost} />);
expect(screen.getByText("OSSのテスト戦略")).toBeInTheDocument();
expect(screen.getByText("山田太郎")).toBeInTheDocument();
});
it("記事ページへのリンクが正しい", () => {
render(<PostCard post={mockPost} />);
const link = screen.getByRole("link");
expect(link).toHaveAttribute("href", "/blog/oss-testing-strategy");
});
it("いいねボタンをクリックするとカウントが増える", async () => {
const onLike = vi.fn();
render(<PostCard post={mockPost} onLike={onLike} />);
await userEvent.click(screen.getByRole("button", { name: /いいね/ }));
expect(onLike).toHaveBeenCalledWith("1");
expect(onLike).toHaveBeenCalledTimes(1);
});
});
// src/lib/posts.test.ts - Server Functionのユニットテスト(DBモック)
import { describe, it, expect, vi, beforeEach } from "vitest";
import { getPublishedPosts, createPost } from "@/lib/posts";
// DBモジュールをモック
vi.mock("@/lib/db", () => ({
db: {
query: {
posts: {
findMany: vi.fn(),
findFirst: vi.fn(),
},
},
insert: vi.fn(),
update: vi.fn(),
},
}));
import { db } from "@/lib/db";
describe("getPublishedPosts", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("公開された記事のみを返す", async () => {
const mockPosts = [
{ id: "1", title: "記事1", status: "published" },
{ id: "2", title: "記事2", status: "published" },
];
vi.mocked(db.query.posts.findMany).mockResolvedValue(mockPosts);
const result = await getPublishedPosts(1, 10);
expect(result).toHaveLength(2);
expect(db.query.posts.findMany).toHaveBeenCalledWith(
expect.objectContaining({ where: expect.anything() })
);
});
});
Playwright
2020年にMicrosoftが公開したOSSのブラウザ自動化・E2Eテストフレームワークです。GitHubスター68k+。Chromium・Firefox・Safari(WebKit)の3ブラウザで並列テストが可能で、TypeScriptファースト・自動待機(flaky test防止)・スクリーンショット・ビデオ録画・トレービューア・APIテストをサポート。playwright codegenでブラウザ操作からテストコードを自動生成できます。
# Playwrightのインストールとセットアップ
npm install -D @playwright/test
npx playwright install # Chromium・Firefox・WebKitをダウンロード
// playwright.config.ts - Playwright設定
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests/e2e",
fullyParallel: true, // テストを並列実行
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 2 : 0, // CI環境では2回リトライ
workers: process.env.CI ? 1 : undefined,
reporter: [["html", { outputFolder: "playwright-report" }]],
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry", // 失敗時にトレースを保存
screenshot: "on",
video: "retain-on-failure",
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "safari", use: { ...devices["Desktop Safari"] } },
{ name: "mobile-chrome", use: { ...devices["Pixel 5"] } },
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
// tests/e2e/auth.spec.ts - 認証フローのE2Eテスト
import { test, expect } from "@playwright/test";
test.describe("認証フロー", () => {
test("メールアドレスとパスワードでログインできる", async ({ page }) => {
await page.goto("/login");
// フォームに入力
await page.getByLabel("メールアドレス").fill("test@example.com");
await page.getByLabel("パスワード").fill("TestPassword123");
await page.getByRole("button", { name: "ログイン" }).click();
// ログイン後のリダイレクト確認
await expect(page).toHaveURL("/dashboard");
await expect(page.getByRole("heading", { name: "ダッシュボード" })).toBeVisible();
});
test("間違ったパスワードでエラーが表示される", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("メールアドレス").fill("test@example.com");
await page.getByLabel("パスワード").fill("wrongpassword");
await page.getByRole("button", { name: "ログイン" }).click();
await expect(page.getByText("メールアドレスまたはパスワードが正しくありません")).toBeVisible();
await expect(page).toHaveURL("/login");
});
});
// tests/e2e/blog.spec.ts - 記事一覧・詳細のE2Eテスト
test.describe("ブログ", () => {
test("記事一覧から詳細ページへ遷移できる", async ({ page }) => {
await page.goto("/blog");
const firstArticle = page.getByRole("article").first();
const title = await firstArticle.getByRole("heading").textContent();
await firstArticle.getByRole("link").first().click();
await expect(page.getByRole("heading", { name: title! })).toBeVisible();
});
test("モバイルレイアウトが正しく表示される", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto("/blog");
// ハンバーガーメニューが表示される
await expect(page.getByRole("button", { name: "メニュー" })).toBeVisible();
// デスクトップナビが非表示
await expect(page.getByRole("navigation", { name: "main" })).not.toBeVisible();
});
});
機能比較表
| 比較項目 | Vitest | Jest | Playwright |
|---|---|---|---|
| 対象テスト | Unit/Integration | Unit/Integration | E2E |
| 起動速度 | 最速(Viteネイティブ) | 遅い(変換あり) | - |
| ブラウザテスト | ブラウザモード(実験) | jsdom(模擬) | ✅(実ブラウザ) |
| TypeScript | ネイティブ | Babel変換 | ✅(ネイティブ) |
| 並列実行 | ✅ | ✅ | ✅(3ブラウザ並列) |
| GitHub Stars | 13k+ | 44k+ | 68k+ |
テストを自動実行するCI/CDパイプラインにはDevOpsカテゴリ/categories/devopsのGitHub ActionsでPull RequestごとにVitest+Playwrightを実行します。テスト結果をSlackに通知するにはCommunicationカテゴリ/categories/communicationのSlack Webhookと組み合わせます。
FAQ
Q. VitestとJestはどちらを新規プロジェクトで採用すべきですか?
A. 新規Vite/Next.js(Viteベース)プロジェクトはVitestを推奨します。理由: ①起動時間がJestの1/10以下(npx vitestが1秒以下で起動)②TypeScriptをトランスフォームなしで実行できる③vi.mock()・vi.spyOn()等のAPIがJestと互換性がある(既存Jestコードの移行が楽)④HMR的なウォッチモードでテスト変更を即座にフィードバック。Jestを使い続けるケース: ①既存のJestテストが大量にある②create-react-app(webpack)ベース③Babel変換が必要な特殊なプロジェクト。2026年時点では新規プロジェクトはVitestがデファクトスタンダードになっています。
Q. PlaywrightでCIのflakyテスト(不安定なテスト)を防ぐには?
A. ①自動待機: page.getByRole("button").click()はPlaywrightが自動で要素が見えるまで待機するためpage.waitForTimeout()は避ける②ロケーターの安定性: page.locator(".btn-123abc")(クラス名)ではなくpage.getByRole("button", { name: "送信" })(役割)を使う③テストの独立性: 各テストが独立したステート(DBやCookie)を持つようにして順序依存をなくす④リトライ設定: playwright.config.tsでCI時にretries: 2を設定⑤トレースビューア: 失敗時のトレース(trace.zip)をCIのアーティファクトに保存してデバッグを効率化。
Q. Playwrightのcodegenでテストコードを自動生成するには?
A. npx playwright codegenコマンドで起動するとブラウザが開き、操作をコードに変換してくれます。
# ローカルサーバーに対してcodegen実行
npx playwright codegen http://localhost:3000
# 認証済み状態でcodegen(ログイン後の状態を保存)
npx playwright codegen --save-storage=auth.json http://localhost:3000/login
# ログイン操作後にブラウザを閉じると auth.json に保存
npx playwright codegen --load-storage=auth.json http://localhost:3000/dashboard
Q. Next.js App RouterのServer Actionをテストするには?
A. Server ActionはNode.jsサーバーで実行されるため、IntegrationテストはVitestで直接関数を呼び出す形で書けます("use server"ディレクティブはテスト環境では無視される)。実際のHTTPリクエストレベルでテストするにはPlaywrightのAPIテスト機能(requestフィクスチャ)またはSupertestを使います。
// Vitest でServer Actionを直接テスト
import { createPostAction } from "@/app/blog/new/actions";
vi.mock("@/auth", () => ({ auth: vi.fn(() => ({ user: { id: "user-1" } })) }));
vi.mock("@/lib/db");
it("認証済みユーザーが記事を作成できる", async () => {
const fd = new FormData();
fd.append("title", "テスト記事");
fd.append("content", "テスト本文");
await expect(createPostAction(fd)).resolves.not.toThrow();
});
まとめ
| ユースケース | 推奨ツール |
|---|---|
| Unit/IntegrationテストのVite環境 | Vitest |
| 既存Jest資産・非Viteプロジェクト | Jest |
| E2Eブラウザテスト・マルチブラウザ | Playwright |