LIMS-MCP:基于AI与MCP协议,实现自动化测试元素定位的智能生成与自愈
1. 项目概述:当AI助手学会“找东西”
在自动化测试的世界里,写代码让机器点击一个按钮、输入一段文本,听起来是件挺酷的事。但干过这行的都知道,最让人头疼的往往不是写逻辑,而是告诉机器“你要点的那个按钮到底在哪”。这个问题,我们称之为“定位器(Locator)问题”。
想象一下,你对着一个网页,告诉你的AI助手:“帮我写个脚本,点一下那个登录按钮。”AI助手很听话,它生成了代码:page.click(‘button’)。结果呢?页面上可能有十个<button>,脚本要么点错,要么直接报错。你不得不手动去浏览器开发者工具里,在一堆HTML标签里翻找,试图找到一个独一无二的属性,比如>npm install -g @playwright/mcp@latest
接下来,配置Cursor使其能调用LIMS。Cursor的MCP服务器配置位于用户主目录下的.cursor/mcp.json文件中(如果不存在,请创建该文件)。
{ "mcpServers": { "Playwright": { "command": "npx", "args": ["-y", "@playwright/mcp@latest"] }, "LIMS": { "command": "npx", "args": ["-y", "lims-mcp"], "env": { "LIMS_PLAYWRIGHT_MCP_COMMAND": "npx", "LIMS_PLAYWRIGHT_MCP_ARGS": "[\"-y\", \"@playwright/mcp@latest\"]", "LIMS_PLAYWRIGHT_AUTO_BRIDGE": "false", "LIMS_LEARNING_ENABLED": "true", "LIMS_ARTIFACTS_ENABLED": "true", "LIMS_ARTIFACTS_DIR": "/Users/你的用户名/.lims/artifacts", "LIMS_PLAYWRIGHT_MCP_TIMEOUT_MS": "10000", "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1" } } } }关键配置项解释:
LIMS_PLAYWRIGHT_MCP_COMMAND/ARGS: 告诉LIMS如何启动它自己的Playwright MCP子进程用于验证。这里也使用npx,确保版本一致。LIMS_PLAYWRIGHT_AUTO_BRIDGE: 设置为false,因为我们已显式配置了MCP命令。LIMS_LEARNING_ENABLED: 开启学习功能,让LIMS越用越聪明。LIMS_ARTIFACTS_DIR: 指定元素快照的存储路径。请将/Users/你的用户名替换为你自己电脑上的实际用户目录。PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 设为1,因为全局安装的@playwright/mcp会管理浏览器,避免重复下载。
方式二:本地克隆与开发(适合贡献者或深度定制)如果你需要修改LIMS的源代码,或者想了解其内部机制,可以选择此方式。
git clone https://github.com/imran-imz7/lims-mcp cd lims-mcp npm install npm run build # 将TypeScript源码编译到dist目录 npm test # 运行项目自带的67个测试用例,确保一切正常编译成功后,在~/.cursor/mcp.json中配置使用本地构建的版本:
{ "mcpServers": { "LIMS": { "command": "node", "args": ["/绝对路径/到/lims-mcp/dist/cli.js"], "env": { // ... 环境变量与方式一类似 } } } }3.2 在Cursor中发起你的第一个请求
配置完成并重启Cursor后,你就可以开始使用了。在Cursor的聊天界面或编辑器内,直接用自然语言描述你的需求。
基础请求示例:
“为 https://demo.playwright.dev/todomvc 页面上的输入框生成Playwright定位器,并创建一个添加待办项的测试。”
更详细的请求(推荐):
“捕获 https://example.com/login 页面的DOM,为用户名输入框、密码输入框和登录按钮生成定位器。使用TypeScript,采用Page Object模式,并生成对应的 spec 测试文件。”
Cursor会理解你的意图,依次调用Playwright MCP进行页面导航和快照,然后将快照和你的描述传递给LIMS。LIMS处理完成后,会将结果(包括最佳定位器、备选方案、置信度)以及生成的代码文件路径返回给Cursor,Cursor再呈现给你。
3.3 运行时模式详解:如何连接浏览器?
LIMS与浏览器验证的交互方式是灵活可配的,理解这三种模式对排查问题至关重要。
| 模式 | 原理 | 激活方式 | 适用场景 |
|---|---|---|---|
| HTTP - 共享服务器模式 | LIMS连接到一个独立运行的、共享的Playwright MCP HTTP服务器。Cursor和LIMS共用同一个浏览器实例。 | 设置环境变量LIMS_PLAYWRIGHT_MCP_URL=http://localhost:8931/mcp,并手动启动npx @playwright/mcp --headless --port 8931。 | 团队共享一个浏览器服务,资源利用率高,但需要额外维护一个服务进程。 |
| stdio - 子进程模式(默认推荐) | LIMS在需要验证时,动态启动一个Playwright MCP子进程。LIMS独占这个浏览器实例。 | 设置LIMS_PLAYWRIGHT_MCP_COMMAND和LIMS_PLAYWRIGHT_MCP_ARGS(如NPX方式配置所示)。 | 最常用。自包含,隔离性好,无需额外管理。 |
| Standalone - 本地DOM模式 | 不启动真实浏览器,仅使用Cheerio库解析静态HTML/DOM进行定位器生成和验证。 | 不设置上述任何MCP相关环境变量。 | 离线环境、CI/CD流水线早期阶段、或无法安装/运行浏览器的场景。精度较低,无法验证可见性和可交互性。 |
避坑指南:绝大多数问题都出在Playwright MCP的连接上。如果遇到验证失败或超时,首先检查:
@playwright/mcp是否已正确安装(npm list -g @playwright/mcp)。- 环境变量
LIMS_PLAYWRIGHT_MCP_COMMAND和ARGS是否配置正确。如果使用npx,确保网络通畅。- 查看LIMS的日志(日志级别可通过
LIMS_LOG_LEVEL=debug设置)来确认它尝试以何种模式启动。
4. 深度定制:让LIMS更贴合你的项目
LIMS的强大之处在于其高度的可配置性。你的项目可能有特殊的前端框架、独特的属性命名规范,或者你对定位器的稳定性有更极致的追求。以下是如何进行深度定制的指南。
4.1 调整定位器优先级与权重
定位器的生成策略和评分权重是LIMS行为的核心杠杆,相关配置集中在几个关键文件中。
修改定位器生成优先级优先级决定了LIMS尝试各种定位策略的顺序。默认优先级(src/utils/locator-priority.ts)是行业经验的总结,但你可以调整。例如,如果你的项目大量使用aria-label且规范良好,你可能想把它提升到和>// 示例:调整优先级,将 aria-label 提升到 Tier 1,与 test-id 并列 export const LOCATOR_PRIORITY_TIERS = [ { name: 'Test attributes', providers: [/*>export const RANKING_WEIGHTS = { uniqueness: 0.35, // 必须匹配唯一元素,权重最高 attributeStability: 0.25, // 属性稳定性(如testid vs class) readability: 0.15, // 可读性 maintainability: 0.15, // 可维护性(避免复杂表达式) length: 0.10, // 长度 }; // 注意:所有权重之和必须为 1.0。
如果你想更激进地偏好短小精悍的定位器,可以适当提高length的权重,并相应降低其他权重。例如,调整为length: 0.20, 同时将readability和maintainability各降低0.05。
自定义动态文本分类规则LIMS能智能区分静态文本和动态文本。规则定义在src/domain/dynamic/index.ts。你可以根据项目特点添加新的模式。
// 示例:添加一个项目特有的动态文本模式,如包含特定前缀的翻译键 export function classifyText(text: string): TextStabilityClass { if (!text || text.length < 2) return TextStabilityClass.HIGHLY_DYNAMIC; // 原有规则... // 新增规则:如果文本匹配翻译键格式如 `i18n.button.submit`,视为静态(因为键名不变) if (text.match(/^i18n\.\w+\.\w+$/)) { return TextStabilityClass.STATIC; } // 新增规则:如果文本是特定格式的日期(如项目日志格式),视为高度动态 if (text.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)) { return TextStabilityClass.HIGHLY_DYNAMIC; } }4.2 为特定UI框架编写自定义定位器提供者
如果你的项目使用了复杂的UI组件库,如AG Grid、React Virtualized或Flutter Web,标准的定位策略可能不够高效。LIMS的插件化架构允许你为其编写自定义的LocatorCandidateProvider。
步骤:
- 创建提供者文件:在
src/domain/locator/providers/目录下创建新文件,例如my-grid-provider.ts。 - 实现接口:实现
LocatorCandidateProvider接口,核心是provide方法,它接收一个DOM元素(Cheerio对象)和上下文信息,返回该元素特有的定位器候选数组。 - 注册提供者:在
src/di/container.ts文件中,找到locatorCandidateProviders的注册处,将你的新提供者类添加进去。
示例:为某个自定义数据表格组件提供定位器假设你的表格行有一个特殊的属性>// src/domain/locator/providers/custom-grid-provider.ts import { LocatorCandidateProvider } from '../locator-extension'; import { LocatorCandidate } from '../../contracts/types'; export class CustomGridProvider implements LocatorCandidateProvider { name = 'custom-grid-provider'; provide(element: CheerioElement, context: any): LocatorCandidate[] { const candidates: LocatorCandidate[] = []; const $ = context.$; // 假设上下文中有Cheerio实例 // 1. 检查是否是我们的自定义表格行 const rowKey = $(element).attr('data-row-key'); if (rowKey) { candidates.push({ locator: `page.locator('[data-row-key="${rowKey}"]')`, strategy: 'data-row-key', confidence: 0.95, // 给予高置信度 platform: 'playwright', }); // 2. 也许还可以基于行索引生成一个备用定位器 const rowIndex = $(element).parent().children().index(element); candidates.push({ locator: `page.locator('.my-grid tbody tr').nth(${rowIndex})`, strategy: 'row-index', confidence: 0.60, // 索引稳定性较低 platform: 'playwright', }); } return candidates; } } // 在 src/di/container.ts 中注册 // 找到 container.register(...) 部分,添加: container.register('LocatorCandidateProvider', CustomGridProvider);
4.3 配置详解:环境变量全指南
LIMS的所有行为几乎都可以通过环境变量控制。以下是所有可用变量的详细说明:
| 环境变量 | 默认值 | 说明与建议 |
|---|---|---|
LIMS_PLAYWRIGHT_MCP_URL | (空) | HTTP模式。指向已运行的Playwright MCP服务器URL,如http://localhost:8931/mcp。 |
LIMS_PLAYWRIGHT_MCP_COMMAND | (空) | 子进程模式。启动Playwright MCP的命令,如npx。 |
LIMS_PLAYWRIGHT_MCP_ARGS | [] | 子进程模式。传递给上述命令的JSON数组参数,如["-y", "@playwright/mcp@latest"]。 |
LIMS_PLAYWRIGHT_MCP_TIMEOUT_MS | 7000 | Playwright MCP调用的超时时间(毫秒)。网络慢可适当增加。 |
LIMS_PLAYWRIGHT_AUTO_BRIDGE | true | 当未配置MCP时,是否自动启动一个本地HTTP验证桥接。通常设为false以使用明确的MCP配置。 |
LIMS_LEARNING_ENABLED | true | 是否启用学习功能。记录定位器成功/失败历史,用于微调未来排名。 |
LIMS_ARTIFACTS_ENABLED | true | 是否保存元素快照(指纹)。必须开启才能使用自愈功能。 |
LIMS_ARTIFACTS_DIR | .lims/artifacts | 元素快照的存储目录。建议设置为绝对路径,如/home/user/.lims/artifacts。 |
LIMS_LOG_LEVEL | info | 日志级别:debug,info,warn,error。调试时设为debug。 |
LIMS_HEALTH_PROFILE | balanced | 健康检查模式:balanced(均衡),dom-first(优先DOM检查),screenshot-first(优先截图检查)。 |
5. 实战:将LIMS集成到现有Playwright框架
LIMS不仅能通过Cursor交互生成代码,其生成的定位器和Page Object文件可以直接融入你现有的Playwright测试框架。以下是逐步集成指南。
5.1 理解生成的文件结构
当你要求LIMS为某个“功能”(feature)生成代码时,它会创建三个文件,遵循经典的Page Object Model (POM) 模式:
{feature}.locator.ts:定位器映射文件。这是核心,以常量的形式导出所有UI元素的定位器字符串。// login.locator.ts export const LoginPageLocators = { usernameInput: `page.getByTestId('username-field')`, passwordInput: `page.getByRole('textbox', { name: /password/i })`, submitButton: `page.locator('#login-submit')`, errorMessage: `page.locator('.alert-error')`, } as const;{feature}.page.ts:页面对象文件。封装了页面上的操作和行为,使用上面定义的定位器。// login.page.ts import { Locator, Page } from '@playwright/test'; import { LoginPageLocators } from './login.locator'; export class LoginPage { constructor(private readonly page: Page) {} get usernameInput(): Locator { return this.page.locator(LoginPageLocators.usernameInput); } // ... 其他元素的getter async login(username: string, password: string) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.submitButton.click(); } async getErrorMessage(): Promise<string | null> { return await this.errorMessage.textContent(); } }{feature}.spec.ts:测试用例文件。使用页面对象来编写实际的测试逻辑。// login.spec.ts import { test, expect } from '@playwright/test'; import { LoginPage } from './login.page'; test.describe('Login Functionality', () => { test('should login with valid credentials', async ({ page }) => { const loginPage = new LoginPage(page); await page.goto('https://example.com/login'); await loginPage.login('validUser', 'validPass'); // 添加断言... }); });
5.2 手动集成生成的代码
你可以将LIMS生成的文件直接复制到你的Playwright项目中的相应目录(例如tests/pages/和tests/specs/)。然后,在你的测试中导入并使用这些页面对象。
项目结构示例:
my-playwright-project/ ├── tests/ │ ├── pages/ # 存放 .page.ts 和 .locator.ts 文件 │ │ ├── login.page.ts │ │ └── login.locator.ts │ ├── specs/ # 存放 .spec.ts 测试文件 │ │ └── login.spec.ts │ └── playwright.config.ts # Playwright 配置文件 └── package.json在测试中使用:
// tests/specs/dashboard.spec.ts import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; import { DashboardPage } from '../pages/dashboard.page'; // 另一个LIMS生成的页面对象 test('user can login and see dashboard', async ({ page }) => { const loginPage = new LoginPage(page); const dashboardPage = new DashboardPage(page); await page.goto('/login'); await loginPage.login('testuser', 'password123'); await expect(dashboardPage.welcomeMessage).toBeVisible(); });5.3 利用自愈功能维护测试健壮性
LIMS的自愈功能是其长期价值的体现。要启用它,你需要做两件事:
- 确保
LIMS_ARTIFACTS_ENABLED=true:这样LIMS才会在生成定位器时保存元素指纹。 - 在测试运行后提供反馈:这通常需要一些简单的脚本集成。理想情况下,你可以在测试套件的
afterEach或afterAll钩子中,根据测试结果调用LIMS的report_locator_result工具(通过其HTTP接口或CLI)。
一个简化的概念性脚本示例:
// 伪代码:在Playwright测试运行后调用 const { exec } = require('child_process'); const testResult = await runPlaywrightTest(); // 你的测试运行逻辑 if (testResult.failedDueToLocator) { // 假设你知道是哪个定位器失败了,以及对应的artifactRef const command = `npx lims-mcp heal_locator --artifact-ref ${failedArtifactRef} --current-url ${pageUrl}`; exec(command, (error, stdout) => { if (!error) { const healedData = JSON.parse(stdout); console.log('LIMS建议的新定位器:', healedData.healedLocator); // 自动或手动更新你的 .locator.ts 文件 updateLocatorFile(healedData.healedLocator); } }); }在实际团队协作中,你可以将这一步集成到CI/CD流水线中。当测试因定位器失效而失败时,自动触发LIMS的自愈分析,并将修复建议以评论的形式提交到Pull Request中,供开发人员审查和合并。
6. 故障排除与性能优化
6.1 常见问题与解决方案
即使配置正确,在实际使用中也可能遇到一些问题。以下是一些常见问题及其排查步骤。
问题一:Cursor提示“无法连接到LIMS服务器”或工具调用无响应。
- 检查1:配置文件路径与格式。确保
~/.cursor/mcp.json文件存在且JSON格式正确(无尾随逗号)。可以使用在线JSON验证器检查。 - 检查2:Node版本与全局安装。确保Node版本>=20,并且已全局安装
@playwright/mcp(npm install -g @playwright/mcp@latest)。 - 检查3:命令路径。如果使用本地构建模式,确保
args中的路径是绝对路径且指向正确的cli.js文件。 - 检查4:重启Cursor。任何对
mcp.json的修改都需要重启Cursor才能生效。 - 检查5:查看日志。在
mcp.json的LIMS配置中临时添加"env": { "LIMS_LOG_LEVEL": "debug", ... },然后重启Cursor并查看终端输出(如果从终端启动Cursor)或Cursor的内置日志,寻找错误信息。
问题二:定位器生成成功,但运行时验证失败或超时。
- 检查1:Playwright MCP连接模式。确认
LIMS_PLAYWRIGHT_MCP_COMMAND和ARGS配置正确。如果是npx,确保网络能访问npm registry。 - 检查2:超时设置。对于复杂页面或网络较慢的环境,可以适当增加
LIMS_PLAYWRIGHT_MCP_TIMEOUT_MS的值(例如设为15000)。 - 检查3:页面加载状态。LIMS在验证时,页面必须已经加载完成。如果页面有大量异步内容,可以尝试在指令中提示Cursor:“等待页面完全加载后,再为元素X生成定位器”。
- 检查4:浏览器上下文。确保Cursor的Playwright MCP和LIMS的Playwright MCP没有因浏览器上下文(如iframe、弹窗)问题而找不到元素。尝试在指令中明确指定“在主框架中”。
问题三:生成的定位器置信度普遍很低(<0.7)。
- 原因1:页面缺乏稳定的测试属性。这是最常见原因。LIMS被迫使用不稳定的属性(如类名、动态ID)或文本。
- 对策:推动开发团队为关键交互元素添加
>
