写点什么

Playwright MCP 入门指南:从零开始构建自动化测试

作者:测试人
  • 2025-10-14
    北京
  • 本文字数:5733 字

    阅读完需:约 19 分钟

在当今快速迭代的软件开发环境中,自动化测试已成为保证产品质量的关键环节。然而,传统的测试脚本编写往往需要测试人员具备专业的编程技能,这在一定程度上限制了自动化测试的普及和应用效率。正是基于这样的背景,自然语言驱动测试应运而生。

本文将带你使用 Playwright 结合 MCP 框架,从零开始构建你的第一个自然语言驱动自动化测试。无论你是测试新手还是经验丰富的开发者,都能通过本指南快速掌握这一前沿技术。

环境准备

安装必要的工具

首先,确保你的系统已安装以下软件:

# 安装Node.js(版本16或以上)# 从官网 https://nodejs.org/ 下载安装
# 安装Playwrightnpm init playwright@latest
# 安装MCP相关依赖npm install @modelcontextprotocol/sdk playwright-core
复制代码

项目结构初始化

创建项目目录并初始化基本结构:

my-nl-test-project/├── src/│   ├── mcp-client.js│   ├── test-generator.js│   └── utils/├── tests/│   └── generated/├── package.json└── README.md
复制代码

构建 MCP 客户端

基础客户端实现

// src/mcp-client.jsconst { Client } = require('@modelcontextprotocol/sdk');const { PlaywrightTestGenerator } = require('./test-generator');
class MCPTestClient {constructor() { this.client = new Client({ name: 'playwright-test-generator', version: '1.0.0' }); this.testGenerator = new PlaywrightTestGenerator(); }
async connectToServer(serverUrl) { try { awaitthis.client.connect(serverUrl); console.log('成功连接到MCP服务器'); } catch (error) { console.error('连接MCP服务器失败:', error); throw error; } }
async generateTestFromNaturalLanguage(nlDescription) { const prompt = ` 请将以下自然语言测试描述转换为Playwright测试代码: "${nlDescription}" 要求: 1. 使用Page Object模式 2. 包含必要的等待和断言 3. 代码结构清晰易读 4. 包含错误处理 只返回JavaScript代码,不需要解释。 `;
const response = awaitthis.client.request({ method: 'tools/call', params: { name: 'generate_code', arguments: { prompt: prompt, language: 'javascript' } } });
return response.result.content; }}
module.exports = { MCPTestClient };
复制代码

创建测试生成器

// src/test-generator.jsclass PlaywrightTestGenerator {  generateBaseTestStructure(testName) {    return`      const { test, expect } = require('@playwright/test');            test('${testName}', async ({ page }) => {        try {          // 测试步骤将在这里生成    `;  }
addNavigationStep(url) { return` // 导航到页面 await page.goto('${url}'); await page.waitForLoadState('networkidle'); `; }
addClickStep(selector, description) { return` // ${description} await page.click('${selector}'); await page.waitForTimeout(1000); `; }
addFillStep(selector, value, description) { return` // ${description} await page.fill('${selector}', '${value}'); `; }
addAssertionStep(assertionType, selector, expectedValue) { switch(assertionType) { case'visible': return` // 验证元素可见 await expect(page.locator('${selector}')).toBeVisible(); `; case'text': return` // 验证文本内容 await expect(page.locator('${selector}')).toHaveText('${expectedValue}'); `; case'url': return` // 验证当前URL await expect(page).toHaveURL('${expectedValue}'); `; default: return''; } }
completeTestStructure() { return` } catch (error) { console.error('测试执行失败:', error); throw error; } }); `; }
// 自然语言解析方法 parseNaturalLanguage(description) { const steps = []; if (description.includes('登录')) { steps.push({ type: 'navigation', url: 'https://example.com/login' }); if (description.includes('用户名')) { steps.push({ type: 'fill', selector: '#username', value: 'testuser', description: '输入用户名' }); } if (description.includes('密码')) { steps.push({ type: 'fill', selector: '#password', value: 'password123', description: '输入密码' }); } steps.push({ type: 'click', selector: '#login-btn', description: '点击登录按钮' }); if (description.includes('成功')) { steps.push({ type: 'assertion', assertionType: 'url', expectedValue: 'https://example.com/dashboard', description: '验证登录成功' }); } } return steps; }
generateTestFromSteps(testName, steps) { let testCode = this.generateBaseTestStructure(testName); steps.forEach(step => { switch(step.type) { case'navigation': testCode += this.addNavigationStep(step.url); break; case'click': testCode += this.addClickStep(step.selector, step.description); break; case'fill': testCode += this.addFillStep(step.selector, step.value, step.description); break; case'assertion': testCode += this.addAssertionStep( step.assertionType, step.selector, step.expectedValue ); break; } }); testCode += this.completeTestStructure(); return testCode; }}
module.exports = { PlaywrightTestGenerator };
复制代码

实现自然语言驱动测试

主程序入口

// src/main.jsconst { MCPTestClient } = require('./mcp-client');const fs = require('fs');const path = require('path');
class NaturalLanguageTestRunner {constructor() { this.mcpClient = new MCPTestClient(); this.testCount = 0; }
async initialize() { // 在实际应用中,这里应该连接到你选择的MCP服务器 // await this.mcpClient.connectToServer('your-mcp-server-url'); console.log('自然语言测试运行器初始化完成'); }
async createTestFromNaturalLanguage(description, testName = null) { const name = testName || `generated-test-${++this.testCount}`; console.log(`正在生成测试: ${name}`); console.log(`测试描述: ${description}`);
// 方法1: 使用MCP服务器生成测试(推荐) // const testCode = await this.mcpClient.generateTestFromNaturalLanguage(description); // 方法2: 使用本地解析器生成测试(备选方案) const steps = this.mcpClient.testGenerator.parseNaturalLanguage(description); const testCode = this.mcpClient.testGenerator.generateTestFromSteps(name, steps);
awaitthis.saveTestFile(name, testCode); console.log(`测试文件已生成: tests/generated/${name}.spec.js`); return testCode; }
async saveTestFile(testName, testCode) { const testDir = path.join(__dirname, '..', 'tests', 'generated'); if (!fs.existsSync(testDir)) { fs.mkdirSync(testDir, { recursive: true }); } const filePath = path.join(testDir, `${testName}.spec.js`); fs.writeFileSync(filePath, testCode, 'utf8'); }
async runGeneratedTests() { const { exec } = require('child_process'); const playwrightConfigPath = path.join(__dirname, '..', 'playwright.config.js'); returnnewPromise((resolve, reject) => { exec(`npx playwright test tests/generated/ --config=${playwrightConfigPath}`, (error, stdout, stderr) => { if (error) { console.error('测试执行错误:', error); reject(error); } else { console.log('测试执行完成'); console.log(stdout); resolve(stdout); } } ); }); }}
// 使用示例asyncfunction main() {const runner = new NaturalLanguageTestRunner();await runner.initialize();
// 示例1: 登录功能测试const loginTest = await runner.createTestFromNaturalLanguage( '测试用户登录功能:输入用户名和密码,点击登录按钮,验证登录成功跳转到仪表板页面', 'user-login-test' );
// 示例2: 搜索功能测试const searchTest = await runner.createTestFromNaturalLanguage( '在首页搜索框中输入"Playwright教程",点击搜索按钮,验证搜索结果页面显示相关内容', 'search-functionality-test' );
console.log('生成的登录测试代码:');console.log(loginTest);
// 运行生成的测试// await runner.runGeneratedTests();}
// 如果直接运行此文件,执行示例if (require.main === module) { main().catch(console.error);}
module.exports = { NaturalLanguageTestRunner };
复制代码

高级功能扩展

测试数据管理

// src/utils/test-data-manager.jsclass TestDataManager {constructor() {    this.testData = newMap();  }
generateTestData(scenario) { const baseData = { user: { username: `testuser_${Date.now()}`, email: `test_${Date.now()}@example.com`, password: 'TestPassword123!' }, product: { name: `测试产品_${Date.now()}`, price: Math.floor(Math.random() * 1000) + 1, description: '自动化测试创建的产品' } };
this.testData.set(scenario, baseData); return baseData; }
getTestData(scenario) { returnthis.testData.get(scenario) || this.generateTestData(scenario); }}
module.exports = { TestDataManager };
复制代码

智能元素定位器

// src/utils/smart-locator.jsclass SmartLocator {constructor(page) {    this.page = page;  }
async findElement(description) { // 基于描述智能查找元素 const strategies = [ // 按文本内容查找 `text=${description}`, // 按placeholder查找 `[placeholder*="${description}"]`, // 按label查找 `label=${description}`, // 按按钮文本查找 `button:has-text("${description}")` ];
for (const selector of strategies) { const element = this.page.locator(selector); if (await element.count() > 0) { return element.first(); } }
thrownewError(`未找到描述为"${description}"的元素`); }}
module.exports = { SmartLocator };
复制代码

配置 Playwright

// playwright.config.jsconst { defineConfig, devices } = require('@playwright/test');
module.exports = defineConfig({testDir: './tests',fullyParallel: true,forbidOnly: !!process.env.CI,retries: process.env.CI ? 2 : 0,workers: process.env.CI ? 1 : undefined,reporter: 'html',
use: { baseURL: 'https://your-test-site.com', trace: 'on-first-retry', screenshot: 'only-on-failure', },
projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, ],});
复制代码

实际应用示例

完整的测试场景

// 示例:完整的电商流程测试const e2eTestDescription = `测试完整的电商购买流程:1. 用户打开电商网站首页2. 搜索"笔记本电脑"3. 从搜索结果中选择第一个商品4. 将商品加入购物车5. 进入购物车页面6. 点击结算按钮7. 填写收货地址信息8. 选择支付方式9. 确认订单并完成购买10. 验证订单成功页面显示`;
// 生成并执行测试asyncfunction runE2ETest() {const runner = new NaturalLanguageTestRunner();await runner.initialize();
const testCode = await runner.createTestFromNaturalLanguage( e2eTestDescription, 'ecommerce-purchase-flow' );
console.log('生成的端到端测试代码:');console.log(testCode);}
复制代码

最佳实践和注意事项

1. 自然语言描述规范

  • 使用清晰、简洁的语言描述测试步骤

  • 明确指定预期的验证点

  • 包含具体的测试数据和条件

2. 测试维护策略

  • 定期审查生成的测试代码

  • 建立测试用例版本管理

  • 设置测试代码质量检查

3. 错误处理和调试

  • 为生成的测试添加充分的错误处理

  • 实现详细的日志记录

  • 建立测试失败分析机制

用户头像

测试人

关注

专注于软件测试开发 2022-08-29 加入

霍格沃兹测试开发学社,测试人社区:https://ceshiren.com/t/topic/22284

评论

发布
暂无评论
Playwright MCP入门指南:从零开始构建自动化测试_软件测试_测试人_InfoQ写作社区