Cursor结合Playwright MCP Server支持自动化
今天分享一下 playwright MCP Server,其提供了浏览器自动化能力,使大型语言模型能够在真实的浏览器环境中与网页交互,
也可以执行任务,例如运行JavaScript、截屏和导航网页元素,同时无缝处理 API 测试以验证端点并确保可靠性。
特性介绍:
一个模型上下文协议(Model Context Protocol)服务器,提供使用 Playwright 的浏览器自动化功能
使 LLM(大语言模型)能够与网页交互、截取屏幕截图,并在真实的浏览器环境中执行 JavaScript
具备的功能
- 🌐 完整的浏览器自动化能力
- 📸 整个页面或特定元素的屏幕截图捕获
- 🖱️ 全面的网页交互(导航、点击、表单填写)
- 📊 控制台日志监控
- 🔧 在浏览器上下文中执行 JavaScript
一、本地安装
1. 使用 npx安装playwright浏览器
npx playwright install
本地安装时,检查了兼容性:
2. 按照提示,需要升级node的版本后,版本如下
3. 再次执行, 安装成功结束截图如下
注意: 确保您已安装 Node.js,并且 npx
在您的系统 PATH 中可用。
二、在Cursor的配置方法
方法一、使用 Smithery 为 Cursor 安装 MCP Server Playwright**:
npx -y @smithery/cli install @automatalabs/mcp-server-playwright --client cursor
方法二、在cursor手动配置
1. cursor打开MCP配置
在 Cursor 的 Cursor Settings 中找到 MCP,点击右侧上方的 Add new global MCP server 按钮,便自动打开 mcp.json文件,然后将mcp server 的配置信息粘贴进去。
2. 填写如下配置
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@automatalabs/mcp-server-playwright"]
}
}
}
3. 安装后展示如下
案例展示
案例一、使用其进行自动化访问项目捕获
1. 访问公网项目,进行界面捕获
2. 服务自动书写脚本
const puppeteer = require('puppeteer');
async function screenshotBaidu() {
try {
// 使用系统Chrome浏览器
const browser = await puppeteer.launch({
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
// 设置视口大小
await page.setViewport({ width: 1280, height: 720 });
// 访问百度
console.log('正在访问百度网站...');
await page.goto('https://www.baidu.com/', { waitUntil: 'networkidle2' });
// 截图
console.log('正在截图...');
await page.screenshot({
path: 'baidu_screenshot.png',
fullPage: true
});
// 生成PDF
console.log('正在生成PDF...');
await page.pdf({
path: 'baidu_page.pdf',
format: 'A4',
printBackground: true
});
console.log('完成!截图保存为 baidu_screenshot.png,PDF保存为 baidu_page.pdf');
await browser.close();
} catch (error) {
console.error('发生错误:', error);
}
}
screenshotBaidu();
三、生成的PDF如下
案例二、访问公司项目,自动化测试执行测试
1. 发布命令
帮我访问zmp.iwhalecloud.com项目,帮我输入账号密码登录测试,账号为0027022977 密码为 xxx
2. 系统生成了执行js的内容
const puppeteer = require('puppeteer');
async function loginZmp() {
try {
// 使用系统Chrome浏览器,添加更多启动参数
const browser = await puppeteer.launch({
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
headless: false, // 设置为false以便观察登录过程
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--disable-gpu'
],
timeout: 60000
});
const page = await browser.newPage();
// 设置视口大小
await page.setViewport({ width: 1280, height: 720 });
// 访问zmp.iwhalecloud.com
console.log('正在访问zmp.iwhalecloud.com...');
await page.goto('https://zmp.iwhalecloud.com', {
waitUntil: 'networkidle2',
timeout: 60000
});
// 等待页面加载
await page.waitForTimeout(3000);
// 截图记录初始页面
await page.screenshot({
path: 'zmp_initial.png',
fullPage: true
});
console.log('正在查找登录表单...');
// 尝试查找用户名输入框
const usernameSelectors = [
'input[name="username"]',
'input[name="user"]',
'input[name="account"]',
'input[type="text"]',
'input[placeholder*="用户名"]',
'input[placeholder*="账号"]',
'input[placeholder*="用户名"]',
'#username',
'#user',
'#account'
];
let usernameInput = null;
for (const selector of usernameSelectors) {
try {
usernameInput = await page.$(selector);
if (usernameInput) {
console.log(`找到用户名输入框: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
// 尝试查找密码输入框
const passwordSelectors = [
'input[name="password"]',
'input[type="password"]',
'input[placeholder*="密码"]',
'#password',
'#pwd'
];
let passwordInput = null;
for (const selector of passwordSelectors) {
try {
passwordInput = await page.$(selector);
if (passwordInput) {
console.log(`找到密码输入框: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (usernameInput && passwordInput) {
console.log('正在输入登录信息...');
// 清空并输入用户名
await usernameInput.click();
await usernameInput.clear();
await usernameInput.type('0027022977');
// 清空并输入密码
await passwordInput.click();
await passwordInput.clear();
await passwordInput.type('xxxxx');
// 截图记录输入后的状态
await page.screenshot({
path: 'zmp_login_filled.png',
fullPage: true
});
console.log('正在查找登录按钮...');
// 尝试查找登录按钮
const loginButtonSelectors = [
'button[type="submit"]',
'input[type="submit"]',
'button:contains("登录")',
'button:contains("Login")',
'input[value="登录"]',
'input[value="Login"]',
'.login-btn',
'#login-btn',
'.btn-login',
'button.login',
'input.login'
];
let loginButton = null;
for (const selector of loginButtonSelectors) {
try {
loginButton = await page.$(selector);
if (loginButton) {
console.log(`找到登录按钮: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (loginButton) {
console.log('正在点击登录按钮...');
await loginButton.click();
// 等待登录响应
await page.waitForTimeout(5000);
// 截图记录登录后的状态
await page.screenshot({
path: 'zmp_after_login.png',
fullPage: true
});
console.log('登录完成!');
} else {
console.log('未找到登录按钮,尝试按回车键...');
await page.keyboard.press('Enter');
await page.waitForTimeout(3000);
await page.screenshot({
path: 'zmp_after_enter.png',
fullPage: true
});
}
} else {
console.log('未找到登录表单,截图记录当前页面状态...');
await page.screenshot({
path: 'zmp_no_form.png',
fullPage: true
});
}
// 获取页面内容以便分析
const pageContent = await page.content();
console.log('页面标题:', await page.title());
// 保存页面HTML内容
const fs = require('fs');
fs.writeFileSync('zmp_page.html', pageContent);
console.log('页面HTML已保存为 zmp_page.html');
console.log('测试完成!');
console.log('生成的文件:');
console.log('- zmp_initial.png: 初始页面截图');
console.log('- zmp_login_filled.png: 填写登录信息后截图');
console.log('- zmp_after_login.png: 登录后截图');
console.log('- zmp_page.html: 页面HTML内容');
// 保持浏览器打开以便观察
console.log('浏览器将保持打开状态,您可以观察登录结果...');
// await browser.close();
} catch (error) {
console.error('发生错误:', error);
}
}
loginZmp();
案例三、访问自建项目,自动化测试执行测试
1. 输入登录指令,要求如下:
登录系统地址:http://localhost:3000,用户名为admin,密码为123456,打开系统管理下的用户管理,在查询框输入admin,点击搜索按钮进行筛选,筛选出来后,第一条数据的操作列有编辑按钮,点击编辑按钮打开弹窗,在弹窗界面修改其手机号为18724145878,然后点击确认按钮,将每一步截图,把测试过程的操作过程测试报告使用mardown文件写到项目目录的test-report目录,截图放到相同目录
2. cursor开始自动自动化脚本
3. 编写后的自动化脚本如下
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
async function detailedUserTest() {
let browser;
try {
console.log('=== 开始详细用户管理系统测试 ===');
// 创建测试报告
const testReport = {
testName: '详细用户管理系统测试',
startTime: new Date().toISOString(),
steps: [],
screenshots: [],
details: []
};
// 使用系统Chrome浏览器
browser = await puppeteer.launch({
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
headless: false,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-web-security',
'--disable-features=VizDisplayCompositor',
'--disable-gpu',
'--no-first-run',
'--no-zygote'
],
timeout: 60000
});
const page = await browser.newPage();
// 步骤1: 访问登录页面
console.log('步骤1: 访问登录页面');
testReport.steps.push({
step: 1,
action: '访问登录页面',
description: '打开 http://localhost:3000'
});
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded',
timeout: 60000
});
await new Promise(resolve => setTimeout(resolve, 2000));
await page.screenshot({ path: 'test-report/01-login-page.png' });
testReport.screenshots.push('01-login-page.png');
testReport.details.push('✓ 登录页面截图已保存');
console.log('✓ 登录页面截图已保存');
// 步骤2: 输入用户名
console.log('步骤2: 输入用户名');
testReport.steps.push({
step: 2,
action: '输入用户名',
description: '输入用户名: admin'
});
const usernameInput = await page.$('input[name="username"], input[type="text"], #username');
if (usernameInput) {
await usernameInput.click();
await usernameInput.evaluate(el => el.value = '');
await usernameInput.type('admin');
testReport.details.push('✓ 用户名输入完成: admin');
console.log('✓ 用户名输入完成');
} else {
testReport.details.push('⚠ 未找到用户名输入框');
console.log('⚠ 未找到用户名输入框');
}
// 步骤3: 输入密码
console.log('步骤3: 输入密码');
testReport.steps.push({
step: 3,
action: '输入密码',
description: '输入密码: 123456'
});
const passwordInput = await page.$('input[name="password"], input[type="password"], #password');
if (passwordInput) {
await passwordInput.click();
await passwordInput.evaluate(el => el.value = '');
await passwordInput.type('123456');
testReport.details.push('✓ 密码输入完成: 123456');
console.log('✓ 密码输入完成');
} else {
testReport.details.push('⚠ 未找到密码输入框');
console.log('⚠ 未找到密码输入框');
}
await page.screenshot({ path: 'test-report/02-login-form-filled.png' });
testReport.screenshots.push('02-login-form-filled.png');
testReport.details.push('✓ 登录表单填写完成截图已保存');
console.log('✓ 登录表单填写完成截图已保存');
// 步骤4: 点击登录按钮
console.log('步骤4: 点击登录按钮');
testReport.steps.push({
step: 4,
action: '点击登录按钮',
description: '提交登录表单'
});
const loginButton = await page.$('button[type="submit"], input[type="submit"], .login-btn, #login-btn');
if (loginButton) {
await loginButton.click();
testReport.details.push('✓ 登录按钮点击完成');
console.log('✓ 登录按钮点击完成');
} else {
testReport.details.push('⚠ 未找到登录按钮,尝试按回车键');
console.log('⚠ 未找到登录按钮,尝试按回车键');
await page.keyboard.press('Enter');
}
// 等待登录响应
await new Promise(resolve => setTimeout(resolve, 3000));
await page.screenshot({ path: 'test-report/03-after-login.png' });
testReport.screenshots.push('03-after-login.png');
testReport.details.push('✓ 登录后页面截图已保存');
console.log('✓ 登录后页面截图已保存');
// 步骤5: 导航到系统管理
console.log('步骤5: 导航到系统管理');
testReport.steps.push({
step: 5,
action: '导航到系统管理',
description: '点击系统管理菜单'
});
// 尝试查找系统管理菜单
const systemMenuSelectors = [
'a[href*="system"]',
'span[class*="system"]',
'.system-management',
'#system-management',
'li a[href*="system"]',
'div[class*="system"]',
'a[title*="系统"]',
'span[title*="系统"]'
];
let systemMenu = null;
for (const selector of systemMenuSelectors) {
try {
systemMenu = await page.$(selector);
if (systemMenu) {
testReport.details.push(`✓ 找到系统管理菜单: ${selector}`);
console.log(`✓ 找到系统管理菜单: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (systemMenu) {
await systemMenu.click();
testReport.details.push('✓ 系统管理菜单点击完成');
console.log('✓ 系统管理菜单点击完成');
} else {
testReport.details.push('⚠ 未找到系统管理菜单,尝试直接访问用户管理');
console.log('⚠ 未找到系统管理菜单,尝试直接访问用户管理');
}
await new Promise(resolve => setTimeout(resolve, 2000));
await page.screenshot({ path: 'test-report/04-system-management.png' });
testReport.screenshots.push('04-system-management.png');
testReport.details.push('✓ 系统管理页面截图已保存');
console.log('✓ 系统管理页面截图已保存');
// 步骤6: 导航到用户管理
console.log('步骤6: 导航到用户管理');
testReport.steps.push({
step: 6,
action: '导航到用户管理',
description: '点击用户管理子菜单'
});
// 尝试查找用户管理菜单
const userManagementSelectors = [
'a[href*="user"]',
'span[class*="user"]',
'.user-management',
'#user-management',
'li a[href*="user"]',
'div[class*="user"]',
'a[title*="用户"]',
'span[title*="用户"]'
];
let userManagementMenu = null;
for (const selector of userManagementSelectors) {
try {
userManagementMenu = await page.$(selector);
if (userManagementMenu) {
testReport.details.push(`✓ 找到用户管理菜单: ${selector}`);
console.log(`✓ 找到用户管理菜单: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (userManagementMenu) {
await userManagementMenu.click();
testReport.details.push('✓ 用户管理菜单点击完成');
console.log('✓ 用户管理菜单点击完成');
} else {
testReport.details.push('⚠ 未找到用户管理菜单,尝试直接访问用户管理页面');
console.log('⚠ 未找到用户管理菜单,尝试直接访问用户管理页面');
// 尝试直接访问用户管理页面
await page.goto('http://localhost:3000/system/user', { waitUntil: 'domcontentloaded' });
}
await new Promise(resolve => setTimeout(resolve, 3000));
await page.screenshot({ path: 'test-report/05-user-management.png' });
testReport.screenshots.push('05-user-management.png');
testReport.details.push('✓ 用户管理页面截图已保存');
console.log('✓ 用户管理页面截图已保存');
// 步骤7: 在查询框输入admin
console.log('步骤7: 在查询框输入admin');
testReport.steps.push({
step: 7,
action: '在查询框输入admin',
description: '在搜索框中输入admin'
});
// 查找搜索框
const searchInput = await page.$('input[placeholder*="搜索"], input[placeholder*="查询"], input[name="search"], #search');
if (searchInput) {
await searchInput.click();
await searchInput.evaluate(el => el.value = '');
await searchInput.type('admin');
testReport.details.push('✓ 查询条件输入完成: admin');
console.log('✓ 查询条件输入完成');
} else {
testReport.details.push('⚠ 未找到查询框');
console.log('⚠ 未找到查询框');
}
await page.screenshot({ path: 'test-report/06-search-input.png' });
testReport.screenshots.push('06-search-input.png');
testReport.details.push('✓ 查询条件输入后截图已保存');
console.log('✓ 查询条件输入后截图已保存');
// 步骤8: 点击搜索按钮
console.log('步骤8: 点击搜索按钮');
testReport.steps.push({
step: 8,
action: '点击搜索按钮',
description: '点击搜索按钮执行查询'
});
// 查找搜索按钮
const searchButtonSelectors = [
'button[class*="search"]',
'button[class*="query"]',
'.search-btn',
'#search-btn',
'button[type="submit"]',
'input[type="submit"]'
];
let searchButton = null;
for (const selector of searchButtonSelectors) {
try {
searchButton = await page.$(selector);
if (searchButton) {
testReport.details.push(`✓ 找到搜索按钮: ${selector}`);
console.log(`✓ 找到搜索按钮: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (searchButton) {
await searchButton.click();
testReport.details.push('✓ 搜索按钮点击完成');
console.log('✓ 搜索按钮点击完成');
} else {
testReport.details.push('⚠ 未找到搜索按钮,尝试按回车键');
console.log('⚠ 未找到搜索按钮,尝试按回车键');
await page.keyboard.press('Enter');
}
await new Promise(resolve => setTimeout(resolve, 2000));
await page.screenshot({ path: 'test-report/07-search-results.png' });
testReport.screenshots.push('07-search-results.png');
testReport.details.push('✓ 搜索结果截图已保存');
console.log('✓ 搜索结果截图已保存');
// 步骤9: 点击第一条数据的编辑按钮
console.log('步骤9: 点击第一条数据的编辑按钮');
testReport.steps.push({
step: 9,
action: '点击第一条数据的编辑按钮',
description: '点击admin用户的编辑按钮打开弹窗'
});
// 查找编辑按钮
const editButtonSelectors = [
'button[class*="edit"]',
'a[class*="edit"]',
'.edit-btn',
'#edit-btn',
'button[title="编辑"]',
'a[title="编辑"]',
'tr:first-child button[class*="edit"]',
'tr:first-child a[class*="edit"]',
'table button[class*="edit"]',
'table a[class*="edit"]',
'button[onclick*="edit"]',
'a[onclick*="edit"]'
];
let editButton = null;
for (const selector of editButtonSelectors) {
try {
editButton = await page.$(selector);
if (editButton) {
testReport.details.push(`✓ 找到编辑按钮: ${selector}`);
console.log(`✓ 找到编辑按钮: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (editButton) {
await editButton.click();
testReport.details.push('✓ 编辑按钮点击完成');
console.log('✓ 编辑按钮点击完成');
} else {
testReport.details.push('⚠ 未找到编辑按钮');
console.log('⚠ 未找到编辑按钮');
}
await new Promise(resolve => setTimeout(resolve, 2000));
await page.screenshot({ path: 'test-report/08-edit-modal.png' });
testReport.screenshots.push('08-edit-modal.png');
testReport.details.push('✓ 编辑弹窗截图已保存');
console.log('✓ 编辑弹窗截图已保存');
// 步骤10: 在弹窗中修改手机号
console.log('步骤10: 在弹窗中修改手机号');
testReport.steps.push({
step: 10,
action: '在弹窗中修改手机号',
description: '将手机号修改为18724145878'
});
// 查找手机号输入框
const phoneInput = await page.$('input[name="phone"], input[placeholder*="手机"], #phone, input[type="tel"]');
if (phoneInput) {
await phoneInput.click();
await phoneInput.evaluate(el => el.value = '');
await phoneInput.type('18724145878');
testReport.details.push('✓ 手机号修改完成: 18724145878');
console.log('✓ 手机号修改完成');
} else {
testReport.details.push('⚠ 未找到手机号输入框');
console.log('⚠ 未找到手机号输入框');
}
await page.screenshot({ path: 'test-report/09-phone-modified.png' });
testReport.screenshots.push('09-phone-modified.png');
testReport.details.push('✓ 手机号修改后截图已保存');
console.log('✓ 手机号修改后截图已保存');
// 步骤11: 点击保存按钮
console.log('步骤11: 点击保存按钮');
testReport.steps.push({
step: 11,
action: '点击保存按钮',
description: '点击保存按钮提交修改'
});
// 查找保存按钮
const saveButtonSelectors = [
'button[class*="save"]',
'button[class*="confirm"]',
'button[class*="submit"]',
'.save-btn',
'#save-btn',
'button[type="submit"]',
'.modal button[class*="save"]',
'.dialog button[class*="save"]',
'button[onclick*="save"]',
'button[onclick*="confirm"]'
];
let saveButton = null;
for (const selector of saveButtonSelectors) {
try {
saveButton = await page.$(selector);
if (saveButton) {
testReport.details.push(`✓ 找到保存按钮: ${selector}`);
console.log(`✓ 找到保存按钮: ${selector}`);
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (saveButton) {
await saveButton.click();
testReport.details.push('✓ 保存按钮点击完成');
console.log('✓ 保存按钮点击完成');
} else {
testReport.details.push('⚠ 未找到保存按钮');
console.log('⚠ 未找到保存按钮');
}
await new Promise(resolve => setTimeout(resolve, 2000));
await page.screenshot({ path: 'test-report/10-save-completed.png' });
testReport.screenshots.push('10-save-completed.png');
testReport.details.push('✓ 保存完成截图已保存');
console.log('✓ 保存完成截图已保存');
// 完成测试
testReport.endTime = new Date().toISOString();
testReport.status = 'completed';
// 保存测试报告
const reportPath = path.join(__dirname, 'test-report.json');
fs.writeFileSync(reportPath, JSON.stringify(testReport, null, 2));
console.log('=== 测试完成 ===');
console.log('测试报告已保存到: test-report/test-report.json');
console.log('所有截图已保存到: test-report/ 目录');
} catch (error) {
console.error('测试过程中发生错误:', error);
} finally {
if (browser) {
console.log('浏览器将保持打开状态,您可以观察测试结果...');
// await browser.close();
}
}
}
detailedUserTest();
4. 处理完成后,生成了测试报告
5. 重新分析界面元素,修改脚本
6. 生成最终的结果报告
7. 结果为修改成功
案例四 访问某个项目,采集页面捕获页面,供二次开发
1. 输入指令
帮我访问此界面@https://www.iwhalecloud.com/ ,将页面所有元素捕获,生成静态文件,供二次开发 ,将页面所有元素捕获,生成静态文件,供二次开发
2.系统开始捕获