Playwright MCP 工具与 CLI 命令开发指南:从工具定义、能力过滤到配置解析的全链路实践

发布时间:2026/9/7 7:04:55
Playwright MCP 工具与 CLI 命令开发指南:从工具定义、能力过滤到配置解析的全链路实践 Playwright MCP 工具与 CLI 命令开发指南从工具定义、能力过滤到配置解析的全链路实践【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright本文基于 Playwright 仓库内的开发者指南 .claude/skills/playwright-dev/tools.md系统讲解如何为 Playwright 的 MCPModel Context Protocol服务器扩展新工具、如何为其添加对应的 CLI 命令、以及新增配置项需要触碰的完整链路。读完后你将能够独立完成一个browser_*工具从 zod 入参定义、能力capability过滤、响应Response序列化到 CLI daemon 命令映射、帮助文档生成与tests/mcp测试落地的全流程并理解core*能力恒启用、skillOnly仅技能模式可见、配置按「默认值 → 配置文件 → 环境变量 → CLI 参数」逐层合并等关键机制的源码依据。一、整体架构backend / mcp / cli-client / cli-daemon 四块拼图文档给出的目录结构对应 packages/playwright-core/src/tools/ 下的真实布局各层职责如下packages/playwright-core/src/tools/ ├── backend/ # 所有 MCP 工具实现tool.ts、tools.ts、form.ts、navigate.ts… ├── mcp/ # MCP 服务器config.d.ts、config.ts、program.ts、index.ts ├── cli-client/ # CLI 客户端program.ts、session.ts、registry.ts ├── cli-daemon/ # CLI 守护进程command.ts、commands.ts、helpGenerator.ts、daemon.ts └── utils/mcp/ # MCP SDK 封装server.ts、tool.ts、http.ts文档中的执行流程可以直接对照源码验证MCP 模式LLM → MCP 协议 → Server.callTool(name, args) → zod 校验入参 → Tool.handle(context|tab, params, response) → response.serialize() → MCP 协议 → LLM。CLI 模式playwright-cli my-command arg1 --optval → 客户端 minimist 解析 → 经 socket 发往 Daemon → parseCommand() 用 zod 把 CLI 参数映射为 MCP 工具参数 → backend.callTool(toolName, toolParams) → Response 格式化 → 打印到 stdout。CLI 命令本质是 MCP 工具的“薄包装”一条命令通过toolName/toolParams映射到一个后端工具这与 packages/playwright-core/src/tools/cli-daemon/command.ts 中CommandSchema的类型定义完全一致——toolName可以是字符串也可以是接收全部参数并动态返回工具名的函数。二、新增 MCP 工具Step 1创建工具文件在 packages/playwright-core/src/tools/backend/ 下新建your-tool.ts从 MCP 专用的 zod 打包模块和tool.ts导入构建器import { z } from ../../zodBundle; import { defineTool, defineTabTool } from ./tool;defineTabTool与defineTool的选型这一点可以从 tool.ts 的源码得到精确印证defineTabTool——绝大多数工具使用它。handle接收的是Tab对象tab.page即 Playwright 的 Page 对象。关键在于它会自动处理模态状态弹窗 / 文件选择器源码中defineTabTool包装的handle会先context.ensureTab()然后检查tab.modalStates()——如果声明了clearsModalState但当前没有对应模态如页面没有 dialog 却调用处理 dialog 的工具会直接response.addError(...can only be used when there is related modal state present.)反之若页面存在模态而工具未声明处理也会报错。也就是说模态状态的保护逻辑是框架自动注入的工具作者无需自己判断。defineTool——handle接收完整的Context。当你需要不依赖具体 tab 调用context.ensureBrowserContext()如读取 cookie 列表或需要自定义 tab 管理时使用。工具定义模式继承自文档的完整骨架const myTool defineTabTool({ capability: core, // ToolCapability — 见 Step 2 // 可选仅在 skill 模式可用不通过 MCP 暴露 // skillOnly: true, // 可选声明本工具清除哪种模态状态dialog | fileChooser // clearsModalState: dialog, schema: { name: browser_my_tool, // MCP 工具名browser_ 前缀 title: My Tool, // 人类可读标题 description: Does something, // 展示给 LLM 的描述 inputSchema: z.object({ ref: z.string().describe(Element reference from snapshot), value: z.string().optional().describe(Optional value), }), type: action, // input | assertion | action | readOnly }, handle: async (tab, params, response) { // 通过 tab.pagePlaywright Page 对象实现 await tab.page.click([ref${params.ref}]); // 追加生成的 Playwright 代码 response.addCode(await page.click([ref${params.ref}]);); // 导航/状态变化类操作在响应中附带 ARIA 快照 response.setIncludeSnapshot(); // 或追加纯文本结果 response.addTextResult(Done); }, }); export default [myTool];schema.type的四个取值及其语义来自文档且与 tool.ts 中ToolSchema的type: input | assertion | action | readOnly定义一致type语义典型场景action改变页面状态的操作navigate、click、fillinput用户输入类typing、keyboardreadOnly不改变状态的查询列 cookie、获取 snapshotassertion测试/验证类verify 断言Response API——response.ts 中均有对应实现文档列出的方法可以进一步补充其实际行为response.addTextResult(text)— 追加文本到 Result 区段response.addError(error)— 追加错误信息序列化后进入 Error 区段response.addCode(code)— 追加一条生成的 Playwright 代码片段response.setIncludeSnapshot()— 在响应中附带 ARIA 快照注意源码实现是取this._context.config.snapshot?.mode ?? full即实际快照模式由配置snapshot.modefull | none决定还继承配置的snapshot.boxes选项是否在快照中内联元素包围盒response.setIncludeFullSnapshot(filename?, root?, depth?)— 强制完整快照支持指定文件名、Locator 根节点与裁剪深度response.addResult(title, data, file)— 写文件结果字符串数据且模板无建议文件名时退化为文本结果否则写入输出目录并在响应中生成- title链接response.registerImageResult(data, png|jpeg)— 登记图片结果源码实际还支持webp。Context 级工具示例浏览器上下文级操作const myContextTool defineTool({ capability: storage, schema: { /* ... */ type: readOnly }, handle: async (context, params, response) { const browserContext await context.ensureBrowserContext(); const cookies await browserContext.cookies(); response.addTextResult(cookies.map(c ${c.name}${c.value}).join(\n)); }, });Step 2按需扩展 ToolCapability能力capability是工具与 MCP 配置之间的过滤开关。类型定义同时存在于两处mcp/config.d.ts面向配置面的声明与 backend/tool.ts工具侧使用两者取值一致export type ToolCapability config | core | // Always enabled core-navigation | // Always enabled core-tabs | // Always enabled core-input | // Always enabled core-install | // Always enabled network | pdf | storage | testing | vision | devtools; // 在此追加你的新能力能力过滤规则在 tools.ts 的filteredTools()中可以逐字验证export function filteredTools(config: PickContextConfig, capabilities) { return browserTools.filter(tool tool.capability.startsWith(core) || config.capabilities?.includes(tool.capability)) .filter(tool !tool.skillOnly) // ... }三条规则由此得到源码级确认core*能力含core、core-navigation、core-tabs、core-input、core-install恒启用不受capabilities配置影响其他能力必须通过--capsCLI 参数或配置文件的capabilities数组显式开启Config类型中capabilities?: ToolCapability[]见 config.d.tsskillOnly: true的工具在 MCP 通道中被filteredTools直接剔除仅存在于技能模式CLI daemon 解析出的配置会带上skillMode: true见 config.ts。另外可以注意到filteredTools还会给每个工具临时extend再omit掉selector/startSelector/endSelector三个字段——从源码结构看这是为了在工具 schema 上保留这些可选参数位置而不让它们出现在最终暴露给 LLM 的 inputSchema 中。Step 3注册工具把新工具加入 tools.ts 的注册表。该文件按字母序 import 各工具模块browserTools数组通过展开运算符合并import myTool from ./myTool; export const browserTools: Toolany[] [ // ... existing tools ... ...myTool, ];工具模块的导出约定是export default [tool1, tool2, ...]数组注册表负责摊平。Step 4编写测试新建 tests/mcp/ 下的category.spec.tsfixtures 从同目录 fixtures.ts 导入。测试客户端基于modelcontextprotocol/sdk的ClientStdioClientTransport启动真实 MCP 子进程源码中可见startClient会向子进程追加--headless、--caps...、--timeout-action10000等参数并按mcpServerType区分mcp与test-mcp两种服务器类型。import { test, expect } from ./fixtures; test(browser_my_tool, async ({ client, server }) { // 先导航到测试页 await client.callTool({ name: browser_navigate, arguments: { url: server.PREFIX }, }); expect(await client.callTool({ name: browser_my_tool, arguments: { ref: e1 }, })).toHaveResponse({ code: await page.click([refe1]);, snapshot: expect.stringContaining(some content), }); }); test(browser_my_tool error case, async ({ client }) { expect(await client.callTool({ name: browser_my_tool, arguments: { ref: invalid }, })).toHaveResponse({ error: expect.stringContaining(Error:), isError: true, }); });测试 fixtures 要点与 fixtures.ts 的类型声明一致client— MCP 客户端client.callTool({ name, arguments })直接调工具startClient(options?)— 客户端工厂支持自定义args/config/roots/env/cwd等用于验证自定义配置启动行为server— HTTP 测试服务器提供server.PREFIX、server.HELLO_WORLD、server.setContent(path, html, contentType)httpsServer— HTTPS 测试服务器。自定义匹配器与响应解析toHaveResponse({ code?, snapshot?, page?, error?, isError?, result?, events?, modalState? })匹配的是经parseResponse解析后的区段parseResponse由tests/mcp/fixtures.ts从packages/playwright-core/lib/coreBundle的tools导出toHaveTextResponse(text)则对原始文本做归一化匹配。解析出的区段对应Response.serialize()输出的固定结构code生成的 Playwright 代码不带js 围栏、snapshotARIA 快照yaml 围栏、pageURL/title、error、result、eventsconsole 消息、下载、modalState活动 dialog/file chooser、tabs、isError。运行方式npm run ctest-mcp category该脚本在 package.json 中定义为playwright test --configtests/mcp/playwright.config.ts --projectchrome。文档特别提醒不要使用test --debug。三、新增 CLI 命令CLI 命令是 MCP 工具的薄包装它们住在 daemon 一侧职责是把 CLI 参数映射为一次toolName/toolParams的工具调用。Step 1先实现对应的 MCP 工具顺序不可颠倒——先按上一节实现工具再声明命令。Step 2在 commands.ts 声明命令在 packages/playwright-core/src/tools/cli-daemon/commands.ts 中使用declareCommand()import { z } from ../../zodBundle; import { declareCommand } from ./command; const myCommand declareCommand({ name: my-command, // CLI 命令名kebab-case description: Does something, // help 中显示 category: core, // help 分组类别 // 位置参数有序按 CLI 位置参数解析 args: z.object({ url: z.string().describe(The URL to navigate to), ref: z.string().optional().describe(Optional element reference), }), // 命名选项--flag 或 --flagvalue options: z.object({ submit: z.boolean().optional().describe(Whether to submit), filename: z.string().optional().describe(Output filename), }), // MCP 工具名——字符串或动态路由函数 toolName: browser_my_tool, // 动态示例 // toolName: ({ submit }) submit ? browser_submit : browser_type, // 把 CLI 参数映射到 MCP 工具参数 toolParams: ({ url, ref, submit, filename }) ({ url, ref, submit, filename }), });然后加入文件底部的commandsArraycommands.ts 附近放在正确的分类区段中const commandsArray: AnyCommandSchema[] [ // core category open, close, // ... existing commands ... myCommand, // -- 放到对应分类 // ... ];**分类Category**定义在 command.tstype Category core | navigation | keyboard | mouse | export | storage | tabs | network | devtools | browsers | config | install;要新增分类需两处同步1)command.ts的Category联合类型2) helpGenerator.ts 中的categories数组{ name, title }列表后者驱动generateHelp/generateHelpJSON的输出分组。特殊模式均可在现有命令中找到实例toolName: — 命令由 daemon 特殊处理如opentoolParams: () ({})、close、detach均见 commands.ts数值参数用numberArg文件顶部定义了const numberArg z.preprocess(...)它把字符串转成数字、非数字时输出友好的expected number, received val错误例如x: numberArg.describe(X coordinate)参数改名toolParams: ({ dx: deltaX, dy: deltaY }) ({ deltaX, deltaY })如mousewheel命令动态 toolNametoolName: ({ clear }) clear ? browser_clear : browser_list。参数解析的底层机制parseCommand()command.ts把--flag类键值与位置参数分别对options/args两个 zod schema 做strict解析——未知选项会报error: unknown --xxx option位置参数过多且末位参数非数组类型时报error: too many arguments: expected N, received M。最后一个位置参数若 schema 接受数组isVariadicArg会穿透ZodOptional/ZodUnion/ZodPipe判断会吸收剩余所有 argv。解析完成后统一执行toolName({...args, ...options})与toolParams({...args, ...options})得到发给后端的工具调用。Step 3更新 SKILL 文件把新命令文档写入 packages/playwright/src/skill/SKILL.md复杂功能可在 packages/playwright/src/skill/references/ 下补充参考文档该目录包含request-mocking.md、running-code.md、session-management.md、storage-state.md、test-generation.md、tracing.md、video-recording.md等主题。验证 help 输出npm run playwright-cli -- --helpplaywright-cli脚本对应 package.json 中的node packages/playwright-core/lib/tools/cli-client/cli.js。Step 4编写 CLI 测试新建tests/mcp/cli-category.spec.tsfixtures 来自同目录 cli-fixtures.tsimport { test, expect } from ./cli-fixtures; test(my-command, async ({ cli, server }) { // 先打开页面 await cli(open, server.PREFIX); const { output, snapshot } await cli(my-command, arg1, --optionvalue); expect(output).toContain(expected text); expect(snapshot).toContain(expected snapshot content); });cli(...args)返回{ output, error, exitCode, snapshot, attachments }output为 stdout 文本snapshot是抽取出的 ARIA 快照若存在attachments为{ name, data }[]文件附件error为 stderrexitCode为进程退出码。运行方式npm run ctest-mcp cli-category同样避免test --debug。四、新增配置项五步贯穿类型、解析、CLI 与环境变量需要新增配置选项时按以下顺序更新各文件前四步均在 packages/playwright-core/src/tools/mcp/ 内1. 类型定义— config.d.ts把选项加入Config类型并附 JSDoc。export type Config { // ... existing ... /** * Description of the new option. */ myOption?: string; };2. CLI 选项类型— config.ts 的CLIOptions类型中加入myOption?: string。若选项在解析后必须是确定值同步更新FullConfig与defaultConfigexport const defaultConfig: FullConfig { // ... existing ... myOption: default-value, };3. CLI → 配置映射—configFromCLIOptions()把 CLI 选项写进构造出的Config对象该函数同时承担 launch/context 选项组装、设备模拟、代理合并等职责是选项进入配置系统的主入口。4. 环境变量 → 配置—configFromEnv()为选项添加环境变量映射。可用解析辅助函数均有 config.ts 中的实现可查options.myOption envToString(process.env.PLAYWRIGHT_MCP_MY_OPTION); // 布尔envToBoolean(...) // 识别 true/1 与 false/0 // 数字numberParser(...) // 逗号分隔列表commaSeparatedList(...) // 如 PLAYWRIGHT_MCP_CAPS // 分号分隔列表semicolonSeparatedList(...) // 如 PLAYWRIGHT_MCP_BLOCKED_ORIGINS5. MCP 服务器 CLI 参数— program.ts 中追加命令选项command .option(--my-option value, description of option)6. 嵌套选项需更新mergeConfig()若选项是嵌套结构需让 config.ts 中的mergeConfig()对其做深合并。现有实现已按browser/console/network/server/snapshot/timeouts逐块用pickDefined做浅层深合并丢弃undefined值保证低优先级层的默认值不被高优先级层的空值覆盖新嵌套块需照此模式补充。配置解析优先级defaultConfig→ 配置文件 → 环境变量 → CLI 参数后者胜出。这一顺序在resolveCLIConfigForMCP()config.ts中是明确的四步mergeConfig链let result defaultConfig; result mergeConfig(result, resolveConfigPaths(configInFile, configDir)); result mergeConfig(result, resolveConfigPaths(envOverrides, process.cwd())); result mergeConfig(result, resolveConfigPaths(cliOverrides, process.cwd()));补充几个从源码可以确认的解析细节对实操很有用配置文件格式loadConfig()支持 JSON 与 INI.ini后缀或 JSON 解析失败时回退 INI 解析见 config.tsCLI daemon 场景resolveCLIConfigForCLI()还会额外读取全局配置~/.playwright/cli.config.json以及默认项目级.playwright/cli.config.json并注入skillMode: true标记——这正是skillOnly工具在 CLI 侧可见的开关相对路径解析initPage/initScript路径按来源分别锚定在「配置文件所在目录」或「当前工作目录」resolveConfigPaths保证从不同 cwd 启动 CLI 时路径依然有效默认值示例defaultConfig中timeouts为{ action: 5000, navigation: 60000, expect: 5000, settle: 500 }与 config.d.ts 的 JSDoc 默认值说明一致。五、SKILL 文件与架构速查SKILL 文件位于 packages/playwright/src/skill/SKILL.md收录全部 CLI 命令与 MCP 工具的面向 Agent 的说明新增命令/工具后必须同步更新复杂功能在 packages/playwright/src/skill/references/ 下放置参考文档。可用npm run playwright-cli -- --help获取最新命令列表作为更新依据。执行链路速查文档 Architecture Reference 原文MCP Server mode: LLM → MCP protocol → Server.callTool(name, args) → zod validates input → Tool.handle(context|tab, params, response) → response.serialize() → MCP protocol → LLM CLI mode: User → playwright-cli my-command arg1 --optval → Client parses with minimist → sends to Daemon via socket → parseCommand() maps CLI args to MCP tool params via zod → backend.callTool(toolName, toolParams) → Response formatted → printed to stdout测试文件布局tests/mcp/fixtures.tsMCP 工具测试 fixtures、cli-fixtures.tsCLI 测试 fixtures、category.spec.tsMCP 工具测试、cli-category.spec.tsCLI 命令测试与npm run ctest-mcp的运行方式一一对应。六、自检清单新增一个工具 命令的完整改动面packages/playwright-core/src/tools/backend/your-tool.ts— 工具实现defineTabTool/defineToolpackages/playwright-core/src/tools/backend/tools.ts— 注册进browserTools按需扩展ToolCapabilitymcp/config.d.tsbackend/tool.tspackages/playwright-core/src/tools/cli-daemon/commands.ts—declareCommandcommandsArray新分类时同步command.ts的Category与helpGenerator.ts的categories新配置项时按四步链路更新config.d.ts/config.tsCLIOptions、FullConfig、defaultConfig、configFromCLIOptions、configFromEnv、mergeConfig与program.ts的 CLI flagpackages/playwright/src/skill/SKILL.mdreferences/— 文档更新tests/mcp/category.spec.ts与tests/mcp/cli-category.spec.ts— 用toHaveResponse/cli()分别覆盖成功与错误路径。全部完成后npm run ctest-mcp category与npm run playwright-cli -- --help应分别展示新的测试通过与 help 分组即完成闭环。【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻