基于Playwright与MCP协议构建AI驱动的UI自动化测试与修正系统
1. 项目概述当AI学会“看”界面最近在折腾一个挺有意思的玩意儿我把它叫做“给AI插上眼睛”。核心想法很简单我们总在说AI能写代码、能改Bug但让它去“看”一个网页或者一个软件界面然后基于视觉反馈去迭代优化UI设计这事儿听起来就有点科幻了。但现实是通过把Playwright、MCPModel Context Protocol和Claude Code这三样东西攒到一起这事儿还真能跑通。简单来说这个项目构建了一个能“看见”并“理解”用户界面的AI代理。它不再只是对着代码文件空想而是能像真人测试工程师或设计师一样启动一个真实的浏览器加载你的网页或应用用“眼睛”Playwright的截图和DOM分析能力去观察当前的UI状态。然后它基于预设的设计规范比如间距、颜色对比度、对齐方式或你的自然语言指令比如“把这个按钮调大点”、“让这两个模块对齐”通过Claude Code的代码生成能力直接修改源代码并驱动Playwright执行刷新、重新截图、再次评估的循环直到UI达到满意的“像素级”标准。这解决了一个什么痛点呢在传统的UI开发或走查流程里设计师标注了问题前端工程师需要肉眼比对、手动测量、再修改代码、刷新页面看效果如此反复效率低下且容易遗漏。而这个自动化流程相当于把一个不知疲倦、绝对严谨的“像素眼”质检员“手速超快”的修改员结合到了一起实现了UI问题的自动发现、自动修正和自动验证闭环。它特别适合需要频繁进行UI微调、响应式适配检查、或者维护大型设计系统一致性的场景。2. 核心架构与工具选型解析要搭建这么一个系统工具链的选择至关重要每个组件都承担着不可替代的角色。我经过多轮对比和实测最终敲定了下面这套组合拳理由我会一一拆解。2.1 为什么是Playwright不只是自动化测试首先我们需要一个强大的“眼睛”和“手”。Selenium、Cypress和Playwright是前端自动化测试的三驾马车。我选择Playwright原因有四无头浏览器支持与高性能截图Playwright对Chromium、Firefox、WebKit的原生支持非常出色尤其是在无头模式下运行稳定截图速度快且质量高。这对于需要高频次截屏进行视觉分析的场景至关重要。它的page.screenshot()API功能强大可以截取全屏、某个元素、甚至带透明背景的PNG为我们提供了丰富的“视觉素材”。丰富的DOM与状态查询能力除了截图Playwright能轻松获取元素的精确位置、尺寸、计算样式、甚至阴影DOM内的内容。我们可以通过page.locator(‘.btn’).boundingBox()拿到一个按钮的x, y, width, height或者用page.locator(‘.text’).evaluate(el getComputedStyle(el).fontSize)获取计算后的字体大小。这些数据是进行“像素级”分析的基础。可靠的页面操作与等待机制AI生成修改代码后需要驱动浏览器执行刷新、点击等操作。Playwright的自动等待机制等待元素可见、可点击、网络空闲能极大提高脚本的稳定性避免因页面加载延迟导致的执行失败。跨平台与易集成性Playwright支持Node.js、Python、.NET、Java等多种语言这给了我们很大的集成灵活性。我们这里主要用其Node.js版本可以很好地与后续的JavaScript/TypeScript工具链融合。注意有些同学可能会想到用Puppeteer。Puppeteer也很优秀但Playwright由微软维护在多浏览器支持、录制工具和社区生态上目前显得更活跃一些对于这种需要长期维护和扩展的项目我倾向于选择Playwright。2.2 MCP模型上下文协议打通AI与工具的“万能插座”MCP是Anthropic提出的一套协议你可以把它理解为一个标准化的“插座”或“适配器”。它的核心价值在于让像Claude这样的AI模型能够安全、标准化地调用外部工具、访问外部数据和执行操作而无需为每个工具编写特定的、复杂的集成代码。在我们的项目里MCP扮演了“中间件”或“总线”的角色对AIClaude Code而言它不需要知道Playwright的具体API细节。它只需要知道通过MCP这个标准接口可以发出一个“截图”或“获取元素样式”的请求。对我们开发者而言我们编写一个MCP Server。这个Server封装了所有Playwright的操作如启动浏览器、导航、截图、获取元素信息、执行脚本等并将其暴露为一系列标准的MCP工具Tools。这样Claude Code通过MCP客户端连接到我们的Server就能像调用内置函数一样调用这些浏览器操作工具。这种解耦使得系统非常灵活未来如果我们想把“眼睛”从Playwright换成别的什么库只需要更新MCP Server的实现AI侧的调用方式完全不用变。2.3 Claude Code具备“动手能力”的AI编码引擎Claude Code不是指Claude聊天机器人而是特指其具备代码执行能力的版本或配置通常通过Claude Desktop应用或特定API配置启用。它集成了代码解释器Code Interpreter功能不仅能生成代码还能在受控的沙箱环境中执行代码并看到执行结果。在这个项目中Claude Code是“大脑”和“手”的协调中心接收视觉与数据反馈通过MCP它从Playwright获得截图和元素数据。分析与决策它“看”截图并结合获取的样式数据理解当前UI与目标状态设计规范或你的指令的差距。生成并执行修正代码它根据差距分析生成修改CSS、HTML或JavaScript的代码。然后关键的一步来了它可以通过MCP调用另一个工具让Playwright在页面上注入并执行这段CSS/JS代码例如通过page.addStyleTag({content: newCSS})或page.evaluate()实现即时预览。或者它可以直接修改项目源文件。驱动迭代循环执行修改后它再次通过MCP调用截图工具评估修正效果决定是否继续迭代。3. 环境搭建与核心组件实现理论讲完了我们动手搭起来。整个系统的核心是那个承上启下的MCP Server。3.1 初始化项目与安装依赖首先创建一个新的Node.js项目目录。mkdir ai-ui-corrector cd ai-ui-corrector npm init -y安装核心依赖npm install modelcontextprotocol/sdk playwrightmodelcontextprotocol/sdkAnthropic官方提供的MCP Server开发SDK帮助我们快速构建符合协议的服务器。playwright我们的浏览器自动化核心。另外为了运行Playwright的浏览器还需要安装浏览器二进制文件npx playwright install chromium这里我选择Chromium因为它在无头模式下性能最好也最稳定。3.2 构建Playwright MCP Server接下来我们创建server.js实现一个具备基本功能的MCP Server。// server.js const { Server } require(‘modelcontextprotocol/sdk/server/index.js’); const { StdioServerTransport } require(‘modelcontextprotocol/sdk/server/stdio.js’); const { playwright } require(‘playwright’); class PlaywrightMCPServer { constructor() { this.server new Server( { name: ‘playwright-ui-assistant’, version: ‘0.1.0’, }, { capabilities: { tools: {}, // 声明我们将提供工具 }, } ); this.browser null; this.context null; this.page null; // 注册工具处理函数 this.setupToolHandlers(); // 设置连接监听 this.setupConnection(); } setupToolHandlers() { this.server.setRequestHandler(‘tools/call’, async (request) { const { name, arguments: args } request.params; console.log([MCP Server] 调用工具: ${name}, args); try { switch (name) { case ‘navigate_to_url’: return await this.navigateToUrl(args); case ‘take_screenshot’: return await this.takeScreenshot(args); case ‘get_element_styles’: return await this.getElementStyles(args); case ‘inject_css’: return await this.injectCss(args); case ‘close_browser’: return await this.closeBrowser(); default: throw new Error(未知工具: ${name}); } } catch (error) { console.error([MCP Server] 工具执行错误:, error); return { content: [ { type: ‘text’, text: 工具执行失败: ${error.message}, }, ], }; } }); } setupConnection() { const transport new StdioServerTransport(); this.server.connect(transport).catch((error) { console.error(‘[MCP Server] 连接失败:’, error); process.exit(1); }); console.log(‘[MCP Server] MCP Server 已启动通过 stdio 通信。’); } // 工具实现 1: 导航到URL async navigateToUrl({ url }) { if (!this.browser) { this.browser await playwright.chromium.launch({ headless: true }); this.context await this.browser.newContext({ viewport: { width: 1280, height: 720 }, }); this.page await this.context.newPage(); } await this.page.goto(url, { waitUntil: ‘networkidle’ }); return { content: [{ type: ‘text’, text: 已导航至: ${url} }], }; } // 工具实现 2: 截图 async takeScreenshot({ selector null, fullPage false }) { if (!this.page) throw new Error(‘请先导航至一个网页’); let buffer; if (selector) { const element await this.page.locator(selector).first(); await element.waitFor({ state: ‘visible’ }); buffer await element.screenshot(); } else { buffer await this.page.screenshot({ fullPage }); } // 将截图转换为Base64方便在文本协议中传输 const base64Image buffer.toString(‘base64’); return { content: [ { type: ‘image’, data: base64Image, mimeType: ‘image/png’, }, { type: ‘text’, text: ‘截图已完成。’ }, ], }; } // 工具实现 3: 获取元素样式 async getElementStyles({ selector }) { if (!this.page) throw new Error(‘请先导航至一个网页’); const element await this.page.locator(selector).first(); await element.waitFor({ state: ‘attached’ }); const styles await element.evaluate((el) { const computed window.getComputedStyle(el); return { width: el.offsetWidth, height: el.offsetHeight, top: el.offsetTop, left: el.offsetLeft, fontSize: computed.fontSize, color: computed.color, backgroundColor: computed.backgroundColor, margin: computed.margin, padding: computed.padding, // 可以添加更多感兴趣的样式属性 }; }); return { content: [{ type: ‘text’, text: JSON.stringify(styles, null, 2) }], }; } // 工具实现 4: 注入CSS async injectCss({ css }) { if (!this.page) throw new Error(‘请先导航至一个网页’); await this.page.addStyleTag({ content: css }); return { content: [{ type: ‘text’, text: ‘CSS已注入页面。’ }], }; } // 工具实现 5: 关闭浏览器 async closeBrowser() { if (this.browser) { await this.browser.close(); this.browser null; this.context null; this.page null; } return { content: [{ type: ‘text’, text: ‘浏览器已关闭。’ }], }; } } // 启动服务器 new PlaywrightMCPServer();这个Server提供了五个核心工具导航、截图支持全屏和元素、获取元素样式、注入CSS和关闭浏览器。它通过标准输入输出与Claude Code通信。3.3 配置Claude Desktop以连接MCP Server要让Claude Code使用我们的Server需要配置Claude Desktop应用。找到它的配置文件通常在~/Library/Application Support/Claude/claude_desktop_config.json或%APPDATA%\Claude\claude_desktop_config.json进行如下配置{ “mcpServers”: { “playwright-ui-assistant”: { “command”: “node”, “args”: [“/你的/项目/绝对/路径/ai-ui-corrector/server.js”], “env”: { “NODE_ENV”: “production” } } } }配置完成后重启Claude Desktop。如果配置成功在Claude的聊天界面你应该能看到一个类似“工具已连接”的提示或者能在工具调用区域看到可用的工具列表。4. 实战像素级UI修正工作流环境搭好了我们来跑一个完整的场景。假设我们有一个简单的网页其按钮的垂直间距不符合设计规范8px我们需要AI自动发现并修正它。4.1 目标网页与问题定义我们创建一个简单的test-page.html作为测试目标!DOCTYPE html html head style .container { padding: 20px; } .btn { display: block; margin: 4px 0; /* 问题在这里垂直间距是4px设计规范要求8px */ padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } /style /head body div class“container” button class“btn”按钮 A/button button class“btn”按钮 B/button button class“btn”按钮 C/button /div /body /html用任何本地HTTP服务器如npx serve .运行这个页面假设地址是http://localhost:3000/test-page.html。我们的设计规范是.btn类元素的上下外边距margin-top 和 margin-bottom应为8px。4.2 与Claude Code的协作对话现在我们打开Claude Desktop开始与集成了我们MCP工具的Claude Code对话。第一轮探索与发现问题我们给Claude指令“请使用Playwright工具导航到 http://localhost:3000/test-page.html然后对页面进行截图并获取类名为 ‘btn’ 的元素的样式信息。”Claude Code会通过MCP调用我们的Server执行navigate_to_url然后调用take_screenshot和get_element_styles。它会将截图展示给我看并返回类似下面的样式数据{ “width”: 89, “height”: 41, “top”: 20, “left”: 20, “fontSize”: “16px”, “color”: “rgb(255, 255, 255)”, “backgroundColor”: “rgb(0, 123, 255)”, “margin”: “4px 0px”, “padding”: “10px 20px” }第二轮分析与生成修正方案我们继续指令“根据获取的样式我发现按钮的垂直间距margin是’4px 0px’这意味着上下边距是4px左右为0。这不符合设计规范要求上下边距为8px。请分析如何修改CSS来修复这个问题并通过工具将修正后的CSS注入到页面中预览效果。”Claude Code会进行分析分析当前.btn的margin是4px 0px。要改为上下8px左右保持0新的margin值应为8px 0px。生成CSS它需要生成一个CSS规则来覆盖原有样式。更精准的做法是只修改垂直边距。.btn { margin-top: 8px !important; margin-bottom: 8px !important; }使用!important是为了确保能覆盖可能存在的其他样式规则在自动化修正中常用但实际项目中需谨慎。调用工具Claude Code会通过MCP调用inject_css工具将上面生成的CSS注入页面。第三轮验证与迭代我们再次指令“很好。现在请再次对页面进行截图并重新获取 ‘.btn’ 元素的样式确认margin值是否已更新为8px。”Claude Code会再次调用take_screenshot和get_element_styles。新的样式数据会显示“margin”: “8px 0px”。通过对比前后截图我们也能直观看到按钮间距变大了。至此一个完整的“发现问题 - 分析问题 - 生成方案 - 执行修改 - 验证结果”的UI迭代自修正闭环就完成了。你可以将这个对话过程保存为提示词模板未来只需给出目标URL和设计规范Claude Code就能自动执行多轮迭代。4.3 扩展从“预览修正”到“源码修正”上面的流程是在运行时注入CSS进行预览是“热修复”。更彻底的修正应该是直接修改源代码文件。这需要扩展我们的MCP Server和协作流程。扩展MCP Server工具在server.js中增加一个read_file和write_file工具需要确保有安全的文件路径访问限制。Claude Code工作流升级AI通过read_file读取项目的CSS源文件如styles.css。AI分析文件内容定位到.btn相关的规则将margin: 4px 0;修改为margin: 8px 0;。AI通过write_file将修改后的内容写回文件。最后通过navigate_to_url重新加载页面或利用开发服务器的热重载来验证修改。这样AI就完成了从视觉发现问题到直接修改源码的完整闭环真正成为了开发工作流的一部分。5. 避坑指南与效能提升技巧在实际搭建和运行过程中我踩了不少坑也总结了一些提升效率和稳定性的技巧。5.1 稳定性与性能优化浏览器实例管理在我们的示例Server中浏览器实例是单例的。长时间运行可能导致内存泄漏。在生产环境中需要考虑超时自动关闭、错误恢复重启等机制。一个简单的策略是为每个“会话”或“任务”创建独立的浏览器实例任务结束后立即关闭。截图与数据传输全屏截图尤其是高分辨率下生成的Base64数据会非常庞大可能超出MCP或模型上下文的限制。解决方案按需截图只截取有变化的区域或特定元素。压缩与缩放在Playwright截图时指定scale: ‘css’或调整quality或者在后端对图像进行压缩后再编码为Base64。使用文件系统对于非常大的图像可以考虑将截图保存为临时文件然后将文件路径或URL通过MCP传给AI。这需要更复杂的Server设计。元素定位的稳定性依赖CSS选择器如.btn可能在不稳定的动态页面中失效。结合使用更稳定的定位策略如>

相关新闻