首页
/ chrome-devtools-mcp 工程协作规范:AGENTS.md 中的构建、TypeScript 与测试纪律及源码级实现对照

chrome-devtools-mcp 工程协作规范:AGENTS.md 中的构建、TypeScript 与测试纪律及源码级实现对照

2026-09-05 18:56:49作者:齐添朝

本文以 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_REPORTERCI)使用 spec 输出,本地默认 dot
  • 环境隔离:注入 CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS=trueCHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=true 等环境变量,确保测试期间不产生遥测与更新检查;
  • 重试机制--retry 标志最多允许 3 次运行,用于吸收偶发性失败。

测试环境的初始化在 tests/setup.ts 中完成:它通过 overrideDevToolsGlobals 替换 DevTools 全局资源加载,并配置了快照路径(build/tests 映射回 tests 目录)与快照序列化器(用 String 替代默认 JSON.stringify,让换行在快照中可读)。

二、TypeScript 类型纪律

AGENTS.md 对 TypeScript 的使用列出了七条硬性规则:

  1. 禁止使用 any 类型;
  2. 禁止使用 as 关键字做类型转换;
  3. 禁止使用 ! 非空断言;
  4. 禁止使用 // @ts-ignore 注释;
  5. 禁止使用 // @ts-nocheck 注释;
  6. 禁止使用 // @ts-expect-error 注释;
  7. 优先使用 for..of 而非 forEach
  8. 不要对已经类型安全的类型做冗余检查(例如对静态类型变量做多余的 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: errorconsistent-type-exports: error,强制类型导入/导出的写法一致性;
  • curly: ['error', 'all'] 强制所有分支语句带花括号。

从源码结构看,禁止 @ts-expect-error 等注释规则意味着所有类型问题都必须通过重构类型定义本身来解决,而不是压制编译器——这保证了 src/ 下大量与 Puppeteer、DevTools Protocol 打交道的代码(如 src/McpPage.tssrc/ToolHandler.ts)保持类型可追踪。

三、测试结构与关注点分离

优先 Mock 单元测试,而非真实浏览器测试

AGENTS.md 测试章节的第一原则是:优先基于 mock 的单元测试,避免真实浏览器测试。不要使用 withMcpContext 或启动真实浏览器,除非测试确实需要真实浏览器或 DevTools 协议集成(例如实时 CDP 事件、浏览器生命周期、二级会话)。理由是 Puppeteer 上游已经测试过浏览器行为,而单元测试更快、无浏览器开销。

这里有个细节:withMcpContextwithBrowser 这两个真实浏览器辅助函数就定义在 tests/utils.ts 中。withBrowser 按启动参数缓存浏览器实例、内置 60 秒超时与最多 3 次重试、支持 blockedUrlPattern/allowedUrlPattern 白黑名单;withMcpContext 在其上封装出完整的 McpResponse + McpContext 环境。因此使用它们是"真实浏览器测试"的明确标志,应只用于确实需要浏览器行为的场景(如 tests/tools/emulation.test.ts 中验证离线网络模拟的用例)。

工具处理器测试(tests/tools/*.test.ts)

规则要求 tests/tools/ 下的测试只验证两件事:

  • 工具处理器解析并校验参数
  • 精确期望的参数调用 pagecontextresponse 上对应的方法。

并且明确禁止在 mock 中重新实现业务逻辑或状态跟踪(例如不要在 mock 方法里模拟状态变化)。这一规则对应着实际的调用链:src/ToolHandler.ts 中的 ToolHandler.handle() 是统一入口——先校验禁用状态与未知参数(buildUnknownArgumentsMessage 生成错误提示),经 toolMutex 串行化后,对页面级工具以 {params, page} 调用 this.tool.handler(...),对上下文级工具以 {params} 调用,并传入 responsecontext。也就是说,业务逻辑全部在 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 工厂(getMockPagegetMockBrowsermockListenergetMockRequestgetMockResponse)尚位于 tests/utils.ts 内——从这些实现看,mockListener() 返回带 on/off/emit 的最小事件对象,getMockRequest 刻意镜像了 Puppeteer redirectChain() 每次返回新数组的行为,防止格式化器共享并意外修改同一数组。这正是 AGENTS.md 要求迁移集中化的现状与目标态;
  • 使用 sinon.createStubInstance(Class):不要手写 mock 对象或自定义 mock 接口,用 createStubInstance 让原型上的所有方法自动成为 stub;
  • 类型标注:mock 类型使用 sinon.SinonStubbedInstance<Class>(如 MockMcpPageMockMcpContextMockMcpResponse)。sinon@types/sinon 均为 package.json 中声明的 devDependencies;
  • Handler mock 辅助函数:工具处理器测试统一用 const {page, context, response} = createHandlerMocks(); 一次建立三个 mock;
  • 保持 mock 通用:不要为某个具体工具或测试套件定制 mock;
  • 命名约定:辅助函数与变量名用 mock 而非 fake(如 createMockPuppeteerPagecreateMockMcpPage);被 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.jsonsync 脚本为 npm ci && git submodule update --init,说明该目录由子模块机制管理而非仓库本体;
  • eslint.config.jsglobalIgnoresthird_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,产出都能被同一套构建与测试体系无歧义地校验。

登录后查看全文
热门项目推荐
相关项目推荐