E2E Testing with Playwright
Execute E2E testing workflows using Playwright MCP.
Use TodoWrite to track these 4 phases:
- Determine action (parse args or ask)
- Execute action (run/record/generate/verify)
- Verify results
- Present output
Phase 1: Parse Arguments
$ARGUMENTS:
run→ Run existing E2E testsrecord→ Record browser session for test generationgenerate→ Generate test from URL or page descriptionverify <feature>→ Verify specific feature works in browser- (empty) → Ask what to do
If no argument provided, use AskUserQuestion:
| Header | Question | Options | | ------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Action | What E2E testing task to run? | 1. Run tests - Execute existing Playwright tests 2. Record - Record browser session 3. Generate - Create test from URL/description 4. Verify - Check feature |
Phase 2: Execute Action
Run Tests
npx playwright test
For specific test:
npx playwright test login.spec.ts
For headed mode (visible browser):
npx playwright test --headed
Record Session
Use Playwright MCP tools for browser interaction:
browser_navigate- Go to target URLbrowser_snapshot- Inspect page structurebrowser_click,browser_type,browser_fill_form- Interact- Generate test file with Page Object pattern
Generate Test
Spawn playwright-tester agent:
Task(
subagent_type="playwright-tester",
description="Generate E2E test",
prompt="Generate E2E test for:
URL: {target URL}
Flow: {user flow description}
Requirements:
- Use Page Object pattern
- Use semantic locators (getByRole, getByLabel, getByText)
- Include assertions for expected outcomes
- No hardcoded waits (use waitFor patterns)
- Include accessibility checks where appropriate"
)
Verify Feature
Spawn playwright-tester agent for feature verification:
Task(
subagent_type="playwright-tester",
description="Verify feature",
prompt="Verify this feature works correctly in the browser:
Feature: {feature description}
Steps:
1. Navigate to appropriate page
2. Execute user flow
3. Assert expected outcomes
4. Report PASS/FAIL with evidence (screenshots if needed)"
)
Phase 3: Verify Results
npx playwright test --headed
If tests fail, review output and fix issues.
Phase 4: Output
E2E TESTING
===========
Action: {run|record|generate|verify}
Result: {outcome}
Tests: {pass/fail count}
Details:
- [test results or generation summary]
Key Tools
| Tool | Purpose |
| -------------------------- | ---------------------- |
| browser_navigate | Go to URL |
| browser_snapshot | Get accessibility tree |
| browser_click | Click elements |
| browser_type | Type text |
| browser_fill_form | Fill form fields |
| browser_generate_locator | Get best locator |
| browser_verify_text | Assert text content |
Supported Stacks
- TypeScript: Playwright Test with Page Objects
- Go/HTMX: Test HTMX interactions, form submissions, partial updates
HTMX Testing Tips
- Use
browser_snapshotto verify DOM updates after HTMX swaps - Test
hx-trigger,hx-swap,hx-targetbehaviors - Verify
HX-*response headers in network requests - Assert partial page updates without full reload
Error Scenarios & Handling
Element Not Found
// BAD: Immediate failure
await page.click("#submit-btn");
// GOOD: Wait with timeout + fallback
const btn = page.locator("#submit-btn");
if ((await btn.count()) === 0) {
// Try alternative selector
await page.click('button[type="submit"]');
} else {
await btn.click();
}
Recovery strategies:
- Use
browser_snapshotto inspect current DOM state - Try alternative locators (text, role, data-testid)
- Check if element is in iframe/shadow DOM
- Verify page loaded correctly (check URL, title)
Timeout Errors
| Error | Cause | Solution | | ------------------ | ------------------------ | --------------------------------- | | Navigation timeout | Slow page load | Increase timeout, check network | | Action timeout | Element not interactable | Wait for visibility/enabled state | | Expect timeout | Assertion failed | Verify DOM state with snapshot |
// Configure timeouts
test.setTimeout(60000); // Test timeout
page.setDefaultTimeout(30000); // Action timeout
// Or per-action
await page.click("#btn", { timeout: 10000 });
Network Issues
// Wait for network idle
await page.waitForLoadState("networkidle");
// Mock failing endpoints
await page.route("**/api/**", (route) => {
route.fulfill({ status: 500, body: "Server Error" });
});
Flaky Test Patterns
Avoid:
- Fixed
page.waitForTimeout(1000)delays - Brittle selectors like
.btn-23 - Tests depending on animation timing
Prefer:
waitForSelector,waitForLoadState- Role/text-based selectors:
getByRole('button', { name: 'Submit' }) - Retry patterns for known flaky operations
Debugging Failed Tests
- Get snapshot:
browser_snapshotshows accessibility tree - Screenshot: Capture current visual state
- Console logs: Check browser console for JS errors
- Network tab: Verify API calls succeeded
- Trace: Enable Playwright trace for post-mortem
# Run with trace
npx playwright test --trace on
# View trace
npx playwright show-trace trace.zip
Execute E2E testing workflow now.