E2Eテスト(Playwright)
ストーリー
「ユニットテスト、インテグレーションテストの次は E2Eテストだ。ユーザーの目線でシステム全体をテストする」
松本先輩がブラウザを開いた。
「Playwright は Microsoft が開発しているE2Eテスト フレームワークだ。Chromium、Firefox、WebKit の 全てに対応していて、高速で安定している。 実際に触ってみよう」
Playwright とは
Playwright は、Webアプリケーションのエンドツーエンドテストを自動化するフレームワークです。
セットアップ
bash
# インストール
npm init playwright@latest
# ブラウザのインストール
npx playwright install設定ファイル
typescript
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 30000,
retries: 2,
use: {
baseURL: 'http://localhost:3000',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
});基本的なテストの書き方
ページナビゲーションと要素の操作
typescript
import { test, expect } from '@playwright/test';
test('トップページが表示される', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle('My Application');
await expect(page.locator('h1')).toHaveText('Welcome');
});
test('ログインフォーム', async ({ page }) => {
await page.goto('/login');
// フォーム入力
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'password123');
// ボタンクリック
await page.click('[data-testid="login-button"]');
// ログイン後のリダイレクトを確認
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('[data-testid="welcome-message"]'))
.toHaveText('ようこそ、ユーザーさん');
});ロケーター(要素の取得方法)
typescript
// data-testid(推奨)
page.locator('[data-testid="submit-button"]');
// テキスト
page.getByText('送信');
page.getByRole('button', { name: '送信' });
// ラベル
page.getByLabel('メールアドレス');
page.getByPlaceholder('例: user@example.com');
// CSS セレクタ
page.locator('.submit-button');
page.locator('#email-input');実践的なE2Eテストシナリオ
ユーザー登録からログインまで
typescript
test.describe('ユーザー認証フロー', () => {
test('新規ユーザー登録 → ログイン → ダッシュボード表示', async ({ page }) => {
// 1. ユーザー登録
await page.goto('/register');
await page.fill('[data-testid="name"]', 'テスト太郎');
await page.fill('[data-testid="email"]', `test-${Date.now()}@example.com`);
await page.fill('[data-testid="password"]', 'SecurePass123!');
await page.fill('[data-testid="password-confirm"]', 'SecurePass123!');
await page.click('[data-testid="register-button"]');
// 登録完了の確認
await expect(page.locator('[data-testid="success-message"]'))
.toBeVisible();
// 2. ログイン
await page.goto('/login');
await page.fill('[data-testid="email"]', `test-${Date.now()}@example.com`);
await page.fill('[data-testid="password"]', 'SecurePass123!');
await page.click('[data-testid="login-button"]');
// 3. ダッシュボード確認
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('[data-testid="user-name"]'))
.toContainText('テスト太郎');
});
});商品検索と購入フロー
typescript
test.describe('商品購入フロー', () => {
test.beforeEach(async ({ page }) => {
// ログイン済み状態にする
await page.goto('/login');
await page.fill('[data-testid="email"]', 'buyer@example.com');
await page.fill('[data-testid="password"]', 'password123');
await page.click('[data-testid="login-button"]');
await expect(page).toHaveURL('/dashboard');
});
test('商品を検索してカートに追加し購入完了する', async ({ page }) => {
// 1. 商品検索
await page.goto('/products');
await page.fill('[data-testid="search-input"]', 'TypeScript');
await page.click('[data-testid="search-button"]');
// 検索結果の確認
const results = page.locator('[data-testid="product-card"]');
await expect(results).toHaveCount(3);
// 2. 商品詳細
await results.first().click();
await expect(page.locator('[data-testid="product-title"]'))
.toBeVisible();
// 3. カートに追加
await page.click('[data-testid="add-to-cart"]');
await expect(page.locator('[data-testid="cart-badge"]'))
.toHaveText('1');
// 4. カートに移動
await page.click('[data-testid="cart-icon"]');
await expect(page.locator('[data-testid="cart-item"]'))
.toHaveCount(1);
// 5. 購入手続き
await page.click('[data-testid="checkout-button"]');
await page.fill('[data-testid="address"]', '東京都渋谷区...');
await page.click('[data-testid="confirm-order"]');
// 6. 注文完了
await expect(page.locator('[data-testid="order-complete"]'))
.toBeVisible();
await expect(page.locator('[data-testid="order-id"]'))
.toBeVisible();
});
});E2Eテストのベストプラクティス
data-testid を使う
html
<!-- ❌ CSSクラスやIDに依存(UIリファクタリングで壊れる) -->
<button class="btn-primary submit">送信</button>
<!-- ✅ data-testid を使う(UIの変更に強い) -->
<button class="btn-primary submit" data-testid="submit-button">送信</button>テストの安定性を高める
typescript
// ❌ 固定の待ち時間(フレーキーテストの原因)
await page.waitForTimeout(3000);
// ✅ 要素の出現を待つ(安定)
await expect(page.locator('[data-testid="result"]')).toBeVisible();
// ✅ ネットワークリクエストの完了を待つ
await page.waitForResponse(response =>
response.url().includes('/api/orders') && response.status() === 200
);テストの独立性
| プラクティス | 説明 |
|---|---|
| 各テストで独自のデータを作成 | 他のテストのデータに依存しない |
| テスト後にクリーンアップ | 作成したデータを削除 |
| ユニークな値を使用 | タイムスタンプをメールに含める等 |
| テスト順序に依存しない | どの順序で実行しても通る |
まとめ
| 項目 | ポイント |
|---|---|
| Playwright | 高速・安定したE2Eテストフレームワーク |
| ロケーター | data-testid の使用を推奨 |
| テストシナリオ | ユーザーの操作フローを忠実に再現 |
| 安定性 | waitForTimeout を避け、要素やレスポンスを待つ |
チェックリスト
- Playwright のセットアップと基本構文を理解した
- ロケーターの使い分け(data-testid 推奨)を理解した
- ユーザー操作フローのE2Eテストを書ける
- フレーキーテストを避ける方法を理解した
次の ステップへ
E2Eテストを学んだら、次はテスト自動化とCI統合を学びます。テストをCI/CDパイプラインに組み込んで、自動的に品質を守る仕組みを作りましょう。
推定読了時間: 30分