webapp-testing
Skill1 starsChanged 8 months agoMITView on GitHub
Toolkit for interacting with and testing web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
- Reads credentials
196 other projects use a version of this, 168 of them edited.
Adopted by
Most recently taken up by
- rvdbreemen/OTGW-firmwareedited, first seen Nov 8, 2026
- BrightGold70/skillsedited, first seen Nov 8, 2026
- DnsQueen/orcinus-skillsedited, first seen Nov 8, 2026
- Liang-MH/skillsedited, first seen Nov 8, 2026
- smart-coder-labs/nexus-mindedited, first seen Nov 8, 2026
- codewhale-hq/codewhale-plugin-marketplacetheir own version, first seen Nov 8, 2026
Used with
Projects with it also use academy-guide, skill-share and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
AnthropicSkillacademy-guide
Stop and check this skill before finishing any reply to a question about how to use Claude or a Claude product — it recommends matching courses, tutorials, and use cases from Claude Academy (academy.claude.com), Anthropic's learning hub. Trigger on: "how do I", "how can I", "getting started with", "what can Claude do", "teach me", "learn to use"; questions about artifacts, projects, skills, plugins, connectors, MCP; requests about rolling Claude out to a team, class, or organization; and any ask for training materials, onboarding content, or learning resources. Use it when the user is learning how to use a feature or product — not when they are mid-task and just want the task done. This skill composes with other skills: after consulting product documentation to answer how a Claude feature works, also check here for a matching course or tutorial — a docs-grounded answer and an Academy recommendation belong together. Only recommend on a strong match; never invent Academy content.
Also in 8 projects that use this
ArogyaReddySkillskill-share
A skill that creates new Claude skills and automatically shares them on Slack using Rube for seamless team collaboration and skill discovery.
Also in 23 projects that use this
SKB3002Skillintelligent-routing
Automatic agent selection and intelligent task routing. Analyzes user requests and automatically selects the best specialist agent(s) without requiring explicit user mentions.
Also in 5 projects that use this
ArogyaReddySkillraffle-winner-picker
Picks random winners from lists, spreadsheets, or Google Sheets for giveaways, raffles, and contests. Ensures fair, unbiased selection with transparency.
Also in 26 projects that use this
zephyrwang6Skilltopic-collector
AI热点采集工具。从Twitter/X、Product Hunt、Reddit、Hacker News、博客等采集AI相关热点内容。当用户说"开始今日选题"、"采集热点"、"看看今天有什么新闻"、"今日AI热点"时触发。聚焦领域:Vibe Coding、Claude Skill、AI知识管理、AI模型更新、AI新产品、海外热点。
Also in 4 projects that use this
zephyrwang6Skilldeepl
Übersetzt Texte, Dokumente und XLIFF-Dateien via DeepL API. Verwende für Übersetzungen mit /deepl translate, /deepl file oder /deepl xliff.
Also in 4 projects that use this
- +4 more from zephyrwang6
- +1 more from ArogyaReddy
Other versions196 projects use a version of this.
Word for word · 0
None found.
Edited · 168
- anthropics/skills★ 189k85% same
- ComposioHQ/awesome-claude-skills★ 75k85% same
- ComposioHQ/awesome-codex-skills★ 17k85% same
- composio-community/awesome-codex-skills★ 16k85% same
- Galaxy-Dawn/claude-scholar★ 5.9k83% same
- microsoft/FluidFramework★ 4.9k16% same
- Prat011/awesome-llm-skills★ 1.7k85% same
- ChenglinPoly/infiAgent★ 1.2k85% same
- polyuiislab/infiAgent★ 1.2k85% same
- warpdotdev/oz-skills★ 82580% same
158 more not listed
Wrote their own version · 28
- github/awesome-copilot★ 41ksame name
- vudovn/ag-kit★ 8.2ksame name
- vudovn/antigravity-kit★ 8.2ksame name
- Prompthon-IO/agent-systems-handbook★ 315same name
- RedWoodOG/Hermes-Desktop★ 177same name
- xenitV1/Antigravity-Workflows★ 130same name
- AutumnsGrove/ClaudeSkills★ 30same name
- sabahattink/antigravity-fullstack-hq★ 30same name
- VoDaiLocz/kilo-kit-mcp★ 28same name
- alebgl77/claude-inc★ 21same name
18 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository51 other files it gives its agents.
- algorithmic-artSkill
- audit-componentsSkill
- canvas-designSkill
- cascade-orchestrationSkill
- consultSkill
- data-reconciliationSkill
- debug-collaborateSkill
- deep-root-causeSkill
- deploy-allSkill
- design-reverse-engineerSkill
- design-showcaseSkill
- docxSkill
- env-auditSkill
- evolveSkill
- frontend-designSkill
- full-stack-buildSkill
- full-stack-traceSkill
- gh-repo-reconSkill
- grill-meSkill
- handoffSkill
- html-slidesSkill
- insightSkill
- internal-commsSkill
- isolation-testSkill
- mcp-builderSkill
- model-selectionSkill
- pdfSkill
- persistent-issueSkill
- pre-merge-reviewSkill
- premise-auditSkill
- propagate-featureSkill
- regression-bisectSkill
- research-gateSkill
- review-implSkill
- schema-checkSkill
- session-notesSkill
- skill-auditSkill
- skill-creatorSkill
- slack-gif-creatorSkill
- start-daySkill
- temporal-forensicsSkill
- theme-factorySkill
- throwaway-scriptSkill
- triage-ideasSkill
- verify-completeSkill
- web-artifacts-builderSkill
- wisdomSkill
- worker-buildSkill
- worktree-guardSkill
- write-planSkill
- xlsxSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
22 rules. 1 is common, 8 are distinctive, and 13 are very specific to this project.
1
Common
Most of the 190 edited versions we read keep these.
8
Distinctive
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 11
Chrome cleanup safety: Never broad-scope-kill Chrome processes.
- Very specific: line 23
If list_pages also fails, an orphaned Chrome holds the lock.
- Very specific: line 32
Do NOT use new_page with isolatedContext for auth-gated pages — an isolated context carries no auth cookie and bounces to the login page.
- Very specific: line 54
Write a temp script at /tmp/verify-<feature>.mjs:
- Distinctive: line 67
Run the script with credentials injected from your secrets manager.
- Very specific: line 69
Read the screenshot with the Read tool — visually verify.
- Distinctive: line 150
NOTE: If you previously referenced a reusable helper script for this flow, write the cookie-exchange inline or store the snippet in a temp …
- Very specific: line 211
Bootstrap admin short-circuit gotcha: If the app has a hardcoded list of admin emails in the frontend (e.g.
- Distinctive: line 249
When hiding or renaming a UI label (e.g.
- Very specific: line 253
Check any default-fallback code paths that humanize a route ID (e.g.
- Very specific: line 261
Headed mode: Add headless: false when user wants to watch
- Very specific: line 263
Screenshots directory: Use a consistent path like screenshots/
- Distinctive: line 312
Write a throwaway script that imports the template function/module directly (no HTTP round-trip, no server needed).
- Distinctive: line 313
Call the template function with representative and edge-case data to get the HTML string.
- Distinctive: line 316
Read the screenshot, compare against the goal, iterate — edit the template, rerun, re-screenshot.
- Distinctive: line 317
Delete the throwaway script when done, or keep it if it should become a repeatable check.
- Very specific: line 324
Do wait for page.wait_for_load_state('networkidle') before inspection
- Very specific: line 337
Use bundled scripts as black boxes: - Use --help to see usage, then invoke directly
- Very specific: line 338
Use sync_playwright() for synchronous scripts
- Common: line 339
Always close the browser when done
- Very specific: line 340
Use descriptive selectors: text=, role=, CSS selectors, or IDs
- Very specific: line 341
Add appropriate waits: page.wait_for_selector() or page.wait_for_timeout()
“Common” is measured: kept by at least 6 in 10 of the 190 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Web Application Testing
Browser Session Safety (MANDATORY)
Chrome cleanup safety: Never broad-scope-kill Chrome processes. Only close pages you opened via a specific page ID. "Browser already running" errors from chrome-devtools MCP mean the MCP profile is in use — reuse it, open an isolated context, or ask the user to close that window.
chrome-devtools MCP — "browser already running" recovery
The chrome-devtools MCP server attaches to a persistent profile so auth SSO stays logged in. It does not close that browser at session end, so orphaned chrome.exe processes accumulate and keep the profile locked. A fresh session's first MCP call then fails with "browser already running ... Use --isolated", and list_pages / navigate_page may all fail until the server establishes its own connection — this is the recurring intermittent failure.
Prefer the Playwright temp-script for deployed-page verification. The headless loop below (throwaway context + auth cookie injection) has none of these failure modes — no persistent profile, no lock collision, no auth bounce. Use the chrome-devtools MCP only for interactive single-page poking, not routine post-deploy checks.
Recovery when the MCP IS needed and the lock is stuck:
- Try
list_pages→select_pagefirst — if the running browser belongs to this MCP server you can simply reuse it (no kill needed). - If
list_pagesalso fails, an orphaned Chrome holds the lock. Kill ONLY the MCP-profile Chrome processes (never broad-kill):
Then retryGet-CimInstance Win32_Process -Filter "Name='chrome.exe'" | Where-Object { $_.CommandLine -like '*chrome-devtools-mcp*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }navigate_page. This filter matches only chrome-devtools-mcp processes — the user's real Chrome is untouched. - Do NOT use
new_pagewithisolatedContextfor auth-gated pages — an isolated context carries no auth cookie and bounces to the login page. Isolated contexts only work for public URLs. - A secondary MCP server using a separate profile is not auth-logged-in by default — only a real fallback after a one-time manual login in that profile.
Permanent prevention (optional): a
SessionEndhook that runs the kill command above would stop orphans from accumulating between sessions. Not yet wired — ask the user before adding it tosettings.json.
To test web applications, write native Playwright scripts (Node.js or Python, depending on your environment).
Helper Scripts Available:
scripts/cf-session.py- Legacy persistent sessions (cookie-based, requires manual login)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 absolutely 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.
Temp Playwright Script Iteration Loop (canonical)
For any visual UI verification, the reliable loop is:
- Write a temp script at
/tmp/verify-<feature>.mjs:import { chromium } from 'playwright'; const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext(); // CF Access service-token cookie injection (required for CF Access-protected apps) await ctx.addCookies([{ name: 'CF_Authorization', value: process.env.CF_ACCESS_TOKEN, domain: '{{your-app}}.example.com', path: '/' }]); const page = await ctx.newPage(); await page.goto('https://{{your-app}}.example.com/dashboard'); await page.screenshot({ path: '/tmp/screenshots/verify-1.png', fullPage: true }); await browser.close(); - Run the script with credentials injected from your secrets manager.
- Read the screenshot with the
Readtool — visually verify. - Edit the script (add selectors, clicks, state capture) — re-run — re-read screenshot.
This beats the MCP chrome-devtools flow for scripted multi-step verification because the temp script is version-controllable (paste into the session notes) and re-runnable in the background. CF Access cookie injection (exchange service-token headers for CF_Authorization cookie via ctx.addCookies()) is required for CF-Access-protected apps — this beats the MCP because cookies are injected pre-navigation.
For CSS/layout work: Read screenshot → Grep CSS class usage → Edit → npm run build → re-screenshot → repeat (2–3 rounds typical). The same Write→Screenshot→Read→iterate cycle applies to email-HTML composition (generate HTML draft → screenshot via Playwright → iterate on the template).
See references/iteration-loops.md for the full temp-script template, CF Access cookie injection detail, CSS debug loop, email-HTML composition loop, and minified deployed-bundle grep technique.
Decision Tree: Choosing Your Approach
User task → Is the target behind CF Access (dev/prod sites)?
├─ Yes → Use CF Access Service Token (PREFERRED — no manual login needed)
│ 1. Set extraHTTPHeaders with CF_ACCESS_CLIENT_ID + CF_ACCESS_CLIENT_SECRET
│ 2. Navigate freely — all requests auto-authenticate
│ 3. See "CF Access Bypass" section below
│
└─ No → Is it behind another auth (SSO, API key, etc.)?
├─ Yes → Use service token / API key headers (PREFERRED)
│ See "Service Token Authentication" below
│
└─ No → 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 selectorsCF Access Bypass — Choose Your Pattern
Two working patterns. Pick based on what you're doing:
| Pattern | Best for | Why |
CF_Authorization cookie injection (simplest) | Visual verification, multi-page flows, SPAs | Cookie fires on every request including SPA fetch() — no route intercepts needed |
extraHTTPHeaders + route intercepts | Full admin impersonation (identity, groups, admin flag) | Lets you mock identity + auth endpoints for admin-only UI |
Pattern A — Cookie Injection (PREFERRED for visual checks)
Under the hood: call the target URL with CF-Access-Client-Id + CF-Access-Client-Secret
headers, extract the CF_Authorization JWT from the Set-Cookie response header, then
inject it into a Playwright context before the first navigation. Subsequent fetch()
calls in the SPA automatically include the cookie.
const { chromium } = require('playwright')
// Exchange service-token headers for CF_Authorization cookie
const CF_ID = process.env.CF_ACCESS_CLIENT_ID
const CF_SECRET = process.env.CF_ACCESS_CLIENT_SECRET
const res = await fetch('https://{{your-app}}.example.com/', {
headers: { 'CF-Access-Client-Id': CF_ID, 'CF-Access-Client-Secret': CF_SECRET },
redirect: 'manual',
})
const setCookie = res.headers.get('set-cookie') || ''
const cfAuth = /CF_Authorization=([^;]+)/.exec(setCookie)?.[1]
const browser = await chromium.launch({ headless: true })
const context = await browser.newContext()
await context.addCookies([{ name: 'CF_Authorization', value: cfAuth, domain: '{{your-app}}.example.com', path: '/' }])
const page = await context.newPage()
await page.goto('https://{{your-app}}.example.com/admin')Why this beats chrome-devtools MCP: The MCP can't set cookies before the first
request, so CF Access redirects to cloudflareaccess.com before any JS runs. Playwright's
context.addCookies() is pre-navigation, so the browser sends CF_Authorization on the
first GET.
NOTE: If you previously referenced a reusable helper script for this flow, write the cookie-exchange inline or store the snippet in a temp file for one-off runs.
Pattern B — extraHTTPHeaders + Route Intercepts
For all CF Access-protected sites, use the service token. This requires no manual login — set headers on the browser context and all requests authenticate automatically.
Server-side ALLOW_SERVICE_TOKEN_AUTH: On dev projects, an env var like ALLOW_SERVICE_TOKEN_AUTH=true lets the auth middleware trust a CF-Access-Authenticated-User-Email header without a real JWT. Set only on dev — never on production.
Credentials: Load from your secrets manager (environment variables, encrypted .env, etc.). Never hardcode credentials in scripts or skills.
Credential Loading
# Shell — load from environment
CF_ID=$CF_ACCESS_CLIENT_ID
CF_SECRET=$CF_ACCESS_CLIENT_SECRET// Node.js — read from process.env (set before running)
const CF_HEADERS = {
'CF-Access-Client-Id': process.env.CF_ACCESS_CLIENT_ID,
'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET,
'CF-Access-Authenticated-User-Email': 'test-user@example.com',
};Basic Usage (Node.js CJS)
const { chromium } = require('playwright');
const CF_HEADERS = {
'CF-Access-Client-Id': process.env.CF_ACCESS_CLIENT_ID,
'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET,
'CF-Access-Authenticated-User-Email': 'test-user@example.com',
};
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
extraHTTPHeaders: CF_HEADERS,
viewport: { width: 1920, height: 1080 },
});
const page = await context.newPage();
await page.goto('https://{{your-app}}.example.com/', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
await page.screenshot({ path: 'screenshots/app.png' });
await browser.close();Full Admin Access (Route Intercepts + Header Injection)
Service token extraHTTPHeaders only apply to page navigation, NOT to SPA fetch() calls. To get full admin access with live API data, you need two layers:
- Specific intercepts (register FIRST) — mock identity, group membership, admin status, tab visibility
- Catch-all header injection (register LAST) — inject service token headers into ALL
fetch()calls
CRITICAL: Playwright routes match LIFO (last registered first). The catch-all must use route.fallback() for URLs handled by specific intercepts, otherwise it steals the request.
Finding the real isAdmin source: Many SPAs have a consolidated auth-init endpoint (e.g. /api/auth-init) that returns role flags in one payload. Intercept this rather than individual check endpoints to reliably flip admin status for RBAC UI tests.
Bootstrap admin short-circuit gotcha: If the app has a hardcoded list of admin emails in the frontend (e.g. ADMIN_EMAILS = ['superuser@example.com']), the app may grant admin even when your /api/auth-init intercept returns isAdmin: false. Use a non-bootstrap test email (e.g. non-admin-test@example.com) to test the non-admin UI path.
const TEST_EMAIL = asAdmin ? 'admin@example.com' : 'non-admin-test@example.com';
// --- Register SPECIFIC intercepts FIRST ---
// Real source of isAdmin / role flags (adapt endpoint to your app).
await page.route('**/api/auth-init', route => {
route.fulfill({ status: 200, contentType: 'application/json',
body: JSON.stringify({ email: TEST_EMAIL, isAdmin: asAdmin, roles: [] }) });
});
await page.route('**/cdn-cgi/access/get-identity', route => {
route.fulfill({ status: 200, contentType: 'application/json',
body: JSON.stringify({ email: TEST_EMAIL, name: asAdmin ? 'Admin User' : 'Non Admin' }) });
});
await page.route('**/api/check-group*', route => {
route.fulfill({ status: 200, contentType: 'application/json',
body: JSON.stringify({ isMember: asAdmin }) });
});
// --- Register CATCH-ALL LAST ---
await page.route('**/api/**', async (route) => {
const url = route.request().url();
if (url.includes('/api/auth-init') || url.includes('/api/check-group')) {
await route.fallback(); // Delegate to specific intercept above
return;
}
const headers = { ...route.request().headers(),
'CF-Access-Client-Id': CF_HEADERS['CF-Access-Client-Id'],
'CF-Access-Client-Secret': CF_HEADERS['CF-Access-Client-Secret'],
'CF-Access-Authenticated-User-Email': TEST_EMAIL,
};
await route.continue({ headers });
});UI Label Audit — Visual Verification (MANDATORY)
When hiding or renaming a UI label (e.g. "Admin" for non-admin users), the same text is often rendered in multiple places: sidebar, main header, document title, tab labels, tooltip titles, breadcrumbs. Fixing one site and shipping leaves the others leaking.
Before claiming a label change is done:
grep -rn '"YourLabel"' src/— find ALL render sites- Check any default-fallback code paths that humanize a route ID (e.g.
activeView.charAt(0).toUpperCase() + activeView.slice(1)turnsadmin→"Admin"for free, bypassing any lookup map) - Visual verification is mandatory — HTTP 200 and unit tests don't prove what the user sees. Take a Playwright screenshot of the actual rendered page.
Symptom of this gotcha: your RBAC intercept returns isAdmin: false, the JS unit test passes, but a main page header still renders "Admin" through a default fallback path that wasn't gated.
Key Notes
- Use
domcontentloadednotnetworkidle— React SPAs keep connections alive - Wait 3-5s after navigation for data-driven content; 8-10s for heavy views
- Headed mode: Add
headless: falsewhen user wants to watch - Auth failure signal: HTTP 307 redirect = service token not accepted
- Screenshots directory: Use a consistent path like
screenshots/
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.pyMultiple 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.pyTo 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
Server-Generated HTML Template → PNG Verification
For a report/document/email HTML template that's rendered server-side with no live URL to navigate to, don't reason about layout from the template source — render it and look:
- Write a throwaway script that imports the template function/module directly (no HTTP round-trip, no server needed).
- Call the template function with representative and edge-case data to get the HTML string.
page.setContent(html)in a headless Playwright page.- Screenshot to a scratch path.
- Read the screenshot, compare against the goal, iterate — edit the template, rerun, re-screenshot.
- Delete the throwaway script when done, or keep it if it should become a repeatable check.
A computed or predicted layout is never visual proof — bounding-box math or "the HTML string contains the right elements" doesn't establish that a report, invoice, or email actually renders correctly at a usable size. Only a rendered screenshot does.
Common Pitfall
- Don't inspect the DOM before waiting for
networkidleon dynamic apps - Do wait for
page.wait_for_load_state('networkidle')before inspection
Screenshot Timing
Wait 1-2 seconds after:
- Starting a dev server
- Navigating to a new page
- Theme transitions
before taking screenshots. Without this pause, captures may show loading states or incomplete renders.
Best Practices
- Use bundled scripts as black boxes - 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()
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
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

