| name | web-debug |
| description | Systematic web application debugging using Chrome DevTools MCP and Playwright MCP with intelligent validation and app-specific context discovery. Use for debugging web apps, APIs, authentication flows, and UI issues. |
| tools | mcp__chrome-devtools__*, mcp__playwright__*, mcp__fetch__fetch, Read, Grep, mcp__memory__* |
Web Application Debugging with Chrome DevTools MCP and Playwright MCP
Overview
This skill guides systematic, efficient web debugging using Chrome DevTools MCP and
Playwright MCP. It emphasizes validation before action to minimize slow operations
and automatic context discovery from project documentation.
Decision Trees
"Which MCP should I use?"
Browser debugging needed?
│
├─▶ Chrome is already open with target page?
│ └─▶ Use Chrome DevTools MCP
│ ├─▶ Requires: --remote-debugging-port=9222 flag
│ └─▶ Check: curl http://localhost:9222/json/version
│
├─▶ Need to launch fresh browser instance?
│ └─▶ Use Playwright MCP
│ ├─▶ Creates headless or headed browser
│ └─▶ Cleaner state, no extension interference
│
├─▶ Need to test multiple browsers (Brave, Firefox, Safari)?
│ └─▶ Playwright MCP supports multiple engines
│
├─▶ Need to connect to existing DevTools session?
│ └─▶ Chrome DevTools MCP only
│
└─▶ Automated testing / repeatable scenarios?
└─▶ Playwright MCP (better API for automation)
"How should I debug this issue?"
Web debugging task?
│
├─▶ Page not loading / blank screen?
│ ├─▶ 1. Validate URL: mcp__fetch__fetch (check status)
│ ├─▶ 2. Check console: list_console_messages({ types: ["error"] })
│ ├─▶ 3. Check network: list_network_requests (look for 4xx/5xx)
│ └─▶ 4. Only then: browser_snapshot (see what rendered)
│
├─▶ Authentication not working?
│ ├─▶ 1. Check docs for auth method (Context Discovery)
│ ├─▶ 2. Inspect storage: browser_evaluate localStorage/cookies
│ ├─▶ 3. Check network: filter for auth endpoints
│ └─▶ 4. Inspect response headers (Set-Cookie, WWW-Authenticate)
│
├─▶ API call failing?
│ ├─▶ 1. list_network_requests({ resourceTypes: ["xhr", "fetch"] })
│ ├─▶ 2. get_network_request({ reqid: N }) for details
│ ├─▶ 3. Check: status, headers, body, CORS errors
│ └─▶ 4. Compare with docs/expected API contract
│
├─▶ Element not found / can't click?
│ ├─▶ 1. Quick check: browser_evaluate("!!document.querySelector(...)")
│ ├─▶ 2. If false: check network for pending loads
│ ├─▶ 3. If still false: browser_snapshot to see actual page
│ └─▶ 4. Check: wrong selector, dynamic loading, iframe
│
├─▶ Page is slow?
│ ├─▶ 1. performance_start_trace({ reload: true, autoStop: true })
│ ├─▶ 2. Review insights from trace
│ └─▶ 3. Check: large network payloads, long JS execution
│
└─▶ Visual/layout issue?
├─▶ 1. browser_snapshot (accessibility tree)
├─▶ 2. browser_take_screenshot (actual visual)
└─▶ 3. For full-page: save to file, then resize-image --check
"Should I take a screenshot or snapshot?"
Need page content?
│
├─▶ Need to interact (click, fill, etc.)?
│ └─▶ browser_snapshot (returns element refs like "e123")
│
├─▶ Need exact visual appearance?
│ └─▶ browser_take_screenshot
│ ├─▶ Viewport only: Usually safe
│ ├─▶ fullPage: true: ⚠️ May exceed API limits
│ │ └─▶ Save to file, then: resize-image --check
│ └─▶ Element screenshot: Specify uid
│
├─▶ Just checking page structure?
│ └─▶ browser_snapshot (faster, includes a11y tree)
│
├─▶ Verifying simple condition?
│ └─▶ browser_evaluate is FASTEST
│ └─▶ "() => document.title"
│ └─▶ "() => !!document.querySelector('.logged-in')"
│
└─▶ Performance investigation?
└─▶ performance_start_trace / performance_stop_trace
Core Principle: Validate Before Acting
CRITICAL: MCP browser operations are expensive. Always validate before taking action:
browser_navigate → browser_snapshot → "oops, 404"
fetch(url) → if 200 then browser_navigate → quick check current URL matches
Smart Debugging Workflow
1. Context Discovery (First Step)
Before debugging, discover app-specific context from the repository:
1. docs/web-debug.md
2. docs/debugging.md
3. docs/authentication.md
4. README.md
5. .env.example / .env.local
6. package.json / Gemfile / etc
What to extract from docs:
- Base URL(s) for local dev / staging / production
- Authentication mechanism (JWT, sessions, OAuth, API keys)
- Test credentials or how to obtain them
- Common routes and expected behavior
- Known issues / quirks
- API endpoint patterns
Store discovered context:
mcp__memory__create_entities({
entities: [{
name: "launchdeck-web-debug",
entityType: "AppDebugContext",
observations: [
"Base URL: http://localhost:3000",
"Auth: JWT token in localStorage key 'auth_token'",
"Test credentials: user@example.com / password123",
"API pattern: /api/v1/{resource}",
"Known issue: CORS errors on Safari, works on Brave"
]
}]
})
2. Pre-Flight Validation (Before Navigation)
Always validate URLs before navigating:
const response = await mcp__fetch__fetch({
url: targetUrl,
prompt: "Return status code only"
});
if (response.includes("404") || response.includes("error")) {
return "URL not reachable: " + targetUrl;
}
const pages = await browser_list_pages();
if (currentPage.url === targetUrl) {
}
3. Efficient State Inspection
Use the lightest operation that answers your question:
| Need | ❌ Slow | ✅ Fast |
|---|
| Check current URL | browser_snapshot | browser_list_pages |
| Verify element exists | browser_snapshot | browser_evaluate({ function: "() => !!document.querySelector('.login-btn')" }) |
| Get simple value | browser_snapshot | browser_evaluate({ function: "() => localStorage.getItem('token')" }) |
| Check if logged in | browser_snapshot | browser_evaluate({ function: "() => document.body.dataset.authenticated" }) |
| Inspect network | browser_snapshot | list_network_requests |
| Check console errors | browser_snapshot | list_console_messages({ types: ["error"] }) |
Batch parallel operations:
Promise.all([
list_console_messages({ types: ["error", "warn"] }),
list_network_requests({ resourceTypes: ["xhr", "fetch"] }),
browser_evaluate({ function: "() => ({ url: window.location.href, token: localStorage.getItem('auth_token') })" })
])
browser_snapshot → list_console_messages → list_network_requests
4. Authentication Handling
Discovery process:
-
Check documentation first (see Context Discovery above)
-
Inspect the app (if docs don't exist):
browser_evaluate({
function: `() => ({
localStorage: Object.keys(localStorage).filter(k =>
k.includes('token') || k.includes('auth') || k.includes('session')
),
cookies: document.cookie,
hasLoginForm: !!document.querySelector('form[action*="login"]'),
userIndicator: document.querySelector('[data-user], .user-name')?.textContent
})`
})
-
If auth mechanism unknown, ask user ONCE and remember:
"I need to authenticate with this app but couldn't find credentials.
How should I log in?"
mcp__memory__add_observations({
entityName: "app-name-web-debug",
observations: ["Auth method: Form login with user@test.com / password123"]
})
5. Systematic Debugging by Issue Type
Network / API Debugging
const requests = await list_network_requests({
resourceTypes: ["xhr", "fetch"],
includeStatic: false
});
const issues = requests.filter(r =>
r.status >= 400 || r.time > 2000
);
if (issues.length > 0) {
const detail = await get_network_request({ reqid: issues[0].id });
}
Console Error Debugging
const errors = await list_console_messages({
types: ["error"],
includePreservedMessages: false
});
if (errors.length > 0) {
const detail = await get_console_message({ msgid: errors[0].id });
}
Authentication Flow Debugging
const authState = await browser_evaluate({
function: `() => ({
token: localStorage.getItem('auth_token'),
cookies: document.cookie.split(';').map(c => c.trim().split('=')[0]),
isLoggedIn: !!document.querySelector('[data-logged-in="true"]')
})`
});
const appContext = await mcp__memory__search_nodes({
query: `${appName} auth credentials`
});
if (appContext.hasCredentials) {
await browser_fill_form({ fields: [...] });
await browser_click({ element: "Submit", ref: "..." });
await wait_for({ text: "Dashboard" });
}
UI / Interaction Debugging
const elementExists = await browser_evaluate({
function: `() => !!document.querySelector('button[data-action="submit"]')`
});
if (!elementExists) {
}
const snapshot = await browser_snapshot();
Decision Framework
When to use full browser_snapshot
- Need to interact with elements (requires refs)
- Visual debugging (need to see layout/hierarchy)
- Unknown page state (first time visiting)
When to use browser_evaluate
- Simple data extraction (get token, check boolean)
- Quick validation (element exists, page ready)
- Performance-critical checks (in loops, pre-flight validation)
When to use list_* tools
- Diagnostic info (console errors, network failures)
- Monitoring (watching for issues during workflow)
- Quick checks (any errors? any failed requests?)
Common Patterns
Pattern: Safe Navigation
async function navigateSafely(url: string) {
const check = await mcp__fetch__fetch({
url,
prompt: "HTTP status code only"
});
if (!check.includes("200")) {
throw new Error(`URL not reachable: ${url}`);
}
const pages = await browser_list_pages();
if (pages.current.url === url) {
return "Already on page";
}
await browser_navigate({ url });
await wait_for({ text: "Expected content" });
}
Pattern: Quick Health Check
async function quickHealthCheck() {
const [console, network, state] = await Promise.all([
list_console_messages({ types: ["error"] }),
list_network_requests({ includeStatic: false }),
browser_evaluate({
function: "() => ({ url: location.href, ready: document.readyState })"
})
]);
return {
errors: console.filter(m => m.type === "error"),
failures: network.filter(r => r.status >= 400),
currentUrl: state.url,
pageReady: state.ready === "complete"
};
}