Playwright入門: インストールから初めてのE2Eテストまで

Shunku

E2E(エンドツーエンド)テストは、ユーザーの操作をシミュレーションして、アプリケーション全体が期待通りに動作するかを検証するテスト手法です。Playwrightは、Microsoftが開発したオープンソースのE2Eテストフレームワークで、Chromium・Firefox・WebKitの3つのブラウザエンジンを単一のAPIで操作できます。

この記事では、Playwrightをゼロからセットアップし、最初のテストを書いて実行するまでを解説します。

なぜPlaywrightか?

E2Eテストフレームワークは複数ありますが、Playwrightが選ばれる理由は明確です。

flowchart LR
    subgraph PW["Playwrightの強み"]
        A["クロスブラウザ対応"]
        B["自動待機"]
        C["並列実行"]
        D["テスト生成"]
        E["トレースビューア"]
    end
    style PW fill:#3b82f6,color:#fff
特徴 説明
クロスブラウザ Chromium、Firefox、WebKit(Safari)をすべてサポート
自動待機 要素が操作可能になるまで自動で待機し、フレーキーなテストを減らす
並列実行 追加サービス不要で複数テストを同時実行
テスト生成 codegenコマンドでブラウザ操作を記録してコードを自動生成
多言語対応 JavaScript/TypeScript、Python、.NET、Javaで利用可能

セットアップ

プロジェクトの作成

新規プロジェクトでPlaywrightを始める最も簡単な方法は、初期化コマンドを使うことです。

# 新しいディレクトリを作成
mkdir my-e2e-tests
cd my-e2e-tests

# Playwrightプロジェクトを初期化
npm init playwright@latest

初期化ウィザードが起動し、以下を選択できます:

  • TypeScript / JavaScriptの選択
  • テストディレクトリの名前(デフォルト: tests
  • GitHub Actionsワークフローの追加
  • ブラウザのインストール

既存プロジェクトへの追加

既にNode.jsプロジェクトがある場合は、パッケージを直接追加します。

# テストフレームワークをインストール
npm install --save-dev @playwright/test

# ブラウザをインストール
npx playwright install

生成されるファイル構成

初期化後、以下のファイルが作成されます。

my-e2e-tests/
├── tests/
│   └── example.spec.ts      # サンプルテスト
├── tests-examples/
│   └── demo-todo-app.spec.ts # 詳細なサンプル
├── playwright.config.ts       # 設定ファイル
├── package.json
└── package-lock.json

設定ファイルの基本

playwright.config.tsはテスト実行の挙動を制御します。最初は最小限の設定から始めましょう。

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // テストファイルのディレクトリ
  testDir: './tests',

  // テストのタイムアウト(ミリ秒)
  timeout: 30000,

  // 並列実行
  fullyParallel: true,

  // CIではリトライを有効化
  retries: process.env.CI ? 2 : 0,

  // テスト対象のブラウザ
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});
設定項目 用途
testDir テストファイルの格納ディレクトリ
timeout 各テストの最大実行時間
fullyParallel ファイル内テストの並列実行を許可
retries 失敗時の再試行回数
projects テスト対象のブラウザ設定

最初のテストを書く

テストの基本構造

テストファイル tests/login.spec.ts を作成します。

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

test('ページタイトルを検証する', async ({ page }) => {
  // ページに遷移
  await page.goto('https://playwright.dev');

  // タイトルに"Playwright"が含まれることを検証
  await expect(page).toHaveTitle(/Playwright/);
});

ポイントを整理します:

  • test関数でテストケースを定義する
  • { page }はPlaywrightが自動で提供するフィクスチャ(ブラウザのページオブジェクト)
  • すべての操作はasync/awaitで記述する
  • expectで結果を検証する

テストの実行

# すべてのテストを実行
npx playwright test

# 特定のファイルを実行
npx playwright test tests/login.spec.ts

# 特定のブラウザで実行
npx playwright test --project=chromium

# ブラウザを表示して実行(デバッグ向き)
npx playwright test --headed

テストレポートの確認

テスト完了後、HTMLレポートを表示できます。

npx playwright show-report

ロケーター: 要素の見つけ方

ロケーターは、テスト対象の要素を特定するための仕組みです。Playwrightは、アクセシビリティに基づくロケーターを推奨しています。

推奨ロケーター(getByシリーズ)

// ロール(WAI-ARIA)で検索 — 最も推奨
await page.getByRole('button', { name: '送信' });
await page.getByRole('heading', { name: 'ようこそ' });
await page.getByRole('link', { name: 'ログイン' });

// ラベルで検索(フォーム要素向け)
await page.getByLabel('メールアドレス');

// プレースホルダーで検索
await page.getByPlaceholder('検索...');

// テキスト内容で検索
await page.getByText('利用規約に同意する');

// テストIDで検索(カスタム属性)
await page.getByTestId('submit-button');

ロケーターの優先順位

flowchart TD
    A["getByRole"] --> B["getByLabel"]
    B --> C["getByPlaceholder"]
    C --> D["getByText"]
    D --> E["getByTestId"]
    E --> F["CSSセレクター"]

    style A fill:#22c55e,color:#fff
    style B fill:#22c55e,color:#fff
    style C fill:#3b82f6,color:#fff
    style D fill:#3b82f6,color:#fff
    style E fill:#f59e0b,color:#fff
    style F fill:#ef4444,color:#fff
優先度 ロケーター 理由
getByRole アクセシビリティに準拠し、ユーザーの視点に最も近い
getByLabel フォーム要素に最適
getByPlaceholder ラベルがない場合の代替
getByText 表示テキストで特定できる場合
getByTestId 他のロケーターが使えない場合の安全策
避ける CSSセレクター UIの変更に壊れやすい

getByRoleが推奨される理由は、スクリーンリーダーなどの支援技術が認識する方法と同じだからです。これにより、テストはアクセシビリティの検証も兼ねます。

アサーション: 結果の検証

自動リトライアサーション

Playwrightのアサーションは条件が満たされるまで自動でリトライします。これがフレーキーなテストを防ぐ鍵です。

// 要素の可視性
await expect(page.getByRole('alert')).toBeVisible();
await expect(page.getByRole('alert')).toBeHidden();

// テキスト内容
await expect(page.getByRole('heading')).toHaveText('ダッシュボード');
await expect(page.locator('.message')).toContainText('成功');

// URL
await expect(page).toHaveURL(/dashboard/);

// 要素の数
await expect(page.getByRole('listitem')).toHaveCount(3);

// 入力値
await expect(page.getByLabel('名前')).toHaveValue('田中太郎');

// CSSクラス
await expect(page.locator('.btn')).toHaveClass(/active/);

ソフトアサーション

通常のアサーションは失敗すると即座にテストを中断しますが、ソフトアサーションはすべてのチェックを実行してからまとめて報告します。

await expect.soft(page.getByTestId('status')).toHaveText('OK');
await expect.soft(page.getByTestId('count')).toHaveText('5');
// 両方のチェック結果がレポートに表示される

実践的なテスト例

ログインフォームのテストを書いてみましょう。

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

test.describe('ログイン機能', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('正しい情報でログインできる', async ({ page }) => {
    await page.getByLabel('メールアドレス').fill('user@example.com');
    await page.getByLabel('パスワード').fill('password123');
    await page.getByRole('button', { name: 'ログイン' }).click();

    await expect(page).toHaveURL(/dashboard/);
    await expect(page.getByRole('heading')).toHaveText('ダッシュボード');
  });

  test('空のフォームでエラーが表示される', async ({ page }) => {
    await page.getByRole('button', { name: 'ログイン' }).click();

    await expect(page.getByRole('alert')).toBeVisible();
    await expect(page.getByRole('alert')).toContainText('入力してください');
  });

  test('間違ったパスワードでエラーが表示される', async ({ page }) => {
    await page.getByLabel('メールアドレス').fill('user@example.com');
    await page.getByLabel('パスワード').fill('wrong');
    await page.getByRole('button', { name: 'ログイン' }).click();

    await expect(page.getByRole('alert')).toContainText('認証に失敗しました');
  });
});

注目すべきポイント:

  • test.describeでテストをグループ化している
  • test.beforeEachで各テスト前にログインページに遷移している
  • getByLabelgetByRoleで要素をアクセシブルに取得している
  • 明示的な待機(waitForsleep)が一切不要

デバッグ

テストが失敗した時、Playwrightは強力なデバッグ機能を提供します。

UIモード

テストの実行状況をリアルタイムで確認できるインタラクティブなUIです。

npx playwright test --ui

デバッグモード

ステップ実行でテストの各操作を確認できます。

npx playwright test --debug

トレースビューア

CI環境で失敗したテストを後から調査できます。設定で有効にします。

// playwright.config.ts
export default defineConfig({
  use: {
    trace: 'on-first-retry', // リトライ時にトレースを記録
  },
});

トレースには以下が記録されます:

  • 各操作のスクリーンショット
  • DOMスナップショット
  • ネットワークリクエスト
  • コンソールログ
# トレースファイルを開く
npx playwright show-trace trace.zip

テスト生成(codegen)

ブラウザを操作するだけで、テストコードを自動生成できます。

npx playwright codegen https://example.com

ブラウザが起動し、操作を記録してPlaywrightのコードに変換します。初心者がロケーターの書き方を学ぶのにも最適です。

自動待機の仕組み

Playwrightが他のフレームワークと一線を画す機能が**自動待機(Auto-wait)**です。

flowchart TD
    A["アクションを実行<br/>例: click()"] --> B{"要素が<br/>存在する?"}
    B -->|No| C["リトライ"]
    C --> B
    B -->|Yes| D{"要素が<br/>表示されている?"}
    D -->|No| C
    D -->|Yes| E{"要素が<br/>安定している?"}
    E -->|No| C
    E -->|Yes| F{"要素が<br/>有効(enabled)?"}
    F -->|No| C
    F -->|Yes| G{"他の要素に<br/>遮られていない?"}
    G -->|No| C
    G -->|Yes| H["アクション実行"]

    style A fill:#3b82f6,color:#fff
    style H fill:#22c55e,color:#fff
    style C fill:#f59e0b,color:#fff

click()を呼ぶだけで、Playwrightは要素が「クリック可能」な状態になるまで自動で待機します。これにより、以下のような不安定なコードを書く必要がなくなります。

// 悪い例: 手動で待機する
await page.waitForTimeout(3000); // 3秒待つ
await page.click('#submit');

// 良い例: Playwrightに任せる
await page.getByRole('button', { name: '送信' }).click();
// 自動でクリック可能になるまで待機する

まとめ

概念 ポイント
セットアップ npm init playwright@latestで即座に始められる
ロケーター getByRoleを最優先で使う
アサーション 自動リトライによりsleep不要
自動待機 要素の操作可能性を自動で確認
デバッグ UIモード、トレースビューア、codegenが利用可能
設定 playwright.config.tsでブラウザ・並列数・リトライを制御

Playwrightの最大の利点は、テストコードがシンプルに保たれることです。自動待機とアクセシブルなロケーターにより、テストは「ユーザーが何をするか」だけに集中でき、「いつ要素が表示されるか」を気にする必要がありません。

参考リンク