Playwright運用の「地獄」から生還した話:CI/CDを鉄壁にするための技術的最適解
朝起きたらGitHub Actionsのログが真紅に染まり、AWSの請求額が普段の10倍になっていた。原因はPlaywrightのテストコードに仕込んだ無限ループ。CIが終了条件を見失い、無駄なリソースを食い続けた結果だ。
「テスト自動化」は甘美な響きだが、設計を怠ればそれはただの「金食い虫」と化す。この記事では、数々の地獄を見てきた私が、現場で生き残るために叩き込んだPlaywright運用の最適解を共有する。
1. 環境変数の型安全な管理:Zodによるバリデーション
「`.env`を誤ってプッシュした」なんて笑えないミスは、仕組みで防ぐ。環境変数をそのまま読み込むのは危険だ。`zod`を使って、アプリ起動時に環境変数が欠損していないか、型が正しいかを強制的にチェックする。
// env.ts
import { z } from 'zod';
const envSchema = z.object({
BASE_URL: z.string().url(),
TEST_API_TOKEN: z.string().min(1),
});
export const env = envSchema.parse(process.env);
`playwright.config.ts`では、この`env`オブジェクトを参照する。これで「CI上で環境変数が足りない」という初歩的なエラーを、テスト開始の瞬間に検知できる。
2. Flakyテストを撲滅する「Auto-waiting」の真髄
`page.waitForTimeout(5000)`を書いていないか? それは「祈る」のと同じだ。テストが不安定(Flaky)になる最大の原因は、アプリの状態を正しく監視していないことにある。
ネットワークリクエストをフックし、APIのレスポンスを待機するのがPlaywrightの正攻法だ。
// APIレスポンスを待ってから操作を確定させる
await Promise.all([
page.waitForResponse(resp =>
resp.url().includes('/api/v1/data') && resp.status() === 200
),
page.getByRole('button', { name: '保存' }).click(),
]);
また、CI側では「ジョブレベルのタイムアウト」を必ず設定すること。無限ループが起きても、自動的にジョブがKillされるようにしておくのが最低限の防衛線だ。
# .github/workflows/playwright.yml
jobs:
test:
timeout-minutes: 10 # これを入れないと財布が死ぬ
runs-on: ubuntu-latest
# ...
3. 並列実行の罠と「テスト独立性」の確保
テストが増えれば並列実行(Sharding)は必須だが、何も考えずに導入するとDBの競合で全滅する。テストAで作成したユーザーをテストBが消すような依存関係は、並列環境では致命的だ。
解決策は「APIによる動的ユーザー生成」だ。
1. `test.describe.configure({ mode: 'parallel' })` でファイル間並列化を有効化。
2. 各テストファイル内で、`test.beforeEach`を使用して一意なユーザーをAPI経由で作成する。
3. `test.afterEach`でそのユーザーをクリーンアップする。
// 各テストで独立した環境を作る
test.beforeEach(async ({ request }) => {
const user = await createUniqueUser(request);
// コンテキストに保持してテストで使用
});
この「泥臭い」データ分離を徹底するだけで、並列実行の安定性は劇的に向上する。
4. CI環境とローカルの「解離」を防ぐ設定
ローカルは日本語、CIは英語環境。この罠で`getByText`が落ちることは珍しくない。さらに、タイムゾーンの不一致で日付計算が狂うのも「あるある」だ。
`playwright.config.ts`で環境を強制的に統一せよ。
export default defineConfig({
use: {
locale: 'ja-JP',
timezoneId: 'Asia/Tokyo',
},
// 再試行回数を制限し、無限ループ時の爆発を防ぐ
retries: process.env.CI ? 2 : 0,
});
教訓:自動化は「魔法」ではなく「規律」だ
Playwrightは最強のツールだが、それを支えるのはエンジニアの泥臭い規律だ。
- `waitForTimeout`は悪。 即刻削除し、状態監視に切り替えろ。
- テストデータは使い捨て。 APIで生成・削除し、テスト間の依存を物理的に断て。
- CIは「壊れるもの」として設計せよ。 タイムアウト制限と再試行回数の制御は、あなたの財布とメンタルを守る盾になる。
デプロイボタンを押すときに震える必要はない。あなたが書いたテストが、確かな信頼を担保してくれるはずだ。