
testing-webapps
Use when testing local web applications using Playwright. Triggers include requests to verify frontend functionality, debug UI behavior, capture browser screenshots, view browser logs, or test any web application interactively. Write native Python Playwright scripts for all web application testing tasks.
- Installs
- 152
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/osmontero/opencode-skills --skill testing-webapps- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads testing-webapps automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Testing Web Applications
To test local web applications, write native Python Playwright scripts.
Prerequisites
Before running any Python scripts, activate the opencode virtual environment:
source ~/.local/opencode-venv/bin/activate
Then use python3 scripts/... normally. If Playwright browser binaries aren't installed yet:
playwright install chromium
Helper Scripts Available:
scripts/with_server.py- Manages server lifecycle (supports multiple servers)
Always run scripts with --help first to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.
Decision Tree: Choosing Your Approach
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
Example: Using with_server.py
To start a server, run --help first, then use the helper:
Single server:
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
Multiple servers (e.g., backend + frontend):
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
To create an automation script, include only Playwright logic (servers are managed automatically):
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()
Reconnaissance-Then-Action Pattern
-
Inspect rendered DOM:
page.screenshot(path='/tmp/inspect.png', full_page=True) content = page.content() page.locator('button').all() -
Identify selectors from inspection results
-
Execute actions using discovered selectors
Common Pitfall
❌ Don't inspect the DOM before waiting for networkidle on dynamic apps
✅ Do wait for page.wait_for_load_state('networkidle') before inspection
Best Practices
- Use bundled scripts as black boxes - To accomplish a task, consider whether one of the scripts available in
scripts/can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use--helpto see usage, then invoke directly. - Use
sync_playwright()for synchronous scripts - Always close the browser when done
- Use descriptive selectors:
text=,role=, CSS selectors, or IDs - Add appropriate waits:
page.wait_for_selector()orpage.wait_for_timeout()
Waiting Correctly
networkidle is the right default for first load, but it is the wrong tool for asserting that a specific thing happened. Prefer waiting on the condition you actually care about.
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
Never wait_for_timeout() as a substitute for a condition. A fixed sleep is either too short (flaky) or too long (slow), and it always hides the real race. The one legitimate use is waiting out a known animation duration before a screenshot.
Prefer role-based locators over CSS — they survive restyling and they double as an accessibility assertion:
page.get_by_role("button", name="Delete account")
page.get_by_label("Email address")
page.get_by_role("heading", level=1)
If get_by_role cannot find a control, that is usually a genuine accessibility defect, not a test problem. See building-accessible-interfaces.
Asserting Console Cleanliness
A page that renders correctly while throwing errors is broken. Capture and assert.
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
Accessibility Auditing
Run axe against the live page. This is the automated third of the accessibility check — the keyboard walk and screen-reader pass in building-accessible-interfaces are still required.
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
Keyboard traversal — catches missing focus styles and unreachable controls:
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}"
Responsive and Visual Checks
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)
Then read the screenshots. A passing assertion says the DOM is not overflowing; it says nothing about whether the page looks right. For a full visual audit use the reviewing-interface-quality skill.
Deterministic screenshots — disable animation and caret so repeat runs match:
page.screenshot(path="x.png", animations="disabled", caret="hide", full_page=True)
Controlling the Network
Testing error and empty states requires forcing them. This is how you exercise the state matrix from 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])
Emulate constrained conditions:
ctx = browser.new_context(color_scheme="dark", reduced_motion="reduce",
locale="ar-EG", viewport={"width": 390, "height": 844})
reduced_motion="reduce" verifies the prefers-reduced-motion block exists. color_scheme="dark" catches contrast that only fails in dark mode.
Debugging a Failing Script
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
Capture a trace when a failure is intermittent:
context.tracing.start(screenshots=True, snapshots=True)
# ...
context.tracing.stop(path="/tmp/trace.zip") # then: playwright show-trace /tmp/trace.zip
Common Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| Element found but click does nothing | Overlay intercepting the event | page.get_by_role(...).click() reports the interceptor — dismiss it first |
| Passes headed, fails headless | Timing, or a font/layout difference | Wait on conditions, not timeouts; set an explicit viewport |
strict mode violation | Locator matched multiple elements | Narrow with .first, a role, or a more specific name |
| Screenshot is blank | Captured before paint | wait_for_load_state("networkidle") plus a settle timeout |
| Works once, fails on rerun | Test mutated state and did not reset | Use a fresh browser.new_context() per run |
| Selector breaks after a restyle | CSS-class-based locator | Use get_by_role / get_by_label / data-testid |
Reference Files
- examples/ - Examples showing common patterns:
element_discovery.py- Discovering buttons, links, and inputs on a pagestatic_html_automation.py- Using file:// URLs for local HTMLconsole_logging.py- Capturing console logs during automation
Related Skills
- reviewing-interface-quality — full visual/UX/a11y audit of a rendered UI
- building-accessible-interfaces — what the axe results mean and how to fix them
- designing-user-experience — the state matrix these network mocks exist to exercise
- systematic-debugging — when a test failure is a real bug, not a test bug
FAQ
What does the testing-webapps skill do?
Use when testing local web applications using Playwright. Triggers include requests to verify frontend functionality, debug UI behavior, capture browser screenshots, view browser logs, or test any web application interactively. Write native Python Playwright scripts for all web application testing tasks.
How do I install testing-webapps?
Run npx skills add https://github.com/osmontero/opencode-skills --skill testing-webapps in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/testing-webapps/ (personal) or .claude/skills/testing-webapps/ (project).
Which AI agents can use testing-webapps?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is testing-webapps safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains testing-webapps?
It is published by osmontero in osmontero/opencode-skills. It has 152 recorded installs.
More from osmontero/opencode-skills
designing-frontend-interfaces
osmontero/opencode-skills
Use when building or restyling any web interface — landing pages, dashboards, marketing sites, web apps, React/Vue components, HTML/CSS layouts, artifacts, posters — or when asked to make a UI look better, more polished, more distinctive, less generic, or less "AI-generated". Also use when a design brief, art direction, visual identity, type scale, or color system is needed before implementation.
designing-user-experience
osmontero/opencode-skills
Open the skill to read its SKILL.md instructions.
reviewing-interface-quality
osmontero/opencode-skills
Open the skill to read its SKILL.md instructions.
building-accessible-interfaces
osmontero/opencode-skills
Open the skill to read its SKILL.md instructions.
systematic-debugging
osmontero/opencode-skills
Open the skill to read its SKILL.md instructions.
verifying-before-completion
osmontero/opencode-skills
Open the skill to read its SKILL.md instructions.
Popular testing & qa skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
grilling
mattpocock/skills
Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
migrate-to-shoehorn
mattpocock/skills
Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.
run-train
lllllllama/rigorpilot-skills
Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.
test-driven-development
obra/superpowers
Use when implementing any feature or bugfix, before writing implementation code
finishing-a-development-branch
obra/superpowers
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work