Playwrightとは何か:E2Eテスト自動化の基本と実践ポイント
Playwrightの特徴、導入方法、テスト設計、CypressやSeleniumとの違い、運用時の注意点を実務目線で整理する。
結論
Playwrightは、WebアプリケーションのE2Eテストを安定して自動化するための強力なツールである。Chromium、Firefox、WebKitを1つのAPIで操作でき、TypeScript、JavaScript、Python、.NET、Javaから利用できる点が大きな特徴だ。
特に、モダンなフロントエンドアプリケーションでは、非同期通信、SPA、遅延レンダリング、認証、複数タブ、モバイル表示などがテストの不安定要因になりやすい。Playwrightは自動待機、Locator、Trace Viewer、並列実行、ブラウザ分離などの仕組みにより、こうした問題に対応しやすい。
一方で、Playwrightを導入すれば自動的に良いテストが書けるわけではない。安定したE2Eテストには、テスト範囲の絞り込み、ユーザー視点のLocator設計、CIでの実行戦略、テストデータ管理が不可欠である。
Playwrightの概要
PlaywrightはMicrosoftが開発するブラウザ自動化フレームワークである。公式サイトでは、Webテスト、スクリプト、AIエージェントのワークフローにおいて、Chromium、Firefox、WebKitを1つのAPIで操作できるツールとして説明されている。
E2Eテストに使う場合は、通常 @playwright/test を利用する。Playwright Testには、テストランナー、アサーション、並列実行、レポート、トレース取得などが含まれている。単なるブラウザ操作ライブラリではなく、E2Eテストの実行基盤まで含む点が重要だ。
npm init playwright@latest
このコマンドにより、Playwrightの基本設定、サンプルテスト、ブラウザ実行環境を用意できる。
主な特徴
複数ブラウザを1つのAPIで操作できる
Playwrightは、Chromium、Firefox、WebKitに対応している。Chrome系だけでなくSafari系の挙動も確認しやすいため、クロスブラウザテストに向いている。
設定ファイルでプロジェクトを分ければ、同じテストを複数ブラウザで実行できる。
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
自動待機により不安定なテストを減らせる
E2Eテストでは、要素がまだ表示されていない、ボタンが無効状態である、アニメーション中でクリックできない、といった理由で失敗することが多い。
Playwrightはクリックや入力などの操作前に、対象要素が表示されているか、操作可能か、安定しているかなどを確認する。これにより、手動の sleep や固定待機に頼る必要が減る。
悪い例は次のような固定待機である。
await page.waitForTimeout(3000);
await page.click('#submit');
より望ましい例は、Locatorと期待状態を使う書き方だ。
await expect(page.getByRole('button', { name: '送信' })).toBeEnabled();
await page.getByRole('button', { name: '送信' }).click();
固定時間の待機は、遅い環境では失敗し、速い環境では無駄な時間を生む。Playwrightでは状態を待つ設計にすることが重要だ。
Locator中心のテストが書ける
Playwrightでは、要素取得にLocatorを使う。Locatorは単なるCSSセレクタではなく、要素を操作する時点で再評価され、自動待機やリトライとも組み合わさる。
代表的なLocatorには次のようなものがある。
page.getByRole('button', { name: 'ログイン' });
page.getByLabel('メールアドレス');
page.getByText('注文完了');
page.getByTestId('checkout-button');
推奨されるのは、ユーザーから見える情報に基づくLocatorである。button.primary > span:nth-child(2) のような構造依存のセレクタは、HTML構造の変更で壊れやすい。
Trace Viewerで失敗原因を追いやすい
PlaywrightのTrace Viewerは、テスト実行時のスクリーンショット、DOMスナップショット、操作履歴、ネットワーク、コンソールログなどを確認できるGUIツールである。
CI上でだけ失敗するテストは、ローカルで再現しにくい。Trace Viewerを有効にしておくと、どの画面状態で、どの操作が、なぜ失敗したのかを後から確認しやすい。
npx playwright test --trace on
設定ファイルで失敗時のみトレースを残すこともできる。
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
trace: 'on-first-retry',
},
});
Codegenで初期コードを生成できる
Playwrightには、ブラウザ操作を記録してテストコードを生成するCodegen機能がある。初学者がAPIを理解する場合や、複雑な画面操作のたたき台を作る場合に有用だ。
npx playwright codegen https://example.com
ただし、生成コードをそのまま本番テストとして使うのは避けるべきである。テストの意図が分かるように整理し、Locatorや期待値を見直す必要がある。
基本的なテスト例
次は、ページを開き、見出しを確認する最小限のテスト例である。
import { test, expect } from '@playwright/test';
test('トップページに見出しが表示される', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.getByRole('heading', { name: 'Example Domain' })).toBeVisible();
});
ログインフォームの例は次のようになる。
import { test, expect } from '@playwright/test';
test('ユーザーがログインできる', async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('メールアドレス').fill('[email protected]');
await page.getByLabel('パスワード').fill('password');
await page.getByRole('button', { name: 'ログイン' }).click();
await expect(page.getByRole('heading', { name: 'ダッシュボード' })).toBeVisible();
});
この例では、CSSクラスではなくラベル、ボタン名、見出しを使っている。画面の意味に近いLocatorを使うことで、テストの可読性と保守性が高くなる。
CypressやSeleniumとの違い
Playwright、Cypress、SeleniumはいずれもWebテスト自動化に使われるが、設計思想と得意領域が異なる。
| 項目 | Playwright | Cypress | Selenium |
|---|---|---|---|
| 主な用途 | E2Eテスト、ブラウザ自動化 | フロントエンドE2Eテスト | 汎用的なブラウザ自動化 |
| 対応ブラウザ | Chromium、Firefox、WebKit | Chromium系、Firefox、WebKit系対応は制約あり | 多数のブラウザに広く対応 |
| 実行モデル | ブラウザ外部から制御 | ブラウザ内実行を中心とする設計 | WebDriver経由で制御 |
| 強み | クロスブラウザ、並列実行、トレース、複数タブ | 開発体験、デバッグ容易性、フロントエンド親和性 | 標準性、長い実績、多言語・多環境 |
| 注意点 | E2E設計を誤るとテストが肥大化する | アーキテクチャ上の制約を理解する必要がある | 設定や待機制御が複雑になりやすい |
Playwrightは、モダンなWebアプリケーションのE2Eテストを高速かつ安定して回したい場合に適している。Seleniumは長い実績と広い互換性が強みであり、既存資産が多い組織では引き続き有力な選択肢となる。Cypressはフロントエンド開発者のローカル開発体験に優れる。
どれが絶対に優れているというより、既存環境、対象ブラウザ、チームのスキル、CI要件によって選ぶべきである。
Playwrightが向いているケース
Playwrightが特に向いているのは、次のようなケースである。
- Chrome、Firefox、Safari系の挙動をまとめて確認したい
- SPAや非同期処理の多いアプリケーションをテストしたい
- CIでE2Eテストを安定して実行したい
- 失敗時の原因分析を効率化したい
- TypeScriptでテストコードを管理したい
- 認証状態を再利用してテスト時間を短縮したい
- PC表示とモバイル表示の両方を検証したい
特に、開発チームがTypeScriptを使っている場合、アプリケーションコードと近い言語でテストを書けるため導入しやすい。
設計上の注意点
すべてをE2Eテストで確認しない
E2Eテストは実際のユーザー操作に近い検証ができる反面、実行時間が長く、失敗原因の切り分けも複雑になりやすい。
そのため、すべてのロジックをE2Eテストで検証するのは適切ではない。基本的には、次のように役割を分ける。
- 単体テスト:関数や小さなロジックを検証する
- コンポーネントテスト:UI部品単位の表示や操作を検証する
- APIテスト:バックエンドの入出力を検証する
- E2Eテスト:ユーザーの主要導線を検証する
Playwrightでは、ログイン、購入、予約、問い合わせ、管理画面での承認など、事業上重要な導線を優先してテストすると効果が高い。
LocatorをHTML構造に依存させすぎない
壊れやすいテストの典型例は、CSSクラスやDOM階層に強く依存したセレクタである。
await page.locator('.container > div:nth-child(3) button').click();
このような書き方は、見た目や構造の変更で簡単に壊れる。代わりに、Role、Label、Text、Test IDを使う。
await page.getByRole('button', { name: '注文する' }).click();
ユーザーが認識する名前で要素を指定すると、テストの意図も明確になる。
テストデータを制御する
E2Eテストでは、テストデータの状態が不安定だと結果も不安定になる。たとえば、在庫数、ユーザー権限、注文履歴、日時、外部APIの状態が毎回変わると、テスト失敗の原因がアプリケーションの不具合なのかデータの問題なのか分からなくなる。
実務では、次のような工夫が必要になる。
- テスト専用ユーザーを用意する
- テスト前にデータを初期化する
- API経由で必要な状態を作る
- 外部サービスはモックまたはテスト環境を使う
- 時刻依存の処理は固定時刻で検証する
認証処理を毎回繰り返さない
すべてのテストでログイン画面から認証すると、実行時間が伸びるうえ、ログイン処理の不安定さが他のテストに波及する。
Playwrightでは、認証済み状態を保存し、複数テストで再利用できる。
// auth.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('ログイン状態を保存する', async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('メールアドレス').fill('[email protected]');
await page.getByLabel('パスワード').fill('password');
await page.getByRole('button', { name: 'ログイン' }).click();
await expect(page.getByRole('heading', { name: 'ダッシュボード' })).toBeVisible();
await page.context().storageState({ path: 'playwright/.auth/user.json' });
});
設定ファイルでは、保存した状態を読み込む。
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
storageState: 'playwright/.auth/user.json',
},
});
CIで運用する際のポイント
CIでPlaywrightを実行する場合は、ローカル環境よりもリソース制約やネットワーク遅延の影響を受けやすい。そのため、次の設計が重要だ。
- 失敗時のスクリーンショット、動画、トレースを保存する
- リトライは原因調査を前提として限定的に使う
- テストを並列実行できるように独立させる
- テストデータの競合を避ける
- 重要導線と補助的導線で実行頻度を分ける
- Pull Requestでは軽量なE2E、本番反映前には広めのE2Eを実行する
Playwrightは並列実行に対応しているが、テスト同士が同じユーザーや同じデータを共有していると競合が起きる。並列化するほど、テストの独立性が重要になる。
よくある失敗
固定待機を多用する
waitForTimeout を多用すると、テストは遅く不安定になる。状態を待つアサーションに置き換えるべきである。
await expect(page.getByText('保存しました')).toBeVisible();
実装詳細を検証しすぎる
E2Eテストは、内部実装ではなくユーザーに見える振る舞いを検証するのに向いている。CSSクラス、内部状態、特定のDOM構造を過度に検証すると、リファクタリングのたびにテストが壊れる。
テストケースを増やしすぎる
E2Eテストは価値が高いが、数が増えるほど保守コストも増える。すべての入力パターンをE2Eで網羅するより、主要な正常系と重要な異常系に絞る方が現実的だ。
導入手順の例
新規プロジェクトでPlaywrightを導入する場合の流れは次の通りである。
npm init playwright@latest
生成されたテストを実行する。
npx playwright test
ブラウザを表示して実行する。
npx playwright test --headed
UIモードで実行する。
npx playwright test --ui
レポートを確認する。
npx playwright show-report
最初は、重要なユーザー導線を1つだけ選んでテスト化するのがよい。たとえば、ログイン、商品検索、カート投入、注文完了などである。最初から全画面を網羅しようとすると、テスト設計とデータ管理が追いつかなくなる。
まとめ
Playwrightは、現代的なWebアプリケーションのE2Eテストに適した自動化ツールである。複数ブラウザ対応、自動待機、Locator、Trace Viewer、Codegen、並列実行など、実務で問題になりやすい領域を広く支援する。
ただし、Playwrightの価値はツール単体ではなく、テスト設計と運用方針によって決まる。重要なのは、ユーザーにとって価値のある導線を選び、壊れにくいLocatorを使い、テストデータを制御し、CIで失敗原因を追跡できる状態にすることだ。
E2Eテストは網羅性を追うものではなく、主要な業務価値が壊れていないことを継続的に確認するための仕組みである。Playwrightは、その仕組みを構築するための有力な選択肢となる。