プロダクション環境を壊すのはもうやめよう:Playwright、TypeScript、CI/CDによる実践ガイド

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

金曜午後4時のデプロイの悲劇

金曜日の午後遅く。チームは一見無害に見えるUIの微調整をプロダクション環境にプッシュしたばかりです。10分後、サポートチケットが次々と届き始めます。ログインボタンが反応せず、チェックアウト機能は全く動作していません。CSSセレクターのわずかな変更がユーザー体験全体を破壊し、週末の予定は緊急ロールバック作業に消えてしまいました。

このようなシナリオが発生するのは、手動テストが現代の開発スピードに追いつけないからです。アプリが成長するにつれ、ユーザーパスの数は爆発的に増加します。リリース前にすべてのフォーム、ボタン、リダイレクトを手動でチェックするのは、不可能なゲームに挑んでいるようなものです。それは「リリース不安」を引き起こし、次に何が壊れるか分からないという恐怖から、チームはコードのデプロイを恐れるようになります。

なぜレガシーなツールはモダンフレームワークに苦戦するのか

手動のリグレッションテストが失敗するのは、人間が退屈して「当たり前」のチェックをスキップしてしまうからです。しかし、初期の自動化ツールにも欠点がありました。確認すべき画面が50ある場合、人間がすべてクリックして回るのに3時間かかるかもしれません。自動化スクリプトなら3分以内に完了できますが、それはツールが信頼できる場合に限られます。

長い間、Seleniumが唯一の実質的な選択肢でした。しかし、それは「不安定(Flaky)」であるという評判が定着してしまいました。スクリプトがクリックを試みる前にブラウザの要素レンダリングが完了していないため、テストが失敗することが頻発したのです。開発者は新機能の開発よりも、壊れたテストの修正に多くの時間を費やすことになりました。複雑なブラウザドライバーの管理や同期の問題により、最終的にE2Eテストは手間ばかりかかって割に合わないものと感じられるようになりました。

モダンなツールキット:なぜPlaywrightが選ばれるのか

ReactやNext.jsで構築された現代のシングルページアプリケーション(SPA)には、モダンなWebの仕組みを正しく理解しているテストツールが必要です。現在の主要なツールの状況は以下の通りです:

  • Selenium: 柔軟だが低速。JavaScriptを多用するサイトでは、手動での待機時間設定や重厚なドライバー管理が必要で、古臭さを感じさせます。
  • Cypress: 優れた開発体験を提供しますが、ブラウザ内部で実行されます。そのため、複雑な回避策なしでは複数タブの操作やクロスドメインのテストが困難です。
  • Playwright: Microsoftによって構築された、現在のゴールドスタンダードです。Chromium、WebKit(Safari)、Firefoxを標準でサポートしています。「オートウェイト(Auto-wait)」機能により、要素が操作可能になるまで自動で待機するため、旧来のツールを悩ませた不安定さを事実上排除しています。

ジュニアからシニアエンジニアへと成長するには、人々が信頼できるソフトウェアを構築する必要があります。PlaywrightとTypeScriptを組み合わせることで、ユーザーが不具合を目にする前にバグをキャッチできる、型安全で非常に高速なテストスイートを手に入れることができます。

テストフレームワークの構築

優れたテストスイートは、書きやすく、チームメイトにとって読みやすいものであるべきです。プロフェッショナルな環境をゼロから構築してみましょう。

1. はじめに

プロジェクトのルートディレクトリでターミナルを開き、次のコマンドを実行します:

npm init playwright@latest

プロンプトが表示されたら TypeScript を選択してください。インストーラーが tests フォルダを作成し、GitHub Actions のワークフロー設定も提案してくれます。「Yes」を選択しましょう。後で手動で設定する手間が省けます。

2. 堅牢なテストを書く

ログインフローをスクリプト化してみましょう。.btn-blue のような壊れやすいCSSクラスをターゲットにするのではなく、実際の人間がページを操作する方法を模倣したロケーターを使用します。tests/auth.spec.ts を作成します:

import { test, expect } from '@playwright/test';

test('有効な認証情報でログインできること', async ({ page }) => {
  await page.goto('https://example.com/login');

  // アクセシブルなロケーターを使用することで、テストの安定性が大幅に向上します
  await page.getByLabel('ユーザー名').fill('testuser');
  await page.getByLabel('パスワード').fill('password123');
  
  await page.getByRole('button', { name: 'ログイン' }).click();

  // 結果を検証する
  await expect(page).toHaveURL(/.*dashboard/);
  await expect(page.getByText('ようこそ、testuserさん')).toBeVisible();
});

getByRole を使用することで、ボタンの色を青から赤に変更してもテストは影響を受けません。それが「ログイン」というラベルのボタンである限り、テストはパスします。このアプローチは、アプリがスクリーンリーダーにとってもアクセシブルであることを保証することにも繋がります。

3. ページオブジェクトモデル(POM)によるスケーリング

ページオブジェクトモデル(POM)は、UIロジックを中央集約化します。pages/LoginPage.ts を作成します:

import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.getByLabel('ユーザー名');
    this.passwordInput = page.getByLabel('パスワード');
    this.loginButton = page.getByRole('button', { name: 'ログイン' });
  }

  async navigate() {
    await this.page.goto('/login');
  }

  async login(user: string, pass: string) {
    await this.usernameInput.fill(user);
    await this.passwordInput.fill(pass);
    await this.loginButton.click();
  }
}

これで、実際のテストファイルはクリーンで読みやすくなります:

test('リファクタリング後のログインテスト', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.navigate();
  await loginPage.login('testuser', 'password123');
  await expect(page).toHaveURL(/.*dashboard/);
});

GitHub Actionsによるセーフティネットの自動化

テストスイートは、コードが変更されるたびに実行されてこそ意味があります。記憶力に頼ってはいけません。システムにルールを強制させる必要があります。.github/workflows/playwright.yml ファイルは以下のようになります:

name: Playwrightテスト
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
    - name: 依存関係のインストール
      run: npm ci
    - name: ブラウザのインストール
      run: npx playwright install --with-deps
    - name: テストの実行
      run: npx playwright test
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: playwright-report
        path: playwright-report/

これで、すべてのプルリクエストが仮想マシン上での新しいテスト実行をトリガーします。もし開発者が誤ってチェックアウトフローを壊してしまった場合、CI/CDパイプラインは赤色になり、「Merge」ボタンは無効なままになります。このシンプルな自動化が、バグが顧客に届くのを防ぎます。

結論

Playwrightによる自動テストへの切り替えは、開発ワークフロー全体を変えます。それは「希望」を「データ」に置き換えます。まずは、ログインやチェックアウトなどの最も重要なパスを自動化することから始め、そこから広げていきましょう。TypeScriptとページオブジェクトモデルを使えば、単にスクリプトを書いているのではありません。コードのためのメンテナンス可能な「保険」を構築しているのです。

Share: