Webapp Testing
Toolkit for interacting with and testing local web applications using the Playwright MCP server. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Web Application Testing (Playwright MCP)
Browser automation is available via the Playwright MCP server (playwright in .mcp.json). Use Playwright MCP tools to navigate, inspect, and interact with local web apps.
Scope: This skill covers Playwright MCP — the interactive browser server that agents use during verify and preview-testing phases. It is NOT about the Playwright test runner (
pnpm test:e2e) used for CI E2E test suites. For E2E test patterns, seestandards/core/testing.md.
Prerequisites
The dev server must be running before using browser tools. Start it first:
cd tools/prototype-scaffold && pnpm dev
Then verify the app is up at http://localhost:3000 before proceeding.
Decision Tree
Task → Is a dev server needed?
├─ Yes → Start server, wait for it to be ready
│ Then: Does the page require authentication?
│ ├─ Yes → Authenticate first (see "Authenticated Testing" below)
│ │ Then: navigate → snapshot → interact
│ └─ No → navigate → snapshot → interact
└─ No (static HTML) → navigate to file:// URL directly
Then: snapshot → interact
Core Pattern: Snapshot-Then-Act
Always take a snapshot before interacting to discover element refs:
playwright_navigate— Go to the URLplaywright_screenshot— Optional: capture visual stateplaywright_snapshot— Get accessibility tree (preferred; use before clicks/fills)playwright_click/playwright_fill/playwright_select_option— Interact using refs from snapshotplaywright_screenshot— Capture result
Key Tools
| Tool | When to Use |
|---|---|
playwright_navigate |
Load a URL (starts a new browser session if needed) |
playwright_snapshot |
Get page structure + element refs (use before all interactions) |
playwright_screenshot |
Capture visual state (full page or element) |
playwright_click |
Click a button, link, or element |
playwright_fill |
Clear and type into an input |
playwright_select_option |
Choose a dropdown value |
playwright_press_key |
Send keyboard events (Enter, Tab, Escape, etc.) |
playwright_evaluate |
Run JavaScript in the browser context |
playwright_console_messages |
Retrieve browser console logs |
playwright_wait_for |
Wait for navigation, network idle, or a selector condition |
Common Patterns
Verify a page loads and renders key elements
1. playwright_navigate { url: "http://localhost:3000/admin" }
2. playwright_snapshot → check for expected headings/buttons
3. playwright_screenshot → visual confirmation
Fill and submit a form
For login forms, see the Authenticated Testing section. For other forms:
1. playwright_navigate { url: "http://localhost:3000/items/new" }
2. playwright_snapshot → get refs for form fields and submit button
3. playwright_fill { ref: "[name ref]", value: "Test Item" }
4. playwright_click { ref: "[submit ref]" }
5. playwright_wait_for { state: "networkidle" }
6. playwright_snapshot → verify success state
Capture console errors during interaction
1. playwright_navigate { url: "http://localhost:3000" }
2. [perform interactions]
3. playwright_console_messages → check for errors or warnings
Authenticated Testing (Clerk)
For pages behind Clerk auth (/dashboard, /settings, etc.), authenticate first using test credentials.
Prerequisites
Two environment variables must be set in your shell (typically ~/.zshenv):
CLERK_TEST_EMAIL— Test user email (e.g.,claude@rajababa.io)CLERK_TEST_PASSWORD— Test user password
Never add these to project .env* files — they can be accidentally committed.
If either variable is missing, skip authenticated testing and note it as a gap in your verification report.
Auth Flow
Development instances only — Only use this flow against a Clerk development instance. Never target staging or production.
Clerk's <SignIn /> component uses a multi-step form. Follow this sequence:
Step 0 — Resolve credentials. Retrieve both values before the Playwright flow:
echo $CLERK_TEST_EMAIL
echo $CLERK_TEST_PASSWORD
If either value is empty, skip auth testing and note the gap. The password will appear in the local conversation transcript — this is acceptable because these are dev-instance test credentials with no production access and minimal permissions. Use the returned values in steps 3 and 7.
1. playwright_navigate { url: "http://localhost:3000/sign-in" }
2. playwright_snapshot → find email/identifier input ref
3. playwright_fill { ref: "[email input ref]", value: "[resolved CLERK_TEST_EMAIL]" }
4. playwright_click { ref: "[Continue button ref]" }
5. playwright_wait_for { state: "networkidle" }
6. playwright_snapshot → find password input ref (new form state)
7. playwright_fill { ref: "[password input ref]", value: "[resolved CLERK_TEST_PASSWORD]" }
8. playwright_click { ref: "[Sign in button ref]" }
9. playwright_wait_for { state: "networkidle" }
10. playwright_snapshot → verify redirect to protected page (e.g., /dashboard)
11. playwright_screenshot → capture authenticated state
Key details:
- The sign-in URL is
/sign-in(perNEXT_PUBLIC_CLERK_SIGN_IN_URLconvention) - Clerk uses a two-step form: email first, then password on the next screen. You must snapshot between steps to get fresh refs
- After authentication, the session cookie persists across subsequent
playwright_navigatecalls within the same MCP session - If the snapshot after step 10 still shows the sign-in page, authentication failed — screenshot and report the error
Verifying Auth Worked
After the auth flow, confirm you're on an authenticated page:
1. playwright_snapshot → look for user-specific elements (avatar, user name, dashboard content)
2. playwright_navigate { url: "http://localhost:3000/settings" } → navigate to another protected route
3. playwright_snapshot → confirm it loads (not redirected to /sign-in)
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Email input not found in snapshot | Clerk component hasn't loaded | Add playwright_wait_for before snapshot |
| "Continue" does nothing | Wrong ref — might be a different button | Re-snapshot and look for the correct interactive element |
| Password step never appears | Email not recognized by Clerk | Verify test user exists in Clerk Dev instance |
| Redirected back to /sign-in after auth | Session cookie not persisting | Check if the app has additional middleware blocking |
| Rate limited by Clerk | Too many rapid auth attempts | Wait 60 seconds and retry |
Session cleanup: Authenticated state persists for the duration of the MCP session. If you need a clean unauthenticated state for a subsequent test scenario, sign out programmatically:
1. playwright_evaluate { expression: "await window.Clerk?.signOut()" }
2. playwright_snapshot → confirm redirect to /sign-in
Alternatively, starting a new MCP session is the most reliable way to guarantee a clean state.
Best Practices
- Always snapshot before clicking — Element refs change between page states; get fresh refs each time
- Wait for network idle — After navigation or form submission, wait before inspecting
- Use
playwright_evaluatefor complex checks — Run arbitrary JS to extract data not in the accessibility tree - Screenshot on failure — Capture a screenshot immediately when something unexpected happens for debugging
Fallback: Shell-Based Playwright
If the Playwright MCP server is unavailable, fall back to shell-based automation using pnpm exec playwright commands or direct Playwright scripts. See standards/core/testing.md for E2E testing patterns.