
1. 项目概述当Claude遇上Playwright自动化测试的“智能副驾”来了最近在捣鼓自动化测试尤其是Web UI自动化发现了一个挺有意思的组合用Claude来驱动Playwright。这听起来可能有点“杀鸡用牛刀”但实际体验下来它解决的痛点非常精准——让编写和维护自动化脚本这件事从一项需要高度专注的“编程任务”变成了更像是在和一位精通代码的专家进行“对话协作”。传统的Playwright脚本编写你得熟悉它的API、处理各种异步等待、应对动态元素选择器虽然Playwright本身已经比Selenium友好很多但调试和迭代依然耗时。而这个“Claude Playwright MCP Server”的方案核心思路是引入一个“大脑”Claude和一个“翻译官”MCP Server让你能用自然语言描述测试意图然后自动生成、执行甚至修复Playwright代码。简单来说MCPModel Context Protocol是Anthropic提出的一种协议它允许像Claude这样的AI模型安全、结构化地访问外部工具、数据和功能。在这个场景里我们搭建一个MCP Server它本质上是一个“Playwright操作接口”将Claude的指令翻译成Playwright能执行的命令。这样一来你就不需要逐行敲代码了可以直接告诉Claude“帮我在某网站登录然后检查一下仪表盘上的欢迎消息对不对。” Claude理解后通过MCP Server调用Playwright去执行并把结果反馈回来。这特别适合几种场景一是快速原型验证当你需要快速为某个新功能或页面写一套冒烟测试时二是测试用例的日常维护页面结构变了你可以直接告诉Claude“那个登录按钮的ID好像变了更新一下脚本”三是非开发人员的参与测试人员或产品经理可以用更自然的方式参与自动化测试的设计。当然它并不是要完全取代手工编写复杂、高度定制化的测试框架而是作为一个强大的辅助和生产力倍增器。接下来我就结合自己的搭建和踩坑经历把这个方案的里里外外、实操细节和避坑指南给大家拆解清楚。2. 核心架构与工具选型解析2.1 为什么是Claude Playwright MCP在决定采用这个技术栈之前我评估过几种常见的AI辅助编码方案。比如直接用ChatGPT的代码解释器或者GitHub Copilot在IDE里补全。但它们要么缺乏对本地浏览器环境的直接控制能力要么生成的代码片段需要你手动整合到测试框架中流程是割裂的。而Claude MCP的组合其优势在于提供了一个标准化、可扩展的双向通信通道。Playwright的选择理由很充分它支持Chromium、Firefox、WebKit三大浏览器引擎内置自动等待机制减少Flaky测试录制生成代码的功能强大并且提供了跨语言JS/TS、Python、.NET、Java的API。对于AI驱动来说Playwright相对稳定和高级的API比直接操作更底层的CDPChrome DevTools Protocol要友好和可靠得多。MCP Server在这里扮演核心枢纽。它不是一个现成的软件而是需要我们自己实现的一个服务。这个服务基于MCP协议暴露出一系列“工具”Tools给Claude调用。例如navigate_to_url,click_element,get_page_text等。当Claude收到用户的自然语言指令时它会判断需要调用哪个工具并生成符合该工具要求的参数通常是JSON格式MCP Server收到请求后转换为Playwright的API调用执行操作并将结果成功或失败附带可能的数据返回给Claude由Claude组织语言回复给用户。2.2 环境准备与依赖安装整个环境搭建可以分为三块Claude环境、Playwright环境、以及连接二者的MCP Server。我以最常用的Python环境为例进行说明因为Python在AI和自动化领域生态丰富且Playwright的Python API非常清晰。首先确保你的系统有Python 3.8。然后我们需要两个核心的Python包# 安装Playwright的Python库 pip install playwright # 安装Playwright所需的浏览器推荐安装全部以备不时之需 playwright install chromium firefox webkit # 安装MCP协议相关的Python SDK。 # 注意Anthropic官方提供了多种语言的MCP SDKPython的通常叫 mcp 或 anthropic-mcp请以官方仓库为准。 # 假设包名为 mcp这里是一个示例实际请查阅最新文档 pip install mcp注意mcp这个Python包可能还在快速迭代中其API和安装方式可能会有变化。最可靠的方法是查阅Anthropic官方关于MCP的GitHub仓库例如anthropics/anthropic-mcp或modelcontextprotocol/servers按照其中的Python示例来安装所需的依赖。有时你可能需要直接从GitHub仓库克隆并安装。接下来是Claude部分。你需要一个Claude API密钥。前往Anthropic的官方平台注册并获取。然后你可以选择两种方式来集成直接使用Claude API在你的MCP Server代码里集成anthropic这个Python库直接发起API请求。使用Claude Desktop或第三方客户端许多支持MCP协议的客户端如Claude Desktop、Cursor编辑器可以配置自定义的MCP Server。这种方式更直观像在聊天窗口里使用插件。为了演示的完整性我们会聚焦于第一种方式即构建一个独立的、集成了Claude API调用的MCP Server程序。这样你对整个数据流的控制力更强。3. MCP Server的构建与核心工具实现3.1 初始化MCP Server与Playwright我们先来搭建一个最基础的MCP Server骨架。这个Server需要做几件事启动Playwright浏览器实例、实现几个核心的“工具”函数、将这些工具注册到MCP框架中、并启动一个服务来监听Claude的请求。import asyncio from typing import Any from mcp import Server, Tool from playwright.async_api import async_playwright, Browser, Page class PlaywrightMCPServer: def __init__(self): self.server Server(playwright-mcp-server) self.browser: Browser None self.page: Page None self.context None async def start_browser(self): 启动Playwright浏览器实例 playwright await async_playwright().start() # 默认使用Chromium可配置 self.browser await playwright.chromium.launch(headlessFalse) # 初期调试建议有头模式 self.context await self.browser.new_context() self.page await self.context.new_page() print(Playwright浏览器已启动。) async def stop_browser(self): 关闭浏览器实例 if self.browser: await self.browser.close() print(Playwright浏览器已关闭。)这里我们创建了一个类内部持有MCP的Server对象和Playwright的Browser、Page对象。headlessFalse意味着浏览器会以图形界面打开方便我们观察自动化过程生产环境可以改为True。3.2 定义与注册核心工具函数MCP的核心是“工具”Tool。每个工具需要定义名称、描述、输入参数模式JSON Schema。Claude会根据描述来决定是否以及如何调用它。我们实现几个最基础但必不可少的工具。def create_tools(self): 创建并返回工具列表 navigate_tool Tool( namenavigate_to_url, description导航到指定的URL。, input_schema{ type: object, properties: { url: {type: string, description: 要访问的完整URL。} }, required: [url] } ) click_tool Tool( nameclick_element, description点击页面上的一个元素。需要通过CSS选择器、XPath或文本内容来定位元素。, input_schema{ type: object, properties: { selector: {type: string, description: 用于定位元素的CSS选择器或XPath。}, text: {type: string, description: 要点击的元素的文本内容。如果提供会优先使用文本定位。} }, required: [] # 至少需要selector或text中的一个 } ) fill_tool Tool( namefill_input, description向输入框、文本框等表单元素填充文本。, input_schema{ type: object, properties: { selector: {type: string, description: 表单元素的CSS选择器或XPath。}, text: {type: string, description: 要填充的文本内容。} }, required: [selector, text] } ) get_text_tool Tool( nameget_page_text, description获取当前页面或特定元素的文本内容。用于断言或验证。, input_schema{ type: object, properties: { selector: {type: string, description: 可选。特定元素的CSS选择器或XPath。如果为空则获取整个页面的文本。} }, required: [] } ) # 将工具函数与工具定义绑定 self.server.add_tool(navigate_tool, self.tool_navigate) self.server.add_tool(click_tool, self.tool_click) self.server.add_tool(fill_tool, self.tool_fill) self.server.add_tool(get_text_tool, self.tool_get_text) return [navigate_tool, click_tool, fill_tool, get_text_tool] async def tool_navigate(self, url: str) - str: 工具实现导航 try: await self.page.goto(url, wait_untilnetworkidle) return f成功导航到: {url} except Exception as e: return f导航失败: {str(e)} async def tool_click(self, selector: str None, text: str None) - str: 工具实现点击 try: if text: await self.page.click(ftext{text}) return f成功点击文本为 {text} 的元素。 elif selector: await self.page.click(selector) return f成功点击选择器为 {selector} 的元素。 else: return 错误必须提供 selector 或 text 参数之一。 except Exception as e: return f点击失败: {str(e)} async def tool_fill(self, selector: str, text: str) - str: 工具实现填充 try: await self.page.fill(selector, text) return f已向选择器 {selector} 填充文本: {text} except Exception as e: return f填充失败: {str(e)} async def tool_get_text(self, selector: str None) - str: 工具实现获取文本 try: if selector: element await self.page.wait_for_selector(selector, stateattached) text_content await element.text_content() else: text_content await self.page.text_content(body) # 返回前可以做一些清理比如去除过多空白 return text_content.strip() if text_content else (空内容) except Exception as e: return f获取文本失败: {str(e)}实操心得工具的描述description至关重要。Claude完全依赖这个描述来理解工具的用途和调用方式。描述要清晰、准确并说明参数之间的关系。例如click_element工具我说明了可以通过selector或text定位且text优先。这能引导Claude在用户说“点击登录按钮”时尝试使用text登录来定位。3.3 集成Claude API并启动服务循环有了工具我们需要一个“大脑”来驱动它们。我们在Server类里添加一个方法用于处理用户输入调用Claude API并执行Claude返回的工具调用请求。import anthropic class PlaywrightMCPServer: # ... 之前的初始化代码 ... def __init__(self, claude_api_key: str): self.server Server(playwright-mcp-server) self.browser: Browser None self.page: Page None self.context None self.claude_client anthropic.Anthropic(api_keyclaude_api_key) self.tools [] # 存储工具定义 async def process_user_query(self, user_message: str) - str: 处理用户查询调用Claude并执行工具调用 # 1. 将我们的工具定义格式化为Claude API需要的格式 claude_tools [] for tool in self.tools: claude_tools.append({ name: tool.name, description: tool.description, input_schema: tool.input_schema }) # 2. 构建初始消息包含系统提示词 system_prompt 你是一个控制网页浏览器的自动化助手。你可以通过我提供的工具来操作浏览器。 用户会告诉你他们想在网页上做什么。你需要理解用户的意图并规划一系列工具调用来完成它。 一次只调用一个工具等待我的执行结果后再决定下一步。 如果工具执行失败分析原因并尝试其他方法例如换一种元素定位方式。 你的最终目标是完成用户的指令并在完成后简要总结做了什么。 message_history [ {role: user, content: user_message} ] # 3. 调用Claude API response self.claude_client.messages.create( modelclaude-3-5-sonnet-20241022, # 使用合适的模型 max_tokens1024, systemsystem_prompt, messagesmessage_history, toolsclaude_tools ) # 4. 处理Claude的响应 final_response for content in response.content: if content.type text: final_response content.text print(fClaude回复: {content.text}) elif content.type tool_use: # Claude要求调用工具 tool_name content.name tool_args content.input print(fClaude请求调用工具: {tool_name}, 参数: {tool_args}) # 5. 在实际的MCP Server中这里应该将工具调用分发给对应的处理函数。 # 我们这里简化处理直接调用我们类中对应的方法。 # 注意真实的MCP Server通信是异步且持续的这里仅为演示逻辑。 tool_result await self._execute_tool(tool_name, tool_args) print(f工具执行结果: {tool_result}) # 6. 将工具执行结果作为新的消息追加让Claude继续思考 # 在真正的循环中需要将结果发送回Claude并获取下一个响应。 # 此处为简化我们将结果拼接到最终回复中。 final_response f\n[执行工具 {tool_name}: {tool_result}]\n # 在实际循环中这里应该将 tool_result 作为新的 assistant message 的一部分发送回Claude # 并再次调用 messages.create形成多轮对话直到任务完成。 return final_response async def _execute_tool(self, name: str, args: dict) - str: 根据工具名和参数执行对应的工具函数 # 这里需要建立一个工具名到实际异步方法的映射 # 为了示例我们简单判断 if name navigate_to_url: return await self.tool_navigate(args[url]) elif name click_element: return await self.tool_click(args.get(selector), args.get(text)) elif name fill_input: return await self.tool_fill(args[selector], args[text]) elif name get_page_text: return await self.tool_get_text(args.get(selector)) else: return f未知工具: {name} async def run(self): 启动服务器主循环 await self.start_browser() self.tools self.create_tools() print(MCP Server工具已注册。) # 注意一个完整的MCP Server应该启动一个Socket或HTTP服务器来监听标准输入输出(stdio)。 # 这里我们用一个简单的异步输入循环来模拟与Claude客户端的交互。 try: while True: user_input input(\n请输入指令 (或输入 quit 退出): ) if user_input.lower() quit: break result await self.process_user_query(user_input) print(f\n--- 最终回复 ---\n{result}) finally: await self.stop_browser()这个process_user_query方法模拟了核心的交互循环用户输入 - Claude思考并可能请求调用工具 - 我们执行工具 - 将结果反馈给Claude - Claude继续思考或给出最终回答。在真正的MCP Server实现中这个循环是通过标准的输入输出流stdio与Claude Desktop等客户端进行的遵循MCP协议定义的消息格式。4. 从指令到执行一个完整的自动化用例拆解让我们用一个具体的例子把上面的代码串起来看看从用户说一句话到浏览器自动完成操作的全过程。假设我们要测试一个简单的登录流程。用户指令“打开知乎首页点击登录按钮在出现的弹窗里往手机号输入框里填上我的测试号13800138000然后点击获取验证码按钮。”指令解析与Claude思考我们将这条指令传给process_user_query。Claude收到后结合系统提示词开始规划。它可能会先拆解出几个步骤导航、点击、填充、再点击。它发现第一步需要导航于是它生成一个tool_use请求调用navigate_to_url工具参数{url: https://www.zhihu.com}。执行导航我们的_execute_tool方法接收到这个请求调用tool_navigate(https://www.zhihu.com)。Playwright控制浏览器打开知乎首页并等待页面基本加载完成。执行成功返回结果“成功导航到: https://www.zhihu.com”。反馈与下一步在真实的MCP循环中这个成功结果会作为一条新的消息role: “tool” content: tool_result发送回Claude。Claude知道第一步成功了接着规划第二步“点击登录按钮”。它可能会尝试用文本定位生成tool_use请求调用click_element参数{text: 登录}。执行点击登录tool_click被调用Playwright寻找页面中包含“登录”文本的元素并点击。这可能会触发一个登录弹窗的出现。处理动态内容与等待这里有一个关键点。点击后弹窗可能不是立即出现的。我们的tool_click函数使用的是Playwright默认的点击它本身有自动等待机制。但如果Claude在弹窗完全出现前就急着去定位“手机号输入框”可能会失败。这时Claude根据我们工具“获取文本”的描述可能会先调用get_page_text看看当前页面有什么或者更智能的方案是我们在工具实现里就内置更稳健的等待。例如改进tool_click在点击后可以加一个短暂的通用等待或者由Claude在调用填充工具前先调用一个“等待元素可见”的工具这个工具我们需要额外实现。执行填充与再次点击假设弹窗顺利出现。Claude接着调用fill_input参数{“selector”: “input[type‘tel’]”, “text”: “13800138000”}。这里Claude需要“知道”手机号输入框的选择器。这依赖于用户的指令足够精确“手机号输入框”。Claude对常见网页元素的认知它可能知道手机号输入框常对应type‘tel’的input。或者我们提供一个更强大的工具如find_element让Claude描述元素工具去尝试多种选择器定位。执行填充后Claude最后调用click_element点击“获取验证码”按钮。任务完成与总结所有步骤成功后Claude会生成一段文本回复总结它完成了哪些操作。避坑技巧这个流程中最容易出错的环节是元素定位。网页上的“登录”按钮可能有多个文本可能是“登录”、“Sign in”、“Log in”。Claude可能会选错。为了提高成功率在系统提示词中引导Claude建议它优先使用具有唯一性的文本或者结合按钮的视觉位置如“顶部的登录按钮”来描述。虽然我们的工具目前不支持位置描述但可以引导Claude在文本定位失败时尝试让用户提供更精确的选择器。实现更强大的定位工具例如一个find_and_click工具接收元素描述如“蓝色的、在页面右上角的登录按钮”内部使用Playwright的get_by_role,get_by_label等多种定位器进行尝试并返回使用了哪种定位方式成功供Claude学习。引入视觉辅助高级可以集成截图工具让Claude“看到”当前页面结合多模态能力来更准确地理解元素位置。但这会显著增加复杂度和成本。5. 进阶优化与生产级考量上面实现的是一个基础原型。要把它用于更严肃的自动化场景还需要考虑以下几个关键方面5.1 状态管理与会话隔离我们的Server目前只有一个browser和一个page。如果多个用户或同时进行多个测试流程就会互相干扰。生产环境需要引入会话Session管理。思路为每个新的对话或任务创建一个独立的PlaywrightBrowserContext甚至独立的Browser实例。每个会话拥有自己的页面和状态。MCP协议支持服务器管理多个并行的“资源”和“工具调用上下文”我们需要在Server实现中维护一个会话ID到Playwright上下文对象的映射。class SessionManager: def __init__(self): self.sessions: Dict[str, BrowserContext] {} async def create_session(self, session_id: str): playwright await async_playwright().start() browser await playwright.chromium.launch(headlessTrue) context await browser.new_context() self.sessions[session_id] {context: context, browser: browser, playwright: playwright} page await context.new_page() return page async def close_session(self, session_id: str): if session_id in self.sessions: data self.sessions.pop(session_id) await data[context].close() await data[browser].close() await data[playwright].stop()然后在工具函数中需要从当前请求中解析出session_id并获取对应的page对象来执行操作。5.2 增强的工具集与错误恢复基础工具集只能完成简单操作。一个实用的自动化测试MCP Server需要更丰富的工具screenshot截取当前页面或元素图片用于Claude分析或生成测试报告。wait_for_element显式等待某个元素出现、可见或具有特定状态。这对于处理动态加载的页面至关重要。execute_script在页面上下文中执行JavaScript代码。可以处理一些Playwright API不易直接操作的情况或者获取复杂的页面状态。get_element_property获取元素的属性、CSS值等用于更细致的断言。drag_and_drop处理拖拽交互。handle_alert处理JavaScript弹窗。错误恢复机制当工具调用失败如元素未找到时不应直接返回一个错误字符串就结束。应该将详细的错误信息包括截图、堆栈返回给Claude并在系统提示词中要求Claude具备“重试”或“尝试替代方案”的能力。例如点击text登录失败后Claude可以尝试selector.LoginButton。5.3 集成到CI/CD与测试报告最终这个方案要产生价值需要能集成到持续集成流程中。命令行接口将MCP Server包装成一个命令行工具可以接收一个用自然语言或结构化格式如YAML描述的测试场景文件然后运行并输出结果。结果标准化工具执行的结果成功/失败需要被捕获并转化为标准的测试断言如Pytest的assert。可以设计一个工具叫assert_text_contains它调用get_page_text然后判断文本是否包含预期字符串并返回一个结构化的断言结果对象。报告生成结合截图工具在关键步骤特别是失败时自动截图并生成一个包含操作步骤、截图和结论的HTML或Markdown格式的测试报告。5.4 性能与成本权衡使用Claude API是有成本的按Token计费。每个工具调用和结果反馈都会消耗Token。对于冗长的测试流程成本可能不容忽视。优化提示词精炼系统提示词和工具描述减少不必要的文本。任务批处理对于一组固定的、可预见的操作不如直接写成传统的Playwright脚本。这个方案更适合探索性的、非固定的、或需要AI进行逻辑判断的环节。本地模型替代对于模式固定的任务可以考虑使用开源的、可在本地运行的小型代码生成模型如DeepSeek-Coder, CodeLlama通过类似的MCP架构来驱动以消除API成本。但这需要较强的本地部署和模型调优能力。6. 常见问题与实战排错指南在实际搭建和运行过程中你肯定会遇到各种问题。下面是我踩过的一些坑和解决方案问题1Claude不调用工具而是用文字描述应该怎么做。原因系统提示词没有强调“你必须使用我提供的工具来操作浏览器”。或者工具的描述不够清晰Claude不确定如何调用。解决强化系统提示词。例如“你必须且只能通过调用我提供的工具来与浏览器交互。不要用文字描述步骤直接调用工具。我会为你执行工具并返回结果。” 同时检查工具的描述是否准确说明了其功能和参数。问题2元素定位失败但手动操作浏览器明明存在。原因A等待不足。页面或元素尚未加载完成。Playwright的API虽然有自动等待但在某些复杂的单页应用SPA中可能需要更长的等待或等待特定条件。解决A实现并让Claude优先使用wait_for_element工具。或者在点击、填充等操作的工具实现中增加更稳健的等待逻辑例如await page.wait_for_selector(selector, state“visible”)。原因B选择器不唯一或动态生成。Claude生成的CSS选择器或XPath可能过于宽泛或者包含了动态变化的ID/类名。解决B引导Claude使用更稳定的定位策略。在系统提示词中加入建议“定位元素时优先考虑>