E2E Testing Skill
E2Eテストの新規作成・設計・保守を支援する。
When to Use This Skill
-
新規E2Eテストを作成する時
-
テスト設計方針を決める時(page object設計、fixture設計等)
-
既存テストをメンテナンス・リファクタリングする時
-
flaky予防のための設計改善時
テスト失敗の修正: e2e-test-fixer エージェントを使うこと(タイムアウト、セレクタエラー、flake等)
ワークフロー: 新規テスト作成 (MANDATORY)
"Build evaluations FIRST before writing extensive documentation. This ensures your Skill solves real problems rather than documenting imagined ones." — Anthropic Skill Best Practices
新規テスト作成時は、以下の検証フローを必ず実行してください。
Step 1: 機能の実装状況を確認
テスト対象の機能が実際に実装されているか確認します。
Server Actionsの確認
ls src/app/actions/ grep -r "対象関数名" src/app/actions/
API Routesの確認(必要な場合)
ls src/app/api/
コンポーネントの確認
grep -r "data-testid" src/components/ | grep "対象要素"
確認項目:
-
Server Action/API Routeが存在する
-
UIコンポーネントが実装されている
-
data-testid属性が定義されている
Step 2: 実装コードを読む
テスト対象の実装を理解してからテストを書く:
例: 翻訳機能のテスト
Read src/app/actions/translate.ts Read src/app/[locale]/preview/[id]/hooks/use-translation.ts
例: アップロード機能のテスト
Read src/app/actions/upload/index.ts Read src/components/upload/upload-dropzone.tsx
確認項目:
-
実装の動作を理解した
-
エラーハンドリングを確認した
-
期待する戻り値/状態遷移を把握した
Step 3: MVP範囲の確認
テスト対象がMVP機能に含まれるか確認します。
MVP機能一覧(テスト対象):
機能 実装場所 テスト優先度
認証 src/app/actions/auth/
高
アップロード src/app/actions/upload/
高
プレビュー src/app/[locale]/preview/
高
翻訳 src/app/actions/translate.ts
高
ダウンロード src/app/api/files/[id]/download/
高
決済 src/app/actions/payment.ts
中
上記以外の機能は、明確な要件がない限りテストしないでください。
Step 4: 既存テストとの重複確認
同じ機能をテストする既存テストがないか確認します。
grep -r "テスト対象機能" e2e/ grep -r "該当data-testid" e2e/
Step 5: テスト設計の妥当性確認
テストを書く前の最終チェック:
妥当性チェックリスト:
- 実装コードを読んで動作を理解した
- テストするセレクタ(data-testid等)が実装に存在する
- 期待する動作が実装されている
- 推測や仮定に基づいていない
- MVP範囲内の機能である
禁止事項
パターン 説明 回避策
推測テスト 「こうあるべき」という仮定 実装を確認してから書く
未実装機能テスト 存在しない機能のテスト 実装完了後に書く
過剰テスト MVP範囲外の機能 MVP範囲に限定
必須ルール
ハードコード禁止
// ❌ WRONG await page.fill("input[name='email']", "test@example.com");
// ✅ CORRECT import { UNIFIED_TEST_CONFIG } from "../config/unified-test-config"; await page.fill("input[name='email']", UNIFIED_TEST_CONFIG.users.standard.email);
認証状態ファイル
// ❌ WRONG storageState: "playwright-auth.json"
// ✅ CORRECT storageState: UNIFIED_TEST_CONFIG.paths.authState // .auth/user.json
ログイン待機
// ❌ WRONG - /api/auth/login は存在しない await page.waitForResponse(r => r.url().includes("/api/auth/login"));
// ✅ CORRECT - ナビゲーション待機 await page.click('button[type="submit"]'); await page.waitForURL("**/dashboard", { timeout: 15000 });
テストコマンド
コマンド 用途
npm run e2e
全E2Eテスト(標準。run.ts envelope)
npm run e2e:smoke
スモークテスト
npm run e2e:flows
setup + flows + cleanup
npm run e2e:ui
UIモード (デバッグ)
npm run e2e:debug
DevTools付きデバッグ
環境変数
CSRF_RELAXED_IN_E2E=true # CSRF緩和 DISABLE_RATE_LIMIT_IN_E2E=true # Rate limit無効 NEXT_PUBLIC_TEST_MODE=true # テストモード ENABLE_TRANSLATION_TEST_FAST_PATH=true # ダミー翻訳(100倍高速)
参照ドキュメント
-
failure-patterns.md - 失敗パターンと解決策
-
page-objects.md - ページオブジェクトの使い方
-
e2e/config/unified-test-config.ts - 設定値
ディレクトリ構造
e2e/ ├── config/ # テスト設定 │ ├── e2e-constants.ts # SSOT定数 │ └── unified-test-config.ts # 統一設定(CRITICAL) ├── smoke/ # スモークテスト ├── flows/ # フローテスト(認証→アップロード→翻訳等) ├── infrastructure/ # インフラテスト ├── server-actions/ # Server Actionsテスト ├── visual/ # ビジュアルテスト ├── fixtures/ # テストフィクスチャ ├── helpers/ # ヘルパー関数 ├── page-objects/ # ページオブジェクト ├── utils/ # ユーティリティ └── test-files/ # テスト用ファイル
セキュリティE2Eテンプレート (3本)
セキュリティ関連のE2Eテストを書く場合、以下の3テンプレートを使う。 増やしすぎない。まずこの3パターンで十分。
Template 1: IDOR (他ユーザーリソースアクセス → 403/404)
import { test, expect } from "@playwright/test"; import { UNIFIED_TEST_CONFIG } from "../config/unified-test-config";
test.describe("IDOR Protection: [対象機能]", () => { // 認証済みユーザーAでセットアップ test.use({ storageState: UNIFIED_TEST_CONFIG.paths.authState });
test("他ユーザーのリソースにアクセスできない", async ({ page }) => {
// 他ユーザーが所有するリソースIDでアクセス
const otherUserResourceId = "known-other-user-resource-id";
const response = await page.goto(/preview/${otherUserResourceId});
// 403 or 404 を期待(実装による)
// ページレベル: エラーページが表示される
await expect(page.locator("[data-testid='error-message']")).toBeVisible();
// または: リダイレクトされる
await expect(page).toHaveURL(/\/(dashboard|404)/);
}); });
使い方: [対象機能] と otherUserResourceId を実際の値に差し替える。
Template 2: Rate Limit (N回超過 → 429)
import { test, expect } from "@playwright/test"; import { UNIFIED_TEST_CONFIG } from "../config/unified-test-config";
test.describe("Rate Limit: [対象エンドポイント]", () => { // NOTE: E2E環境では DISABLE_RATE_LIMIT_IN_E2E=true でスキップされるため、 // このテストは明示的に X-Bypass-Rate-Limit ヘッダーを送らない
test.skip( process.env.DISABLE_RATE_LIMIT_IN_E2E === "true", "Rate limit disabled in E2E environment" );
test("制限回数を超えると429が返る", async ({ request }) => { const endpoint = "/api/translate/single"; const limit = 50; // エンドポイントの制限値
// 制限値+1回リクエストを送信
let lastStatus = 200;
for (let i = 0; i <= limit; i++) {
const response = await request.post(endpoint, {
data: { text: "test", targetLang: "en" },
});
lastStatus = response.status();
if (lastStatus === 429) break;
}
expect(lastStatus).toBe(429);
}); });
使い方: endpoint と limit を対象に合わせる。E2E環境ではskipされることに注意。
Template 3: Auth (無効/期限切れトークン → 401)
import { test, expect } from "@playwright/test";
test.describe("Auth Protection: [対象エンドポイント]", () => { // storageState を使わない = 未認証状態 test.use({ storageState: { cookies: [], origins: [] } });
test("未認証リクエストは401/リダイレクトされる", async ({ page }) => { // 認証必須ページにアクセス await page.goto("/dashboard");
// ログインページにリダイレクトされることを確認
await expect(page).toHaveURL(/\/login/);
});
test("未認証APIリクエストは401が返る", async ({ request }) => { const response = await request.get("/api/files"); expect(response.status()).toBe(401); }); });
使い方: 対象のページ/APIに差し替える。ページはリダイレクト、APIは401を期待。
AI Assistant Instructions
このスキルが有効化された時:
新規テスト作成時 (CRITICAL)
-
実装を確認する: テスト対象の機能が実装されているか確認
-
コードを読む: 実装の動作を理解してからテストを書く
-
MVP範囲を確認: テスト対象がMVP機能に含まれるか確認
-
セレクタを確認: data-testid属性が実装に存在するか確認
Always
-
UNIFIED_TEST_CONFIG から設定値を取得する
-
ハードコードされた値を使用しない
-
修正後は必ず単一テストで検証する
-
新規テスト作成前に実装コードを読む
Never
-
/api/auth/login を待機しない(存在しない)
-
playwright-auth.json をハードコードしない
-
タイムアウトを過度に長くしない(最大30秒)
-
実装を確認せずにテストを書かない
-
「こうあるべき」という推測でテストを書かない
-
MVP範囲外の機能をテストしない