e2e-testing について
e2e-testingは、ユーザーワークフロー全体や重要経路を対象とするPlaywrightのエンドツーエンドテスト設計スキルです。ページオブジェクト、セレクター、テストデータ、認証の再利用、デバッグ、CIの指針を示します。
主な機能
- 01Playwright E2Eテスト構成
- 02ページオブジェクトモデルの指針
- 03堅牢なセレクター戦略
- 04明示的な待機と認証の再利用
- 05テストデータの作成と削除
- 06不安定なテストのデバッグとCI
活用シーン
- 01ログインとCRUDワークフローのテスト
- 02クロスブラウザー動作の確認
- 03ファイルのアップロードとダウンロードの検証
- 04デプロイ時のスモークテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hieutrtr/ ai1-skills --skill e2e-testingインストール後、Claude Code で/e2e-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/e2e-test-template.tspage-object-template.tsplaywright-config-example.tsscripts/run-e2e-with-report.sh詳細
E2E テスト
使用する場面
以下の場合にこのスキルを有効にします。
- ユーザーの一連のワークフロー(ログイン、CRUD 操作、複数ページにわたるフロー)の E2E テストを作成するとき
- スタック全体を検証する、重要な経路の回帰テストを作成するとき
- クロスブラウザー互換性(Chromium、Firefox、WebKit)をテストするとき
- 認証フローをエンドツーエンドで検証するとき
- ファイルのアップロード/ダウンロードのワークフローをテストするとき
- デプロイを検証するスモークテストを作成するとき
以下の場合はこのスキルを使用しないでください。
- React コンポーネントのユニットテスト(
react-testing-patternsを使用) - Python バックエンドのユニットテスト/統合テスト(
pytest-patternsを使用) - TDD ワークフローの適用(
tdd-workflowを使用) - ブラウザーを使用しない API コントラクトテスト(httpx を使用する
pytest-patternsを使用)
手順
テストの構成
e2e/
├── playwright.config.ts # Global Playwright configuration
├── fixtures/
│ ├── auth.fixture.ts # Authentication state setup
│ └── test-data.fixture.ts # Test data creation/cleanup
├── pages/
│ ├── base.page.ts # Base page object with shared methods
│ ├── login.page.ts # Login page object
│ ├── users.page.ts # Users list page object
│ └── user-detail.page.ts # User detail page object
├── tests/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ └── logout.spec.ts
│ ├── users/
│ │ ├── create-user.spec.ts
│ │ ├── edit-user.spec.ts
│ │ └── list-users.spec.ts
│ └── smoke/
│ └── critical-paths.spec.ts
└── utils/
├── api-helpers.ts # Direct API calls for test setup
└── test-constants.ts # Shared constants
命名規則:
- テストファイル:
<feature>.spec.ts - ページオブジェクト:
<page-name>.page.ts - フィクスチャ:
<concern>.fixture.ts - テスト名:ユーザーの操作と期待される結果を説明する、人が読んで理解しやすい文
ページオブジェクトモデル
各ページには、セレクターとアクションをカプセル化するページオブジェクトクラスを用意します。テストからセレクターを直接操作してはいけません。
基本ページオブジェクト:
// e2e/pages/base.page.ts
import { type Page, type Locator } from "@playwright/test";
export abstract class BasePage {
constructor(protected readonly page: Page) {}
/** Navigate to the page's URL. */
abstract goto(): Promise<void>;
/** Wait for the page to be fully loaded. */
async waitForLoad(): Promise<void> {
await this.page.waitForLoadState("networkidle");
}
/** Get a toast/notification message. */
get toast(): Locator {
return this.page.getByRole("alert");
}
/** Get the page heading. */
get heading(): Locator {
return this.page.getByRole("heading", { level: 1 });
}
}
具体的なページオブジェクト:
// e2e/pages/users.page.ts
import { type Page, type Locator } from "@playwright/test";
import { BasePage } from "./base.page";
export class UsersPage extends BasePage {
// ─── Locators ─────────────────────────────────────────
readonly createButton: Locator;
readonly searchInput: Locator;
readonly userTable: Locator;
constructor(page: Page) {
super(page);
this.createButton = page.getByTestId("create-user-btn");
this.searchInput = page.getByRole("searchbox", { name: /search users/i });
this.userTable = page.getByRole("table");
}
// ─── Actions ──────────────────────────────────────────
async goto(): Promise<void> {
await this.page.goto("/users");
await this.waitForLoad();
}
async searchFor(query: string): Promise<void> {
await this.searchInput.fill(query);
// Wait for search results to update (debounced)
await this.page.waitForResponse("**/api/v1/users?*");
}
async clickCreateUser(): Promise<void> {
await this.createButton.click();
}
async getUserRow(email: string): Promise<Locator> {
return this.userTable.getByRole("row").filter({ hasText: email });
}
async getUserCount(): Promise<number> {
// Subtract 1 for header row
return (await this.userTable.getByRole("row").count()) - 1;
}
}
ページオブジェクトのルール:
- ページまたは主要な UI セクションごとにページオブジェクトを 1 つ作成します
- ロケーターは public readonly プロパティにします
- アクションは async メソッドにします
- ページオブジェクトにアサーションを含めてはいけません。アサーションはテストで行います
- ページオブジェクトは、アクション後の待機を内部で処理します
セレクター戦略
優先順位(高い順):
| 優先度 | セレクター | 例 | 使用場面 |
|---|---|---|---|
| 1 | data-testid | getByTestId("submit-btn") | インタラクティブ要素、動的コンテンツ |
| 2 | ロール | getByRole("button", { name: /save/i }) | ボタン、リンク、見出し、入力欄 |
| 3 | ラベル | getByLabel("Email") | ラベルのあるフォーム入力欄 |
| 4 | プレースホルダー | getByPlaceholder("Search...") | 検索入力欄 |
| 5 | テキスト | getByText("Welcome back") | 静的なテキストコンテンツ |
次は絶対に使用しないでください。
- CSS セレクター(
.class-name、#id)-- 壊れやすく、スタイルの変更で動作しなくなります - XPath(
//div[@class="foo"])-- 読みにくく、非常に壊れやすいです - DOM 構造セレクター(
div > span:nth-child(2))-- レイアウトの変更で動作しなくなります
data-testid 属性の追加:
// In React components -- add data-testid to interactive elements
<button data-testid="create-user-btn" onClick={handleCreate}>
Create User
</button>
// Convention: kebab-case, descriptive
// Pattern: <action>-<entity>-<element-type>
// Examples: create-user-btn, user-email-input, delete-confirm-dialog
待機戦略
ハードコードした待機は絶対に使用しないでください。
// BAD: Hardcoded wait -- flaky, slow
await page.waitForTimeout(3000);
// BAD: Sleep
await new Promise((resolve) => setTimeout(resolve, 2000));
明示的な待機条件を使用してください。
// GOOD: Wait for a specific element to appear
await page.getByRole("heading", { name: "Dashboard" }).waitFor();
// GOOD: Wait for navigation
await page.waitForURL("/dashboard");
// GOOD: Wait for API response
await page.waitForResponse(
(response) =>
response.url().includes("/api/v1/users") && response.status() === 200,
);
// GOOD: Wait for network to settle
await page.waitForLoadState("networkidle");
// GOOD: Wait for element state
await page.getByTestId("submit-btn").waitFor({ state: "visible" });
await page.getByTestId("loading-spinner").waitFor({ state: "hidden" });
自動待機: Playwright は、クリックや入力などの前に、要素が操作可能になるまで自動で待機します。明示的な待機が必要なのは、アサーションや複雑な状態遷移の場合のみです。
認証状態の再利用
すべてのテストの前にログインするのは避けてください。認証状態を保存して再利用します。
認証状態を一度だけセットアップする:
// e2e/fixtures/auth.fixture.ts
import { test as base } from "@playwright/test";
import path from "path";
const AUTH_STATE_PATH = path.resolve("e2e/.auth/user.json");
export const setup = base.extend({});
setup("authenticate", async ({ page }) => {
// Perform real login
await page.goto("/login");
await page.getByLabel("Email").fill("[email protected]");
await page.getByLabel("Password").fill("TestPassword123!");
await page.getByRole("button", { name: /sign in/i }).click();
// Wait for auth to complete
await page.waitForURL("/dashboard");
// Save signed-in state
await page.context().storageState({ path: AUTH_STATE_PATH });
});
テストで再利用する:
// playwright.config.ts
export default defineConfig({
projects: [
// Setup project runs first and saves auth state
{ name: "setup", testDir: "./e2e/fixtures", testMatch: "auth.fixture.ts" },
{
name: "chromium",
use: {
storageState: "e2e/.auth/user.json", // Reuse auth state
},
dependencies: ["setup"],
},
],
});
テストデータ管理
原則:
- テストでは独自のデータを作成します(既存のデータに依存しないでください)
- テストでは実行後にデータをクリーンアップします(または API を使ってリセットします)
- セットアップには UI 操作ではなく API 呼び出しを使用します(より高速で信頼性が高いため)
テストデータ用 API ヘルパー:
// e2e/utils/api-helpers.ts
import { type APIRequestContext } from "@playwright/test";
export class TestDataAPI {
constructor(private request: APIRequestContext) {}
async createUser(data: { email: string; displayName: string }) {
const response = await this.request.post("/api/v1/users", { data });
return response.json();
}
async deleteUser(userId: number) {
await this.request.delete(`/api/v1/users/${userId}`);
}
async createOrder(userId: number, items: Array<Record<string, unknown>>) {
const response = await this.request.post("/api/v1/orders", {
data: { user_id: userId, items },
});
return response.json();
}
}
テストでの使用:
test("edit user name", async ({ page, request }) => {
const api = new TestDataAPI(request);
// Setup: create user via API (fast)
const user = await api.createUser({
email: "[email protected]",
displayName: "Before Edit",
});
try {
// Test: edit via UI
const usersPage = new UsersPage(page);
await usersPage.goto();
// ... perform edit via UI ...
} finally {
// Cleanup: remove test data
await api.deleteUser(user.id);
}
});
不安定なテストのデバッグ
1. 失敗の調査にはトレースビューアーを使用します。
// playwright.config.ts
use: {
trace: "on-first-retry", // Capture trace only on retry
}
トレースを表示:npx playwright show-trace trace.zip
2. デバッグでは headed モードで実行します。
npx playwright test --headed --debug tests/users/create-user.spec.ts
3. 不安定なテストの一般的な原因:
| 原因 | 対処方法 |
|---|---|
| ハードコードした待機 | 明示的な待機条件を使用する |
| テストデータの共有 | 各テストで独自のデータを作成する |
| アニメーションの干渉 | 設定で animations: "disabled" を指定する |
| 競合状態 | アサーションの前に API レスポンスを待つ |
| ビューポートに依存する動作 | 設定でビューポートを明示的に指定する |
| テスト間のセッション漏れ | storageState を正しく使用し、Cookie をクリアする |
4. 再試行戦略:
// playwright.config.ts
export default defineConfig({
retries: process.env.CI ? 2 : 0, // Retry in CI only
});
CI の設定
# .github/workflows/e2e.yml
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Start application
run: |
docker compose up -d
npx wait-on http://localhost:3000 --timeout 60000
- name: Run E2E tests
run: npx playwright test
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 14
- name: Upload traces on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-traces
path: test-results/
npx playwright を HTML レポート出力付きでローカル実行するには、scripts/run-e2e-with-report.sh を使用します。
例
注釈付きのページオブジェクトクラスについては、references/page-object-template.ts をご覧ください。
注釈付きの E2E テストについては、references/e2e-test-template.ts をご覧ください。
本番環境向けの Playwright 設定については、references/playwright-config-example.ts をご覧ください。