Chrome DevTools Developer Training & Resource Guide
What is Chrome DevTools?
Manual panels + CDP + the chrome-devtools MCP — verify in a real browser, gate on CWV.
Three faces: the panels (F12) for hands-on debugging; CDP for automation (`Network.emulateNetworkConditions`, `Emulation.setCPUThrottlingRate`); and `chrome-devtools-mcp` (Google, Apache-2.0) so Claude Code drives a real Chrome — navigate, screenshot, read console/network, run `lighthouse_audit`, capture traces. The scoreboard is Core Web Vitals at the 75th percentile: LCP ≤2.5s, INP ≤200ms (replaced FID in 2024), CLS ≤0.1. For codeAmani, make 3G + Disable-cache the default test condition and gate PRs on LCP/CLS via Lighthouse CI — a page fast on fibre but failing at 3G fails our users.
Six core surfaces
The panels you live in plus the agent surface that drives them. Tap a card for the shortcuts + gotchas.
Will it survive 3G? — Core Web Vitals playground
Pick a connection, then drag page weight, server time, and render-blocking resources. Watch TTFB / FCP / LCP shift against the official budgets and the request waterfall stretch. This is exactly the test codeAmani runs before shipping: if it’s slow on a Nairobi 3G link, it’s slow for our users.
The green content transfer bar is what page weight inflates. On a slow link, a heavy bundle pushes LCP past the 2.5s budget — that's why splitting + lazy-loading matters.
Thresholds are exact/official (web.dev/articles/vitals). Connection bandwidth/RTT are representative— DevTools intentionally doesn’t publish exact preset values; the load model here is illustrative. Measure real numbers in the Performance panel, Lighthouse, or the web-vitals library.
██████╗██╗ ██╗██████╗ ██████╗ ███╗ ███╗███████╗
██╔════╝██║ ██║██╔══██╗██╔═══██╗████╗ ████║██╔════╝
██║ ███████║██████╔╝██║ ██║██╔████╔██║█████╗
██║ ██╔══██║██╔══██╗██║ ██║██║╚██╔╝██║██╔══╝
╚██████╗██║ ██║██║ ██║╚██████╔╝██║ ╚═╝ ██║███████╗
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
██████╗ ███████╗██╗ ██╗████████╗ ██████╗ ██████╗ ██╗ ███████╗
██╔══██╗██╔════╝██║ ██║╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██╔════╝
██║ ██║█████╗ ██║ ██║ ██║ ██║ ██║██║ ██║██║ ███████╗
██║ ██║██╔══╝ ╚██╗ ██╔╝ ██║ ██║ ██║██║ ██║██║ ╚════██║
██████╔╝███████╗ ╚████╔╝ ██║ ╚██████╔╝╚██████╔╝███████╗███████║
╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝Chrome DevTools Developer Training & Resource Guide
Focus: Everything a developer can do with Chrome DevTools — every panel, the Chrome DevTools Protocol (CDP), the
chrome-devtools-mcpserver for Claude Code, Core Web Vitals budgets, and end-to-end testing with Playwright / Puppeteer / Lighthouse CI. Grounded indeveloper.chrome.com,web.dev, and the official MCP repo; reviewed 2026-06-07.
How to use this guide
DevTools has three faces — learn them in order:
- The panels (manual) → Part 1. What you open with F12 / Cmd+Opt+I to inspect, debug, and profile by hand.
- The protocol (scriptable) → CDP. Everything the UI does, exposed as a websocket API that Playwright, Puppeteer, Lighthouse, and the MCP all build on.
- The agent surface (Claude Code) → the MCP.
chrome-devtools-mcplets Claude drive a real browser to verify your work.
The interactive learn module above this page is a live Core Web Vitals + network-throttle playground — start there to build intuition, then use the panel reference below.
Overview
Chrome DevTools is the browser's built-in inspection, debugging, and profiling suite. Under the UI sits the Chrome DevTools Protocol (CDP) — a JSON-over-websocket API that exposes the same capabilities to automation. chrome-devtools-mcp (by Google, Apache-2.0) wraps a Puppeteer-driven Chrome as an MCP server so a coding agent can navigate, click, screenshot, read the console/network, run Lighthouse, and capture performance traces — the practical way to test in a browser before claiming done.
The three faces of DevTools, and how they connect:
Official Documentation
| Resource | URL |
|---|---|
| Chrome DevTools docs | https://developer.chrome.com/docs/devtools |
| Chrome DevTools Protocol (CDP) | https://chromedevtools.github.io/devtools-protocol/ |
| chrome-devtools-mcp (official) | https://github.com/ChromeDevTools/chrome-devtools-mcp |
| Web Vitals | https://web.dev/articles/vitals |
| Playwright | https://playwright.dev/docs/intro |
| Puppeteer | https://pptr.dev |
| Lighthouse CI | https://github.com/GoogleChrome/lighthouse-ci |
Part 1 — The DevTools panels
Open DevTools: F12, Cmd+Opt+I (macOS), or Ctrl+Shift+I (Win/Linux). The Command Menu (Cmd/Ctrl+Shift+P) jumps to any panel or action by name — the single most useful shortcut.
| Panel | What it's for | Reach for it when… |
|---|---|---|
| Elements | Inspect & edit the live DOM + CSS (the Styles pane) | Tweaking layout, debugging CSS specificity, checking computed styles |
| Console | Log output + a live JS REPL against the page | Reading errors, poking at window/page state, quick scripting |
| Sources | View files, set breakpoints, step through JS, edit + save (Workspaces) | Debugging logic, watching variables, blackboxing scripts |
| Network | Every request/response, timing, headers, throttling | Checking payload size, caching, CORS, slow-connection behaviour |
| Performance | Record a trace of main-thread work; measures Core Web Vitals | Diagnosing jank, long tasks, layout thrash, slow LCP |
| Memory | Heap snapshots + allocation timelines | Hunting memory leaks, detached DOM nodes |
| Application | Storage (localStorage, IndexedDB, cookies), Service Workers, Cache, manifest | Debugging PWAs, auth tokens, offline behaviour |
| Lighthouse | One-click audit: Performance, Accessibility, Best Practices, SEO, PWA | A scored baseline + prioritised fixes |
| Recorder | Record, replay & measure a user flow; export to Puppeteer/Playwright | Turning a manual flow into a repeatable test |
| Security | Certificate + mixed-content checks | Verifying a page is fully HTTPS |
| Rendering (drawer) | Paint flashing, FPS meter, emulate prefers-color-scheme, CSS media | Spotting unnecessary repaints, testing dark mode |
| Coverage (drawer) | Unused JS/CSS per file | Trimming dead code from the bundle |
| Issues / Changes / Animations / Sensors / WebAuthn | Aggregated warnings · CSS diff · animation timeline · geolocation/orientation emulation · authenticator emulation | Targeted debugging of each concern |
Open drawer tools with Esc → the ⋮ menu → More tools, or via the Command Menu.
Elements + Styles — DOM & CSS
Inspect Cmd/Ctrl+Shift+C, then click an element
Edit DOM double-click a node / press F2
Force state :hov → toggle :hover/:focus/:active to debug states
Color click a swatch in Styles for the eyedropper + contrast ratioThe contrast ratio readout in the color picker is your fastest accessibility check — it flags AA/AAA pass/fail inline.
Console — REPL + logging
// $0 is the currently-selected Elements node; $$ is querySelectorAll
$0.getBoundingClientRect();
$$('img:not([alt])'); // find images missing alt text
monitorEvents($0, 'click'); // log events on an element
copy(JSON.stringify(window.__DATA__)); // copy a value to clipboard
console.table(performance.getEntriesByType('resource')); // request tableSources — the debugger
Breakpoint click a line number
Conditional bp right-click a line → "Add conditional breakpoint"
Logpoint inject a console.log without editing source
DOM breakpoint Elements → right-click → "Break on" → subtree/attribute change
Step F10 over · F11 into · Shift+F11 out · F8 resumeNetwork — payload & throttling
Throttling presets (Chrome 127+): Offline · 3G · Slow 4G · Fast 4G · Custom
(formerly "Slow 3G"/"Fast 3G"; DevTools no longer prints exact kbps —
the presets are tuned to match real-world conditions.)
Disable cache check the box (while DevTools is open) to test cold loads
Filter `larger-than:500k`, `-domain:*.google.com`, `mixed-content:all`
Copy as cURL right-click a request → reproduce it from the terminalcodeAmani habit: test every page under 3G (the old "Slow 3G") with Disable cache on. If LCP blows past 2.5s, the bundle is too heavy for the median Kenyan connection — lazy-load and split before shipping.
Performance — traces & Core Web Vitals
Record click ● (or Cmd/Ctrl+E) → interact → stop
Reload trace click ⟳ to capture a full page load
Read it Main track = JS/layout/paint; red-cornered bars = long tasks (>50ms)
CWV LCP/CLS/INP markers overlay the timelineApplication — storage & PWAs
Service Workers update-on-reload, bypass-for-network, push test
Storage localStorage / sessionStorage / IndexedDB / Cookies (edit inline)
Clear storage one button to reset to a first-visit state
Manifest install + icon/colour validation for PWAsPart 2 — Core Web Vitals & performance budgets
The scoreboard for real-user performance. Thresholds are measured at the 75th percentile of page loads, segmented by mobile/desktop (source: web.dev/vitals).
| Metric | Measures | Good | Needs improvement | Poor |
|---|---|---|---|---|
| LCP — Largest Contentful Paint | Loading | ≤ 2.5 s | 2.5 – 4.0 s | > 4.0 s |
| INP — Interaction to Next Paint | Interactivity | ≤ 200 ms | 200 – 500 ms | > 500 ms |
| CLS — Cumulative Layout Shift | Visual stability | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |
INP replaced FID as a Core Web Vital in 2024 — it measures the latency of all interactions, not just the first.
Supporting diagnostics:
| Metric | Good threshold | Helps explain |
|---|---|---|
| FCP — First Contentful Paint | ≤ 1.8 s | LCP |
| TTFB — Time to First Byte | ≤ 0.8 s | FCP / LCP |
Measure vitals in code
npm install web-vitalsimport { onLCP, onINP, onCLS } from "web-vitals";
// Field data — report from real users to your analytics endpoint
onLCP((m) => navigator.sendBeacon("/vitals", JSON.stringify(m)));
onINP((m) => navigator.sendBeacon("/vitals", JSON.stringify(m)));
onCLS((m) => navigator.sendBeacon("/vitals", JSON.stringify(m)));Lab vs field: DevTools / Lighthouse give you lab data (one synthetic run). web-vitals + analytics give you field data (real users, the 75th-percentile that actually counts). Optimise in the lab; verify in the field.
Part 3 — Chrome DevTools Protocol (CDP)
Everything the UI does is a CDP command. Playwright, Puppeteer, Lighthouse, and the MCP all speak it. Start Chrome with a debugging port and you can drive it from anything:
# Launch Chrome with the protocol exposed
google-chrome --remote-debugging-port=9222 --headless=new
# List targets (tabs) — each has a websocket debugger URL
curl http://localhost:9222/json// Raw CDP via Puppeteer's session — e.g. emulate a slow network
const client = await page.createCDPSession();
await client.send("Network.emulateNetworkConditions", {
offline: false, downloadThroughput: (1.6 * 1024 * 1024) / 8,
uploadThroughput: (750 * 1024) / 8, latency: 150,
});
await client.send("Emulation.setCPUThrottlingRate", { rate: 4 }); // 4× slowdownDomains you'll use most: Page, Network, Runtime, Performance, Emulation, DOM, Accessibility. Full reference: chromedevtools.github.io/devtools-protocol.
Part 4 — chrome-devtools MCP for Claude Code
The official chrome-devtools-mcp server (Google LLC, Apache-2.0) wraps a Puppeteer-controlled Chrome as MCP tools, so Claude Code can drive a real browser.
Setup
# Add via the Claude Code CLI
claude mcp add chrome-devtools -- npx -y chrome-devtools-mcp@latest// .mcp.json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest"],
"env": {
"CHROME_PATH": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
}
}
}
}Available MCP tools
Grouped by what they do (names as exposed by the server):
| Group | Tools |
|---|---|
| Navigation / pages | new_page · navigate_page · list_pages · select_page · close_page · wait_for |
| Interaction | click · hover · drag · fill · fill_form · type_text · press_key · upload_file · handle_dialog |
| Inspect / capture | take_snapshot (accessibility tree) · take_screenshot · evaluate_script |
| Console | list_console_messages · get_console_message |
| Network | list_network_requests · get_network_request |
| Performance | performance_start_trace · performance_stop_trace · performance_analyze_insight · lighthouse_audit |
| Emulation / memory | emulate (device/network/CPU) · resize_page · take_heapsnapshot |
take_snapshotreturns the accessibility tree — both your a11y check and the most reliable way for the agent to locate elements (by role/name) instead of brittle CSS selectors.
Part 5 — Claude Code commands, hooks & testing workflows
Slash command: visual + Lighthouse review
.claude/commands/screenshot.md:
Open $ARGUMENTS in a browser and review it.
1. `new_page` then `navigate_page` to $ARGUMENTS
2. `resize_page` to 1280×900, then `take_screenshot`
3. `emulate` a "Slow 4G" network and reload; `take_screenshot` again
4. `lighthouse_audit` for performance + accessibility scores
5. `list_console_messages` for errors
Report: layout/contrast issues, the two screenshots' differences, Lighthouse
scores vs the budgets (LCP ≤ 2.5s, CLS ≤ 0.1), and console errors with fixes.Usage: /project:screenshot http://localhost:3000/dashboard
Hook: nudge a browser check after a UI change
.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command",
"command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE '\\.(tsx|jsx|css)$'; then echo 'UI file changed — verify with the chrome-devtools MCP before claiming done'; fi" }
]
}
]
}
}Playwright — E2E tests
npm init playwright@latest
npx playwright test --ui # interactive runner
npx playwright codegen <url> # record a test by clicking
npx playwright show-trace trace.zip// tests/auth.spec.ts
import { test, expect } from "@playwright/test";
test("user can sign in", async ({ page }) => {
await page.goto("/login");
await page.getByTestId("email").fill("test@example.com");
await page.getByTestId("password").fill("secret123");
await page.getByTestId("submit").click();
await expect(page).toHaveURL("/dashboard");
await expect(page.getByRole("heading", { level: 1 })).toContainText("Welcome");
});
test("renders within a 3G budget", async ({ page }) => {
const client = await page.context().newCDPSession(page);
await client.send("Network.emulateNetworkConditions", {
offline: false, latency: 300,
downloadThroughput: (400 * 1024) / 8, uploadThroughput: (400 * 1024) / 8,
});
const t0 = Date.now();
await page.goto("/", { waitUntil: "load" });
expect(Date.now() - t0).toBeLessThan(8000); // hard ceiling on a slow link
});Puppeteer — scripted browser
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();
await page.emulateNetworkConditions(puppeteer.networkConditions["Slow 3G"]);
await page.goto("https://example.com", { waitUntil: "networkidle0" });
await page.screenshot({ path: "shot.png", fullPage: true });
await browser.close();Lighthouse CI — budgets in CI
# .github/workflows/lighthouse.yml
name: Lighthouse
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm ci && npm run build
- run: npm install -g @lhci/cli && lhci autorun
env: { LHCI_GITHUB_APP_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }// lighthouserc.js — fail the PR if budgets regress
module.exports = {
ci: {
collect: { startServerCommand: "npm run preview", url: ["http://localhost:4173/"], numberOfRuns: 3 },
assert: { assertions: {
"categories:performance": ["warn", { minScore: 0.8 }],
"categories:accessibility": ["error", { minScore: 0.9 }],
"largest-contentful-paint": ["error", { maxNumericValue: 2500 }],
"cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
} },
upload: { target: "temporary-public-storage" },
},
};Common use cases
| Goal | Fastest path |
|---|---|
| Verify a UI change | chrome-devtools MCP navigate_page + take_screenshot |
| Accessibility audit | take_snapshot (a11y tree) or lighthouse_audit |
| Check the slow-connection budget | Network panel 3G + Disable cache, or emulate in the MCP |
| Find a memory leak | Memory panel heap snapshots over time / take_heapsnapshot |
| Diagnose jank | Performance trace → look for long tasks (>50ms) |
| Reproduce an API call | Network → right-click → Copy as cURL |
| Record a test | Recorder panel → export to Playwright, or playwright codegen |
| Trim the bundle | Coverage drawer → delete unused JS/CSS |
Troubleshooting
| Issue | Fix |
|---|---|
| Chrome not found (MCP) | Set CHROME_PATH to your Chrome/Chromium binary |
| Screenshot blank | Wait for load: MCP wait_for, or Playwright waitForLoadState("networkidle") |
| CDP connection refused | Launch Chrome with --remote-debugging-port=9222 |
| Playwright browser missing | npx playwright install chromium |
| Lighthouse times out | Raise --max-wait-for-load, or reduce CPU throttling |
| MCP server won't connect | Ensure Chrome can launch headlessly; check npx -y chrome-devtools-mcp@latest runs |
| Throttling preset looks different | Chrome 127 renamed them: "Slow 3G"→"3G", "Fast 3G"→"Slow 4G", + "Fast 4G" |
| INP shows "—" | INP needs a real interaction; click/scroll, then re-measure |
codeAmani notes
- Test on the network our users have. The African-market mandate is 2G/3G-first. Make 3G + Disable cache the default test condition, and gate PRs on LCP ≤ 2.5s and CLS ≤ 0.1 via Lighthouse CI. A page that's fast on office fibre but fails at 3G fails our users.
- Verify, don't assume. Use the
chrome-devtoolsMCP to actually load a page after a UI change — screenshot it, check the console, run a Lighthouse pass — before claiming done. This is the browser half of the "evidence before assertions" rule. - Mobile-first emulation. Device Mode +
emulatelets you check the median Android viewport and a slow CPU (Emulation.setCPUThrottlingRate) — closer to a real low-end handset than your laptop. - M-Pesa flows. Drive the STK-Push UI end-to-end in a real browser (Recorder → Playwright), and use the Network panel to confirm the callback round-trip and that no secrets (Daraja keys, tokens) leak into client requests or the console.
- Secrets stay server-side. Never paste credentials into the Console on a production page, and scrub
evaluate_scriptsnippets of any secret before saving them to a command or repo. DevTools output can end up in screenshots and logs.