「E2Eテストが不安定で辛い」と嘆く前に、お前の設計を見直せ。
「E2Eテストは時間がかかるし、すぐ落ちるからクリティカルパスだけでいい」。
本気でそう思っているなら、お前は一生「デプロイボタンを押すたびに心拍数が上がる」という呪いから抜け出せないぞ。
俺も昔はそうだった。深夜のリリース作業中にテストが落ち、原因が「環境の不整合」なのか「コードのバグ」なのか判別できず、結局手動で全画面をポチポチ確認する。そんな無様な夜を何度も過ごした。だが、断言する。「Flaky(不安定)なテスト」の9割は、ツールのせいではなく、お前の設計ミスだ。
今日は、泥臭い試行錯誤の果てにたどり着いた、Playwrightを「ただの自動化ツール」から「最強の守り神」へと昇華させるための極意を叩き込む。
1. 「ログイン地獄」からの脱却:状態を使い回せ
テストのたびにログイン処理を走らせるなんて、演算リソースと時間の無駄だ。ログインAPIが一つ死ぬだけで、全テストが全滅する。依存関係を切り離すのがエンジニアの基本だろう?
`storageState`を使って、認証後のセッションを保存しろ。
// tests/auth.setup.ts
import { test as setup } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', process.env.TEST_USER!);
await page.fill('input[name="password"]', process.env.TEST_PASS!);
await page.click('button[type="submit"]');
// 認証後の状態をファイルに吐き出す
await page.context().storageState({ path: 'auth.json' });
});
`playwright.config.ts`でこれを読み込めば、すべてのテストは「ログイン済み」の状態でスタートできる。これだけで実行時間は劇的に短縮される。
2. Page Object Model (POM) なきテストは「ただのスパゲッティ」
「UIが変わったからテストが落ちた」。そう嘆く奴らは、テストコードの中に直接セレクタを埋め込んでいる。画面構成が変わるたびにテストを書き直すなんて、保守コストが高すぎて破綻するに決まっている。
テストコードとUIの構造を分離するPage Object Model (POM)を導入しろ。
// models/LoginPage.ts
export class LoginPage {
constructor(private page: Page) {}
async login(user: string, pass: string) {
await this.page.fill('[data-testid="email"]', user);
await this.page.fill('[data-testid="password"]', pass);
await this.page.click('[data-testid="login-button"]');
}
}
こうしておけば、UIの変更は`LoginPage`クラスの中を直すだけで済む。テストロジック側を触る必要は一切ない。テストコードこそが、プロダクトの品質を保証する唯一の防波堤だ。 汚すことを恐れるな。
3. 並列実行の罠と「データ競合」の排除
CI/CDでテストを回す際、`--workers`を増やして「速くなった!」と喜ぶのは素人だ。DBを共有している環境で並列実行すれば、テスト同士でデータが干渉し合い、CIが「ガチャ」のように赤くなったり緑になったりする。
対策は「テストごとのデータの分離」だ。動的にユニークなIDを生成し、干渉を防げ。
// テストごとにユニークなユーザー名を作成する例
const uniqueEmail = `test-${Date.now()}-${Math.random()}@example.com`;
await page.fill('[data-testid="email"]', uniqueEmail);
GitHub Actionsの最適化戦略
CIの実行速度を上げるには、キャッシュ戦略が鍵になる。`node_modules`とPlaywrightのブラウザバイナリをキャッシュし、無駄なダウンロードを削れ。- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
4. 「見た目」ではなく「行動」をテストせよ
「ボタンが青いか」を確認するテストはゴミだ。ユーザーにとって重要なのは「ボタンを押した結果、何が起きたか」だ。
- NG: `expect(button).toHaveCSS('background-color', 'blue')`
- OK: `await button.click(); await expect(page.getByText('保存しました')).toBeVisible();`
さあ、明日から何をすべきか?
この記事を読み終わって満足するな。以下のタスクを今すぐ自分のプロジェクトに適用しろ。
1. `data-testid`の徹底: CSSクラスやDOM構造に依存したセレクタを、すべて`data-testid`に書き換える。
2. ログインの共通化: `auth.json`による認証永続化を実装し、テスト実行時間を計測する。
3. POMへのリファクタリング: 既存のテストコードを`Page Object`に切り出す。まずは一番頻繁に落ちるテストからだ。
4. CIのキャッシュ設定: GitHub Actionsの設定を見直し、キャッシュヒット率を最大化する。
E2Eテストは「書く」のではなく「守り神として育てる」ものだ。不安定なテストに苛立つ時間はもう終わりだ。今すぐ手を動かし、デプロイに対する「恐怖」を「確信」に変えていけ。