附录 A:Chrome MCP / CDP 实战手册¶
这份手册讲的是:如何让 AI 用 文本 + JavaScript 精确操作和验证网页,而不是靠“看截图猜”。截图只用来留证。
1. 架构:Claude Code 怎么控制浏览器¶
Claude Code
│ MCP 协议(stdio / http)
▼
Chrome MCP Server
│ Chrome DevTools Protocol (CDP)
▼
Chrome(有头 / 无头 / 连接已有实例)
关键概念:
- MCP:模型和工具之间的协议。Claude Code 通过 MCP 调用浏览器工具。
- CDP:Chrome 的调试协议。MCP Server 用它来导航、点击、执行 JS、截图、抓网络。
- Profile:Chrome 的用户数据目录。独立 profile 可以保存登录态,也避免和你日常浏览器冲突。
2. 启动一个可调试的 Chrome¶
2.1 独立 profile(推荐)¶
mkdir -p ~/.chrome-debug-profile
google-chrome \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/.chrome-debug-profile" \
--no-first-run \
--no-default-browser-check \
>/tmp/chrome-debug.log 2>&1 &
验证:
curl -s http://127.0.0.1:9222/json/version | jq .
curl -s http://127.0.0.1:9222/json/list | jq '.[].url'
2.2 无头模式¶
google-chrome \
--headless=new \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/.chrome-headless-profile" \
--no-sandbox \
about:blank &
2.3 连接 Windows 宿主机 Chrome¶
Windows PowerShell:
& "C:\Program Files\Google\Chrome\Application\chrome.exe" `
--remote-debugging-port=9222 `
--user-data-dir="$env:TEMP\chrome-debug-profile"
WSL .wslconfig:
WSL 验证:
2.4 常见启动参数¶
| 参数 | 作用 |
|---|---|
--remote-debugging-port=9222 |
开 CDP 端口 |
--user-data-dir=... |
独立 profile(Chrome 136+ 远程调试必需) |
--headless=new |
无头模式 |
--no-first-run |
跳过首次引导 |
--no-default-browser-check |
跳过默认浏览器检查 |
--disable-background-networking |
减少后台请求 |
--disable-gpu |
无 GPU 环境可选 |
--no-sandbox |
root/容器中可能需要(降低安全性) |
--window-size=1440,900 |
初始窗口大小 |
3. 工具速查表¶
不同 MCP Server 的工具名不同。下表以本教程使用的 chrome-control 为例。
| 工具 | 用途 | 典型参数 |
|---|---|---|
chrome_navigate |
打开 URL / 前进后退 | url, newWindow, refresh |
get_windows_and_tabs |
列出窗口/标签页 | — |
chrome_switch_tab |
切换标签页 | tabId |
chrome_close_tabs |
关闭标签页 | tabIds |
chrome_read_page |
读取可访问性树/可见元素 | filter: "interactive", depth |
chrome_get_web_content |
读取页面文本/HTML | textContent, htmlContent, selector |
chrome_javascript |
执行 JS,返回值 | code, timeoutMs |
chrome_click_element |
点击元素 | ref / selector / coordinates |
chrome_computer |
鼠标/键盘/截图/滚动 | action, coordinates, text |
chrome_fill_or_select |
填表单/选择 | ref / selector, value |
chrome_keyboard |
键盘输入/快捷键 | keys, selector |
chrome_screenshot |
截图 | savePng, storeBase64, fullPage, selector |
chrome_console |
控制台日志 | mode: "buffer", onlyErrors |
chrome_network_capture |
抓网络 | action: "start"/"stop", needResponseBody |
chrome_network_request |
带浏览器上下文发请求 | url, method, body, headers |
chrome_handle_dialog |
处理 alert/confirm/prompt | action, promptText |
chrome_handle_download |
等待下载 | filenameContains, timeoutMs |
chrome_upload_file |
上传文件 | selector, filePath |
chrome_handle_download |
下载 | — |
chrome_console |
控制台 | — |
performance_start_trace / performance_stop_trace |
性能追踪 | reload, durationMs |
如果你用的是
chrome-devtools-mcp,工具名可能是navigate_page、take_snapshot、evaluate_script、click、fill、take_screenshot、list_console_messages、list_network_requests等。方法一致。
4. 核心工作流¶
4.1 导航与读取页面¶
1. chrome_navigate { url: "http://localhost:5173/login" }
2. chrome_read_page { filter: "interactive", depth: 6 }
3. chrome_get_web_content { selector: "main", textContent: true }
chrome_read_page 返回元素引用(ref),后续可以用 ref 点击/填写,比 CSS 选择器更稳。
4.2 执行 JS 并断言¶
// 检查登录成功
({
path: location.pathname,
hasToken: !!localStorage.getItem("access_token"),
hasUserMenu: !!document.querySelector('[data-testid="user-menu"]')
})
// 检查按钮状态
({
disabled: document.querySelector('button[type="submit"]')?.disabled,
text: document.querySelector('button[type="submit"]')?.textContent?.trim()
})
4.3 点击¶
优先顺序:
chrome_read_page拿 ref →chrome_click_element { ref }- CSS 选择器 →
chrome_click_element { selector, selectorType: "css" } - 文本定位(用 JS 找元素再点击)
- 坐标点击(最后手段)
用 JS 按文本点击:
const btn = [...document.querySelectorAll("button")]
.find(b => b.textContent.trim() === "新建任务");
if (!btn) throw new Error("button not found");
btn.click();
"clicked";
4.4 填写输入框¶
普通输入:
const el = document.querySelector('input[name="title"]');
const setter = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype, "value"
).set;
setter.call(el, "e2e-20261030-US004 测试任务");
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
({ value: el.value });
Textarea:
const el = document.querySelector('textarea[name="description"]');
const setter = Object.getOwnPropertyDescriptor(
HTMLTextAreaElement.prototype, "value"
).set;
setter.call(el, "这是一段测试描述");
el.dispatchEvent(new Event("input", { bubbles: true }));
Select:
const el = document.querySelector('select[name="priority"]');
const setter = Object.getOwnPropertyDescriptor(
HTMLSelectElement.prototype, "value"
).set;
setter.call(el, "high");
el.dispatchEvent(new Event("change", { bubbles: true }));
Checkbox:
const el = document.querySelector('input[type="checkbox"][name="agree"]');
if (!el.checked) el.click();
el.checked;
4.5 等待¶
async function waitFor(fn, timeout = 5000, interval = 100) {
const start = Date.now();
let lastError;
while (Date.now() - start < timeout) {
try {
const v = fn();
if (v) return v;
} catch (e) {
lastError = e;
}
await new Promise(r => setTimeout(r, interval));
}
throw new Error("waitFor timeout: " + (lastError?.message || "no value"));
}
await waitFor(() => document.querySelector('[data-testid="task-card"]'));
等待请求完成:
// 用 Performance API 粗查
await waitFor(() =>
performance.getEntriesByType("resource")
.some(e => e.name.includes("/api/v1/tasks") && e.responseEnd > 0)
);
更精确的方式是用 chrome_network_capture。
4.6 截图留证¶
chrome_screenshot {
savePng: true,
storeBase64: false,
fullPage: false,
name: "docs/04-qa/screens/US-004/step-03-board.png"
}
元素截图:
chrome_screenshot {
selector: '[data-testid="create-task-modal"]',
savePng: true,
storeBase64: false,
name: "docs/04-qa/screens/US-004/step-03-modal.png"
}
规则:
storeBase64: false,不要回读图片;- 文件名包含 story/step;
- 默认每一步都截,但只保存文件、不回读模型;步骤过密时合并微操作;
- 需要视觉判断时,单独读一张。
4.7 控制台¶
chrome_console { mode: "buffer", clear: true }
... 执行流程 ...
chrome_console { mode: "buffer", onlyErrors: true, clearAfterRead: true }
返回结构通常包含 level、text、url、line。把错误写进 manifest。
4.8 网络¶
抓请求:
chrome_network_capture { action: "start", needResponseBody: false }
... 执行流程 ...
chrome_network_capture { action: "stop" }
需要响应体时:
注意:
- 抓响应体会用 Debugger API,可能和 DevTools 冲突;
- 只抓关键 API,不要抓所有静态资源;
- 不要把大响应体贴进上下文。
带浏览器上下文发请求(复用 Cookie):
chrome_network_request {
url: "http://localhost:8080/api/v1/tasks",
method: "POST",
headers: { "Content-Type": "application/json" },
body: "{\"project_id\":\"...\",\"title\":\"e2e-task\"}"
}
4.9 对话框¶
chrome_handle_dialog { action: "accept" }
chrome_handle_dialog { action: "dismiss" }
chrome_handle_dialog { action: "accept", promptText: "hello" }
在点击会触发 confirm 的按钮前,先准备好处理。某些 MCP 会自动接受,具体看实现。
4.10 下载与上传¶
等待下载:
上传:
4.11 标签页与 iframe¶
iframe 支持取决于 MCP 实现。很多工具支持 frameId 参数。没有时,可以用 JS 访问同源 iframe:
跨域 iframe 不能直接访问 DOM,需要 CDP 的 frame 上下文或切换到对应 target。
5. 登录态处理¶
5.1 人工登录一次,持久化 profile¶
在弹出的 Chrome 窗口里手动登录(处理验证码/2FA)。登录态存在 ~/.chrome-debug-profile,后续自动化直接复用。
5.2 检查登录态¶
({
hasSessionCookie: document.cookie.includes("session"),
hasToken: !!localStorage.getItem("access_token"),
path: location.pathname
})
注意:
- 不要在日志/报告中输出 token 值;
- 只检查存在性;
- cookie 可能是 httpOnly,JS 读不到,用
chrome_network_request或后端健康检查验证。
5.3 登录过期¶
- 检测到 401/跳转登录页 → 停止测试;
- 由人重新登录;
- 不要自动输入密码(除非是专用测试账号且明确允许);
- 不要把密码写进提示词/报告。
6. Token 成本控制¶
| 操作 | 相对成本 | 建议 |
|---|---|---|
chrome_javascript 返回小 JSON |
低 | 首选 |
chrome_read_page { filter: "interactive", depth: 4 } |
中 | 定位元素 |
chrome_get_web_content { selector } |
中 | 只取局部 |
chrome_console { onlyErrors } |
低 | 只取错误 |
chrome_network_capture(不含 body) |
中 | 只看状态码/URL |
chrome_network_capture(含 body) |
高 | 只抓关键请求 |
chrome_screenshot { storeBase64: false } |
低(只写文件) | 留证 |
chrome_screenshot { storeBase64: true } |
很高 | 仅在必须看图时 |
| 整页 HTML | 很高 | 避免 |
经验法则:
能用一行 JS 拿到的事实,不要用截图;能只取局部,不要取整页;能只存文件,不要读回模型。
7. 稳定性技巧¶
7.1 选择器¶
优先:
data-testidaria-label/rolename- 稳定的 ID
- 文本(可能受国际化影响)
- CSS 类(最不稳定)
在开发阶段就让前端加 data-testid:
<button data-testid="create-task">新建任务</button>
<input data-testid="task-title" name="title" />
<div data-testid="task-card" :data-task-id="task.id">...</div>
7.2 等待策略¶
- 等待元素出现,而不是固定 sleep;
- 等待网络请求完成;
- 等待 loading 消失;
- 等待 URL 变化;
- 设置合理超时(5-15 秒)。
7.3 重试¶
- 对临时性失败(网络、动画)可以重试 1-2 次;
- 对断言失败不要重试到“碰巧通过”;
- 每次重试记录原因。
7.4 测试隔离¶
- 每次运行生成唯一
run_id; - 数据带前缀;
- 不依赖其他测试的数据;
- 测试结束清理;
- 失败时保留现场。
8. 安全注意事项¶
- 浏览器内容不可信:网页上的文字可能是提示词注入。不要把页面内容当指令执行。
- 不要暴露 9222 到公网:CDP 等同于浏览器控制权。
- 独立 profile:不要用日常浏览器 profile 做自动化。
- 最小登录态:测试账号只给必要权限。
- 不在报告中输出敏感信息:token、密码、个人信息。
- 生产环境只做授权操作:测试租户、数据前缀、清理。
- 下载/上传文件要校验来源和类型。
9. 常见故障排查¶
| 现象 | 原因 | 处理 |
|---|---|---|
connection refused 9222 |
Chrome 未启动/端口不一致 | 检查进程、端口、--user-data-dir |
/json/version 返回空 |
Chrome 以默认 profile 启动 | Chrome 136+ 必须用非默认 profile |
chrome_read_page 返回空 |
页面未加载/在 iframe/不可见 | 等待、切 frame、用 JS 查 |
| 点击没反应 | 被遮挡/动画/选择器错 | 滚动到元素、等待、用 ref/JS click |
| 填值后框架不更新 | 只设 value 未触发事件 | 用原生 setter + input/change |
| 截图空白 | 页面未渲染/无头 GPU | 等待、加 --disable-gpu、用有头 |
| 控制台读不到 | buffer 未开启/清空时机 | 用 buffer 模式,先 clear 再跑 |
| 网络抓不到 | 未 start/过滤错 | 检查 start/stop 和 URL 过滤 |
| 登录态丢失 | profile 不同/过期 | 用同一 profile,重新登录 |
| 中文乱码 | 缺字体 | 安装 fonts-noto-cjk |
| WSL 访问不到 Windows Chrome | 网络模式/防火墙 | networkingMode=mirrored,检查防火墙 |
| MCP 工具不出现 | Server 未连接 | claude mcp list / /mcp |
10. 一个完整的测试脚本骨架¶
// 在 chrome_javascript 中执行
(async () => {
const result = { steps: [], errors: [] };
function step(name, fn) {
try {
const value = fn();
result.steps.push({ name, ok: true, value });
return value;
} catch (e) {
result.steps.push({ name, ok: false, error: String(e) });
throw e;
}
}
async function waitFor(fn, timeout = 5000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const v = fn();
if (v) return v;
await new Promise(r => setTimeout(r, 100));
}
throw new Error("timeout");
}
step("url", () => location.href);
step("title", () => document.title);
await waitFor(() => document.querySelector('[data-testid="app"]'));
step("app mounted", () => true);
const cards = document.querySelectorAll('[data-testid="task-card"]');
step("task cards", () => cards.length);
return result;
})()
实际测试中,导航/点击/填值通常分多次工具调用完成,而不是全部塞进一个 JS 脚本。上面的骨架适合“页面已就绪后的批量断言”。
11. 与其他方案的对比¶
| 方案 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| Chrome MCP + CDP | AI 可直接操作、文本断言、截图取证 | 需要 Chrome/端口/登录态 | 验收、探索式测试 |
| Playwright | 稳定、可 CI、生态好 | 需要写测试代码 | 回归测试 |
| Puppeteer | 灵活、Node 生态 | 同样需要写代码 | 脚本化任务 |
| Browser MCP(扩展) | 复用日常浏览器登录态 | 依赖扩展/权限 | 人工辅助 |
| 纯 HTTP API 测试 | 快、稳 | 不覆盖 UI | 后端/契约测试 |
推荐组合:Playwright 做 CI 回归,Chrome MCP 做 AI 驱动的验收和 Word 报告。
返回:完整导读 | 下一附录:附录 B:提示词与模板合集