Webアプリの品質を担保するうえでE2Eテストは欠かせませんが、テストコードを手作業で書くのはなかなか骨の折れる作業です。特にUIの変更が頻繁に入るプロジェクトだと、テストのメンテナンスだけで相当な時間を持っていかれます。
自分も以前、リリース直前に手動で画面を確認していた時期がありましたが、リグレッションを見逃してしまった経験から、E2Eテストの自動化に本腰を入れることにしました。そこで試してみたのが、Claude CodeとPlaywrightの組み合わせです。
この記事では、Claude Codeを使ってPlaywrightのE2Eテストを効率よく自動生成する手順を紹介します。環境構築からページオブジェクトモデルの設計、CI/CDへの組み込みまで、一通りのワークフローをカバーしています。
使用環境:
- Node.js 22 LTS
- Playwright 1.50
- TypeScript 5.7
- Claude Code(最新版)
なぜE2Eテストの自動化にPlaywrightを選ぶのか
主要E2Eフレームワーク比較
E2Eテストフレームワークは複数の選択肢がありますが、2026年現在の主要なものを比較してみます。
| 特徴 | Playwright | Cypress | Selenium |
|---|---|---|---|
| 対応ブラウザ | Chromium / Firefox / WebKit | Chromium / Firefox / WebKit | 主要ブラウザすべて |
| 言語 | TS / JS / Python / Java / C# | JavaScript / TypeScript | 多言語対応 |
| 自動待機 | 標準搭載 | 標準搭載 | 手動実装が必要 |
| 並列実行 | 標準搭載 | 有料プラン | Grid構築が必要 |
| デバッグツール | Trace Viewer / Inspector | Time Travel Debug | ログベース |
| 学習コスト | 低~中 | 低 | 中~高 |
Seleniumは歴史が長い分だけ情報も多いですが、セットアップの手間やフレーク(不安定)テストの多さが課題です。Cypressは手軽さが魅力ですが、並列実行が有料プランでしか使えない点がネックになります。
Playwrightが選ばれる理由
公式ドキュメントを読んでみると、Playwrightには他のフレームワークにない特長がいくつかあります。
- Auto-wait機能: 要素が操作可能になるまで自動的に待機するため、
sleepやwaitForを手動で書く必要がほとんどない - マルチブラウザ対応: 1つのテストコードでChromium、Firefox、WebKitの3エンジンをカバーできる
- Trace Viewer: テスト実行の各ステップをスクリーンショット付きで振り返れるデバッグツール
- Codegen: ブラウザ操作を記録してテストコードを生成する機能が標準搭載
特にAuto-waitとTrace Viewerは、テストの安定性とデバッグ効率に直結するので、プロジェクトへの導入障壁が低いと感じています。
環境構築 — Claude CodeでPlaywrightプロジェクトをセットアップ
プロジェクト初期化のプロンプト例
Playwrightプロジェクトのセットアップは、Claude Codeに依頼するとスムーズです。ターミナルでClaude Codeを起動して、以下のように指示します。
Playwrightの E2Eテスト環境をTypeScriptでセットアップしてほしい。
- テスト対象は http://localhost:3000 で動くNext.jsアプリ
- ページオブジェクトモデル(POM)のディレクトリ構成も作って
- playwrightの設定はChromiumのみ、リトライ2回で
Claude Codeが以下のようなプロジェクト構成を生成してくれます。
e2e/
├── fixtures/
│ └── base.ts # カスタムフィクスチャ
├── pages/
│ ├── login.page.ts # ログインページオブジェクト
│ └── dashboard.page.ts # ダッシュボードページオブジェクト
├── tests/
│ └── login.spec.ts # ログインテスト
├── playwright.config.ts
└── package.json
手作業だと設定ファイルの記述やディレクトリ作成で数十分かかるところが、一度のプロンプトで完了します。生成されたplaywright.config.tsの中身も確認しておきましょう。
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e/tests',
fullyParallel: true,
retries: 2,
workers: process.env.CI ? 2 : undefined,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
$ npx playwright test --list
Listing tests:
[chromium] > e2e/tests/login.spec.ts > ログインページ > 正しい情報でログインできる
[chromium] > e2e/tests/login.spec.ts > ログインページ > バリデーションエラーが表示される
Total: 2 tests in 1 file
CLAUDE.mdにテストルールを定義する
プロジェクトで一貫したテストコードを生成させるために、CLAUDE.mdにルールを追加しておくと便利です。Claude CodeのCLAUDE.md設計 — チーム開発ルールを統一する仕組みでも紹介していますが、テスト関連のルールを追記しておくと生成コードの品質が安定します。
# E2Eテストのルール
- Playwrightのテストは e2e/tests/ に配置する
- ページオブジェクトは e2e/pages/ に配置する
- ロケータはdata-testid属性を優先して使う
- テスト名は日本語で記述する
- 各テストは独立して実行可能にする(前のテストの状態に依存しない)
- ハードコードされた待機時間(waitForTimeout)は使わない
このルールを入れておくだけで、Claude Codeがdata-testidを優先したロケータを使い、テスト間の依存を避けるコードを生成するようになります。
E2Eテスト自動生成の基本フロー
ログインフォームのテスト生成
実際にClaude Codeでテストを生成してみます。まずは典型的なログインフォームのテストです。テスト対象の画面構成やdata-testidを具体的に伝えるのがコツになります。
import { test, expect } from '@playwright/test';
test.describe('ログインページ', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('正しい情報でログインするとダッシュボードに遷移する', async ({ page }) => {
await page.getByTestId('email-input').fill('user@example.com');
await page.getByTestId('password-input').fill('correct-password');
await page.getByTestId('login-button').click();
await expect(page).toHaveURL('/dashboard');
await expect(
page.getByRole('heading', { name: 'ダッシュボード' })
).toBeVisible();
});
test('空のフォームで送信するとバリデーションエラーが表示される', async ({ page }) => {
await page.getByTestId('login-button').click();
await expect(page.getByText('メールアドレスは必須です')).toBeVisible();
await expect(page.getByText('パスワードは必須です')).toBeVisible();
});
test('間違ったパスワードでエラーメッセージが表示される', async ({ page }) => {
await page.getByTestId('email-input').fill('user@example.com');
await page.getByTestId('password-input').fill('wrong-password');
await page.getByTestId('login-button').click();
await expect(
page.getByText('メールアドレスまたはパスワードが正しくありません')
).toBeVisible();
});
});
$ npx playwright test e2e/tests/login.spec.ts
Running 3 tests using 1 worker
✓ ログインページ > 正しい情報でログインするとダッシュボードに遷移する (1.2s)
✓ ログインページ > 空のフォームで送信するとバリデーションエラーが表示される (0.8s)
✓ ログインページ > 間違ったパスワードでエラーメッセージが表示される (1.0s)
3 passed (4.1s)
正常系・異常系をバランスよくカバーしたテストが生成されています。getByTestIdによるロケータ指定やAuto-waitを活かした記述になっている点もポイントです。
NGなプロンプトとOKなプロンプトの違い
Claude Codeに渡すプロンプトの書き方で、生成されるテストの品質が大きく変わります。まず、ありがちなNGパターンから見てみましょう。
# NGなプロンプト
ログインページのテストを書いて
このような曖昧な指示だと、テスト対象のURLも不明で何を検証すべきかも伝わりません。結果として、汎用的で実際のアプリに合わないテストコードが生成されがちです。
# OKなプロンプト
ログインページ(/login)のE2Eテストを書いて。
- フォームにはdata-testid="email-input"とdata-testid="password-input"がある
- 正常系: user@example.com / correctpassで/dashboardに遷移
- 異常系: 空送信でバリデーション、誤パスワードでエラーメッセージ
- 認証状態はAPI mockではなく実際のテスト用DBを使う
ポイントは、テスト対象のURL、画面上の要素の特定方法、期待する挙動、テストデータの扱いを明確に伝えることです。指示が具体的であるほど、そのまま動くテストが生成されやすくなります。
既存コードを読ませてテストを生成させる
プロンプトを細かく書くのが面倒な場合は、対象の画面コンポーネントをClaude Codeに直接読ませるアプローチも有効です。
src/app/login/page.tsx を読んで、このログインフォームのE2Eテストを生成して。
正常系・異常系それぞれ2ケースずつ。
Claude Codeがコンポーネントのソースコードからフォーム要素やイベントハンドラを解析して、適切なテストケースを生成してくれます。data-testidやaria属性がコンポーネントに付いていれば、それらを使った安定したロケータでテストが書かれます。
ページオブジェクトモデルをClaude Codeで構築する
POMなしのテスト(アンチパターン)
テストが増えてくると、ロケータの重複がメンテナンスの負担になります。以下はPOMを使わないアンチパターンです。
// アンチパターン: ロケータがテストごとに散在する
test('プロフィール更新ができる', async ({ page }) => {
await page.goto('/login');
await page.getByTestId('email-input').fill('user@example.com');
await page.getByTestId('password-input').fill('password');
await page.getByTestId('login-button').click();
// ログイン処理が毎回テストに重複して書かれる...
await page.goto('/profile');
await page.getByTestId('name-input').fill('新しい名前');
await page.getByTestId('save-button').click();
await expect(page.getByText('保存しました')).toBeVisible();
});
# このパターンの問題点
- ログインフォームのdata-testidが変わると、全テストファイルを修正する必要がある
- テストが10個、20個と増えたら修正漏れのリスクが高まる
- 同じ操作手順のコピペが大量に発生する
Claude CodeでPOMを生成する
Claude Codeに「既存のテストをページオブジェクトモデルにリファクタリングして」と依頼すると、きれいに分離してくれます。
// e2e/pages/login.page.ts
import { type Page, type Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByTestId('email-input');
this.passwordInput = page.getByTestId('password-input');
this.loginButton = page.getByTestId('login-button');
this.errorMessage = page.getByTestId('error-message');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
// e2e/tests/login.spec.ts(POM適用後)
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test.describe('ログインページ', () => {
let loginPage: LoginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.goto();
});
test('正しい情報でログインできる', async ({ page }) => {
await loginPage.login('user@example.com', 'correct-password');
await expect(page).toHaveURL('/dashboard');
});
test('間違ったパスワードでエラーが表示される', async () => {
await loginPage.login('user@example.com', 'wrong-password');
await expect(loginPage.errorMessage).toBeVisible();
});
});
$ npx playwright test e2e/tests/login.spec.ts
Running 2 tests using 1 worker
✓ ログインページ > 正しい情報でログインできる (1.1s)
✓ ログインページ > 間違ったパスワードでエラーが表示される (0.9s)
2 passed (3.2s)
ロケータの変更があっても、修正箇所はページオブジェクト内の1か所で済みます。Claude Codeは既存テストのパターンを認識して一貫したPOMクラスを生成してくれるので、リファクタリングの負担がかなり軽減されます。
テスト失敗時のデバッグをClaude Codeで効率化する
エラーメッセージを使ったデバッグ
テストが失敗したときのデバッグも、Claude Codeに任せると効率的です。失敗時のエラーメッセージをそのまま渡すだけで、原因の分析と修正案を出してくれます。
# テスト失敗時の出力例
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
Locator: getByTestId('success-toast')
at e2e/tests/profile.spec.ts:24:5
# Claude Codeへの依頼例
このテストが失敗した。トーストの表示タイミングの問題だと思うけど、
原因を調べて修正してほしい。
ファイル: e2e/tests/profile.spec.ts:24
Claude Codeは該当箇所のコードを読み、トーストのアニメーション時間やAPIレスポンスの遅延を考慮した修正を提案してくれます。調べてみると、PlaywrightのtoBeVisible()はデフォルトで5秒のタイムアウトが設定されているので、非同期処理を伴うUIの場合はタイムアウト値の調整が必要になることがあります。
Trace Viewerの活用
PlaywrightのTrace Viewerは、テスト実行の各ステップをスクリーンショット付きで確認できる強力なデバッグツールです。playwright.config.tsでtrace: 'on-first-retry'に設定しておくと、リトライ時にトレースファイルが自動生成されます。
$ npx playwright show-trace test-results/profile-spec-ts/trace.zip
# ブラウザでTrace Viewerが起動し、各ステップの画面状態を確認できる
トレースファイルには、各操作時点でのDOMスナップショットやネットワークリクエストの情報が含まれています。Claude Codeに「このトレースの内容を踏まえて、なぜテストが落ちているか分析して」と依頼すると、画面遷移のタイミングやDOM状態の変化を踏まえた分析をしてくれます。
GitHub ActionsでE2Eテストを自動実行する
ワークフロー定義
E2Eテストをプルリクエスト単位で自動実行すると、リグレッションの早期検知に役立ちます。Claude CodeのHooks機能を活用して開発を自動化しようの記事で紹介しているHooksと組み合わせると、さらに効率的なワークフローが構築できます。
# .github/workflows/e2e.yml
name: E2E Tests
on:
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Start application
run: npm run dev &
- name: Wait for app
run: npx wait-on http://localhost:3000 --timeout 30000
- 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: 7
Run npx playwright test
Running 12 tests using 2 workers
12 passed (28.4s)
テストのシャーディングで高速化する
テストが増えてCIの実行時間が長くなったら、シャーディング(テスト分割)が有効です。Playwrightは--shardオプションで簡単にテストを分割実行できます。
# シャーディングの設定例(3分割)
jobs:
e2e:
strategy:
matrix:
shard: [1, 2, 3]
steps:
# ...セットアップ省略
- name: Run E2E tests
run: npx playwright test --shard=${{ matrix.shard }}/3
# 3ジョブが並列実行される
Job 1: Running 4 tests using 1 worker - 4 passed (9.8s)
Job 2: Running 4 tests using 1 worker - 4 passed (10.2s)
Job 3: Running 4 tests using 1 worker - 4 passed (8.6s)
# トータル実行時間: 約10秒(直列だと28秒)
12テストを3分割すると、CIの実行時間が約3分の1に短縮されます。テスト数が多いプロジェクトでは、この設定だけでCI待ちの体感が大きく変わります。
まとめ
- Playwrightはクロスブラウザ対応・Auto-wait・Trace Viewerが強みのE2Eフレームワーク
- Claude Codeに具体的な指示(URL、ロケータ、期待挙動)を伝えると、品質の高いテストが生成される
CLAUDE.mdにテストルールを定義することで、生成コードの一貫性を保てる- ページオブジェクトモデル(POM)を活用すると、テストのメンテナンスコストが大幅に下がる
- テスト失敗時のエラーメッセージやトレースをClaude Codeに渡すと、デバッグが効率化する
- GitHub ActionsでPR単位の自動実行を設定すると、リグレッションの早期検知が可能になる
E2Eテストは書き始めのハードルが高い分野ですが、Claude Codeと組み合わせることで初期構築の手間をかなり軽減できます。まずは主要な画面のハッピーパスから始めて、少しずつカバレッジを広げていくのがおすすめです。関連記事として、Claude Codeでテスト駆動開発を実践する|TDDによる品質向上ガイドもあわせて参考にしてみてください。
よくある質問(FAQ)
Q1. PlaywrightとCypressどちらを選ぶべき?
プロジェクトの要件次第ですが、マルチブラウザ対応が必要ならPlaywright、Chromium系だけでよく学習コストを最小限にしたいならCypressが向いています。Playwrightは並列実行やTrace Viewerが標準搭載されているので、中~大規模プロジェクトでは特に強みを発揮します。
Q2. Claude Codeで生成したテストはそのまま使える?
体感では80~90%はそのまま動きますが、テスト対象のアプリ固有のロケータやテストデータによっては調整が必要です。生成されたテストは必ず一度ローカルで実行し、失敗するケースがあればClaude Codeに修正を依頼するのが効率的な進め方です。
Q3. E2Eテストの実行時間が長くなったらどう対処する?
PlaywrightのfullyParallel: trueを有効にして並列実行するのが基本です。それでも遅い場合は、本文でも紹介したシャーディングでCIの複数ジョブに分割する方法が効果的です。また、テストごとに不要なページ遷移がないか見直して、storageStateで認証状態を使い回すことで無駄なログイン処理を削減できます。

