chrome-devtools-mcp 工程协作规范:AGENTS.md 中的构建、TypeScript 与测试纪律及源码级实现对照
本文以 AGENTS.md 为核心,系统讲解 chrome-devtools-mcp(面向编码 Agent 的 Chrome DevTools MCP Server 与 CLI)仓库对开发者和 AI 协作者的全部工程约定:构建与测试命令、TypeScript 类型纪律、测试分层与 Mock 规范、Sinon 断言实践。读完你可以直接按照该仓库的标准提交符合规范、可被 CI 验证的代码与测试,并理解每条规则背后的实现机制。
一、仓库定位与基本运行指令
AGENTS.md 开篇明确了仓库性质:它同时包含一个 MCP Server 和一个 Chrome DevTools 的 CLI,两者共享同一套 TypeScript 源码与测试体系。文档第一条指令要求:只允许使用 package.json 中定义的 scripts 来执行命令。对照 package.json 可以看到,与日常开发直接相关的核心脚本为:
| 命令 | 实际执行内容 | 用途 |
|---|---|---|
npm run build |
tsc && node scripts/post-build.ts |
编译 TypeScript 并执行后处理,验证构建正确性 |
npm run test |
npm run build && node scripts/test.js --test-skip-pattern=THIRD_PARTY_NOTICES |
先构建再运行全部测试 |
npm run test tests/McpContext.test.ts |
同上,但只运行单个测试文件 | 构建并运行指定测试 |
npm run format |
eslint --cache --fix . && prettier --write --cache . |
自动修复格式并暴露 lint 错误 |
npm run typecheck |
tsc --noEmit |
仅做类型检查 |
单测运行的细节值得深入。scripts/test.js 是测试的真正入口,它做了几件关键的事:
- 路径映射:用户传入
.ts路径(如tests/McpContext.test.ts)时,自动替换为build/tests/McpContext.test.js再交给 Node 测试运行器执行,因此必须先build——这就是npm run test要先构建的原因; - 使用 Node 内置测试运行器:以
node --test启动,附加--import ./build/tests/setup.js预加载测试环境、--test-timeout=120000(单测 120 秒超时)、--test-force-exit; - Reporter 选择:CI 环境(设置
NODE_TEST_REPORTER或CI)使用spec输出,本地默认dot; - 环境隔离:注入
CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS=true、CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=true等环境变量,确保测试期间不产生遥测与更新检查; - 重试机制:
--retry标志最多允许 3 次运行,用于吸收偶发性失败。
测试环境的初始化在 tests/setup.ts 中完成:它通过 overrideDevToolsGlobals 替换 DevTools 全局资源加载,并配置了快照路径(build/tests 映射回 tests 目录)与快照序列化器(用 String 替代默认 JSON.stringify,让换行在快照中可读)。
二、TypeScript 类型纪律
AGENTS.md 对 TypeScript 的使用列出了七条硬性规则:
- 禁止使用
any类型; - 禁止使用
as关键字做类型转换; - 禁止使用
!非空断言; - 禁止使用
// @ts-ignore注释; - 禁止使用
// @ts-nocheck注释; - 禁止使用
// @ts-expect-error注释; - 优先使用
for..of而非forEach; - 不要对已经类型安全的类型做冗余检查(例如对静态类型变量做多余的
typeof判断)。
这些规则并非纯口头约定,eslint.config.js 中有对应的机器强制项:
@typescript-eslint/no-explicit-any: error(仅允许ignoreRestArgs场景),直接禁止any的显式声明;@typescript-eslint/no-floating-promises: error,禁止未 await 的 Promise,这与"不要冗余检查"一样是对异步代码正确性的约束;@typescript-eslint/consistent-type-imports: error与consistent-type-exports: error,强制类型导入/导出的写法一致性;curly: ['error', 'all']强制所有分支语句带花括号。
从源码结构看,禁止 @ts-expect-error 等注释规则意味着所有类型问题都必须通过重构类型定义本身来解决,而不是压制编译器——这保证了 src/ 下大量与 Puppeteer、DevTools Protocol 打交道的代码(如 src/McpPage.ts、src/ToolHandler.ts)保持类型可追踪。
三、测试结构与关注点分离
优先 Mock 单元测试,而非真实浏览器测试
AGENTS.md 测试章节的第一原则是:优先基于 mock 的单元测试,避免真实浏览器测试。不要使用 withMcpContext 或启动真实浏览器,除非测试确实需要真实浏览器或 DevTools 协议集成(例如实时 CDP 事件、浏览器生命周期、二级会话)。理由是 Puppeteer 上游已经测试过浏览器行为,而单元测试更快、无浏览器开销。
这里有个细节:withMcpContext 与 withBrowser 这两个真实浏览器辅助函数就定义在 tests/utils.ts 中。withBrowser 按启动参数缓存浏览器实例、内置 60 秒超时与最多 3 次重试、支持 blockedUrlPattern/allowedUrlPattern 白黑名单;withMcpContext 在其上封装出完整的 McpResponse + McpContext 环境。因此使用它们是"真实浏览器测试"的明确标志,应只用于确实需要浏览器行为的场景(如 tests/tools/emulation.test.ts 中验证离线网络模拟的用例)。
工具处理器测试(tests/tools/*.test.ts)
规则要求 tests/tools/ 下的测试只验证两件事:
- 工具处理器解析并校验参数;
- 以精确期望的参数调用
page、context或response上对应的方法。
并且明确禁止在 mock 中重新实现业务逻辑或状态跟踪(例如不要在 mock 方法里模拟状态变化)。这一规则对应着实际的调用链:src/ToolHandler.ts 中的 ToolHandler.handle() 是统一入口——先校验禁用状态与未知参数(buildUnknownArgumentsMessage 生成错误提示),经 toolMutex 串行化后,对页面级工具以 {params, page} 调用 this.tool.handler(...),对上下文级工具以 {params} 调用,并传入 response 与 context。也就是说,业务逻辑全部在 handler 与 McpPage/McpContext 内部,工具层测试只需断言"参数如何流转、下游方法被以什么参数调用"即可,不需要 mock 去重放业务。
核心类测试(如 tests/McpPage.test.ts)
对核心类,规则要求实例化真实类 + mock 依赖:例如用 createMockPuppeteerPage() 提供的 mock Puppeteer page 构造 new McpPage(...),然后断言该类以期望参数调用了底层 Puppeteer 方法。这样既保留了真实类的业务逻辑参与,又切断了与真实浏览器的耦合。
四、Mock 编写指南
AGENTS.md 的 Mock 章节给出了五条可执行的规范:
- Mock 集中管理:所有可复用 mock 工厂集中放在
tests/mocks.ts,测试直接从该文件导入(不要从tests/utils.ts再导出)。在当前仓库快照中,可观察到的 mock 工厂(getMockPage、getMockBrowser、mockListener、getMockRequest、getMockResponse)尚位于 tests/utils.ts 内——从这些实现看,mockListener()返回带on/off/emit的最小事件对象,getMockRequest刻意镜像了 PuppeteerredirectChain()每次返回新数组的行为,防止格式化器共享并意外修改同一数组。这正是 AGENTS.md 要求迁移集中化的现状与目标态; - 使用
sinon.createStubInstance(Class):不要手写 mock 对象或自定义 mock 接口,用createStubInstance让原型上的所有方法自动成为 stub; - 类型标注:mock 类型使用
sinon.SinonStubbedInstance<Class>(如MockMcpPage、MockMcpContext、MockMcpResponse)。sinon与@types/sinon均为 package.json 中声明的 devDependencies; - Handler mock 辅助函数:工具处理器测试统一用
const {page, context, response} = createHandlerMocks();一次建立三个 mock; - 保持 mock 通用:不要为某个具体工具或测试套件定制 mock;
- 命名约定:辅助函数与变量名用
mock而非fake(如createMockPuppeteerPage、createMockMcpPage);被 mock 的 Puppeteer page 实例统一命名为pptrPage。
五、断言与 Sinon 最佳实践
AGENTS.md 明确要求用 Sinon 自身的断言 API,替代 Node assert 手写验证:
- 不要写
assert.ok(stub.calledOnce)或assert.deepStrictEqual(stub.firstCall.args[0], ...); - 单次调用且参数需精确匹配时:
sinon.assert.calledOnceWithExactly(stub, ...args); - 校验后续调用:
sinon.assert.calledWithExactly(stub.secondCall, ...args); - 断言某方法未被调用:
sinon.assert.notCalled(stub); - 清理 stub:使用 Sinon 的测试套件必须包含
afterEach(() => sinon.restore()),防止 stub 状态跨用例泄漏。
与之配套的是该仓库的快照断言体系。测试基于 Node 内置 node:test 运行器,快照能力由 t.assert.snapshot 提供,路径解析与序列化器在 tests/setup.ts 中配置;快照文件以 .js.snapshot 后缀存放于 tests/ 下(如 tests/tools/lighthouse.test.js.snapshot)。当快照需要更新时,用 npm run test:update-snapshots(对应 node scripts/test.js --test-update-snapshots),而不是手工编辑快照文件。此外 tests/utils.ts 中的 stabilizeResponseOutput 会把日期、本地端口、User-Agent、快照文件路径等易变内容替换为占位符,保证快照稳定——这也是"只测试真实场景"原则在输出层面的落实。
六、测试代码整洁度
- 不要为简短、自描述的 mock 函数或测试添加冗余注释或冗长 JSDoc;
- 只测试真实场景,避免测试现实中不可能发生的冗余或人为调用;
- 新建测试文件的版权头使用当前年份(2026)。
版权头要求有机制背书:eslint.config.js 将自定义规则 @local/check-license: 'error' 设为错误级,该规则实现在 scripts/eslint_rules/check-license-rule.js。因此 npm run format(含 eslint --fix)之外的 npm run check-format 会在 CI 上拦住版权头不合规的文件,AGENTS.md 中"Copyright 2026"的要求与现有测试文件头(如 tests/utils.ts 顶部的 Copyright 2025 Google LLC / SPDX-License-Identifier: Apache-2.0)保持同一格式。
七、third_party 边界:只读的 git submodule
AGENTS.md 最后一条规则:除实验目的外,永不修改 third_party/devtools-frontend——它是一个 git submodule,是真实代码库的镜像。仓库内有两处佐证:
- package.json 的
sync脚本为npm ci && git submodule update --init,说明该目录由子模块机制管理而非仓库本体; - eslint.config.js 的
globalIgnores将third_party/devtools-frontend/**整体排除在 lint 之外,src/third_party/lighthouse-devtools-mcp-bundle.js亦被忽略。
这意味着对 src/third_party/ 的适配(如 src/third_party/index.ts 对 DevTools 类型的封装)是本仓库应做的边界工作,而镜像源码本身不应在本地分支上改动。
八、日常开发工作流速查
综合以上规范,一次标准的变更流程为:
# 1. 修改代码/测试后,先格式化并修复 lint 问题
npm run format
# 2. 构建并跑全量测试验证正确性
npm run test
# 3. 只验证某个测试文件
npm run test tests/McpContext.test.ts
# 4. 需要更新快照时
npm run test:update-snapshots
# 5. 需要更新工具文档/CLI 生成物时(gen = build + cli:generate + docs:generate + update-metrics + format)
npm run gen
这套规范的价值在于:命令全部收敛于 package.json scripts,类型与代码风格由 ESLint/Prettier 机器强制,测试分层(handler 测试 vs 核心类测试 vs 真实浏览器测试)与断言方式在 AGENTS.md 中显式化——无论执行者是人还是编码 Agent,产出都能被同一套构建与测试体系无歧义地校验。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00