testing-webapps について
Python Playwright スクリプトでローカル Web アプリをテストするスキルです。サーバー管理、ブラウザー操作、アクセシビリティとレスポンシブ表示の確認、ネットワーク状態のテスト、デバッグ方法を扱います。
主な機能
- 01Python Playwright によるブラウザーテスト
- 02単一または複数のローカルサーバー管理
- 03ページ検査とロケーター操作
- 04アクセシビリティとレスポンシブ表示の確認
- 05ネットワークとアプリ状態の再現
- 06トレースと Inspector によるデバッグ
活用シーン
- 01リリース前のローカル Web アプリのテスト
- 02フォームや操作部品の検証
- 03画面サイズ別のレイアウト確認
- 04エラー・空データ・オフライン状態の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ osmontero/ opencode-skills --skill testing-webappsインストール後、Claude Code で/testing-webappsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSE.txtexamples/console_logging.pyelement_discovery.pystatic_html_automation.pyscripts/with_server.py詳細
Web アプリケーションのテスト
ローカルの Web アプリケーションをテストするには、ネイティブの Python Playwright スクリプトを作成します。
前提条件
Python スクリプトを実行する前に、opencode の仮想環境を有効化してください。
source ~/.local/opencode-venv/bin/activate
その後、通常どおり python3 scripts/... を使用します。Playwright のブラウザーバイナリがまだインストールされていない場合:
playwright install chromium
利用可能なヘルパースクリプト:
scripts/with_server.py- サーバーのライフサイクルを管理します(複数のサーバーに対応)
必ず最初にスクリプトを --help 付きで実行し、使い方を確認してください。スクリプトの実行を試して、カスタマイズした解決策が絶対に必要だと分かるまでは、ソースを読まないでください。これらのスクリプトは非常に大きい場合があり、コンテキストウィンドウを圧迫するためです。コンテキストウィンドウに取り込むのではなく、ブラックボックスのスクリプトとして直接呼び出すことを想定しています。
判断ツリー:アプローチの選択
User task → Is it static HTML?
├─ Yes → Read HTML file directly to identify selectors
│ ├─ Success → Write Playwright script using selectors
│ └─ Fails/Incomplete → Treat as dynamic (below)
│
└─ No (dynamic webapp) → Is the server already running?
├─ No → Run: python scripts/with_server.py --help
│ Then use the helper + write simplified Playwright script
│
└─ Yes → Reconnaissance-then-action:
1. Navigate and wait for networkidle
2. Take screenshot or inspect DOM
3. Identify selectors from rendered state
4. Execute actions with discovered selectors
例:with_server.py の使用
サーバーを起動するには、まず --help を実行してから、ヘルパーを使用します。
単一のサーバー:
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
複数のサーバー(例:バックエンド+フロントエンド):
python scripts/with_server.py \
--server "cd backend && python server.py" --port 3000 \
--server "cd frontend && npm run dev" --port 5173 \
-- python your_automation.py
自動化スクリプトを作成する場合は、Playwright のロジックだけを含めます(サーバーは自動的に管理されます)。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
page = browser.new_page()
page.goto('http://localhost:5173') # Server already running and ready
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
# ... your automation logic
browser.close()
調査してから実行するパターン
-
レンダリングされた DOM を確認します:
page.screenshot(path='/tmp/inspect.png', full_page=True) content = page.content() page.locator('button').all() -
確認結果からセレクターを特定します
-
見つかったセレクターを使って操作を実行します
よくある落とし穴
❌ しないでください 動的なアプリで networkidle を待つ前に DOM を確認する
✅ してください 確認する前に page.wait_for_load_state('networkidle') を待つ
ベストプラクティス
- 同梱スクリプトはブラックボックスとして使用します - タスクを達成するために、
scripts/にあるスクリプトが役立つかどうかを検討してください。これらのスクリプトは、コンテキストウィンドウを圧迫することなく、一般的で複雑なワークフローを確実に処理します。使い方を確認するには--helpを使用し、その後直接呼び出します。 - 同期スクリプトでは
sync_playwright()を使用します - 完了したら必ずブラウザーを閉じます
- わかりやすいセレクターを使用します:
text=、role=、CSS セレクター、または ID - 適切な待機を追加します:
page.wait_for_selector()またはpage.wait_for_timeout()
適切な待機方法
初回読み込みには networkidle が適切なデフォルトですが、特定の処理が発生したことを確認する用途には適していません。実際に確認したい条件を待つようにしてください。
page.wait_for_selector("[data-testid=results]", state="visible")
page.wait_for_function("() => document.querySelectorAll('.row').length > 0")
page.get_by_role("button", name="Save").click()
expect(page.get_by_text("Saved")).to_be_visible() # auto-retries
条件の代わりに wait_for_timeout() を使ってはいけません。 固定時間の待機は、短すぎれば不安定になり、長すぎれば遅くなり、実際の競合状態を常に隠してしまいます。唯一の正当な用途は、スクリーンショットを撮る前に既知のアニメーション時間が経過するのを待つことです。
CSS よりもロールベースのロケーターを優先してください。スタイルの変更に強く、アクセシビリティの検証も兼ねられます。
page.get_by_role("button", name="Delete account")
page.get_by_label("Email address")
page.get_by_role("heading", level=1)
get_by_role でコントロールを見つけられない場合、それはたいていテストの問題ではなく、実際のアクセシビリティ上の欠陥です。building-accessible-interfaces を参照してください。
コンソールのクリーンさを検証する
エラーを出しながら正しくレンダリングされるページは、正常ではありません。エラーを記録して検証してください。
errors, warnings = [], []
page.on("console", lambda m: (errors if m.type == "error" else warnings).append(m.text)
if m.type in ("error", "warning") else None)
page.on("pageerror", lambda e: errors.append(f"uncaught: {e}"))
page.on("requestfailed", lambda r: errors.append(f"{r.method} {r.url} — {r.failure}"))
page.goto(URL); page.wait_for_load_state("networkidle")
assert not errors, errors
アクセシビリティの監査
実際に表示されているページに対して axe を実行します。これはアクセシビリティチェックの自動化された 3 つ目の要素です。building-accessible-interfaces のキーボード操作とスクリーンリーダーによる確認も引き続き必要です。
page.add_script_tag(url="https://cdn.jsdelivr.net/npm/axe-core@4/axe.min.js")
result = page.evaluate("async () => await axe.run()")
violations = [(v["id"], v["impact"], len(v["nodes"])) for v in result["violations"]]
assert not violations, violations
キーボード操作による移動 — フォーカススタイルの欠落や、操作できないコントロールを検出します。
seq = []
for _ in range(40):
page.keyboard.press("Tab")
seq.append(page.evaluate("""() => {
const el = document.activeElement;
if (!el || el === document.body) return null;
const s = getComputedStyle(el);
return {tag: el.tagName, name: el.ariaLabel || el.innerText?.slice(0,30),
outline: s.outlineStyle, ring: s.boxShadow !== 'none'};
}"""))
invisible = [s for s in seq if s and s["outline"] == "none" and not s["ring"]]
assert not invisible, f"focusable elements with no visible focus: {invisible}"
レスポンシブとビジュアルのチェック
for name, w, h in [("mobile", 390, 844), ("tablet", 768, 1024), ("desktop", 1440, 900)]:
page.set_viewport_size({"width": w, "height": h})
page.wait_for_timeout(200) # settle layout/animation
overflow = page.evaluate("""() => [...document.querySelectorAll('*')]
.filter(e => e.getBoundingClientRect().right > document.documentElement.clientWidth + 1)
.slice(0,5).map(e => e.tagName + '.' + e.className)""")
assert not overflow, f"{name}: horizontal overflow from {overflow}"
page.screenshot(path=f"/tmp/{name}.png", full_page=True)
その後、スクリーンショットを確認してください。 アサーションに合格しても、DOM がはみ出していないことしか分かりません。ページの見た目が適切かどうかは分かりません。ビジュアルを全面的に監査するには、reviewing-interface-quality スキルを使用してください。
再現性のあるスクリーンショット — アニメーションとキャレットを無効にして、実行のたびに同じ結果になるようにします。
page.screenshot(path="x.png", animations="disabled", caret="hide", full_page=True)
ネットワークの制御
エラー状態や空の状態をテストするには、それらの状態を強制的に発生させる必要があります。これにより、designing-user-experience の状態マトリックスをテストできます。
page.route("**/api/items", lambda r: r.fulfill(status=200, json=[])) # empty state
page.route("**/api/items", lambda r: r.fulfill(status=500)) # error state
page.route("**/api/items", lambda r: r.abort()) # network failure
page.context.set_offline(True) # offline state
# Slow response — verifies the loading state actually appears
page.route("**/api/**", lambda r: (page.wait_for_timeout(3000), r.continue_())[1])
制約のある条件をエミュレートします。
ctx = browser.new_context(color_scheme="dark", reduced_motion="reduce",
locale="ar-EG", viewport={"width": 390, "height": 844})
reduced_motion="reduce" は、prefers-reduced-motion ブロックが存在することを検証します。color_scheme="dark" は、ダークモードでのみ発生するコントラストの問題を検出します。
失敗するスクリプトのデバッグ
browser = p.chromium.launch(headless=False, slow_mo=400) # watch it run
page.pause() # opens Playwright Inspector
PWDEBUG=1 python3 your_script.py # step through
python3 -m playwright codegen localhost:5173 # record interactions into code
失敗が断続的に発生する場合は、トレースを記録します。
context.tracing.start(screenshots=True, snapshots=True)
# ...
context.tracing.stop(path="/tmp/trace.zip") # then: playwright show-trace /tmp/trace.zip
よくある落とし穴
| 症状 | 原因 | 対処方法 |
|---|---|---|
| 要素は見つかるのに、クリックしても何も起きない | オーバーレイがイベントを横取りしている | page.get_by_role(...).click() でイベントを横取りしている要素が報告されるので、先に閉じます |
| headed では成功するが、headless では失敗する | タイミング、またはフォント/レイアウトの違い | タイムアウトではなく条件を待ち、ビューポートを明示的に設定します |
strict mode violation | ロケーターが複数の要素に一致した | .first、ロール、またはより具体的な名前を使って絞り込みます |
| スクリーンショットが真っ白 | 描画前にキャプチャした | wait_for_load_state("networkidle") と、描画が落ち着くまでのタイムアウトを組み合わせます |
| 一度は動作するが、再実行すると失敗する | テストが状態を変更した後、リセットしていない | 実行ごとに新しい browser.new_context() を使います |
| スタイル変更後にセレクターが機能しなくなる | CSS クラスベースのロケーター | get_by_role / get_by_label / data-testid を使います |
参考ファイル
- examples/ - よくあるパターンの例:
element_discovery.py- ページ上のボタン、リンク、入力欄を検出するstatic_html_automation.py- ローカル HTML に file:// URL を使うconsole_logging.py- 自動化中にコンソールログを記録する
関連スキル
- reviewing-interface-quality — 描画された UI の視覚・UX・a11y に関する包括的な監査
- building-accessible-interfaces — axe の結果の意味と、その修正方法
- designing-user-experience — これらのネットワークモックで検証する状態マトリクス
- systematic-debugging — テストの失敗がテストの問題ではなく、実際のバグである場合