Cursor结合Playwright MCP Server支持自动化

发布于:2025-08-03 ⋅ 阅读:(43) ⋅ 点赞:(0)

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.系统开始捕获

在这里插入图片描述
在这里插入图片描述

3. 最终成果

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到