Seleniumという「負債」を捨て、Playwrightでテストを自動化する:現場で生き残るための戦術

Seleniumという「負債」を捨て、Playwrightでテストを自動化する:現場で生き残るための戦術

朝の4時。CIが落ちた通知で叩き起こされ、ログを追いかけて「要素が見つかりません」という無機質なエラーメッセージを眺める。そんなSeleniumの泥沼に浸かっている時間は、もう終わりだ。

正直に言おう。現代のフロントエンド開発において、Seleniumを選択するのは「自ら進んで苦行を選ぶ」のと同義だ。Cypressも悪くはないが、iframeや別タブの扱いで挙動不審になるたび、俺たちの生産性は削られていく。

本稿では、Playwrightへの移行で実行速度を3分から45秒に縮めた経験をもとに、現場レベルで「死なない」E2Eテスト環境を構築する知見を共有する。


1. なぜPlaywrightの「Locators」が最強なのか

Seleniumの脆弱なセレクタ指定に疲弊しているなら、PlaywrightのLocators APIを使うだけで世界が変わる。

PlaywrightのLocatorsは、単なるCSSセレクタのラッパーではない。Auto-waiting(要素が操作可能になるまで待つ)とStrict mode(複数の要素がマッチした場合に例外を投げる)が標準で組み込まれている。

# 良い例:厳密なセレクタ指定
# get_by_roleを使うことで、アクセシビリティに基づいた堅牢なテストが可能になる
submit_btn = page.get_by_role("button", name="ログイン")
submit_btn.click()

これだけで、「要素が描画される前にクリックして落ちる」というSelenium時代の古典的なバグが消滅する。

2. POM(Page Object Model)を「動的な管理」へ進化させる

単にクラスに分けるだけでは不十分だ。環境変数を利用して、ベースURLや認証情報を切り分けられるようにしろ。

import os
from playwright.sync_api import Page

class LoginPage:
    def __init__(self, page: Page):
        self.page = page
        self.url = os.getenv("APP_BASE_URL", "https://staging.myapp.com")
        self.username = page.get_by_label("ユーザー名")
        self.password = page.get_by_label("パスワード")

    def navigate(self):
        self.page.goto(f"{self.url}/login")

    def login(self, user, pwd):
        self.username.fill(user)
        self.password.fill(pwd)
        self.page.get_by_role("button", name="送信").click()

こうすることで、ローカル、ステージング、CI環境での切り替えが環境変数一つで完結する。シークレット管理にはGitHub Secretsを活用し、コード内に認証情報をハードコーディングするような愚行は避けること。

3. CIの闇:非同期イベントと「無限ループ」の罠

以前、`page.on('console', ...)`でログを監視していた際、コールバック内で`page`の操作を行い、循環参照でメモリを食い潰してCIを爆破させたことがある。

Playwrightは強力な非同期イベント駆動モデルだが、リスナーの管理を怠ると即座に破綻する。

教訓:リスナーは使い終わったら即座に解除しろ。

def test_with_logging(page):
    def log_handler(msg):
        if "error" in msg.text:
            print(f"Detected: {msg.text}")
    
    page.on("console", log_handler)
    try:
        # テスト実行
        page.goto("/")
    finally:
        # 確実に解除する設計にする
        page.remove_listener("console", log_handler)

4. GitHub Actions:車輪の再発明はするな

自前でブラウザをインストールするDockerコンテナを作るのは、時間の無駄だ。公式イメージを使い、静的解析で「古い記法」を弾くのがプロの流儀だ。

`.eslintrc.json` や `flake8` を設定し、`page.waitForNavigation` のような非推奨コードをCIの時点でビルドエラーにしろ。AIが吐き出す「死んだコード」を本番環境に混入させないための防衛線だ。

# .github/workflows/e2e.yml
jobs:
  test:
    runs-on: ubuntu-latest
    container: mcr.microsoft.com/playwright:v1.40.0-jammy
    steps:
      - uses: actions/checkout@v3
      - name: Run tests
        run: npx playwright test
        env:
          APP_BASE_URL: ${{ secrets.STAGING_URL }}

5. 最後に:メンテナンスし続けるための「型」

テストコードがゴミ屋敷化する最大の理由は「命名規則の欠如」と「ディレクトリ構成の混乱」にある。

推奨するディレクトリ構成:

tests/
├── e2e/              # ユーザーの行動ベースのテスト
├── fixtures/         # APIモックや認証状態の共通処理
├── pages/            # POMの定義
└── conftest.py       # Pytestのフィクスチャ定義

テスト設計のコツは、「テストデータとロジックの完全分離」だ。APIモックには `page.route()` を使い、バックエンドの不安定さにテストが左右されない環境を構築しろ。

Playwrightは魔法の杖ではない。しかし、お前が「テストの設計」に集中するための、現時点で最も鋭利なナイフだ。道具を使いこなすか、道具に振り回されて深夜のデバッグに明け暮れるか。すべては、今日お前が書くコードの「設計力」にかかっている。