• 简体中文
  • 集成到 Playwright

    Playwright.js 是由微软开发的一个开源自动化库,主要用于对网络应用程序进行端到端测试(end-to-end test)和网页抓取。

    与 Playwright 的集成方式有以下两种方式:

    • 直接用脚本方式集成和调用 Midscene Agent,适合快速体验、原型开发、数据抓取和自动化脚本等场景。
    • 在 Playwright 的测试用例中集成 Midscene,适合需要执行 UI 测试的场景。

    配置 AI 模型服务

    通过环境变量设置模型。选择模型时,请参考模型策略

    export MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1"
    export MIDSCENE_MODEL_API_KEY="替换为你的 API Key"
    export MIDSCENE_MODEL_NAME="替换为你的模型名称"
    export MIDSCENE_MODEL_FAMILY="替换为你的模型系列"

    全部配置项请参考模型配置

    直接集成 Midscene Agent

    样例项目

    你可以在这里看到向 Playwright 集成的样例项目:https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo

    第一步:安装依赖

    npm
    yarn
    pnpm
    bun
    deno
    npm install @midscene/web playwright @playwright/test tsx --save-dev

    第二步:编写脚本

    编写下方代码,保存为 ./demo.ts

    import { chromium } from 'playwright';
    import { PlaywrightAgent } from '@midscene/web/playwright';
    import 'dotenv/config'; // read environment variables from .env file
    
    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
    
    Promise.resolve(
      (async () => {
        const browser = await chromium.launch({
          headless: true, // 'true' means we can't see the browser window
          args: ['--no-sandbox', '--disable-setuid-sandbox'],
        });
    
        const page = await browser.newPage();
        await page.setViewportSize({
          width: 1280,
          height: 768,
        });
        await page.goto('https://www.ebay.com');
        await sleep(5000); // 👀 init Midscene agent
        const agent = new PlaywrightAgent(page);
    
        // 👀 type keywords, perform a search
        await agent.aiAct('type "Headphones" in search box, hit Enter');
    
        // 👀 wait for the loading
        await agent.aiWaitFor('there is at least one headphone item on page');
        // or you may use a plain sleep:
        // await sleep(5000);
    
        // 👀 understand the page content, find the items
        const items = await agent.aiQuery(
          '{itemTitle: string, price: Number}[], find item in list and corresponding price',
        );
        console.log('headphones in stock', items);
    
        const isMoreThan1000 = await agent.aiBoolean(
          'Is the price of the headphones more than 1000?',
        );
        console.log('isMoreThan1000', isMoreThan1000);
    
        const price = await agent.aiNumber(
          'What is the price of the first headphone?',
        );
        console.log('price', price);
    
        const name = await agent.aiString(
          'What is the name of the first headphone?',
        );
        console.log('name', name);
    
        const location = await agent.aiLocate(
          'What is the location of the first headphone?',
        );
        console.log('location', location);
    
        // 👀 assert by AI
        await agent.aiAssert('There is a category filter on the left');
    
        // 👀 click on the first item
        await agent.aiTap('the first item in the list');
    
        await browser.close();
      })(),
    );

    更多 Agent 的 API 讲解请参考 API 参考

    第三步:运行

    使用 tsx 来运行,你会看到命令行打印出了耳机的商品信息:

    # run
    npx tsx demo.ts
    
    # 命令行应该有如下输出
    #  [
    #   {
    #     itemTitle: 'JBL Tour Pro 2 - True wireless Noise Cancelling earbuds with Smart Charging Case',
    #     price: 551.21
    #   },
    #   {
    #     itemTitle: 'Soundcore Space One无线耳机40H ANC播放时间2XStronger语音还原',
    #     price: 543.94
    #   }
    # ]

    第四步:查看运行报告

    当上面的命令执行成功后,会在控制台输出:Midscene - report file updated: /path/to/report/some_id.html,通过浏览器打开该文件即可看到报告。

    在 Playwright 的测试用例中集成 Midscene

    这里我们假设你已经拥有一个集成了 Playwright 的测试项目。

    样例项目

    你可以在这里看到向 Playwright 集成的样例项目:https://github.com/web-infra-dev/midscene-example/blob/main/playwright-testing-demo

    第一步:新增依赖,更新配置文件

    新增依赖

    npm
    yarn
    pnpm
    bun
    deno
    npm install @midscene/web --save-dev

    更新 playwright.config.ts

    export default defineConfig({
      testDir: './e2e',
    + timeout: 90 * 1000,
    + reporter: [["list"], ["@midscene/web/playwright-reporter", { type: "merged" }]],
    });

    Reporter 配置项说明:

    • type: 报告模式,可选值为 merged(默认)或 separatemerged 表示多个测试用例生成一个合并报告,separate 表示为每个测试用例生成独立报告。

    • outputFormat: 控制报告的生成格式。'single-html'(默认)将所有截图作为 base64 内嵌到单个 HTML 文件中。'html-and-external-assets' 将截图保存为独立的 PNG 文件到子目录,适用于报告文件过大的场景。注意:使用 'html-and-external-assets' 时,报告必须通过 HTTP 服务器访问,无法直接使用 file:// 协议打开(因为浏览器的 CORS 限制会阻止从 file 协议加载相对路径的本地图片)。进入报告目录后运行以下命令之一:

      • 使用 Node.js:npx serve
      • 使用 Python:python -m http.serverpython3 -m http.server

      然后通过 http://localhost:3000(或终端显示的端口)访问报告。

    第二步:扩展 test 实例

    把下方代码保存为 ./e2e/fixture.ts;

    import { test as base } from '@playwright/test';
    import type { PlayWrightAiFixtureType } from '@midscene/web/playwright';
    import { PlaywrightAiFixture } from '@midscene/web/playwright';
    
    export const test = base.extend<PlayWrightAiFixtureType>(
      PlaywrightAiFixture({
        waitForNetworkIdleTimeout: 2000, // 可选, 交互过程中等待网络空闲的超时时间, 默认值为 2000ms, 设置为 0 则禁用超时
        replanningCycleLimit: 30, // 可选,覆盖 aiAct 默认的重规划次数上限
      }),
    );

    PlaywrightAiFixture() 也支持传入共享的 PlaywrightAgent 配置,因此你可以在创建 fixture 时统一配置 replanningCycleLimitwaitAfterActionmodelConfig 等 Agent 行为。testIdreportFileNamegroupNamegroupDescription 这类由 fixture 管理的元信息仍会自动生成。

    第三步:编写测试用例

    完整的交互、查询和辅助 API 请参考 Agent API 参考。如果需要调用更底层的能力,可以使用 agentForPage 获取 PageAgent 实例,再直接调用对应的方法:

    test('case demo', async ({ agentForPage, page }) => {
      const agent = await agentForPage(page);
    
      await agent.recordToReport();
      const logContent = agent._unstableLogContent();
      console.log(logContent);
    });

    示例代码

    ./e2e/ebay-search.spec.ts
    import { expect } from '@playwright/test';
    import { test } from './fixture';
    
    test.beforeEach(async ({ page }) => {
      page.setViewportSize({ width: 400, height: 905 });
      await page.goto('https://www.ebay.com');
      await page.waitForLoadState('networkidle');
    });
    
    test('search headphone on ebay', async ({
      ai,
      aiQuery,
      aiAssert,
      aiInput,
      aiTap,
      aiScroll,
      aiWaitFor,
      aiRightClick,
      recordToReport,
    }) => {
      // 使用 aiInput 输入搜索关键词
      await aiInput('Headphones', '搜索框');
    
      // 使用 aiTap 点击搜索按钮
      await aiTap('搜索按钮');
    
      // 等待搜索结果加载
      await aiWaitFor('搜索结果列表已加载', { timeoutMs: 5000 });
    
      // 使用 aiScroll 滚动到页面底部
      await aiScroll(
        {
          scrollType: 'untilBottom',
        },
        '搜索结果列表',
      );
    
      // 使用 aiQuery 获取商品信息
      const items =
        await aiQuery<Array<{ title: string; price: number }>>(
          '获取搜索结果中的商品标题和价格',
        );
    
      console.log('headphones in stock', items);
      expect(items?.length).toBeGreaterThan(0);
    
      // 使用 aiAssert 验证筛选功能
      await aiAssert('界面左侧有类目筛选功能');
    
      // 使用 recordToReport 记录当前状态
      await recordToReport('搜索结果', { content: '耳机搜索的最终结果' });
    });

    更多 Agent 的 API 讲解请参考 API 参考

    第四步:运行测试用例

    npx playwright test ./e2e/ebay-search.spec.ts

    第五步:查看测试报告

    当上面的命令执行成功后,会在控制台输出:Midscene - report file updated: ./current_cwd/midscene_run/report/some_id.html,通过浏览器打开该文件即可看到报告。

    Advanced

    关于在新标签页打开

    PlaywrightAgent 是 page-level Agent:每个实例都与对应的页面唯一绑定。为了方便开发者调试,Midscene 默认拦截了新 tab 的页面(如点击一个带有 target="_blank" 属性的链接),将其改为在当前页面打开。

    如果你想恢复在新标签页打开的行为,同时让当前 Agent 仍留在原页面,可以设置 forceSameTabNavigationfalse,并自行为每个新标签页创建新的 Agent 实例。

    如果一个 Agent 需要管理整个 browser context 内的页面切换,请使用 PlaywrightBrowserAgent。如果后续操作要自动继续在新打开的标签页中执行,请开启 autoFollowNewPage

    const mid = new PlaywrightBrowserAgent(context, page, {
      autoFollowNewPage: true,
    });

    当你要显式指定初始 active page 时,使用 new PlaywrightBrowserAgent(context, page, options)。当你希望 Midscene 自动选择或创建初始 active page 时,使用 PlaywrightBrowserAgent.create(context, options);这个工厂会优先使用 initialPage,否则复用 context 里的第一个页面,或者创建一个新页面。

    浏览器支持说明

    Midscene 的部分 Web 自动化能力依赖 Chromium-based browser 提供的 Chrome DevTools Protocol(CDP),例如浏览器级事件、触摸手势,以及一些交互操作中使用的 CDP fallback 路径。

    使用 Playwright 时,推荐使用 Chromium。Firefox 和 WebKit 可能可以覆盖基础的 Playwright-native 操作,但依赖 CDP 的 Midscene 能力可能会在这些浏览器内核上报错。

    连接远程 Playwright 浏览器并接入 Midscene Agent

    示例项目

    你可以在这里找到远程 Playwright 集成的示例项目:https://github.com/web-infra-dev/midscene-example/tree/main/remote-playwright-demo

    当你已经在自有基础设施或供应商服务里运行浏览器时,可通过连接远程 Playwright 服务复用这些浏览器,让实例更贴近目标环境、避免重复启动,同时保持相同的 Midscene AI 自动化能力。

    前置依赖

    npm
    yarn
    pnpm
    bun
    deno
    npm install playwright @playwright/test @midscene/web --save-dev

    获取 CDP WebSocket URL

    你可以从多种来源获取 CDP WebSocket URL:

    • BrowserBase:在 https://browserbase.com 注册并获取你的 CDP URL
    • Browserless:使用 https://browserless.io 或运行你自己的实例
    • 本地 Chrome:使用 --remote-debugging-port=9222 参数运行 Chrome,然后使用 ws://localhost:9222/devtools/browser/...
    • Docker:在 Docker 容器中运行 Chrome 并暴露调试端口

    代码示例

    import { chromium } from 'playwright';
    import { PlaywrightAgent } from '@midscene/web/playwright';
    
    // 来自远程浏览器服务的 CDP WebSocket URL
    const cdpWsUrl = 'ws://your-remote-browser.com/devtools/browser/your-session-id';
    
    // 连接并选取页面
    const browser = await chromium.connectOverCDP(cdpWsUrl);
    const context = browser.contexts()[0];
    const page = context.pages()[0] || await context.newPage();
    
    // 创建 Midscene Agent(用法与本地 Playwright agent 一致)
    const agent = new PlaywrightAgent(page);
    
    // 像平常一样调用 AI 方法
    await agent.aiAction('跳转到 https://example.com');
    await agent.aiAction('点击登录按钮');
    
    // 清理
    await agent.destroy();
    await browser.close();

    连接完成后,后续的 PlaywrightAgent 使用方式与本地启动的浏览器保持一致。

    扩展自定义交互动作

    使用 customActions 选项,结合 defineAction 定义的自定义交互动作,可以扩展 Agent 的动作空间。这些动作会追加在内置动作之后,方便 Agent 在规划阶段调用。

    import { getMidsceneLocationSchema, z } from '@midscene/core';
    import { defineAction } from '@midscene/core/device';
    
    const ContinuousClick = defineAction({
      name: 'continuousClick',
      description: 'Click the same target repeatedly',
      paramSchema: z.object({
        locate: getMidsceneLocationSchema(),
        count: z
          .number()
          .int()
          .positive()
          .describe('How many times to click'),
      }),
      async call(param) {
        const { locate, count } = param;
        console.log('click target center', locate.center);
        console.log('click count', count);
        // 在这里结合 locate + count 实现自定义点击逻辑
      },
    });
    
    const agent = new PlaywrightAgent(page, {
      customActions: [ContinuousClick],
    });
    
    await agent.aiAct('点击红色按钮五次');

    更多关于自定义动作的细节,请参考 集成到任意界面

    FAQ

    Playwright 下载浏览器耗时太久

    Playwright 在 npm install 时默认不会下载浏览器镜像,需要单独执行 npx playwright install。这个过程如果网络较慢,耗时会比较久。

    可以用下面两种方式优化:

    1. 使用代理镜像,例如 npmmirror.com
    PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright" npx playwright install
    1. 只下载常用的 chromium
    npx playwright install --with-deps chromium

    下拉框点击不到

    这通常是因为页面使用了原生 select 标签来实现下拉框。浏览器会调用操作系统的原生控件来渲染展开后的选项面板,因此下拉框实际上并没有渲染在浏览器页面里,也就无法被 Playwright 截图捕捉到。

    建议先检查报告中的截图:如果点击下拉框后,报告截图里确实没有出现下拉选项,基本就可以判断是这个问题。

    Midscene 默认开启 forceChromeSelectRendering 选项,强制由 Chrome 来渲染 select 下拉框,这样下拉框就会出现在页面截图中,也能被 Playwright 正常识别。开启后,下拉框样式通常会和操作系统默认样式有明显区别。如果需要恢复系统原生渲染,可将 forceChromeSelectRendering 设为 false

    浏览器界面持续闪动

    在本地可视化界面中遇到持续闪烁,通常是因为 viewport 的 deviceScaleFactor 与系统/浏览器的像素比不匹配(常见于高分辨率或 Retina 屏幕)。

    该闪动不会影响 Midscene 的截图或自动化运行,但会影响本地预览体验。解决方法:将 deviceScaleFactor 设置为与浏览器的 window.devicePixelRatio 一致。

    // Playwright:不支持像 Puppeteer 一样使用 0 表示自动适配
    const page = await browser.newPage({
      deviceScaleFactor: 2, // 请把这里的数字 2 替换为你的 window.devicePixelRatio
    })

    如果不确定浏览器的像素比,可在任意页面按下 F12 打开控制台,输入 window.devicePixelRatio 查看;或在 Chrome 地址栏粘贴下面内容并回车以弹窗显示当前值:

    data:text/html,<script>alert(`deviceScaleFactor of your browser: ${devicePixelRatio}`)</script>

    自定义网络超时

    当在网页上执行某个操作后,Midscene 会自动等待网络空闲。这是为了确保自动化过程的稳定性。如果等待超时,不会发生任何事情。

    默认的超时时间配置如下:

    1. 如果是页面跳转,则等待页面加载完成,默认超时时间为 5000ms
    2. 如果是点击、输入等操作,则等待网络空闲,默认超时时间为 2000ms

    当然,你可以通过配置参数修改默认超时时间,或者关闭这个功能:

    • 使用 Agent 上的 waitForNetworkIdleTimeoutwaitForNavigationTimeout 参数
    • 使用 Yaml 脚本和 PlaywrightAiFixture 中的 waitForNetworkIdle 参数

    截图时报 waiting for fonts to loadpage.screenshot: Timeout ... exceeded

    如果你在 Playwright 环境里看到类似下面的报错:

    page.screenshot: Timeout 10000ms exceeded.
    Call log:
    - taking page screenshot
    - waiting for fonts to load...

    这通常不是 Midscene 自身逻辑的问题,而是 Playwright 在截图时默认会等待页面字体加载完成。在某些 CI、容器或网络环境中,字体资源可能加载很慢,甚至一直无法完成,最终导致截图超时。

    可以通过添加下面的环境变量来规避:

    export PW_TEST_SCREENSHOT_NO_FONTS_READY=1

    如果你是在一条命令里临时执行,也可以这样写:

    PW_TEST_SCREENSHOT_NO_FONTS_READY=1 <你的命令>

    更多背景可参考 Playwright 的 issue:[BUG] Page.screenshot method hangs indefinitely

    更多