言語を切り替える
テーマを切り替える

Next.js E2E テスト:Playwright 自動化テスト実践ガイド

バグチケットに赤い「緊急」ラベル。決済フローがまた壊れた。テスト環境では問題ないのに、本番だけ動かない。

先週のリリース前、30 以上の画面を手動クリックし、十数個のフォームを入力し、3 種類のブラウザを行き来した——それでも、モバイルで最下部までスクロールしないと出ないボタンの不具合を見落とした。プロダクトマネージャーから「クーポンが使えない」と連絡が来る。

もう手動テストは限界。人海戦術は、いずれ自分もチームも疲弊させる。

選定で Cypress、Selenium、Puppeteer、Playwright を比較し、マルチブラウザ対応と設定の手軽さから Playwright を選んだ。導入 1 週間で、手動では見つからなかった 5 件のバグ——Firefox 限定のスタイル崩れや、非同期 API の競合状態など——を検出できた。

最初は設定を何度も直し、テストも 2 回書き直した。今は CI/CD で全自動、コミットのたびにテストが走り、本番バグは半分以下に減った。

なぜ Playwright か(vs Cypress)

Cypress を使ったことがあるなら、設定の簡単さ、ドキュメント、コミュニティの活発さは知っているはず。それでも最終的に Playwright にした理由は 3 つ。

マルチブラウザ対応が弱い。Cypress の Firefox / Safari 対応は長く中途半端で、実質 Chromium 中心。Chrome では完璧な決済ページが Safari で真っ白——Safari 非対応の CSS プロパティが原因だった。Playwright は Chromium、Firefox、WebKit をネイティブサポートし、1 セットのテストで主要ブラウザをカバーできる。

テスト速度。Playwright の並列実行は強い。Cypress は直列で 50 ケースに十数分、Playwright は 8 worker 並列で 5 分。CI/CD では 1 分がコストに直結する。

API 設計。Cypress から Playwright に移った当初は、チェーン呼び出しの書き心地に慣れなかった。しばらく async/await を使ううち、現代 JavaScript の書き方に近く、Next.js の Server Components とも一貫すると感じた。

Cypress が悪いわけではない。Chrome だけでよく、テストに不慣れなチームなら Cypress の方が入りやすい。デバッグツールとタイムトラベル(Time Travel)は初心者に優しい。

ただし私の要件には Playwright が合った。

  • クロスブラウザテストが必要
  • Next.js/React 経験があり、async/await に抵抗がない
  • CI で速いフィードバックが欲しい
  • API ルートと SSR ページもテストしたい

ツールに絶対の優劣はない。小規模でテスト経験が浅いなら Cypress で素早く始める。ある程度の規模で長期投資するなら Playwright がよい選択。

Next.js + Playwright 設定実践

Playwright のインストールは 3 コマンドで済む。

npm init playwright@latest
# または pnpm
pnpm create playwright

インストール時の質問へのおすすめ:

  • TypeScript? Yes(型ヒントでミスを減らせる)
  • テストディレクトリ?tests(デフォルトで OK)
  • GitHub Actions? Yes(後述の CI/CD で使う)

完了すると、次のファイルが増える。

your-nextjs-project/
├── tests/               # テストケースディレクトリ
│   └── example.spec.ts
├── playwright.config.ts # Playwright 設定
└── .github/
    └── workflows/
        └── playwright.yml  # CI 設定

設定ファイルで踏んだ坑

初期の playwright.config.ts は一般 Web 向け。Next.js では調整が必要。半年使って安定した設定は次のとおり。

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // テストディレクトリ
  testDir: './tests',

  // グローバルタイムアウト:単一テスト 30 秒
  timeout: 30 * 1000,

  // グローバル expect タイムアウト:要素検索 5 秒
  expect: {
    timeout: 5000,
  },

  // 失敗時リトライ(CI では有効推奨)
  retries: process.env.CI ? 2 : 0,

  // 並列 worker 数(8 コアマシンなら 4)
  workers: process.env.CI ? 2 : 4,

  // テストレポート
  reporter: [
    ['html'],                    // HTML レポート生成
    ['list'],                    // ターミナルリスト出力
    process.env.CI ? ['github'] : ['list'], // CI では GitHub 形式
  ],

  // Next.js 開発サーバー起動
  webServer: {
    command: 'npm run dev',
    port: 3000,
    timeout: 120 * 1000,         // 初回起動のコンパイル待ち
    reuseExistingServer: !process.env.CI, // ローカルは既存サーバー再利用
  },

  // テストプロジェクト(マルチブラウザ)
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    // モバイル(任意)
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
  ],

  // グローバル設定
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',      // 失敗時 trace 記録
    screenshot: 'only-on-failure', // 失敗時スクリーンショット
    video: 'retain-on-failure',   // 失敗時録画
  },
});

よく踏む坑

  1. webServer.timeout は十分長く。最初 30 秒にしていたが、Next.js のコールドスタートでコンパイルが走り、よくタイムアウトした。120 秒で安定。

  2. ローカルでは reuseExistingServer: true。毎回 Next.js を再起動すると待ち時間が地獄になる。

  3. workers は盛りすぎない。CPU コア数いっぱいにすると PC が固まった。半分くらいがバランスよい。

  4. モバイルテストは任意。レスポンシブな Next.js なら Mobile Chrome でモバイル固有バグを拾えるが、時間は約 2 倍。要件次第。

設定後、公式サンプルを実行。

npx playwright test

緑の passed が出れば環境 OK。本番のテストケースに進める。

画面操作テストのベストプラクティス(Page Object Model)

最初は全部 1 ファイルに書いていた。ログインページだけ 100 行超、page.locatorpage.fillpage.click が散乱。ボタンのセレクタを 1 つ変えたら、十数ファイルを直す羽目になった。

Page Object Model(POM) を学んで、コードがかなりすっきりした。ページ操作をクラスにまとめ、テストはメソッド呼び出しだけにする。

POM なし(反面教師)

// tests/login.spec.ts
import { test, expect } from '@playwright/test';

test('用户登录', async ({ page }) => {
  await page.goto('/login');

  // 直接操作、重複だらけ
  await page.locator('input[name="email"]').fill('user@example.com');
  await page.locator('input[name="password"]').fill('password123');
  await page.locator('button[type="submit"]').click();

  await expect(page.locator('h1')).toContainText('Dashboard');
});

test('登录失败提示', async ({ page }) => {
  await page.goto('/login');

  // 同じ操作をまた書く...
  await page.locator('input[name="email"]').fill('wrong@example.com');
  await page.locator('input[name="password"]').fill('wrongpass');
  await page.locator('button[type="submit"]').click();

  await expect(page.locator('.error')).toBeVisible();
});

input[name="email"]input[id="email"] に変わったら、全テストを直すことになる。

POM でリファクタリング(推奨)

Page Object を作成。

// tests/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;
  readonly dashboardTitle: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.locator('input[name="email"]');
    this.passwordInput = page.locator('input[name="password"]');
    this.submitButton = page.locator('button[type="submit"]');
    this.errorMessage = page.locator('.error');
    this.dashboardTitle = page.locator('h1');
  }

  // ログイン操作
  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  // ナビゲーション
  async goto() {
    await this.page.goto('/login');
  }

  // 検証
  async expectLoginSuccess() {
    await this.dashboardTitle.waitFor();
    await expect(this.dashboardTitle).toContainText('Dashboard');
  }

  async expectLoginError() {
    await expect(this.errorMessage).toBeVisible();
  }
}

テストはこう短くなる。

// tests/login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

test('用户登录', async ({ page }) => {
  const loginPage = new LoginPage(page);

  await loginPage.goto();
  await loginPage.login('user@example.com', 'password123');
  await loginPage.expectLoginSuccess();
});

test('登录失败提示', async ({ page }) => {
  const loginPage = new LoginPage(page);

  await loginPage.goto();
  await loginPage.login('wrong@example.com', 'wrongpass');
  await loginPage.expectLoginError();
});

セレクタ変更は LoginPage.ts だけ。テストは自然言語のように読める。

実プロジェクトのディレクトリ構成

tests/
├── pages/                  # Page Objects
│   ├── LoginPage.ts
│   ├── DashboardPage.ts
│   └── CheckoutPage.ts
├── fixtures/               # テストデータとヘルパー
│   └── testData.ts
├── auth.spec.ts           # 認証テスト
├── checkout.spec.ts       # 決済フローテスト
└── dashboard.spec.ts      # Dashboard テスト

踏んだ坑とアドバイス

  1. 過度な抽象化は不要。1 回しか測らないページは直接書いてよい。POM のための POM は避ける。

  2. メソッド名は意味が伝わるようにfillLoginForm()fillForm() より半年後も読みやすい。

  3. 待機ロジックは Page Object 内へ。Playwright の auto-wait は優秀だが、waitFor() が必要なときもある。テスト側を汚さない。

  4. テストデータは別管理。ユーザー名・パスワードは fixtures/testData.ts に集約。

// tests/fixtures/testData.ts
export const testUsers = {
  validUser: {
    email: 'user@example.com',
    password: 'password123'
  },
  invalidUser: {
    email: 'wrong@example.com',
    password: 'wrongpass'
  }
};

テストでの利用例。

import { testUsers } from './fixtures/testData';

await loginPage.login(testUsers.validUser.email, testUsers.validUser.password);

このパターンで保守コストは大きく下がる。今は Page Object を定義し、数行のテストを書く——それで済む。

API ルートの E2E テスト

Next.js の API Routes もアプリの一部。Postman で手動テストする日々は終わりにした。Playwright 内で API まで測れば、ブラウザを開かなくてよい。

Playwright の request で HTTP を直接送れ、API Routes のテストに向く。

基本的な API テスト

ユーザー一覧 API の例。

// tests/api/users.spec.ts
import { test, expect } from '@playwright/test';

test.describe('用户 API 测试', () => {
  test('GET /api/users - 获取用户列表', async ({ request }) => {
    const response = await request.get('/api/users');

    // ステータスコード
    expect(response.status()).toBe(200);

    // レスポンス形式
    const users = await response.json();
    expect(Array.isArray(users)).toBeTruthy();
    expect(users.length).toBeGreaterThan(0);

    // データ構造
    expect(users[0]).toHaveProperty('id');
    expect(users[0]).toHaveProperty('email');
    expect(users[0]).toHaveProperty('name');
  });

  test('POST /api/users - 创建用户', async ({ request }) => {
    const newUser = {
      email: 'test@example.com',
      name: 'Test User',
      password: 'password123'
    };

    const response = await request.post('/api/users', {
      data: newUser
    });

    expect(response.status()).toBe(201);

    const createdUser = await response.json();
    expect(createdUser.email).toBe(newUser.email);
    expect(createdUser).not.toHaveProperty('password'); // パスワードは返さない
  });

  test('POST /api/users - 邮箱重复应返回错误', async ({ request }) => {
    const duplicateUser = {
      email: 'existing@example.com',
      name: 'Duplicate User',
      password: 'password123'
    };

    const response = await request.post('/api/users', {
      data: duplicateUser
    });

    expect(response.status()).toBe(400);

    const error = await response.json();
    expect(error.message).toContain('邮箱已存在');
  });
});

認証付き API テスト

多くの API はログイン後のみ。先に token を取り、ヘッダーに付ける。

// tests/api/auth.spec.ts
import { test, expect } from '@playwright/test';

let authToken: string;

test.describe('需要认证的 API', () => {
  // 全テスト前にログイン
  test.beforeAll(async ({ request }) => {
    const response = await request.post('/api/auth/login', {
      data: {
        email: 'user@example.com',
        password: 'password123'
      }
    });

    const { token } = await response.json();
    authToken = token;
  });

  test('GET /api/profile - 获取用户资料', async ({ request }) => {
    const response = await request.get('/api/profile', {
      headers: {
        'Authorization': `Bearer ${authToken}`
      }
    });

    expect(response.status()).toBe(200);

    const profile = await response.json();
    expect(profile.email).toBe('user@example.com');
  });

  test('未登录访问应返回 401', async ({ request }) => {
    const response = await request.get('/api/profile');
    expect(response.status()).toBe(401);
  });
});

ハイブリッド:画面 + API

画面テストと API テストを組み合わせる。記事投稿フローの例。

// tests/posts.spec.ts
import { test, expect } from '@playwright/test';

test('发布文章完整流程', async ({ page, request }) => {
  // 1. 画面でログイン
  await page.goto('/login');
  await page.fill('input[name="email"]', 'author@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  // 2. 編集ページへ
  await page.goto('/posts/new');
  await page.fill('input[name="title"]', '测试文章标题');
  await page.fill('textarea[name="content"]', '这是测试内容');
  await page.click('button:has-text("发布")');

  // 3. 詳細ページへ遷移待ち
  await page.waitForURL(/\/posts\/\d+/);

  // 4. API で作成を確認
  const url = page.url();
  const postId = url.split('/').pop();

  const response = await request.get(`/api/posts/${postId}`);
  expect(response.status()).toBe(200);

  const post = await response.json();
  expect(post.title).toBe('测试文章标题');
  expect(post.content).toBe('这是测试内容');
  expect(post.status).toBe('published');
});

フロント操作とバックエンドデータの両方を検証できる。一度、「公開成功」と表示されているのに DB 上は draft のまま——状態更新ロジックのバグをこのパターンで見つけた。

実践アドバイス

  1. 境界値もカバー。正常系だけでなく、パラメータ欠落、型エラー、権限不足も。

  2. テストデータのクリーンアップ。API テストは DB に書き込む。afterAll で片付け。専用 DB を定期クリアする方法も。

test.afterAll(async ({ request }) => {
  await request.delete('/api/test/cleanup');
});
  1. 外部サービスは Mock。決済・SMS などは Mock しないと、テストのたびに課金される。

  2. 応答時間にも目を。Playwright で所要時間を測り、アサーションを足す。

const start = Date.now();
await request.get('/api/users');
const duration = Date.now() - start;

expect(duration).toBeLessThan(1000); // 1 秒以内

API と画面を組み合わせれば、およそ 90% をカバーできる。残り 10% は単体テストで補う。

GitHub Actions CI/CD 統合

テストが書けたら CI/CD へ。コミットのたびに自動実行——何度も「大丈夫」と思って CI が赤になった経験がある。

npm init playwright は GitHub Actions 設定も生成するが、デフォルトは基本的。実運用向けに調整した例を紹介する。

基本 CI 設定

初期 .github/workflows/playwright.yml

name: Playwright Tests

on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - uses: actions/setup-node@v3
      with:
        node-version: 18

    - name: Install dependencies
      run: npm ci

    - name: Install Playwright Browsers
      run: npx playwright install --with-deps

    - name: Run Playwright tests
      run: npx playwright test

    - uses: actions/upload-artifact@v3
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

使えるが、課題もある。

  1. 毎回ブラウザをインストール——遅い
  2. テスト DB がない——API テストが落ちる
  3. レポートはダウンロードしないと見られない

本番級設定

キャッシュ、DB、レポートデプロイを足した実運用版。

name: E2E Tests

on:
  push:
    branches: [ main, dev ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest

    services:
      # テスト DB(PostgreSQL)
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    env:
      DATABASE_URL: postgresql://test:test@localhost:5432/testdb
      NODE_ENV: test

    steps:
    - uses: actions/checkout@v4

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Cache Playwright browsers
      uses: actions/cache@v3
      with:
        path: ~/.cache/ms-playwright
        key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}

    - name: Install Playwright Browsers
      run: npx playwright install --with-deps chromium

    - name: Run database migrations
      run: npm run db:migrate

    - name: Run Playwright tests
      run: npx playwright test

    - name: Upload test results
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

    # main なら GitHub Pages にレポート
    - name: Deploy report to GitHub Pages
      if: always() && github.ref == 'refs/heads/main'
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./playwright-report

設定の要点

  1. services:PostgreSQL をテスト DB に。MySQL なら mysql:8 に差し替え。

  2. ブラウザキャッシュactions/cache で Playwright ブラウザを保存。初回は遅いが 2 回目以降は速い。CI では chromium のみ——3 ブラウザは遅すぎる。

  3. DB マイグレーション:テスト前に npm run db:migratepackage.json に例:

{
  "scripts": {
    "db:migrate": "prisma migrate deploy"
  }
}
  1. レポートデプロイ:main のレポートを GitHub Pages へ。チームがオンラインで確認できる。

環境変数

API キーなどは GitHub Secrets へ。

env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}
  NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
  STRIPE_SECRET_KEY: ${{ secrets.STRIPE_TEST_KEY }}

失敗時のデバッグ

CI が赤になったとき、Playwright の機能が効く。

  1. trace を見るtrace: 'on-first-retry' で失敗時に trace 生成。npx playwright show-trace trace.zip で再生。

  2. スクリーンショットと動画:失敗時に自動保存。画面状態が一目でわかる。

  3. ローカルで CI 再現act で GitHub Actions をローカル実行。

# act インストール
brew install act  # macOS
# または
choco install act  # Windows

# workflow 実行
act -j test

踏んだ坑

  1. タイムアウトは現実的に。30 分にしていたが、ハングで CI 時間を無駄にした。60 分にしつつ、遅いテストを監視。

  2. 並列は控えめ。CI マシンは性能が限られる。worker 2 本で十分なことが多い。

  3. リトライは盛りすぎないretries: 2 はネットワーク揺れ用。本当に壊れたテストは何度リトライしても時間の無駄。

CI 導入後、コード品質は明らかに上がった。PR は緑チェック必須——テストを軽視できなくなった。

テストカバレッジとレポート

実行後は結果確認が重要。Playwright のレポートは情報量が多く、見やすい。

HTML レポート(最常用)

npx playwright show-report

ローカルで Web ページが開き、次が確認できる。

  • 各テストの pass/fail
  • 実行時間
  • 失敗時のスクリーンショットと動画
  • Trace(実行全体を再生)

Trace Viewer が特に強い。失敗テストをクリックすると、各ステップのネットワーク、DOM スナップショット、コンソールログがタイムラインで見える。問題箇所の特定が速い。

テストカバレッジ

E2E のカバレッジはコードではなく機能カバレッジ。チェックリストで管理している。

## 测试覆盖清单

### 用户认证
- [x] 登录(正常流程)
- [x] 登录失败(错误密码)
- [x] 注册
- [x] 找回密码
- [ ] 第三方登录(Google)

### 商品管理
- [x] 添加商品
- [x] 编辑商品
- [x] 删除商品
- [ ] 批量导入

### 订单流程
- [x] 加购物车
- [x] 结算
- [x] 支付(模拟环境)
- [ ] 退款流程

tests/README.md に置き、機能追加のたびに更新。未カバーが一目でわかる。

コードカバレッジ(任意)

どうしてもコードカバレッジが見たいなら Istanbul や v8 coverage を設定できる。

// playwright.config.ts
export default defineConfig({
  use: {
    // コードカバレッジ有効化
    trace: 'on',
    // カバレッジ収集コード注入
    contextOptions: {
      recordVideo: {
        dir: 'test-results/videos'
      }
    }
  }
});

E2E でコードカバレッジを見ることは少ない。ロジックは単体テスト、E2E はフローが通るかを見る。

カスタムレポート

Slack や DingTalk へ結果通知も可能。カスタム reporter の例。

// my-reporter.ts
import { Reporter } from '@playwright/test/reporter';

class SlackReporter implements Reporter {
  onEnd(result) {
    const passed = result.suites.filter(s => s.ok).length;
    const failed = result.suites.length - passed;

    // Slack へ送信
    fetch('https://hooks.slack.com/services/YOUR_WEBHOOK', {
      method: 'POST',
      body: JSON.stringify({
        text: `测试完成:${passed} 通过,${failed} 失败`
      })
    });
  }
}

export default SlackReporter;

設定で有効化。

// playwright.config.ts
export default defineConfig({
  reporter: [
    ['html'],
    ['./my-reporter.ts']
  ]
});

テストトレンド

Playwright Test Runner に trace を上げれば、合格率や実行時間の推移を可視化できる。

私はもっと単純に、CI 後に合格率と時間を CSV に追記し、Google Sheets でグラフ化している。実用的で十分。

レポートの使い分け

  1. ローカル開発:ターミナル出力。失敗時は --debug で再実行。

    npx playwright test --debug
  2. PR レビュー:CI の HTML レポート。失敗テストと実行時間に注目。常にタイムアウトするテストはコード側の問題の可能性。

  3. 定期レビュー:週 1 回、カバレッジチェックリストを見て不足を補う。

レポートは数字以上に、問題発見とプロセス改善の道具。

まとめ

半年前、手動テストで深夜 3 時まで粘った夜を思い出す。今はだいぶ楽になった。

Playwright + Next.js は一度設定すればあとは楽。コミットのたび CI が走り、リリース前の不安が減った。本番バグは減り、PM からの連絡も減った(笑)。

Next.js をまだ手動テストしているなら:

  1. コアフローから。ログイン、決済など重要パスを先に。
  2. Page Object Model を使う。最初は面倒でも、長期では時間を節約。
  3. CI/CD に載せる。自動化テストを自動実行しないなら、書く意味が薄い。
  4. 100% カバレッジを追わない。コアを押さえれば十分。

E2E テストは技術ツールであると同時に、チームの協働の仕組み。品質意識が上がり、コミュニケーションコストが下がり、リリースが予測可能になる。

今は 17:30 に帰れる。空いた時間で、期限切れ間近のジム会員券を使い始めた——以前は更新する暇もなかった。

テストを書き始めよう。未来の自分が感謝してくれる。

FAQ

Playwright と Cypress、結局どっちを選ぶ?
プロジェクト要件次第です。

• Playwright:クロスブラウザ(Chromium/Firefox/WebKit)、並列が速い(8 worker で Cypress の約 3 倍)、async/await 構文、中〜大規模向き
• Cypress:Chrome 中心、タイムトラベルデバッグが強い、コミュニティ資産が豊富、初心者向き

Chrome だけでよく、テスト経験が浅いなら Cypress。クロスブラウザ・CI の速度・React/Next.js 経験があるなら Playwright が向きます。
Page Object Model は必ず使うべき?
必須ではありませんが、強くおすすめします。

小規模(10 ケース未満)や一度きりのページなら、テスト内に直接書いても問題ありません。ただし次のような場合は POM の価値が出ます。
• 同じページを複数テストで触る
• 複数人でテストコードを保守する
• 長期運用するプロジェクト

セレクタを 1 回変えたとき——POM がないと 10 ファイル以上、あれば 1 ファイルだけです。
CI 環境でテストがいつもタイムアウトする
よくある原因と対処:

• webServer.timeout が短い:120 秒に(Next.js のコールドスタートでコンパイルが走る)
• worker が多すぎる:CI マシンは性能が限られるので 2〜4 で十分
• テスト自体の問題:trace でネットワーク遅延か要素待ちか切り分け
• ブラウザインストールが遅い:actions/cache でブラウザをキャッシュ

CI では chromium のみ、ローカルでマルチブラウザ——これだけでかなり速くなります。
テストデータはどう管理する?毎回 DB を手動で片付ける?
主に 3 パターンです。

• 専用テスト DB:テスト専用、定期クリア、開発環境に影響しない
• テスト後にクリーンアップ:test.afterAll() で API を呼ぶが、漏れやすい
• Docker コンテナ:実行のたびに新しい DB、終了後に破棄(最もクリーンだが遅い)

筆者は 1 + 2 の併用。CI は Docker DB、ローカルは専用テスト DB + afterAll クリーンアップです。
API テストで第三者サービスを Mock すべき?
Mock 必須。理由は 3 つ。

• コスト:本物の決済/ SMS API は都度課金
• 速度:外部応答が遅いとテスト全体が遅くなる
• 安定性:外部障害で自分のテストが落ちるべきではない

Playwright はネットワークインターセプトで Mock 可能:
await page.route('**/api/payment', route => route.fulfill({ status: 200, body: '{"success": true}' }));

Next.js API Routes で環境変数を見て、テスト時はモックデータを返す方法もあります。
テストカバレッジはどの程度が合格ライン?
E2E はコードカバレッジではなく機能カバレッジを見ます。

優先度:
• コア(ログイン、決済、注文):100%
• 高頻度(商品閲覧、カート追加):80% 以上
• 低頻度(パスワード再設定、返金):50% 以上
• エッジ(テーマ切替、言語切替):任意

100% 追求は不要。コアフローを 100%、全体機能 60% でも 90% のバグは防げます。
Playwright があれば単体テストは不要?
両方必要。役割が補完関係です。

• E2E(Playwright):機能フロー、UI 操作、前後端連携——遅いが網羅的
• 単体(Jest/Vitest):関数ロジック、境界値、エラー処理——速いが局所的

理想比率は単体 70%、E2E 30%。ユーティリティ、hooks、コンポーネントは単体、ユーザーフロー全体は E2E。

単体で原因を絞り、E2E で本当に動くか確認。両方やりましょう。

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

関連記事

コメント

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