先把话说在前面:现在网上聊 Playwright MCP 的帖子,十篇里有八篇在教你怎么装个包、起个服务,然后让模型打开一个网页截个图。这不叫项目,这叫工具演示。真正让人头疼的是后面那一步——你照着抄完,发现它处理真实业务的时候根本接不上,要么答非所问,要么操作路径乱套,要么权限和安全一塌糊涂。我前后帮几个团队搭过这类自动化底座,自己也踩过不少坑,这篇就把"怎么做才不至于沦为又一个Demo"的关键环节拆开讲清楚。
我需要先界定一下这里说的 Playwright MCP 到底是个什么东西。它本质上是一个把浏览器自动化能力封装成 MCP(Model Context Protocol)工具的服务进程,让大模型能通过标准协议直接调用"打开网页、点击、填表、读取页面内容"这些操作。听起来挺美好,但问题恰恰出在"标准协议"这四个字上——协议只管传输格式,不管你的工具设计得合不合理、权限边界在哪、失败之后怎么恢复,而这些才是项目能不能从"能用"跨到"好用"的分水岭。
1. 先搞清楚"自动化Demo"和"自动化项目"的分界线在哪
很多团队做这个方向,最大的误区是把"跑通了"当成"做完了"。模型能通过 MCP 打开一个页面、点两下按钮、拿到一段文本,就认为万事大吉。但冷静看一眼就会发现,这种程度的东西距离"能在真实业务里顶着用"还有好几层窗户纸。
1.1 Demo 的典型特征:每次都要从零开始讲需求
我见过最多的 Demo 形态是:开发者起了一个 Playwright MCP 服务,然后在客户端里问模型"帮我打开某网站登录一下"。模型确实能调工具,也确实能把页面操作出来。但你再追问一句"登录之后呢?如果验证码弹出来了怎么处理?如果页面加载超时了要不要重试?如果这个操作涉及到钱,能不能加个二次确认?",基本就哑火了。
核心问题在于,这种交互方式是"模型即兴发挥"——它没有一份明确的、可复用的任务上下文,每次对话都在重新摸索页面结构。真实项目恰恰相反,它需要把高频操作沉淀成稳定的、可预测的流程,让模型执行的是"流水线某一道工序",而不是"重新发明一遍轮子"。
打个比方,Demo 像是让一个新人每次上班都重新研究怎么开办公室的门锁,而项目是给他一张门禁卡,他只需要知道"进门前刷一下卡"就行。前者灵活但不可靠,后者受限但稳定。做自动化方向,稳定压倒一切。
1.2 项目化改造的核心:从"开放问答"变成"封闭执行"
我自己的判断标准很简单:如果模型在调用自动化工具之前,需要先花大段对话去理解"这个页面是什么、要做什么",那这个 MCP 服务的抽象就是失败的。好的设计应该是,页面结构、操作路径这些知识被沉淀在服务端的工具描述、模板或者配置里,模型的任务被压缩成"选哪个流程、填什么参数"。
换句话说,项目化的标志是:模型的自由度被刻意回收,交给确定性的代码和配置去处理。
举个例子,在真实项目里,我不会让模型自己去"找"登录按钮,而是提供一个工具叫auth_login(account, password),这个工具内部用稳定的选择器定位输入框、处理登录跳转、断言登录结果,如果失败直接返回明确的错误码。模型只需要决定"要不要调用它",而不需要操心"怎么调用才能成功"。这一步转型,是区分 Demo 和项目的分水岭。
1.3 想让别人用起来,先得学会"预设环境"
另外,只要你的目标是让团队其他人也用上这套东西,那就必须处理一个现实问题:每个人的电脑环境都不一样。屏幕分辨率、浏览器版本、网络快慢、甚至公司代理都不尽相同,如果这些变量没有在项目设计之初被考虑进去,那这个东西就只能在你自己的机器上跑。
这里我强烈建议引入容器化或至少环境一致性方案。Playwright 官方提供了现成的 Docker 镜像,里面预装了浏览器和全部系统依赖,你只需要把 MCP 服务跑在容器里,对外暴露一个标准的 stdio 或者 HTTP 接口就行。无论谁用,底层浏览器环境都是一模一样的,能省掉九成以上的"在我这能跑啊"的纠纷。
注意:不要让"环境问题"成为你项目的第一印象。别人用你的工具第一次就报错,大概率不会觉得是你的环境没配好,而是会觉得这个工具不行。
2. 架构上先想清楚:模型、MCP服务、页面三者怎么分工
拆开看,一套能打的 Playwright MCP 架构,至少要分成三个角色:模型(负责意图理解和任务拆解)、MCP 服务(负责把模型指令转成可执行的浏览器操作)、页面/业务逻辑(负责提供操作对象)。很多项目做散架,就是因为这三个角色之间的边界糊了。
2.1 模型的职责边界:只做决策,不做精细操作
模型擅长的是什么?是用自然语言理解"用户想要什么",然后把目标拆成一系列行动。但模型不擅长什么?是不知道底层页面现在的真实状态、不知道这个按钮此刻是否可点、不知道某个网络请求是否已经返回。也就是说,模型应该是一个"决策者",而不是"操作员"。
可现实里很多人设计 MCP 工具时,恨不得把 Playwright 的所有 API 原封不动抛给模型,什么page.mouse.move、page.keyboard.press都暴露出来。这看起来"能力很强",实际用起来是灾难级的——模型会陷入无穷无尽的试错里,点一下没反应就再点一下,完全不知道问题出在哪。
正确的做法是,把工具粒度收敛到"业务动作"级别:login、search、add_to_cart、submit_order。每个动作内部去处理各种异常分支,对外只暴露"成功/失败/需要人工确认"这几个有限状态。
2.2 MCP服务层的职责:把页面语言翻译成业务语言
MCP 服务这一个层,其实是整个架构里最容易被低估的部分。它不只是一个把 JSON-RPC 请求转发给浏览器驱动的管道,它还是一个"翻译层"——把页面上的原始信号(DOM 节点、网络日志、浏览器事件)翻译成模型能理解的业务信号。
举一个非常具体的例子。假设你的页面是个典型的电商站点,前端有个下单按钮,点击后要经历"校验表单 → 弹出确认弹窗 → 提交订单接口 → 跳转订单详情页"这样一串流程。如果 MCP 服务只是把"点击按钮"的命令发给浏览器,那模型完全不知道点击之后到底发生了什么,它只能继续凭感觉行动。
更合理的设计是,服务层内部封装一个submit_order工具,它内部自动完成:点击 → 等弹窗 → 确认 → 等待接口返回 → 断言跳转成功,然后把结果精简为{status: "success", order_id: "123456", redirect_url: "..."}。模型拿到这个结构,就知道这一步已经尘埃落定,可以放心进行下一步。这就是"页面语言"到"业务语言"的翻译。
2.3 选择器策略:别让"稳定定位"成为项目的阿喀琉斯之踵
写 Playwright 的人都知道,定位元素有好几种策略:按文本、按 CSS 选择器、按 role、按 test-id。Demo 阶段无所谓,怎么方便怎么来。但项目阶段这是要定规矩的,否则今天页面加个按钮、明天改个 class,你的自动化就瞬间全挂。
我个人的习惯是:在项目里优先约定使用>