webapp-testing について
webapp-testingは、PlaywrightのPython APIでローカルWebアプリをテストするスキルです。静的ファイルと稼働中のアプリ向けの手順を備え、操作確認、スクリーンショット、コンソールエラーの確認を行えます。
主な機能
- 01Playwright PythonによるローカルWebアプリのテスト
- 02静的・動的アプリに応じた方法の選択
- 03フロントエンドの動作と操作の確認
- 04スクリーンショット取得とコンソールエラー確認
- 05自動テストスクリプトの生成
活用シーン
- 01フォームやナビゲーションフローの確認
- 02UIの表示や動作のデバッグ
- 03ビジュアル回帰用のスクリーンショット取得
- 04ブラウザーコンソールのJavaScriptエラー検出
MCP 対応
このスキルでは、Puppeteer MCPサーバーを使う任意のブラウザー自動化も説明しています。ナビゲーション、スクリーンショット、クリック、フォーム入力、選択、ホバー、ページ評価のツールを紹介します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ oimiragieo/ agent-studio --skill webapp-testingインストール後、Claude Code で/webapp-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
commands/webapp-testing.mdhooks/post-execute.cjspre-execute.cjsreferences/research-requirements.mdrules/webapp-testing.mdschemas/input.schema.jsonoutput.schema.jsonscripts/main.cjstemplates/implementation-template.md詳細
Web Application Testing Skill
Anthropic の公式スキルリポジトリを基にした、Web アプリケーションテストツールキットです。Python で Playwright を使用してローカルの Web アプリケーションをテストするための体系的な手法を提供します。フロントエンドの検証、UI のデバッグ、スクリーンショットの撮影、ブラウザーのコンソールログ分析を網羅しています。 - Playwright(Python)によるローカル Web アプリケーションのテスト - フロントエンド機能の検証 - UI の動作のデバッグと調査 - 重要な操作時点でのブラウザーのスクリーンショット撮影 - ブラウザーのコンソールログの表示とエラー検出 - 静的 HTML ファイルのテスト(直接読み取り+セレクターの特定) - サーバーのライフサイクル管理を伴う動的 Web アプリのテスト - 自動テストスクリプトの生成 - 要素の検出と CSS セレクターの特定 - ページ読み込み状態の管理(networkidle、domcontentloaded、load)概要
このスキルは、agent-studio フレームワーク向けに Anthropic の webapp-testing スキルを応用したものです。テスト対象の Web コンテンツの種類に応じて適切な戦略を選び、Playwright を使ってローカルの Web アプリケーションをテストするための体系的なアプローチを提供します。
ソースリポジトリ:https://github.com/anthropics/skills
ライセンス:MIT
ツール:Playwright(Python API)
使用する場面
- Web アプリケーションのフロントエンド機能を検証するとき
- UI の動作や視覚的なレンダリングの問題をデバッグするとき
- ビジュアルリグレッションテスト用のスクリーンショットを撮影するとき
- ブラウザーのコンソールで JavaScript エラーを確認するとき
- フォーム送信、ナビゲーションフロー、インタラクティブ要素をテストするとき
- Web アプリケーション用の自動テストスクリプトを生成するとき
鉄則
NEVER INSPECT DOM BEFORE WAITING FOR NETWORKIDLE ON DYNAMIC APPS
動的な Web アプリケーションは、コンテンツを非同期に読み込みます。ページが安定する前に DOM を調べると、不正確または不完全な結果になります。動的アプリでは、レンダリングされたコンテンツを調べる前に必ず page.wait_for_load_state('networkidle') を呼び出してください。
判断フロー:アプローチの選択
Is the target a static HTML file?
YES → Approach A: Direct Read
NO → Is there a running dev server?
YES → Approach B: Reconnaissance-then-Action
NO → Approach C: Helper Script First
アプローチ A:静的 HTML ファイル
サーバーを必要としない静的 HTML ファイルの場合:
- HTML ファイルを直接読み取ります
- 対象の要素に対応する CSS セレクターを特定します
- ファイルを開いてコンテンツを検証する Playwright スクリプトを作成します
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(f"file://{abs_path_to_html}")
# Verify content
title = page.title()
assert title == "Expected Title"
# Check element exists
element = page.query_selector("h1.main-heading")
assert element is not None
# Capture screenshot
page.screenshot(path="screenshot.png")
browser.close()
アプローチ B:サーバー実行中(調査してから操作)
開発サーバーがすでに実行中の場合:
- 調査:アプリに移動し、ページ構造を確認します
- 安定するまで待機:
page.wait_for_load_state('networkidle') - 確認:要素を検索し、コンテンツを読み取り、コンソールログを確認します
- 操作:フォーム、ボタン、ナビゲーションを操作します
- 検証:期待される結果をアサートします
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Capture console messages
console_messages = []
page.on("console", lambda msg: console_messages.append(
f"[{msg.type}] {msg.text}"
))
# Navigate and wait for stability
page.goto("http://localhost:3000")
page.wait_for_load_state("networkidle")
# Discover page structure
headings = page.query_selector_all("h1, h2, h3")
buttons = page.query_selector_all("button")
forms = page.query_selector_all("form")
links = page.query_selector_all("a[href]")
# Print discovered elements
for h in headings:
print(f"Heading: {h.text_content()}")
for btn in buttons:
print(f"Button: {btn.text_content()}")
# Screenshot before interaction
page.screenshot(path="before-interaction.png")
# Interact with form
page.fill("input[name='email']", "[email protected]")
page.fill("input[name='password']", "testpass123")
page.click("button[type='submit']")
page.wait_for_load_state("networkidle")
# Screenshot after interaction
page.screenshot(path="after-interaction.png")
# Check for console errors
errors = [m for m in console_messages if "[error]" in m.lower()]
if errors:
print(f"Console errors found: {errors}")
browser.close()
アプローチ C:サーバー未実行(ヘルパースクリプト)
Web アプリのサーバーを起動する必要がある場合:
import subprocess
import time
from playwright.sync_api import sync_playwright
# Start the server
server_proc = subprocess.Popen(
["npm", "run", "dev"],
cwd="/path/to/project",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False # Security: always shell=False
)
# Wait for server to be ready
time.sleep(5) # Or use a health check loop
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("http://localhost:3000")
page.wait_for_load_state("networkidle")
# Run tests...
page.screenshot(path="test-result.png")
browser.close()
finally:
server_proc.terminate()
server_proc.wait()
よく使われるテストパターン
パターン 1:ビジュアルリグレッションチェック
# Take baseline screenshot
page.screenshot(path="baseline.png", full_page=True)
# After changes, take comparison screenshot
page.screenshot(path="current.png", full_page=True)
# Compare (use image diff library)
パターン 2:フォーム検証テスト
# Test empty submission
page.click("button[type='submit']")
error_msgs = page.query_selector_all(".error-message")
assert len(error_msgs) > 0, "Expected validation errors for empty form"
# Test invalid email
page.fill("input[type='email']", "not-an-email")
page.click("button[type='submit']")
email_error = page.query_selector("input[type='email']:invalid")
assert email_error is not None
パターン 3:ナビゲーションフローテスト
# Test navigation links
nav_links = page.query_selector_all("nav a")
for link in nav_links:
href = link.get_attribute("href")
link.click()
page.wait_for_load_state("networkidle")
assert page.url.endswith(href), f"Expected URL to end with {href}"
page.go_back()
page.wait_for_load_state("networkidle")
パターン 4:レスポンシブデザインテスト
viewports = [
{"width": 375, "height": 812, "name": "mobile"},
{"width": 768, "height": 1024, "name": "tablet"},
{"width": 1920, "height": 1080, "name": "desktop"},
]
for vp in viewports:
page.set_viewport_size({"width": vp["width"], "height": vp["height"]})
page.wait_for_load_state("networkidle")
page.screenshot(path=f"responsive-{vp['name']}.png")
重大な注意点
- networkidle の前に DOM を調べないでください:動的アプリはコンテンツを非同期に読み込みます。早すぎる確認では結果が不完全になります。
- shell=True を使用しないでください:サーバープロセスを起動するときは、安全のため常に配列形式の引数を使って
shell=Falseを指定してください。 - 固定時間の待機を使用しないでください:
time.sleep()の代わりにpage.wait_for_selector()またはpage.wait_for_load_state()を使用してください。 - コンソールエラーを無視しないでください:ブラウザーのコンソールエラーは必ず取得して報告してください。実際の問題を示しています。
- クリーンアップを忘れないでください:必ず
finallyブロックでサーバープロセスを終了してください。
前提条件
Playwright がインストールされていることを確認してください。
pip install playwright
playwright install chromium
Agent-Studio との連携
推奨ワークフロー
webapp-testingを使ってフロントエンドの動作を検証します- スクリーンショットの証拠を
code-reviewerに渡して、視覚的なレビューを行います tddスキルを使って、発見したパターンからテストスイートを生成しますaccessibilityスキルを使って、WCAG への準拠を検証します
補完スキル
| スキル | 関連性 |
|---|---|
tdd | webapp-testing での発見事項からテストスイートを生成します |
accessibility | 機能テスト後に WCAG 準拠を検証します |
frontend-expert | テスト設計のための UI/UX パターンの指針 |
chrome-browser | 代替となるブラウザー自動化アプローチ |
test-generator | テストパターンからテストコードを生成します |
絶対に守るべき原則
- NETWORKIDLE の前に DOM を決して調査しないでください — 動的なウェブアプリケーションはコンテンツを非同期で読み込みます。ページが安定する前に DOM を調査すると、誤った結果や不完全な結果が生じます。
- サーバープロセスの起動に
shell=Trueを決して使用しないでください — 常に配列引数を指定してshell=Falseを使用してください(SE-01 のセキュリティ要件)。 - 常にコンソールエラーを記録してください — ブラウザーのコンソールエラーは実際の問題を示しています。テストレポートで決して無視しないでください。
- 常に
finallyブロックでサーバープロセスを終了してください — サーバープロセスが残ると、後続のテスト実行に支障をきたし、リソースを消費します。 - 固定時間の待機は決して使用しないでください —
time.sleep()の代わりにpage.wait_for_selector()またはpage.wait_for_load_state()を使用してください。
アンチパターン
| アンチパターン | 失敗する理由 | 正しい方法 |
|---|---|---|
| networkidle の前に DOM を調査する | 動的コンテンツがまだ読み込まれておらず、アサーションで誤検知が発生します | 調査の前に必ず page.wait_for_load_state('networkidle') を実行します |
待機に time.sleep() を使用する | 不安定です。低速なマシンでは短すぎ、高速なマシンでは長すぎます | 明示的な待機を使用します:wait_for_selector、wait_for_load_state |
| ブラウザーのコンソールエラーを無視する | 実際の JS エラーが検出されず、アプリが壊れていてもテストに合格します | テスト実行ごとに必ずコンソールエラーを記録して報告します |
サーバープロセスに shell=True を使用する | コマンドインジェクションの脆弱性 | リスト引数を指定して常に shell=False を使用します |
| サーバープロセスをクリーンアップしない | 後続の実行でポート競合やリソースリークが発生します | try/finally を使用して server_proc.terminate() の実行を保証します |
Puppeteer MCP ブラウザー自動化
Python を使わずにエージェントネイティブなブラウザー自動化を行うには、modelcontextprotocol/servers の Puppeteer MCP サーバーを使用します。
セットアップ
mcpServers の下に .claude/settings.json を追加します。
"puppeteer": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-puppeteer"]
}
MCP ツールリファレンス
| ツール | 用途 |
|---|---|
puppeteer_navigate | URL に移動して、ページの読み込みを待機します |
puppeteer_screenshot | スクリーンショットを撮影します(全ページまたは要素) |
puppeteer_click | CSS セレクターをクリックします |
puppeteer_fill | フォーム入力に値を入力します |
puppeteer_select | 値でドロップダウンのオプションを選択します |
puppeteer_hover | 要素にマウスカーソルを合わせます |
puppeteer_evaluate | ブラウザーコンテキストで JavaScript を実行します |
使用パターン
// Navigate and capture state
mcp__puppeteer__puppeteer_navigate({ url: 'http://localhost:3000' });
mcp__puppeteer__puppeteer_screenshot({ name: 'initial-state', fullPage: true });
// Interact with forms
mcp__puppeteer__puppeteer_fill({ selector: 'input[name="email"]', value: '[email protected]' });
mcp__puppeteer__puppeteer_click({ selector: 'button[type="submit"]' });
mcp__puppeteer__puppeteer_screenshot({ name: 'after-submit' });
// Evaluate page state
mcp__puppeteer__puppeteer_evaluate({
script:
'JSON.stringify({ title: document.title, errors: [...document.querySelectorAll(".error")].map(e => e.textContent) })',
});
Puppeteer MCP と Playwright Python の使い分け
| シナリオ | 使用するもの |
|---|---|
| エージェントのワークフロー内で手早くページを確認する | Puppeteer MCP |
| アサーションを含む複雑なテストスイート | Playwright Python |
| 証拠としてスクリーンショットを取得する | Puppeteer MCP |
| フォーム操作とナビゲーション | どちらでも可 |
| CI テストの自動化 | Playwright Python |
| エージェントに組み込まれたブラウザチェック | Puppeteer MCP |
メモリプロトコル(必須)
開始前:
.claude/context/memory/learnings.md を読みます。
次の項目を確認します:
- プロジェクト内の既存のテストスクリプトまたは Playwright の設定
- 過去のセッションで判明したページセレクター
- 以前に発見されたコンソールエラーまたは不安定なテストパターン
完了後:
- 発見したテストパターン ->
.claude/context/memory/learnings.md - テストの不安定さまたはブラウザーの問題 ->
.claude/context/memory/issues.md - テスト戦略に関する決定 ->
.claude/context/memory/decisions.md
中断されることを前提にしてください。メモリに記録されていないことは、起きていないものとして扱います。