深夜のデプロイで心臓を止めるな:Playwrightによる「落ちない」テスト自動化の極意

深夜のデプロイで心臓を止めるな:Playwrightによる「落ちない」テスト自動化の極意

午前2時、本番環境のダッシュボードに浮かぶ「500 Internal Server Error」の文字。
原因は、ユーザープロフィール画面の微細なCSS修正に伴う、ReactコンポーネントのProps受け渡しミスだった。ログイン後のヘッダーにユーザー名が表示されず、APIが401を吐き出し続けている。

「テストを書けばよかった」。そんな後悔は、デプロイボタンを押した瞬間にしか意味をなさない。俺たちは、自分の書いたコードが動いていることを、感覚ではなく「証明」しなければならない。

この記事では、Seleniumの悪夢から抜け出し、Playwrightで泥臭く、しかし確実に「守りの要」を構築する方法を共有する。


1. なぜCypressではなくPlaywrightなのか?

「Cypressで十分じゃないか?」という声はもっともだ。だが、大規模なSPAを運用し、かつCI環境での実行コストを突き詰めると、Playwrightの優位性は圧倒的になる。

  • ブラウザコンテキストの並列実行: Cypressは同一ブラウザプロセス内でテストを隔離するが、Playwrightはブラウザコンテキスト単位で完全に分離する。これが実行速度の「異常な速さ」の正体だ。
  • ネットワークインターセプションの柔軟性: `page.route()` を使えば、特定のAPIレスポンスを動的にモック化し、わざとエラーを返してフロントエンドの例外処理をテストできる。これが実務においてどれほど強力か、一度体験すれば戻れない。
  • マルチタブ・マルチブラウザ: 複数のタブを同時に操作し、チャットアプリのリアルタイム同期をテストするような芸当も、Playwrightなら標準機能だ。

2. 認証地獄からの脱却:`storageState`とCIの罠

ログイン処理を毎回テストの頭で行うのは時間の浪費だ。`storageState`で認証情報をシリアライズし、使い回すのが基本だが、ここに「CIの罠」がある。

CIで「パスが合わない」問題を撲滅する

CI環境とローカルでパスがズレてテストが落ちるのは、初心者あるあるだ。環境変数を使ってパスを動的に制御する構成にすべきだ。

// playwright.config.js
import { defineConfig } from '@playwright/test';
import path from 'path';

const authFile = process.env.AUTH_STATE_PATH || path.join(__dirname, 'playwright/.auth/user.json');

export default defineConfig({
  use: {
    storageState: authFile,
  },
});

GitHub Actionsでは、`actions/cache` を使ってログイン状態をキャッシュし、テスト実行前にログインスクリプトを走らせるフローが最強だ。これで「ローカルでは通るのに」という言い訳を物理的に消せる。


3. POMを超えた「Component Object Model」のすすめ

画面の要素セレクタを直書きするのは、未来の自分へのテロ行為だ。だが、ただのPage Object Model(POM)では不十分だ。UIコンポーネント単位で責務を切り出す、Component Object Modelのアプローチを推奨する。

アクセシビリティロール(`getByRole`)を軸にすることで、DOM構造が変わってもテストが壊れにくい「堅牢なセレクタ」が書ける。

// components/Navigation.js
export class Navigation {
  constructor(page) {
    this.page = page;
    this.profileLink = page.getByRole('link', { name: /profile/i });
  }

  async navigateToProfile() {
    await this.profileLink.click();
  }
}

// テストコード
test('ユーザーはプロフィールに遷移できる', async ({ page }) => {
  const nav = new Navigation(page);
  await nav.navigateToProfile();
  await expect(page).toHaveURL(/.*profile/);
});

`getByRole`を使うことで、テストが「ユーザーからどう見えるか」を記述するようになり、コードの保守性が劇的に向上する。


4. 大規模テストを爆速化する「Sharding」戦略

テストが100を超えた時、直列実行は「死」を意味する。GitHub Actionsの `sharding` を使えば、テストを分割して並列実行できる。

# .github/workflows/playwright.yml
jobs:
  test:
    strategy:
      matrix:
        shard: [1/3, 2/3, 3/3]
    steps:
      - run: npx playwright test --shard=${{ matrix.shard }}

`1/3, 2/3, 3/3` と分割することで、実行時間は単純計算で1/3になる。大規模なE2Eスイートを抱える現場では、この設定なしではCIの待ち時間だけで寿命が削れる。


5. 最後に:テストは「書く」のではなく「育てる」ものだ

Playwrightを導入すれば、深夜のデプロイで震えることはなくなる。しかし、テストコードもまた「プロダクトの一部」だ。

  • トレースファイルの活用: エラー発生時の動画とネットワークログをCIのアーティファクトとして保存しろ。原因特定までの時間が数時間から数秒に短縮される。
  • 失敗を前提に設計する: 「なぜ落ちたか」が即座にわかるエラーメッセージを、アサーションの `message` 引数で丁寧に書く。
自動テストは、寝ている間もプロダクトを監視してくれる「不眠不休の部下」だ。だが、その部下に正しく仕事をさせるのは、他でもないお前の責任だ。

明日もまた、冷めたコーヒーを飲みながらデプロイするのか?それとも、緑色のテスト結果を見て安心して眠りにつくのか?決めるのは、今この瞬間のコードだ。