webapp-testing
Web Application Testing with Playwright
Test the website using Playwright browser automation. Verify functionality, debug UI issues, capture screenshots, and inspect browser state.
When This Skill Activates
- Testing website functionality
- Debugging UI behavior
- Capturing screenshots for documentation
- Verifying responsive design
- Checking dark/light mode
- Testing user flows (navigation, forms)
- Investigating browser console errors
Project Context
Website stack:
- React 19 + Vite 6.2
- Tailwind CSS v4
- Dev server:
npm run dev(typically port 5173) - Build:
npm run build→dist/
Key pages to test:
- Landing page (
/) - Charity list
- Individual charity detail pages
- Methodology page
- About page
Theme states:
- Light mode / Dark mode toggle
- Brand variants (amal / third-bucket)
Testing Workflow
1. Reconnaissance First
Before automating, understand the page:
# Take screenshot to see current state
page.screenshot(path="screenshot.png")
# Get page title
print(page.title())
# Wait for dynamic content
page.wait_for_load_state('networkidle')
# Inspect DOM structure
print(page.content())
2. Decision Tree
Is the dev server running?
- Yes → Connect directly to
http://localhost:5173 - No → Start it first, then connect
Is content static or dynamic?
- Static HTML → Read files directly for selectors
- Dynamic (React) → Wait for
networkidlebefore inspecting
What are you testing?
- Visual appearance → Screenshot
- Functionality → Automated interactions
- Errors → Console log capture
Playwright Patterns
Basic Script Structure
from playwright.sync_api import sync_playwright
def test_page():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
try:
page.goto("http://localhost:5173")
page.wait_for_load_state('networkidle')
# Your test logic here
finally:
browser.close()
if __name__ == "__main__":
test_page()
Waiting for Dynamic Content
# Wait for network to settle (React hydration)
page.wait_for_load_state('networkidle')
# Wait for specific element
page.wait_for_selector('[data-testid="charity-card"]')
# Wait for text to appear
page.wait_for_selector('text=Loading...', state='hidden')
Element Selection
Prefer readable selectors:
# By role (best for accessibility)
page.get_by_role("button", name="Toggle theme")
# By text content
page.get_by_text("View Details")
# By test ID (if available)
page.get_by_test_id("charity-card")
# By CSS (fallback)
page.locator(".charity-card")
# By combined selectors
page.locator("article").filter(has_text="Islamic Relief")
Common Actions
# Click
page.get_by_role("button", name="Submit").click()
# Type
page.get_by_label("Email").fill("test@example.com")
# Select dropdown
page.get_by_role("combobox").select_option("option-value")
# Hover
page.get_by_text("Menu").hover()
# Screenshot
page.screenshot(path="test-result.png", full_page=True)
Assertions
from playwright.sync_api import expect
# Element is visible
expect(page.get_by_text("Welcome")).to_be_visible()
# Element has text
expect(page.locator("h1")).to_have_text("Charity Evaluations")
# Element count
expect(page.locator(".charity-card")).to_have_count(10)
# URL
expect(page).to_have_url("http://localhost:5173/charity/123")
# Title
expect(page).to_have_title("Good Measure Giving")
Test Scenarios
Theme Toggle
def test_theme_toggle():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("http://localhost:5173")
page.wait_for_load_state('networkidle')
# Check initial state
body = page.locator("body")
initial_bg = body.evaluate("el => getComputedStyle(el).backgroundColor")
# Toggle theme
page.get_by_role("button", name="Toggle theme").click()
# Verify change
new_bg = body.evaluate("el => getComputedStyle(el).backgroundColor")
assert initial_bg != new_bg, "Theme should change"
browser.close()
Responsive Design
def test_mobile_view():
with sync_playwright() as p:
browser = p.chromium.launch()
# Mobile viewport
page = browser.new_page(viewport={"width": 375, "height": 667})
page.goto("http://localhost:5173")
page.wait_for_load_state('networkidle')
# Check mobile menu is present
expect(page.get_by_role("button", name="Menu")).to_be_visible()
# Desktop navbar should be hidden
expect(page.locator("nav.desktop-nav")).to_be_hidden()
page.screenshot(path="mobile-view.png")
browser.close()
Charity Card Navigation
def test_charity_navigation():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("http://localhost:5173")
page.wait_for_load_state('networkidle')
# Find first charity card and click
first_card = page.locator(".charity-card").first
charity_name = first_card.locator("h3").text_content()
first_card.click()
# Should navigate to detail page
page.wait_for_load_state('networkidle')
expect(page.locator("h1")).to_contain_text(charity_name)
browser.close()
Console Error Capture
def test_no_console_errors():
errors = []
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# Capture console errors
page.on("console", lambda msg: errors.append(msg.text) if msg.type == "error" else None)
page.goto("http://localhost:5173")
page.wait_for_load_state('networkidle')
# Navigate through pages
page.get_by_text("Methodology").click()
page.wait_for_load_state('networkidle')
browser.close()
assert len(errors) == 0, f"Console errors found: {errors}"
Debugging Tips
Visual Debugging
# Run with browser visible
browser = p.chromium.launch(headless=False, slow_mo=500)
# Pause for manual inspection
page.pause()
# Screenshot on failure
try:
# test code
except Exception as e:
page.screenshot(path="failure.png")
raise
Network Inspection
# Log all requests
page.on("request", lambda req: print(f">> {req.method} {req.url}"))
page.on("response", lambda res: print(f"<< {res.status} {res.url}"))
# Wait for specific API call
with page.expect_response("**/api/charities") as response_info:
page.goto("http://localhost:5173")
response = response_info.value
print(response.json())
Trace Recording
# Record trace for debugging
context = browser.new_context()
context.tracing.start(screenshots=True, snapshots=True)
page = context.new_page()
# ... run tests ...
context.tracing.stop(path="trace.zip")
# View with: npx playwright show-trace trace.zip
Server Management
Start Dev Server for Testing
# Terminal 1: Start server
cd website && npm run dev
# Terminal 2: Run tests
python test_script.py
Programmatic Server Start
import subprocess
import time
def with_server(test_func):
"""Decorator to start/stop dev server"""
def wrapper():
# Start server
server = subprocess.Popen(
["npm", "run", "dev"],
cwd="website",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
time.sleep(5) # Wait for server to start
try:
test_func()
finally:
server.terminate()
server.wait()
return wrapper
@with_server
def test_homepage():
# Test runs with server available
pass
Quick Reference
Viewport Sizes
| Device | Width | Height |
|---|---|---|
| Mobile | 375 | 667 |
| Tablet | 768 | 1024 |
| Desktop | 1280 | 720 |
| Large | 1920 | 1080 |
Common Waits
page.wait_for_load_state('networkidle') # All requests done
page.wait_for_load_state('domcontentloaded') # DOM ready
page.wait_for_selector('selector') # Element exists
page.wait_for_timeout(1000) # Hard wait (avoid if possible)
Screenshot Options
page.screenshot(
path="screenshot.png",
full_page=True, # Entire scrollable page
# OR
clip={"x": 0, "y": 0, "width": 500, "height": 500} # Specific area
)
Test Checklist
Before shipping:
- Homepage loads without console errors
- Theme toggle works (light ↔ dark)
- Mobile responsive (375px viewport)
- Tablet responsive (768px viewport)
- Charity cards render correctly
- Charity detail pages load
- Navigation works (all links)
- Images load (no broken images)
- No accessibility errors (axe-core)
More from uabbasi/good-measure-giving
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
11form990-expert
Deep expertise on IRS Form 990 structure, fields, and nonprofit financial analysis. Activates when working on Form 990 parsing, ProPublica API integration, or charity financial evaluation code.
1zakat-fiqh
Islamic jurisprudence expertise on zakat - the 8 categories of recipients (asnaf), scholarly interpretations across madhabs, and how to assess charity alignment with zakat eligibility. Activates when working on zakat classification, wallet tags, or donor guidance.
1llm-prompting
Expert guidance on LLM prompting patterns used in this project. Covers versioned prompts, schema enforcement, category calibration, multi-provider fallback, and narrative generation. Activates when working on prompts, LLM integration, or evaluation logic.
1data-pipeline
Run the charity evaluation pipeline - extraction, 4-stage V2 workflow, Supabase patterns, versioning. Use when working on collectors, scrapers, pipeline code, database queries, or debugging data flow.
1analytics
Unified analytics (Cloudflare + Firestore + GA4). Pull traffic data, user activity, feature adoption, and giving metrics. Use when checking site performance, user behavior, or feature usage.
1