游戏页面已经写好了,在浏览器里打开也没问题:棋盘能画出来,棋子能移动,"新对局"按钮就在该在的位置。问题是智能体自己一次都没见过这个页面。它处理的是文件里的文本,而浏览器是您打开的,画面是您看的,结果也是您告诉它的。Playwright 正好补上这道缺口:智能体打开同一个页面,找到需要的元素,点击它,再读取之后发生了什么变化。
下面我们以一个国际象棋页面为例,一步步拆解这个循环:打开、查看、选中元素、执行操作、读取新状态。安装和连接服务器的部分这里就不重复了 - 如果您还没连接 Playwright,说明在项目的 README 里。
打开需要的页面
Connected 这个提示到底说明了什么?
只说明一件事:Claude Code 已经和 Playwright 服务器完成握手,拿到了它提供的工具列表。这时候还没有打开任何页面,浏览器甚至有可能都没启动。Connected 说的是连接本身,不代表您的游戏正在运行、智能体也看得到它。
用 /mcp 命令可以查看当前状态:它会列出已连接的服务器,以及每个服务器能做什么。真正的工作从这里才开始,而它首先需要的是页面地址。
本地游戏要么是磁盘上的一个文件,要么跑在本地服务器上,而这种服务器的端口并不是固定不变的。5173、8000、3000 - 具体用哪个,取决于您是怎么启动它的。地址要从实际打印出来的地方获取:启动命令的输出,或者您刚才查看游戏时浏览器的地址栏。猜出来的端口要么给出一个空白页面,要么干脆是别人的项目,而从工具的响应里并不总能一眼看出这一点。
打开页面本身是单独的一次调用,browser_navigate,它只有一个参数:url。交给智能体的指令可以用大白话说出来:
通过 Playwright 打开 http://localhost:5173/
并告诉我打开的页面标题是什么。打开之后核对两件事:智能体实际访问的地址,以及页面标题。这只花一秒钟,却能省下一整串在错误地方执行的操作。尤其是当您同时跑着好几个本地项目,其中一个也在 localhost 上响应的时候。
Playwright 会为自己启动一个独立的浏览器。您的标签页、扩展程序、已经登录的账号都不会带过去:默认情况下,这是一个和您平时用的浏览器完全隔离的独立配置文件。对本地游戏来说这没什么影响,但"它总会打开我已登录的那个网站"这种期待,并不会自动成立。
智能体在页面上能看到什么
查看已打开的页面有三种不同的方式,它们给出的东西也完全不同:
| 想要什么 | 工具 | 返回的是什么 |
|---|---|---|
| 看看长什么样 | browser_take_screenshot | 页面的图片 |
| 了解由什么构成 | browser_snapshot | 元素的文本树:角色、名称、指向元素的引用 |
| 读取具体的值 | browser_evaluate | 直接在页面上计算出的一个小表达式的结果 |
这三条分支都从同一个页面出发,返回的却是不同的数据。快照能帮您找到按钮,而 data-turn 的值得单独读取。棋盘的图片本身并不会透露它的 HTML 属性。
截图就是一张图片。它能很好地回答"看起来是什么样"和"什么东西在哪",但它本身不会给 browser_click 提供元素引用。在 Playwright MCP 的主要工作模式下,执行操作用的是无障碍快照。按坐标点击是单独提供的功能,需要启用相应的工具才能用。
无障碍快照正是元素的描述。不是图片,而是文本:列出页面由什么构成,带角色(按钮、链接、输入框)、名称,以及之后可以用来定位元素的引用。智能体点东西时,靠的正是这份快照。示意一下,一个按钮在里面大致长这样:
- button "新对局" [ref=e12]
- button "悔棋" [ref=e13]这只是帮您理解形式的示意,不是工具真正的输出:具体格式取决于服务器版本,我们也不会为了这篇文章专门跑一次。这里重要的不是标点符号,而是这个事实本身 - 按钮传给智能体的是一行带角色、名称和引用的文本,而不是一个带文字的矩形。
接下来是最容易踩坑的地方。data-turn 这个标记在我们的游戏里用来表示轮到谁走棋,但它不会出现在无障碍快照里:快照是由角色、名称、状态和元素引用构建的,不是由页面的 HTML 结构构建的。任意的 data 属性都不在这个列表里。不过这个标记的值有可能单独出现在快照里 - 前提是页面同时把它作为可见文本打印了出来;但那已经是页面上的文本,不是被读取的属性,两者不能混为一谈。
这类值要用单独的操作来读取,也就是 browser_evaluate 工具。它在页面的上下文里执行一小段表达式并返回结果 - 这已经是在页面上运行代码,而不只是读取,所以智能体有时会在这么做之前先请求许可。表达式由智能体自己写,但大致是这样:
// 找到带走棋标记的元素并返回它的值
() => document.querySelector('[data-turn]')?.getAttribute('data-turn') ?? null无障碍快照是一份提炼,不是页面 HTML 结构的完整拷贝。里面有能用来识别和点击元素的信息,但不是所有东西都在。所以"智能体拍了快照"和"智能体读取了所有属性"是两回事,后者不会从前者自动推出。
智能体怎样选中元素
为什么按坐标点击有时会点偏,错过按钮?
因为坐标描述的是屏幕上的一个点,不是按钮本身。您拉大了窗口、收起了侧边栏、改了字体缩放比例,或者页面顶部多了一行 - 那个点还在原来的位置,按钮却挪走了。点击照样会执行,只不过落在了空白处。
所以主要的选取方式是另一种。browser_click 工具有两个字段在这里很关键:element - 人能看懂的描述,比如"新对局按钮",让您知道智能体准备点哪里;target - 来自快照的元素引用,或者一个唯一的选择器。这里我们看按快照引用来选取的方式。引用绑定在拍出它的那份快照上,点击也正好落在对应元素上。输入文本的原理一样,只是工具换成了 browser_type,元素描述之外还带上了要输入的文本本身。
这里稳定的不是引用本身,而是找到它的方法。角色加名称是一对相当持久的组合:"新对局"按钮就算被设计师挪到了屏幕的另一侧,还是"新对局"按钮。凭着这一对,智能体能在任何新的快照里重新找到需要的元素,并从中拿到最新的引用 - 这也正是为什么"每次操作之后重新查看"这条规则在文章后面还会反复出现。
棋盘上的格子麻烦一些:一共 64 个,角色还都一样。这时候能帮上忙的是应用自己给出的标记。如果格子有自己的名字,比如 e2 或 e4,就能精确定位;如果快照里所有格子都没有名字,光靠角色和名称就选不出想要的那一个。这时候需要检查格子在 HTML 结构里有没有别的唯一标识,或者使用可用的坐标类工具。
Playwright MCP 确实也有坐标类工具,但在标准构建里默认是关闭的,需要单独启用 - 用于那些没有别的办法够到元素的情况,比如 canvas。既然标准构建里没有这些工具,光靠一张图片确实做不了什么:这正是本节开头关于截图的那个提醒所说的意思。
执行操作和新状态
左边的箭头闭合了这个循环:在下一次操作之前,智能体用的是页面的最新描述。如果上一次操作的响应里已经带了最新的快照,直接用它就行。点击这个动作本身执行成功了,并不能证明想要的变化真的发生了。
我们把整件事拼成一条可以整体交出去的路线。五个步骤,每一步之后都清楚该看到什么。这条路线假定我们棋盘上的格子是有标签的 - 就是上一节说的那些 e2、e4;如果没有标签,第 4 步就需要另一种能唯一确定格子的方法。
| 步骤 | 智能体做什么 | 能看到什么 |
|---|---|---|
| 1 | 打开游戏地址 | 页面标题,棋盘就位 |
| 2 | 点击"新对局"按钮 | 棋子回到初始位置 |
| 3 | 读取 data-turn | white |
| 4 | 通过页面界面走 e2-e4 | 兵挪动了两格 |
| 5 | 读取 data-turn 和 e4 上的棋子 | black,e4 上是白方的兵 |
左边是初始状态,右边是走这一步之后的预期结果。下面的方块用一次检查把两者连了起来:智能体重新读取走棋标记和棋子的位置,然后比对数值。要更完整地检查这次移动,最好再确认一下原来的 e2 格子确实空出来了。仅仅是 white 变成 black,还不能证明兵真的到了 e4。
data-turn 这一点得说清楚:它是这个具体应用自己的约定。是页面的作者决定设置这样一个标记,所以才能读到它。Playwright 不会创建它,也不会凭空生成,它只能取出页面上已经存在的东西。换成一个没设这种标记的页面,这个办法就不管用了,只能靠肉眼看到的内容来判断。
还有做完任何操作之后的核心规则:要重新读取状态。点击之前拍的快照,描述的就是点击之前的页面,不管它看起来多新。棋盘重绘之后,快照里的元素引用可能已经指向不存在的东西了;点击之前单独读取的 data-turn 值,也可能早就变了。做完操作 - 就重新看一遍。
如果应用没有返回需要的值,智能体正确的回答应该是"读取失败,页面上没有这个属性"。一个看似合理的 black,如果只是从"棋好像走了"推断出来的,看起来和真正读到的值一模一样,但什么都证明不了。要求它展示自己实际读到的值,而不是转述。
操作没有成功的时候
最常见的失败信息是"找不到元素"。可以按一条简短的链路来排查:
- 检查打开的是不是那个页面。地址和标题 - 也就是最开始核对的那两样;
- 确认页面已经准备好。游戏可能还没加载完,按钮可能比请求晚一秒才出现;
- 拍一份新的快照。不是对话里已经有的那份,而是重新拍一份;
- 根据这份快照里最新的描述重新选中元素,再重复一次操作。
其中一个可能的原因会在第三步暴露出来:元素还在页面上,但指向它的引用已经过期了。
截图在排查时也有用,但不是万能手段。它回答的是"页面看起来是什么样、什么东西在哪" - 比如能看出棋盘渲染成了空的,或者上面盖着一个模态窗口。如果轮到谁走棋是以文本形式显示在页面上的,截图里也能看到。但 data-turn 属性的准确值,只有读取它才能确认。这是两个不同的问题,对应两种不同的工具;把它们混为一谈,就等于盯着一张图片去争论数据。
这如何改变工作方式
交给智能体的指令不再是一串点击动作,而变成一条带检查点的路线。把我们刚才拆解的内容拼成一段话,就是这样:
通过 Playwright 打开 http://localhost:5173/。
1) 点击"新对局";
2) 读取 data-turn 并展示它的值;
3) 通过页面界面走 e2-e4;
4) 再次读取 data-turn 和 e4 上的棋子。
如果这些值当中有哪个在页面上不存在,就直接告诉我,
不要自己编一个。这里有几点值得记在心里:
- 地址永远是真实的,来自启动时的输出,不是估摸出来的;
- 指令里带着"读取并展示"这样的句子 - 没有它们,您得到的就只是一份对结果的转述;
- 状态要成对展示,操作前和操作后各一次。结尾只有一个值什么都证明不了,因为不知道它一开始是什么;
- 快照、截图和读取属性回答的是不同的问题,该用哪个要看问题本身,而不是看习惯;
- 一条示例路线就够了。走通 e2-e4 这一步,不代表接下来整盘棋都会按规则走下去:那是另一项复杂得多的检查。
最简单的记法是:浏览器给了智能体一双手和一双眼睛,但不会给现成的结论。打开页面、点击、读取一个值,它自己就能做;这个值意味着什么,还是得您来判断。
工具名称及其参数已于 2026 年 9 月 19 日对照 Playwright MCP 的 README 核实,具体细节可能随版本变化。总体顺序 - 打开、查看、选中元素、执行操作、重新读取 - 保持不变。
GO TO FULL VERSION