CIでの運用と保守 — 壊れにくいテストを維持する
E2Eテストを書くことよりも難しいのは、書いたテストを維持し続けること です。テストスイートが成長するにつれ、実行時間が伸び、不安定なテスト(Flaky Test)が発生し、UIの変更に追従する作業が発生します。
この章では、E2EテストをCIに組み込み、長期的に健全な状態で運用するための実践的なノウハウを整理します。
学習者たまに失敗するテスト(Flaky)が出てきて、もう信用できなくなってきた…どうすれば?
CIでPlaywrightを動かす
GitHub Actionsの設定
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Build application
run: npm run build
- name: Run E2E tests
run: npx playwright test
- name: Upload test report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30テスト実行の最適化
並列実行
Playwrightはデフォルトで テストを並列実行 します。CIでは環境のCPUコア数に応じてワーカー数を調整します。
// playwright.config.ts
export default defineConfig({
workers: process.env.CI ? 2 : undefined,
});シャーディング
テストが多い場合、複数のCI jobに 分散実行(シャーディング) できます。
jobs:
e2e:
strategy:
matrix:
shard: [1/3, 2/3, 3/3]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}3つのジョブに均等にテストが分配され、全体の実行時間が約1/3になります。
Flaky Testへの対処
Flaky Test(不安定なテスト) は、コードに変更がないのに成功したり失敗したりするテストです。E2Eテストで最もよくある問題であり、放置するとテスト結果への信頼が失われます。
Flaky Testの原因
| 原因 | 例 |
|---|---|
| タイミングの問題 | アニメーション完了前にクリック、API応答前にアサーション |
| テストデータの競合 | 並列実行でテスト同士がデータを書き換え合う |
| 環境依存 | ローカルでは速いがCIでは遅い |
| 外部サービスへの依存 | サードパーティAPIが不安定 |
対処法
1. 自動待機を活用する
Playwrightの自動待機を信頼し、明示的な waitForTimeout は避けます。
// NG — 固定時間の待機
await page.waitForTimeout(3000);
await page.click('#submit');
// OK — Playwrightが自動で待つ
await page.getByRole('button', { name: '送信' }).click();2. リトライを設定する
CIでは retries: 2 を設定し、一時的な不安定さを吸収します。
export default defineConfig({
retries: process.env.CI ? 2 : 0,
});3. テストデータの独立性を保つ
各テストが独自のデータを使い、他のテストのデータに依存しないようにします。
test('ユーザーを作成できる', async ({ page }) => {
const uniqueEmail = `test-${Date.now()}@example.com`;
// このテスト専用のデータを使う
});テストの保守戦略
セレクタの安定性
UIの変更でテストが壊れる最大の原因は セレクタの脆さ です。
// 壊れやすい — CSSクラスやDOM構造に依存
page.locator('.btn-primary.submit-form');
page.locator('div > form > button:nth-child(2)');
// 壊れにくい — ユーザーに見える情報に依存
page.getByRole('button', { name: '送信' });
page.getByLabel('メールアドレス');安定性の優先順位:
getByRole— アクセシビリティロール(最も安定)getByLabel— フォームラベルgetByText— 表示テキストgetByTestId—data-testid属性(UIが不安定な場合の保険)
テストの定期的な棚卸し
四半期に一度くらいの頻度で、テストスイートを棚卸しします。
- 価値のないテストを削除 — 他のテストとほぼ同じフローを通るもの
- Flaky Testを修正 — リトライ頻度が高いものを特定
- 実行時間の長いテストを最適化 — 不要なナビゲーションを省略
- カバーすべきフローの追加 — 新機能のクリティカルパスを追加

テスト実行のタイミング
すべてのPRでフルのE2Eテストを実行すると、CIが遅くなります。タイミングを使い分けます。
| タイミング | 実行するテスト | 理由 |
|---|---|---|
| PR作成時 | クリティカルパスのみ(5〜10件) | フィードバックを速く |
| mainブランチマージ後 | 全E2Eテスト | 網羅的な確認 |
| 定期実行(毎晩) | 全E2E + マルチブラウザ | 環境依存の問題を検出 |
// playwright.config.ts — タグでテストを分類
// クリティカルパスには @critical タグを付けるtest('ログインできる @critical', async ({ page }) => { ... });
test('プロフィール画像を変更できる', async ({ page }) => { ... });# CriticalだけPRで実行
npx playwright test --grep @critical
# 全テストはマージ後に実行
npx playwright testテストレポートの活用
PlaywrightのHTMLレポートには、失敗したテストのスクリーンショット、トレース、エラーメッセージが含まれます。
失敗テストの調査手順
- テストレポート を開き、失敗したテストを確認
- スクリーンショット で失敗時の画面を確認
- トレース をダウンロードし、ステップごとの画面状態を確認
- 原因を特定し、コードまたはテストを修正
# CIでアップロードされたレポートをダウンロードして確認
npx playwright show-report ./downloaded-reportチームでの運用のコツ
テストの所有権
- E2Eテストは 機能を開発した人が書く のが基本
- 専任のQAチームがある場合は、開発者が骨組みを書き、QAがシナリオを拡充する分担もある
テストファーストの判断
- 新機能開発時にE2Eテストを先に書くのは、UIが固まってから の方が効率的
- APIの結合テストはテストファーストで書くと設計の指針になる
失敗したテストの扱い
- テスト失敗は プロダクションコードのバグと同じ緊急度 で扱う
test.skip()で一時的に無効にする場合は、issueを作成して期限を決める
まとめ
- CIでは GitHub Actions + Playwright の設定がシンプルに組める
- 並列実行とシャーディング で実行時間を短縮
- Flaky Testは 自動待機の活用、データの独立性、リトライ設定 で対処
- セレクタは
getByRole>getByLabel>getByText>getByTestIdの優先順位 - PR時はクリティカルパスのみ、マージ後にフルテストを実行する 段階的な戦略
- テストの 定期的な棚卸し で、テストスイートの健全性を維持する
この本では、テストの粒度の考え方から、結合テスト・E2Eテストの実践、Playwrightの活用、CIでの運用までを一通り整理しました。単体テスト本と合わせて、テスト戦略全体の地図として活用してください。