webapp-testingスキルでClaudeにローカルWebアプリをテストさせる
Anthropic公式のwebapp-testingスキルで、Claude Codeにローカルサーバーを起動させPlaywrightでUIを検証させる手順を解説。with_server.pyの挙動、判断木、MCP方式との違いまで扱います。
webapp-testingは、Anthropicが公開しているサンプルスキルの1つです。ローカルで動くWebアプリをPlaywrightのPythonスクリプトで操作し、画面の確認・スクリーンショット・ブラウザログの取得までをClaudeに任せるための手順書(SKILL.md)とヘルパースクリプトで構成されています。
Claude Codeで使う手順は短く、プラグインとして入れて「webapp-testingスキルでローカルのアプリを確認して」と頼むだけです。ただし中身を知らないと、サーバーが起動しない、networkidleで待ちすぎる、サンプルの保存先でコケる、といった場面で原因を追えません。この記事では導入から、同梱スクリプトの実際の挙動、判断木の使いどころ、権限設定までを順に扱います。
webapp-testingスキルの中身
スキルの説明文(description)は、ローカルWebアプリの操作とテストにPlaywrightを使うツールキット、と書かれています。用途として挙がるのは、フロントエンド機能の検証、UI挙動のデバッグ、ブラウザのスクリーンショット取得、ブラウザログの閲覧です。
ディレクトリは次の3要素です。
| 要素 | 役割 |
|---|---|
SKILL.md | 役割判断木、スクリプトの使い方、注意点 |
scripts/with_server.py | 役割サーバーの起動・待機・終了を管理するヘルパー(複数サーバー対応) |
examples/ | 役割要素の洗い出し、静的HTMLの操作、コンソールログ取得の3例 |
作り方の前提は「ネイティブのPython Playwrightスクリプトを書く」ことです。MCPサーバーのようにツールを常駐させず、Claudeがその場でPythonを書いて実行します。
導入 — プラグイン経由か、手動配置か
webapp-testingは、リポジトリが用意するプラグインマーケットプレイスのexample-skillsに含まれています。Claude Codeでの導入は次の2コマンドです。
/plugin marketplace add anthropics/skills
/plugin install example-skills@anthropic-agent-skills/pluginの画面からも選べます。マーケットプレイス追加後にBrowse and install pluginsからanthropic-agent-skillsを開き、example-skillsを選んでInstall nowを押す流れです。example-skillsにはwebapp-testing以外のサンプルも束ねられているため、他のスキルまで入ります。
プラグインを使わず、ディレクトリごと手元に置く方法もあります。Claude Codeはプロジェクトの.claude/skills/<スキル名>/SKILL.mdと、個人用の~/.claude/skills/<スキル名>/SKILL.mdを読み込みます。scripts/やexamples/のような補助ファイルは、スキルのディレクトリ内に置けばSKILL.mdから参照されます。プロジェクトに置いてコミットすれば、チームにも同じスキルが届きます。
# リポジトリを取得し、webapp-testingだけをプロジェクトにコピーする例
git clone --depth 1 https://github.com/anthropics/skills /tmp/anthropics-skills
mkdir -p .claude/skills
cp -r /tmp/anthropics-skills/skills/webapp-testing .claude/skills/スキルの置き場所・優先順位・frontmatterの仕様はClaude Code Skills完全ガイドに詳しくあります。
Playwrightを使える状態にしておく
スキル自体はPlaywrightを同梱しません。手元のPython環境にPlaywrightとブラウザが無いと、Claudeが書いたスクリプトはimportの段階で失敗します。Playwright Pythonの公式導入手順は、pytest-playwrightを入れてからブラウザを入れる形です。
pip install pytest-playwright
playwright installスキルのスクリプトは常にChromiumをヘッドレスで起動する前提です(SKILL.mdに「Always launch chromium in headless mode」とあります)。公式の手順どおりブラウザを入れておけば、この前提は満たせます。
判断木 — 静的HTML、起動前、起動済みで進め方が分かれる
SKILL.mdの中心は、タスクを3つの経路に振り分ける判断木です。
- 静的HTMLか: そうなら、まずHTMLファイルを直接読んでセレクターを特定する。うまくいかない・足りないときは動的アプリとして扱う
- 動的アプリで、サーバーが未起動か:
python scripts/with_server.py --helpを実行し、ヘルパーでサーバーを立てたうえで簡素なPlaywrightスクリプトを書く - 動的アプリで、サーバーが起動済みか: 偵察してから操作する(次節)
判断の分かれ目は「サーバーを誰が管理するか」です。起動済みなら、Claudeはページに繋ぐだけで済みます。未起動ならwith_server.pyが起動から終了までを引き受け、Playwrightスクリプト側にはブラウザ操作だけを書けばよい、という分業です。
with_server.pyが実際にやること
SKILL.mdはこのスクリプトを「ブラックボックスとして呼ぶ」ものと位置づけ、先に--helpを実行し、ソースは読まないよう指示しています。スクリプトが大きくなるとコンテキストを圧迫するから、という理由です。ただ、運用で詰まったときのために、実際の挙動は知っておくと役に立ちます。ソースから読み取れる仕様は次のとおりです。
| 項目 | 挙動 |
|---|---|
--server | 挙動起動コマンド。複数回指定でき、shell=Trueで実行されるためcd backend && python server.pyのような合成コマンドも書ける |
--port | 挙動各サーバーのポート。--serverと同じ個数が必須で、数が合わないとエラー終了する |
--timeout | 挙動サーバーごとの待機秒数。既定は30秒 |
| 待機方法 | 挙動localhostの指定ポートへのTCP接続を0.5秒間隔で試し、繋がった時点で「準備完了」とみなす |
| 起動順 | 挙動--serverの指定順に1台ずつ起動し、各サーバーの準備完了を待ってから次を起動する |
| 実行コマンド | 挙動--の後ろのコマンドを実行し、その終了コードをそのまま返す |
| 終了処理 | 挙動成功・失敗にかかわらず、各サーバーにterminateを送り、5秒待っても終わらなければkillする |
単一サーバーと複数サーバーの呼び出し
SKILL.mdの例を、そのまま使える形で載せます。
# フロントエンド1台
python scripts/with_server.py --server "npm run dev" --port 5173 \
-- python your_automation.py
# バックエンドとフロントエンドの2台
python scripts/with_server.py \
--server "cd backend && python server.py" --port 3000 \
--server "cd frontend && npm run dev" --port 5173 \
-- python your_automation.py2台目は1台目のポートが開いてから起動します。フロントがバックエンドのAPIを起動時に叩く構成では、指定順がそのまま依存関係になります。
「ポートが開いた」と「アプリが使える」は別物
待機の判定はTCP接続の成否だけです。HTTPのレスポンス内容やビルドの完了は見ていません。開発サーバーが先にポートを開いてから初回ビルドを続けるアプリでは、準備完了の表示が出ても最初のページ読み込みが遅れることがあります。その場合のスクリプト側の備えは、後述の待機の書き方で決まります。
もう1点、サーバーの標準出力・標準エラーはパイプで受けるだけで、スクリプトは読み出して表示しません。起動に失敗してタイムアウトしたときも、サーバー側のエラーメッセージは画面に出ない作りです。原因を調べるときは、同じコマンドを手動で実行して出力を確認するのが確実です。
偵察してから操作する
動的アプリでサーバーが動いているときの進め方が、SKILL.mdの「Reconnaissance-Then-Action」です。
- ページを開き、読み込みの完了を待つ
- スクリーンショットかDOMで、描画後の状態を調べる
- 描画後の状態から、使うセレクターを決める
- 見つけたセレクターで操作する
先に操作コードを推測で書かせず、いまの画面に何があるかを一度確認させる順序です。ボタン名やフォームのnameを取り違えたスクリプトを書かせてしまう失敗を、先に潰せます。
examples/element_discovery.pyは、この偵察を実装した例です。ボタンの文言、リンク先、入力欄のname/id/typeを一覧表示し、全体のスクリーンショットも保存します。骨格は次のとおりです。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto('http://localhost:5173')
page.wait_for_load_state('networkidle')
for i, button in enumerate(page.locator('button').all()):
text = button.inner_text() if button.is_visible() else "[hidden]"
print(f" [{i}] {text}")
page.screenshot(path='/tmp/page_discovery.png', full_page=True)
browser.close()Claudeには、この出力を読ませてから本番のテストスクリプトを書かせます。偵察の結果が会話に残るので、後続のスクリプトはその文言とセレクターを根拠に書かれます。
networkidleを必須とするスキルと、非推奨とするPlaywright
SKILL.mdは、動的アプリでは調べる前にpage.wait_for_load_state('networkidle')でJavaScriptの実行完了を待つよう、強い調子で求めています。待たずにDOMを調べると、描画前の空に近いHTMLを見てしまうからです。
一方、PlaywrightのPython公式ドキュメントはnetworkidleに「DISCOURAGED」と付け、ネットワーク接続が500ミリ秒以上ない状態を待つ方式なので、テストには使わず、Webアサーションで準備完了を判定するよう案内しています。
両者は矛盾というより、用途の差です。偵察は「いま何が描画されているかを一度見る」作業なので、雑な待機でも許容されます。反復して回すテスト本体では、特定の要素が表示されるまで待つ形に書き換えるほうが安定します。Claudeに次のように頼むと、偵察とテストで待ち方を分けられます。
webapp-testingスキルで、ローカルのアプリ(npm run dev、ポート5173)の
ログインフォームを確認して。
まず偵察でボタンと入力欄を洗い出し、その結果をもとに
正しい資格情報でログインできることを確かめるスクリプトを書いて。
テスト本体のスクリプトでは networkidle を使わず、
ログイン後に表示される要素の出現を待つ形にして。コンソールログとスクリーンショットを取るときの落とし穴
examples/console_logging.pyは、page.on("console", ...)でブラウザのコンソール出力を拾う例で、メッセージの種別と本文を配列に溜めます。UIが壊れた原因をJavaScriptのエラーから探すときに使えます。
ただし、この例の保存先は/mnt/user-data/outputs/console.logです。static_html_automation.pyのスクリーンショットも/mnt/user-data/outputs/に保存します。Claudeが動作する別環境を想定したパスなので、手元のMacやLinuxには存在せず、書き込みで失敗する可能性があります。
| ファイル | 例の中の固定値 | ローカルで直す点 |
|---|---|---|
console_logging.py | 例の中の固定値URLがhttp://localhost:5173、ログ保存先が/mnt/user-data/outputs/console.log | ローカルで直す点自分のURLとプロジェクト内の保存先に変える |
static_html_automation.py | 例の中の固定値HTMLのパスがpath/to/your/file.html、画像保存先が/mnt/user-data/outputs/ | ローカルで直す点実在するHTMLと保存先に変える |
element_discovery.py | 例の中の固定値URLがhttp://localhost:5173、画像保存先が/tmp/ | ローカルで直す点ポートを合わせれば動く |
プロンプトで「出力はプロジェクトの.tmp-shots/に保存して」のように保存先を指定しておけば、Claudeはサンプルの固定パスを置き換えて書いてくれます。保存先をGitの管理外にしておく設定は、.gitignoreに1行足すだけです。
権限設定 — with_server.pyを許可すると何が通るか
毎回の承認を避けたくなりますが、with_server.pyの許可には注意点があります。--の後ろのコマンドを、そのままsubprocess.runで実行する作りだからです。--serverの値もshell=Trueで実行されます。
Claude Codeの権限ルールは、BashのコマンドをBash(npm run *)のようなパターンで許可します。次の許可ルールは、with_server.py経由で任意のコマンドが実行できる状態を意味します。
{
"permissions": {
"allow": [
"Bash(python .claude/skills/webapp-testing/scripts/with_server.py *)"
]
}
}実行内容を限定したいなら、--serverと--の後ろまで含めて固定します。
{
"permissions": {
"allow": [
"Bash(python .claude/skills/webapp-testing/scripts/with_server.py --server \"npm run dev\" --port 5173 -- python tests/ui_check.py)"
]
}
}前者は手軽ですが許可範囲が広く、後者は決まった検証だけを通します。スキルのfrontmatterにあるallowed-toolsでも同様の事前承認はできますが、付与されるのはそのスキルを呼んだターンの間だけで、次のメッセージを送ると解除されます。リポジトリにコミットされたスキルのallowed-toolsは、確認してから使ってください。
MCP方式との使い分け
PlaywrightでClaude Codeにブラウザを操作させる方法は、スキル方式のほかにPlaywright MCPがあります。MCPサーバーの導入はPlaywright MCPサーバーの使い方にまとめました。
PlaywrightのMCP側のREADMEは、コーディングエージェントにはCLI+SKILLSのほうがトークン効率がよく、ツールスキーマや冗長なアクセシビリティツリーをコンテキストに読み込まずに済むと述べています。一方でMCPは、探索的な自動化・自己修復的なテスト・長時間の自律ワークフローのように、継続的なブラウザの状態に価値がある用途で意味があるとしています。webapp-testingは、前者の考え方に近い位置にあります。
| 観点 | webapp-testingスキル | Playwright MCP |
|---|---|---|
| 操作の主体 | webapp-testingスキルClaudeが書いたPythonスクリプトを実行 | Playwright MCPClaudeがツールを呼んで逐次操作 |
| サーバー起動 | webapp-testingスキルwith_server.pyが管理 | Playwright MCP範囲外(別途起動しておく) |
| ブラウザの状態 | webapp-testingスキルスクリプトごとに起動して終了 | Playwright MCPMCPサーバーが保持して続けられる |
| 実行後に残るもの | webapp-testingスキル再実行できるスクリプトと、スクリーンショット・ログ | Playwright MCP会話の中の操作履歴 |
| 向く用途 | webapp-testingスキルローカルアプリの検証、再現手順の固定 | Playwright MCP探索、状態を保った長い操作 |
残るのは「スクリプトが手元に残る」ことの価値です。偵察で見つけたセレクターを使った検証スクリプトは、そのままファイルに残して再実行できます。ここから先は、既存のE2Eスイートの自動実行とhookへの接続の話になり、Claude CodeでPlaywrightのE2Eテストをhookで自動実行するが受け皿になります。
よくあるつまずき
サーバーが起動しないとき
with_server.pyがServer failed to start on port ...で終了したら、--portの値が実際に待ち受けるポートと一致しているかを確認します。開発サーバーが別のポートで立ち上がっていると、TCP接続は成立せずタイムアウトします。起動に30秒以上かかるアプリでは--timeoutで延ばします。
画面は開くが、セレクターが見つからない
描画前のDOMを調べている可能性があります。偵察の段階ではnetworkidleを待ち、それでも要素が出ないときは、スクリーンショットを撮って実際の画面を確認させます。SPAの描画遅延、ログイン後にしか出ない要素、モーダルの内側にあるボタンが代表的な原因です。
スキルが呼ばれない
スキルはClaudeが説明文を見て自動で選ぶほか、/スキル名でも直接呼べます。プロンプトに「webapp-testingスキルで」と書いておけば、選択の揺れを避けられます。他のスキルと混ぜて使うときの設計は、Claude Code Skillsの書き方 — 使える5つのパターン集が参考になります。
複数のポートを使うアプリ
バックエンドとフロントエンドが別ポートなら、--serverと--portを組にして並べます。個数が合わないとスクリプトはエラーで終わります。起動順は指定順なので、依存される側を先に書きます。
まとめ
webapp-testingスキルは、サーバーの起動管理(with_server.py)と、偵察してから操作する進め方を、Claudeに渡すための小さな手順書です。導入は/plugin marketplace add anthropics/skillsの1コマンドから始まり、動かすうえで押さえる点は次の4つです。
- 判断木で、静的HTML・未起動・起動済みの3経路に分ける
with_server.pyが待つのはポートの開通だけで、サーバーの出力は表示されない- サンプルの保存先は別環境の想定なので、ローカルでは書き換える
- 許可ルールに
with_server.py *を入れると、後続のコマンドを実質的に何でも通す
状態を保ったまま探索したいならPlaywright MCP、再実行できる検証スクリプトを残したいならこのスキル、という切り分けが自然です。