跳到主要内容

Playwright MCP — 这是什么 / 全景 / 主线 / 阅读地图

30 秒导读: Playwright MCP 把浏览器自动化框架 Playwright 包成一个 MCP server,让大模型能像调用工具一样开网页、点按钮、填表单——而且不用截图,靠的是网页的无障碍树快照(accessibility snapshot)这种结构化文本。特别之处:你手上这个仓库不是核心源码,它只是 npm 发行壳;真正的工具实现已经搬进了 Playwright 大仓,本仓库通过一行 require('playwright-core/lib/coreBundle') 把它们引进来。

本页是这一组文档的总入口:先讲"这是什么"(零基础),再给顶层全景图,然后端到端走一遍"一次点击"的主线,最后给出各章的阅读地图。深入细节都在后续章节,本页不重复它们的内容。


1. 这是什么(零基础也能懂)

一句话定义

Playwright MCP = 把 Playwright 的浏览器自动化能力,包装成一个符合 MCP 协议的 server,让 LLM 用『无障碍树快照』来操作真实网页。

拆开这句话里的三个词:

  • Playwright —— 微软的浏览器自动化框架,能用代码驱动 Chromium/Firefox/WebKit 打开网页、点击、输入、截图。
  • MCP(Model Context Protocol) —— 一套标准协议,让"MCP client(如 VS Code、Claude、Cursor)"能发现并调用"MCP server"提供的工具。你写一个 MCP server,任何 MCP client 都能用它。
  • 无障碍树快照(accessibility snapshot) —— 浏览器为读屏软件维护的一棵语义树(每个可见元素是什么角色、什么名字),这里被序列化成一段结构化文本喂给模型,代替截图

README 开头段把定位说得很直白(README.md:3):

"This server enables LLMs to interact with web pages through structured accessibility snapshots, bypassing the need for screenshots or visually-tuned models."

解决什么问题 / 给谁用

想象你想让 AI 帮你在一个网页上完成操作:登录、填表、点提交、抓取结果。有两条路:

  1. 截图流:给模型发一张网页截图,模型"看图"猜坐标,然后点某个像素。缺点是要视觉模型、慢、坐标容易错。
  2. 无障碍树流(本项目):给模型一段文本快照——"这里有一个按钮,名叫 Submit,引用编号是 e2"——模型直接说"点 e2 那个 Submit 按钮"。不需要视觉模型,定位是确定的。

Playwright MCP 走的是第二条路。它的 Key Features(README.md:15-17)正是这三点:

特性白话
Fast and lightweight用无障碍树,不用像素输入,快而轻
LLM-friendly不需要视觉模型,纯结构化数据
Deterministic tool application定位确定,避开截图流常见的歧义

面向的用户:任何 MCP client 的使用者——README.md:21 列了 VS Code、Cursor、Windsurf、Claude Desktop、Goose、Junie 等。

用起来什么样

装它不需要克隆源码,标准配置就是让 MCP client 用 npx 拉起这个 npm 包(README.md:34-45):

{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}

配好后,模型侧看到的是一批 browser_* 工具(browser_navigatebrowser_clickbrowser_snapshot……,完整清单见 tests/capabilities.spec.ts:21-45)。模型调用 browser_click,真实浏览器里的按钮就被点了,并把最新快照回传给模型。

一句话直觉/类比

把它当成"给模型装一双手 + 一副能读懂网页结构的眼睛"。 眼睛不是相机(截图),而是读屏软件那种"这一块是按钮、那一块是链接"的语义视角;手就是 Playwright 的 click()fill() 这些真实动作。


2. 最关键的定位:这是发行层,不是核心源码

这一节单独拎出来,因为它是理解整组文档的前提:你不能在这个仓库里读到工具的具体实现。

核心已搬进 Playwright monorepo

仓库自带的 src/README.md:1-3 把话挑明了:

"Playwright MCP source code is located in the Playwright monorepo … Please refer to the contributor's guide in CONTRIBUTING.md for more details."

src/ 目录下只有这一个 README(实测 src/README.md 外没有任何源码)。CONTRIBUTING.md:20-21 用一个警告块再强调一次:

⚠ "The core of the Playwright MCP was moved to the Playwright monorepo."

真实实现在上游的这两个路径(CONTRIBUTING.md:41-42):packages/playwright-core/src/tools/mcp.../tools/backend

本仓库靠 coreBundle 引入

那本仓库的入口怎么拿到工具?靠 playwright-core 编译产物里的一个"打包出口" coreBundlecli.js:18-19 就两行 require:

const { program } = require('playwright-core/lib/utilsBundle');
const { tools, libCli } = require('playwright-core/lib/coreBundle');

index.js:18-19 同理,把 tools.createConnection 直接再导出。也就是说:本仓库 = 一层薄薄的壳,负责『装依赖、暴露 CLI/库入口、生成文档、跑集成测试』,而不负责实现浏览器工具本身。

边界:哪些能引、哪些不能引

这组文档的取材边界因此很清楚:

能引(本仓库真源码)不能引(在上游 monorepo,本仓库看不到)
cli.js / index.js / index.d.ts / config.d.tscoreBundle 里每个 browser_* 工具的具体实现
update-readme.js / roll.js无障碍树是怎么抓取、序列化的
tests/*(集成测试、fixtures)page.getByRole().click() 背后的驱动逻辑
README.md 的手写段 + 生成段标记backend / CDP 连接的内部实现

后续所有章节都严守这条线:凡是"实现细节在 coreBundle 里"的,只描述接口与可观测行为(靠测试佐证),不假装读过它的源码。


3. 顶层全景(它大概怎么转)

怎么读这张图: 从左到右是一次工具调用的数据流;右侧的响应沿原路回传。虚线框 coreBundle 是本仓库引入、但实现不在本仓库的部分。

MCP client 本仓库(发行壳) ┌ 实现在别处 ┐
VS Code / ┊ coreBundle ┊
Claude / stdio 或 ┌─────────────┐ require ┊ (来自上游 ┊ 真实浏览器
Cursor … SSE │ cli.js │────────────┐ ┊ monorepo) ┊ Chromium/FF/WebKit
│ ───────────────────▶│ (CLI 入口) │ ▼ ┊ ┊ │
│ callTool 请求 └─────────────┘ ┌──────────────────────┐ │ 驱动 │
│ ┌─────────────┐ │ tools.decorateMCP │────┼─────────▶ page.getBy
│ │ index.js │─────▶│ Command / tools. │ │ Role() │ Role().click()
│ │ (库入口: │ │ createConnection │◀───┼──────────│
│ ◀───────────────────│ createConn.) │ └──────────────────────┘ │ 快照 │
│ 响应文本: └─────────────┘ ▲ └──────────┘
│ "### Ran Playwright code" + "### Snapshot" │
└────────────────────────────────────────────────────┘ 结构化文本回传

一句话读图:client 发 callTool → 经 stdio/SSE 进入 cli.js/index.js → 交给 coreBundle 的工具 → 驱动真实浏览器 → 把"跑了哪段 Playwright 代码 + 最新无障碍快照"打包成结构化文本回传。

响应长什么样

回传不是随便一段文字,而是有固定小节标题的结构化文本。测试的解析器 tests/fixtures.ts:256-264 正是按 ### 标题切块,认得这些段名:ErrorResultRan Playwright codeOpen tabsPage stateSnapshotNew console messagesModal stateDownloads。这就是模型每次动作后拿到的"回执"结构。


4. 部件职责表

本仓库(壳层)的关键部件,以及各自干什么、在哪(每章展开见"阅读地图"):

部件干什么在哪(本仓库路径)
CLI 入口命令行拉起 MCP server;install-browser 走安装分支,否则挂 MCP 命令cli.js:21-32
库入口以库形式被 import,导出 createConnection 建立 MCP 连接index.js:18-19
库类型声明createConnection(config?, contextGetter?) 的 TS 签名index.d.ts:22
配置面Config 类型:浏览器、server、capabilities、network、secrets、timeouts 等全部可配项config.d.ts:33-239
文档生成器从编译出的工具元数据 + cli.js --help + config.d.ts 反向生成 README 的工具目录/选项表/配置块update-readme.js
版本滚动器playwright* 依赖滚到新版,拷贝上游 config.d.ts,再重生成 READMEroll.js
集成测试用真 MCP client 起真 server,断言工具清单与"code+snapshot"响应tests/*.spec.tstests/fixtures.ts

5. 主线走一遍:一次 browser_click 端到端

用最经典的一条路径把上面的图落到代码上——从 client 发起点击,到浏览器真的点了,再到快照回传。以下每一步都有测试佐证。

第 1 步:client 发 callTool

测试里 client 直接调 client.callTool({ name: 'browser_click', arguments: { element, target } })(tests/click.spec.ts:39-48)。element 是给人看的元素描述("Submit button"),target 是快照里的引用编号(e2)。

第 2 步:请求进入壳层,交给 coreBundle

请求经 stdio 传输到 cli.js 拉起的进程。cli.js:29-30 把 MCP 命令挂到 program 上:

const p = program.version('Version ' + packageJSON.version).name('Playwright MCP');
tools.decorateMCPCommand(p, packageJSON.version);

tools 就是 coreBundle 导出的对象——从这里往下的实现不在本仓库,我们只知道它会把 browser_click 路由到对应工具。

第 3 步:工具驱动真实浏览器

工具内部把这次点击翻译成一句真实的 Playwright 调用。测试断言响应里回传的 code 段正是(tests/click.spec.ts:46):

await page.getByRole('button', { name: 'Submit' }).click();

注意它用的是 getByRole('button', { name: 'Submit' })——基于无障碍角色 + 名字定位,而不是坐标或脆弱的 CSS 选择器。这正是"用无障碍树操作网页"落到实处的样子。

第 4 步:返回"code + 新快照"

点击后,响应同时带回执行的代码和刷新后的无障碍快照。tests/click.spec.ts:45-48 一次断言两段:

响应段断言内容出处
Ran Playwright codeawait page.getByRole('button', { name: 'Submit' }).click();tests/click.spec.ts:46
Snapshotbutton "Submit" [active] [ref=e2]tests/click.spec.ts:47

[active] 说明点击后按钮拿到了焦点,[ref=e2] 是可供下一次动作复用的引用编号。这个"每次动作都回传最新快照 + 引用编号"的闭环,是模型能连续操作的关键。

更小的一条:browser_navigate

tests/core.spec.ts:19-27 是最短的佐证:导航到 hello-world 页,响应 code 段是 await page.goto('...'),snapshot 段含 generic [active] [ref=e1]: Hello, world!。同一个 "code + snapshot" 响应格式,换个工具照样成立。


6. README vs CLI:什么时候别用 MCP

同一批浏览器能力,微软还提供了一个 Playwright CLI + SKILLS 的形态;README 专门有一段权衡(README.md:5-11),值得读者知道:

形态适合谁为什么
CLI + SKILLS高吞吐的编码 agentCLI 调用更省 token:不必把庞大的工具 schema 和冗长的无障碍树塞进上下文,用简洁的专用命令行动
MCP(本项目)需要持久状态、富自省、对页面结构反复推理的专门 agentic 循环探索式自动化、自愈测试、长时自治流程——持续维护浏览器上下文的价值 > token 成本

一句话:如果你是在做代码库里的编码 agent,README 明说你可能更该用 CLI+SKILLS;MCP 更适合"长时间盯着一个浏览器上下文反复推理"的场景。 这解释了本仓库为何只是发行壳之一。


7. 阅读地图

建议阅读顺序

本页(index)读完后,按下面顺序下钻;每章聚焦壳层的一个面,互不重复:

  1. 01-entrypoints-and-wrapper.md — 入口与包壳。cli.jsinstall-browser 分支与 MCP 命令挂载、index.jscreateConnection 库入口、index.d.ts 的签名。想搞清"请求怎么从命令行/库调用接到 coreBundle"从这里开始。
  2. 02-config-and-capabilities.md — 配置面与能力开关。config.d.tsConfig 类型全景,以及 --caps=pdf|vision|… 如何选择性挂载工具(佐证见 tests/capabilities.spec.ts:48-77)。
  3. 03-readme-generation-and-roll.md — 文档即真相。update-readme.js 怎么从编译产物 + --help + config.d.ts 反生成 README;roll.js 怎么滚版本并拷贝上游 config。理解"为什么 README 是生成的"。
  4. 04-test-harness.md — MCP 集成测试机制。tests/fixtures.ts 如何用真 StdioClientTransport 起真 server、toHaveResponse 如何解析 "code + snapshot" 响应。想验证任何行为断言时回到这里。

顺序建议:1 → 2 打好"入口 + 配置"的地基,再看 3 理解文档生成,最后用 4 学会怎么自己验证。 只关心"怎么用/怎么验证"的读者可先跳 4。

代码地图(导航索引)

带符号名,便于 grep 定位(行号 as-of sourceCommit,失效时按符号找):

主题文件路径符号名 / 锚点
CLI 入口 + install 分支cli.js:21-32program, libCli.decorateProgram, tools.decorateMCPCommand
库入口(导出连接)index.js:18-19createConnection
库入口类型签名index.d.ts:22createConnection(config?, contextGetter?)
核心从哪引入cli.js:18-19require('playwright-core/lib/coreBundle')
核心已搬走的声明src/README.md:1-3CONTRIBUTING.md:20-21"moved to the Playwright monorepo"
配置类型全景config.d.ts:33-239Config, ToolCapability
工具目录生成update-readme.js:124-141updateTools, toolsByCapability, formatToolForReadme
选项表 / 配置块生成update-readme.js:159-236updateOptions, updateConfig
版本滚动roll.js:37-49doRoll, copyConfig, updatePlaywrightVersion
测试用真 MCP client 起 servertests/fixtures.ts:77-121186-222startClient, createTransport, StdioClientTransport
响应断言(code+snapshot)tests/fixtures.ts:226-295toHaveResponse, parseResponse, parseSections
一次点击的主线tests/click.spec.ts:19-49browser_click, page.getByRole('button')
最短导航路径tests/core.spec.ts:19-27browser_navigate, page.goto
工具全清单tests/capabilities.spec.ts:21-45listTools
库可从 CJS 使用tests/library.spec.ts:20-28createConnection(issue #456)