
Playwright Test AgentsでE2Eテストを自動生成してみた
はじめに
データ事業本部の藤川です。E2Eテストをどのくらい書いていますか?
これまで、E2Eテストの整備には地道な作業が必要でした。画面を開いてセレクタを調べ、テストコードを書き、実行しては落ち、セレクタを修正します。UIは頻繁に変更されます。アプリケーションが変われば壊れたテストを保守する。E2Eテストコードを維持するコストは膨大になります。
生成AIにテストコードを書かせることができるようになりました。今回は、実際にアプリケーションサーバーを用意し、Playwright Test AgentsでE2Eテストコードを自動生成させてみます。「最初から動くテスト」が出てくるという特徴を体験してみましょう。
概要
Playwright Test Agentsは、役割の異なる3つのエージェント定義から構成されます。
Playwrightには、AIエージェントにE2Eテストを作らせるPlaywright Test Agentsという仕組みがあります。テスト計画を立てるplanner、テストコードを生成するgenerator、失敗したテストを修復するhealerという3つのエージェントが、実際にブラウザを操作しながらテストを作り上げてくれるというものです。
| エージェント | 役割 |
|---|---|
planner |
アプリを実ブラウザで探索し、テスト計画(Markdown)を作成する |
generator |
計画の各シナリオを実ブラウザで実行しながらテストコードを生成する |
healer |
失敗したテストを実行・デバッグし、原因を特定してコードを修正する |
アプリケーションサーバーには、Java製のペットストアECサイトJPetStore 6を使用します。ランタイム環境の構築はmiseを、Nodeパッケージ管理はpnpmに統一し(npxは使わない方針)、最後にHTML reporterでレポートを表示するところまでやってみます。
準備
検証環境
- macOS(Apple Silicon)
Playwright1.60.0Claude Codemise2026.4.8
JPetStoreの取得とランタイム構築
まず、JPetStoreのリポジトリをcloneします。
git clone https://github.com/mybatis/jpetstore-6.git
cd jpetstore-6
次に、リポジトリ直下にmise.tomlを作成し、JavaとNode、pnpmをまとめて導入します。
JPetStore自体はJava 17ターゲットですが、親POM(mybatis-parent)のenforcerプラグインがビルド時にJDK 21以上を要求するため、temurin-17ではビルドが失敗します。temurin-21を指定すれば問題ありません。
[tools]
java = "temurin-21"
node = "lts"
pnpm = "latest"
次のコマンドを実行し、各ツールのバージョンが表示されれば問題ありません。
mise trust && mise install
mise exec -- java -version # Temurin 21
mise exec -- node -v # v24系(LTS)
mise exec -- pnpm -v
アプリケーションサーバーの起動
WARをビルドし、Tomcat 9で起動します。cargoプラグインのtomcat9プロファイルがデフォルトで有効なため、-Pオプションは不要です。
mise exec -- ./mvnw clean package -DskipTests
mise exec -- ./mvnw cargo:run
ブラウザで http://localhost:8080/jpetstore/ を開き、JPetStoreのトップページが表示されれば準備完了です。
Playwrightのセットアップ
pnpmで@playwright/testとChromiumを導入します。
mise exec -- pnpm add -D @playwright/test@latest
mise exec -- pnpm exec playwright install chromium
playwright.config.tsには、HTML reporterとJPetStoreのbaseURLを設定しておきます。
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
reporter: [['list'], ['html', { open: 'never' }]],
use: {
baseURL: 'http://localhost:8080/jpetstore/',
trace: 'retain-on-failure',
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
Test Agentsの初期化とMCP設定
Test Agentsを初期化します。
playwright init-agentsコマンドを実行すると、エージェント定義(.claude/agents/配下)と、エージェントがブラウザやテストランナーを操作するためのMCPサーバー設定(.mcp.json)が生成されます。--loopオプションで連携先のAIエージェント環境を選択できます。Claude Codeを指定し、Test Agentsの各エージェントをそのサブエージェントとして動かします。
mise exec -- pnpm exec playwright init-agents --loop=claude
次のファイルが生成されます。
.
├── .claude/
│ └── agents/
│ ├── playwright-test-planner.md # plannerエージェント定義
│ ├── playwright-test-generator.md # generatorエージェント定義
│ └── playwright-test-healer.md # healerエージェント定義
├── .mcp.json # MCPサーバー設定
├── specs/
│ └── README.md # テスト計画の置き場所
└── tests/
└── seed.spec.ts # エージェントが起点に使うシードテスト
生成された.mcp.jsonはnpxでMCPサーバーを起動する設定になっています。今回はnpxを使わない方針のため、ローカルインストール済みのバイナリを直接指定するように書き換えました。
{
"mcpServers": {
"playwright-test": {
"command": "/path/to/jpetstore-6/node_modules/.bin/playwright",
"args": ["run-test-mcp-server"]
}
}
}
Claude Codeを再起動すると、.mcp.jsonの承認を求められるので、承認すればplaywright-test MCPサーバーに接続され、エージェントが使えるようになります。
やってみた
plannerでテスト計画を作成する
Claude Codeでplannerエージェントを実行します。@plannerとタイプすると、次のものが候補に表示されるので、確定します。
@.claude/agents/playwright-test-planner.md
JPetStoreの主要フロー(カタログ閲覧、検索、サインイン、カート、注文、新規登録)のテスト計画を依頼しました。plannerは実際にChromiumを起動してJPetStoreを隅々まで操作し、specs/jpetstore-core-flows.mdとして6スイート・8シナリオのテスト計画を作成してくれました。
- カタログをホーム→カテゴリ→商品→アイテム詳細とたどる
- キーワード検索(ヒットあり)
- キーワード検索(ヒットなし)
- サインイン/サインアウト
- 誤ったパスワードでのサインイン失敗
- カートへの追加→数量変更→削除
- サインインして注文確定までのエンドツーエンド
- 新規アカウント登録
計画には、実際の画面で確認したリンクテキストやボタンのラベル、期待値(価格やメッセージの文言)まで具体的に書き込まれており、このまま手動テストの手順書としても使えそうな品質です。
generatorでテストコードを生成する
通常は、plannerを実行すると、generatorも処理されます。
シナリオごとにgeneratorエージェントを起動し、テストコードを生成します。エージェントは単一のMCPブラウザを共有するため、並列ではなく1本ずつ順番に実行する必要があります。1本あたり2〜15分ほどかけて、計画のステップを実ブラウザで再生しながらコードを書いていきます。
生成されたテストの一例として、新規アカウント登録のテストを抜粋します。
test.describe('Account Registration', () => {
test('Register a new account with a unique username', async ({ page }) => {
const username = `testuser${Date.now()}`;
// 2. Click the 'Register Now!' link
await page.locator('a[href*=\'newAccountForm\']').click();
// 3. In the 'User ID:' field, enter a unique username based on the current timestamp
await page.locator('input[name="username"]').fill(username);
await expect(page.locator('input[name="username"]')).toHaveValue(username);
// (中略)
});
});
注目したいのは2点です。
- JPetStoreのインメモリDBは重複ユーザー名を拒否します。ユーザー名にタイムスタンプを加えて、一意にしているため、テストを何度でも再実行できます。
- JPetStoreが使うStripesフレームワークはフォーム要素のIDを動的生成するのものです。
generatorは実ページを調べて壊れにくいinput[name="..."]形式のロケータを自ら選択していました。実ブラウザで検証しながら書くからこその判断です。
テストを実行する
生成された8本(+シードテスト)を実行してみます。
mise exec -- pnpm exec playwright test
結果は8 passed / 1 failedでした。カタログ閲覧のテストが、次のエラーで失敗しています。
Error: expect(locator).toBeVisible() failed
Locator: getByRole('link', { name: 'Fish' })
Expected: visible
Received: hidden
- locator resolved to <area alt="Fish" shape="RECT" ... />
カタログページ中央のイメージマップ(<area alt="Fish">)にロケータが解決されてしまったのが原因です。<area>要素はHTML仕様上レイアウトボックスを持たないため、Playwrightでは常にhidden扱いになります。
healerでテストを修復する
ここでhealerエージェントを使用します。@healerとタイプすると、次のものが入力されます。
@.claude/agents/playwright-test-healer.md
失敗したテストを渡すと、healerはテストを再実行して失敗を再現し、デバッグモードでページの状態を調査して、ロケータをサイドバーの実在するアンカー要素に修正してくれます。
// 修正前: <area>に解決されてhidden扱いになる
await expect(page.getByRole('link', { name: 'Fish' })).toBeVisible();
// 修正後: 実在するアンカー要素を直接指定
await expect(page.locator('#QuickLinks a[href*="categoryId=FISH"]')).toBeVisible();
修正後に全体を再実行すると、9/9 passedとなりました。サーバーを再起動したまっさらな状態でも全件パスすることを確認できたので、再現性も問題ありません。
9 passed (5.2s)
HTMLレポートを表示する
最後に、HTML reporterでレポートを表示します。
mise exec -- pnpm exec playwright show-report
ブラウザで http://localhost:9323 が開き、スイートごとの実行結果、各ステップの所要時間、失敗時のトレースまで確認できます。
<img src="https://devio2024-media.developers.io/image/upload/v1781104002/user-gen-eyecatch/ktbuuiif0k5uryzlb5er.png" alt="HTML reporter" />
さいごに
ご覧の通り、テストコードを1行も手書きすることなく、計画→生成→実行→修復→レポートまで到達できました。実際に触ってみて、いくつか論点も見えてきます。
generatorは実ブラウザで1ステップずつ検証しながらコードを書きます。healerが自走しながら原因を特定し、修正までやり切ります。- 生成は1本あたり2〜15分で、エージェントは直列実行が前提です。人間がゼロから書くより速いとはいえ、CIで毎回生成すべきものではありません。「生成は開発時、実行はCI」という使い分けになるでしょう。AIエージェントのトークン消費量はコストに直結するところです。時間とコストの観点から最適な使い分けと考えます。
- 生成されたテストは「現状の動作」を正として固定化したものです。そのアサーションが仕様として正しいかの判断は、依然として人間の仕事です。テストコードのレビューは欠かせません。これを如何にして効率化するかは、別の機会でご紹介したいと思います。
これまでは、E2Eテストを書かないプロジェクトも多かったと思います。Playwright Test Agentsを活用し、E2Eテストで品質を向上させましょう。どなたかのお役に立てれば幸いです。




