| name | browser-test |
| description | Execute browser-based UI testing using Chrome MCP tools. Tests user interactions, visual outcomes, captures evidence. Use for UI features after implementation and security review. |
| allowed-tools | Read, mcp__claude-in-chrome__tabs_context_mcp, mcp__claude-in-chrome__tabs_create_mcp, mcp__claude-in-chrome__navigate, mcp__claude-in-chrome__read_page, mcp__claude-in-chrome__find, mcp__claude-in-chrome__computer, mcp__claude-in-chrome__form_input, mcp__claude-in-chrome__javascript_tool |
Browser Testing Skill
Purpose
Execute browser-based UI testing for web applications using Claude in Chrome MCP tools. Verify UI acceptance criteria, capture evidence, and validate user flows.
When to Use
Mandatory for:
- UI components or page changes
- User interaction flows (clicks, forms, navigation)
- Visual features (modals, toasts, animations)
- Responsive design changes
Optional for:
- Backend-only changes
- API-only features
- Non-visual refactoring
Browser Testing Process
Step 1: Load Spec and Identify UI Criteria
cat .claude/specs/active/<slug>.md
Extract UI-specific acceptance criteria:
- User interactions (button clicks, form submissions)
- Visual feedback (toasts, modals, error messages)
- Navigation (redirects, route changes)
- State changes (UI updates, data display)
Example:
## UI Acceptance Criteria (from spec)
- AC1.3: Confirmation toast displayed after logout
- AC2.3: Retry button appears on network error
- AC1.2: User redirected to /login page
Step 2: Get Browser Context
tabs_context_mcp({ createIfEmpty: true });
tabs_create_mcp();
Best practice: Use a fresh tab for each test session to avoid state pollution.
Step 3: Navigate to Test Environment
navigate({
url: "http://localhost:3000/dashboard",
tabId: <tabId>
});
Environment options:
- Local dev:
http://localhost:3000
- Staging:
https://staging.example.com
- Production: Only for smoke tests, never for destructive tests
Step 4: Execute UI Test Cases
For each UI acceptance criterion, execute a test case.
Test Case Structure
**Test Case**: AC1.3 - Confirmation toast after logout
**Steps**:
1. Navigate to /dashboard
2. Find logout button
3. Click logout button
4. Wait for toast to appear
5. Verify toast message
**Expected**: Toast with message "You have been logged out"
**Evidence**: Screenshot of toast
Executing Test Steps
navigate({ url: "http://localhost:3000/dashboard", tabId });
find({ query: "logout button", tabId });
computer({
action: "left_click",
ref: "ref_1",
tabId
});
await computer({ action: "wait", duration: 1, tabId });
computer({ action: "screenshot", tabId });
Step 5: Verify Outcomes
Use multiple verification methods:
Visual Verification
const screenshot = computer({ action: "screenshot", tabId });
find({ query: "confirmation toast", tabId });
DOM Verification
read_page({ tabId, filter: "all" });
javascript_tool({
tabId,
action: "javascript_exec",
text: `
const toast = document.querySelector('[role="status"]');
toast?.textContent.includes("logged out");
`
});
Navigation Verification
javascript_tool({
tabId,
action: "javascript_exec",
text: "window.location.pathname"
});
Step 6: Handle Failures and Errors
Element Not Found
find({ query: "logout button", tabId });
Actions:
- Take screenshot to see current page state
- Try alternative selectors ("button with text logout", "sign out button")
- Check if page loaded correctly (read_page)
- If element genuinely missing → Report as test failure
Interaction Failed
computer({ action: "left_click", ref: "ref_1", tabId });
Actions:
- Wait for page to settle (animations, loading)
- Scroll element into view
- Try alternative interaction (keyboard instead of mouse)
- Report as test failure if truly broken
Unexpected Behavior
javascript_tool({ text: "window.location.pathname", tabId });
Action: Document failure with evidence (screenshot) and report.
Step 7: Capture Evidence
For each test case, capture evidence:
computer({ action: "screenshot", tabId });
computer({
action: "zoom",
region: [x0, y0, x1, y1],
tabId
});
Evidence includes:
- Initial state before interaction
- Interaction point (e.g., button being clicked)
- Final state after interaction
- Error states (if testing error paths)
Step 8: Document Test Results
Create test results document:
# Browser Test Results: <Task Name>
**Date**: 2026-01-02 17:30
**Environment**: http://localhost:3000
**Browser**: Chrome
---
## Test Cases
### TC1: Logout Button Click (AC1.1, AC1.2)
**Status**: ✅ PASS
**Steps**:
1. ✅ Navigated to http://localhost:3000/dashboard
2. ✅ Found logout button (ref_1)
3. ✅ Clicked logout button
4. ✅ Verified redirect to /login
**Evidence**: screenshot-001.png, screenshot-002.png
**Result**: User successfully logged out and redirected
---
### TC2: Confirmation Toast (AC1.3)
**Status**: ✅ PASS
**Steps**:
1. ✅ Clicked logout button
2. ✅ Toast appeared with message "You have been logged out"
3. ✅ Toast auto-dismissed after 3 seconds
**Evidence**: screenshot-003.png
**Result**: Confirmation toast displayed correctly
---
### TC3: Retry Button on Error (AC2.3)
**Status**: ❌ FAIL
**Steps**:
1. ✅ Simulated network error (DevTools network throttling)
2. ✅ Clicked logout button
3. ❌ Expected retry button, but only error message shown
**Evidence**: screenshot-004.png
**Result**: FAILURE - Retry button not rendered
**Issue**: Implementation missing retry button component
---
## Summary
**Passed**: 2/3 (67%)
**Failed**: 1/3 (33%)
**Blocker**: TC3 failure blocks merge - retry button required per spec AC2.3
**Action**: Fix retry button implementation, re-run browser tests
Step 9: Update Spec with Browser Test Evidence
Add results to spec:
## Browser Test Results
**Date**: 2026-01-02 17:30
**Environment**: localhost:3000
| AC | Test Case | Status | Evidence |
|----|-----------|--------|----------|
| AC1.1 | Logout clears auth | ✅ Pass | screenshot-001.png |
| AC1.2 | Redirect to /login | ✅ Pass | screenshot-002.png |
| AC1.3 | Confirmation toast | ✅ Pass | screenshot-003.png |
| AC2.3 | Retry button | ❌ Fail | screenshot-004.png |
**Overall**: 3/4 pass (75%) - 1 blocking failure
Step 10: Handle Test Failures
If tests fail:
-
Verify failure is real (not test issue):
- Re-run test to confirm not flaky
- Check environment is correct
- Verify test steps match spec
-
Document failure clearly:
- Screenshot showing actual vs expected
- Steps to reproduce
- Severity (blocking or minor)
-
Route to fix:
- Use
/implement to fix implementation
- Update spec if expectation was wrong
- Re-run browser tests after fix
Testing Patterns
Pattern 1: Form Submission
find({ query: "email input", tabId });
find({ query: "password input", tabId });
form_input({ ref: "ref_1", value: "test@example.com", tabId });
form_input({ ref: "ref_2", value: "password123", tabId });
find({ query: "submit button", tabId });
computer({ action: "left_click", ref: "ref_3", tabId });
await computer({ action: "wait", duration: 1, tabId });
computer({ action: "screenshot", tabId });
Pattern 2: Modal Interaction
find({ query: "delete button", tabId });
computer({ action: "left_click", ref: "ref_1", tabId });
await computer({ action: "wait", duration: 0.5, tabId });
find({ query: "confirmation dialog", tabId });
find({ query: "confirm delete button", tabId });
computer({ action: "left_click", ref: "ref_3", tabId });
await computer({ action: "wait", duration: 0.5, tabId });
computer({ action: "screenshot", tabId });
Pattern 3: Navigation Flow
navigate({ url: "http://localhost:3000/page-a", tabId });
find({ query: "go to page B link", tabId });
computer({ action: "left_click", ref: "ref_1", tabId });
await computer({ action: "wait", duration: 1, tabId });
const currentPath = javascript_tool({
tabId,
action: "javascript_exec",
text: "window.location.pathname"
});
if (currentPath === "/page-b") {
} else {
}
Pattern 4: Error State Testing
javascript_tool({
tabId,
action: "javascript_exec",
text: `
// Mock API to fail
window.fetch = async () => {
throw new Error("Network error");
};
`
});
find({ query: "save button", tabId });
computer({ action: "left_click", ref: "ref_1", tabId });
await computer({ action: "wait", duration: 1, tabId });
find({ query: "error message", tabId });
computer({ action: "screenshot", tabId });
Best Practices
Use Semantic Selectors
find({ query: "logout button", tabId });
find({ query: "button with text logout", tabId });
find({ query: "button with aria-label logout", tabId });
Wait for Interactions to Complete
computer({ action: "left_click", ref: "ref_1", tabId });
await computer({ action: "wait", duration: 1, tabId });
find({ query: "success message", tabId });
Capture Evidence Liberally
computer({ action: "screenshot", tabId });
computer({ action: "left_click", ref: "ref_1", tabId });
await computer({ action: "wait", duration: 1, tabId });
computer({ action: "screenshot", tabId });
Clean Up Test State
javascript_tool({
tabId,
action: "javascript_exec",
text: "localStorage.clear(); sessionStorage.clear();"
});
tabs_create_mcp();
Integration with Other Skills
After browser testing:
- If PASS with public API → Trigger
/docs for documentation, then commit
- If PASS (no public API) → Ready for commit
- If FAIL → Use
/implement to fix, then re-test
Before browser testing:
- Run
/unify for spec-impl-test convergence
- Run
/security for security review
- Browser testing validates UI before final gates
Documentation trigger: If the implementation adds or modifies public APIs, user-facing features, or configuration options, dispatch the documenter subagent after browser tests pass (before commit).
Example Test Suite
Example: Logout Feature Browser Tests
Spec ACs:
- AC1.1: Logout clears token
- AC1.2: Redirect to /login
- AC1.3: Confirmation toast
- AC2.3: Retry button on error
Test Suite:
navigate({ url: "http://localhost:3000/dashboard", tabId });
find({ query: "logout button", tabId });
computer({ action: "screenshot", tabId });
computer({ action: "left_click", ref: "ref_1", tabId });
await computer({ action: "wait", duration: 1, tabId });
computer({ action: "screenshot", tabId });
const path = javascript_tool({
tabId,
action: "javascript_exec",
text: "window.location.pathname"
});
find({ query: "confirmation toast", tabId });
navigate({ url: "http://localhost:3000/dashboard", tabId });
javascript_tool({
tabId,
action: "javascript_exec",
text:
});
({ : , tabId });
({ : , : , tabId });
({ : , : , tabId });
({ : , tabId });
({ : , tabId });
({ : , tabId });
Output: 3/4 ACs pass, 1 blocking failure (retry button missing)