スキル一覧に戻る
WarrenZhu050413

testing-webapps

by WarrenZhu050413

A personal plugin marketplace for Claude Code

5🍴 0📅 2025年12月3日
GitHubで見るManusで実行

SKILL.md


name: Testing Webapps description: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. license: Complete terms in LICENSE.txt

Web Application Testing

Write native Python Playwright scripts to test local webapps.

Helper: scripts/with_server.py manages server lifecycle. Run with --help first.

Approach

Static HTML: Read file → identify selectors → write script

Dynamic webapp:

  • Server not running: Use with_server.py
  • Server running: Navigate → wait networkidle → inspect → act

Server Management

# Single server
python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py

# Multiple servers
python scripts/with_server.py \
  --server "cd backend && python server.py" --port 3000 \
  --server "cd frontend && npm run dev" --port 5173 \
  -- python automation.py

Script Patterns

Automation:

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') # CRITICAL for dynamic apps
    # automation logic here
    browser.close()

Reconnaissance:

page.screenshot(path='/tmp/inspect.png', full_page=True)
page.content() # Get HTML
page.locator('button').all() # Find elements

Problem: Headed mode steals window focus on macOS, disrupting workflow.

Solution: Run headless with trace recording:

import os
headless = os.getenv('HEADED') != '1'  # Default headless, override with HEADED=1

browser = p.chromium.launch(headless=headless)
context = browser.new_context()
context.tracing.start(screenshots=True, snapshots=True, sources=True)
page = context.new_page()

# ... test code ...

# Save trace on completion
context.tracing.stop(path="/tmp/trace_testname_SUCCESS.zip")

Debug traces:

playwright show-trace /tmp/trace_testname_SUCCESS.zip

Why better than headed: Step through at your own pace, inspect DOM at any point, see network requests, no window disruption.

Selector Best Practices

Emoji-safe text matching:

# ❌ Fails with emoji
page.locator('text="Mission Control"')

# ✅ Works with "Mission Control 🚀"
page.locator('text=/Mission Control/')

Button-specific selectors:

# ❌ Too generic, matches any text
page.locator('text="Create World"')

# ✅ Specific to buttons
page.locator('button:has-text("Create World")')

Form field specificity:

# ❌ Fragile, matches wrong element
page.locator('textarea').first

# ✅ Specific placeholder
page.locator('textarea[placeholder*="description"]')
page.locator('input[type="text"]').nth(2)  # If index matters

Wait for both visible AND enabled:

button = page.locator('button:has-text("Submit")')
expect(button).to_be_visible(timeout=5000)
expect(button).to_be_enabled(timeout=5000)  # Critical for form buttons!
button.click()

Form Testing Pattern

Rule: Fill → Wait for enabled → Click

Wrong order (causes timeouts):

# ❌ Button is disabled, causes "element not enabled" timeout
button.click()
textarea.fill("content")

Correct order:

# ✅ Button becomes enabled after fill
textarea = page.locator('textarea[placeholder="description"]')
expect(textarea).to_be_visible(timeout=5000)
textarea.fill("content")

button = page.locator('button:has-text("Submit")')
expect(button).to_be_enabled(timeout=5000)  # Now enabled
button.click()

Why: Most forms disable submit buttons until validation passes. Always fill first.

Test Setup: Database State

Pattern for clean test runs:

# Reset database before tests
rm -f backend/database.db
cd backend && python -c "from src.database import init_db; import asyncio; asyncio.run(init_db())"

In test runner:

from pathlib import Path
import subprocess

def setup_clean_database():
    """Reset database to clean state."""
    db_path = Path("backend/database.db")
    if db_path.exists():
        db_path.unlink()
    subprocess.run([
        "python", "-c",
        "from src.database import init_db; import asyncio; asyncio.run(init_db())"
    ], cwd="backend")

Why: Prevents UUID conflicts, UNIQUE constraint violations, and flaky tests from stale data.

Debugging Triad: Screenshot + Trace + Console

Always capture all three:

# Setup
context.tracing.start(screenshots=True, snapshots=True, sources=True)
logs = []
page.on("console", lambda msg: logs.append(f"[{msg.type}] {msg.text}"))

# During test - take screenshots at key steps
page.screenshot(path='/tmp/test_step1.png')

# On failure
context.tracing.stop(path="/tmp/trace_FAILED.zip")
print(f"Console logs (last 20):")
for log in logs[-20:]:
    print(f"  {log}")

Why each matters:

  • Screenshots: Visual state at failure point
  • Trace: Full interaction timeline, DOM snapshots, network activity
  • Console: React errors, API failures, JavaScript warnings

Debugging workflow:

  1. Check console logs for errors first (fastest)
  2. View screenshot to understand visual state
  3. Open trace with playwright show-trace to step through and inspect DOM

Troubleshooting

"Fix doesn't work" - Tests still fail after code change

Symptom: Fixed a bug but tests still fail with same error.

Causes & Solutions:

  1. Frontend hot reload hasn't applied changes

    • Verify file: grep "new code" file.jsx
    • Check dev server console for reload confirmation
    • Hard restart: Kill dev server, npm run dev
  2. Browser cache

    • Use page.goto(..., wait_until='networkidle')
    • Or clear: context.clear_cookies()

Generic selectors match wrong elements

Symptom: textarea.first or button.last fails unexpectedly or matches wrong element.

Cause: DOM structure changed or multiple matching elements exist.

Solution: Use attribute selectors:

# ❌ Fragile - depends on DOM order
page.locator('textarea').first

# ✅ Robust - matches specific element
page.locator('textarea[placeholder="World description"]')
page.locator('button:has-text("Create")').first  # If multiple, be specific

"Element not enabled" timeouts

Symptom: page.click() times out with "element is not enabled".

Cause: Trying to click button before form validation passes.

Solution: Fill form first, then wait for enabled:

# Fill all required fields first
input1.fill("value1")
input2.fill("value2")

# Then wait for button to enable
button = page.locator('button:has-text("Submit")')
expect(button).to_be_enabled(timeout=5000)
button.click()

Critical Rules

  • Always page.wait_for_load_state('networkidle') before DOM inspection
  • Default to headless with trace recording for debugging without window disruption
  • Fill forms before clicking submit buttons (they're usually disabled)
  • Use specific selectors with attributes, not generic .first/.last
  • Capture triad: screenshots + trace + console logs for debugging
  • Close browser when done
  • See examples/ for more patterns

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です