集成到 Playwright

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

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

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

配置 AI 模型服务

将你的模型配置写入环境变量。更多信息请查看 选择 AI 模型

# 替换为你的 API Key
export OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz"

# 可能需要更多配置,如模型名称、接入点等,请参考 《选择 AI 模型》文档
export OPENAI_BASE_URL="..."

直接集成 Midscene Agent

样例项目

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

第一步:安装依赖

npm
yarn
pnpm
bun
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.aiAction('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, 通过浏览器打开该文件即可看到报告。

如何限制页面在当前 tab 打开

如果你想要限制页面在当前 tab 打开(比如点击一个带有 target="_blank" 属性的链接),你可以设置 forceSameTabNavigation 选项为 true

const mid = new PlaywrightAgent(page, {
  forceSameTabNavigation: true,
});

在 Playwright 的测试用例中集成 Midscene

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

样例项目

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

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

新增依赖

npm
yarn
pnpm
bun
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" }]], // type 可选, 默认值为 "merged",表示多个测试用例生成一个报告,可选值为 "separate",表示为每个测试用例一个报告
});

其中 reporter 配置项的 type 可选值为 mergedseparate,默认值为 merged,表示多个测试用例生成一个报告,可选值为 separate,表示为每个测试用例一个报告。

第二步:扩展 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 则禁用超时
  }),
);

第三步:编写测试用例

基础 AI 操作

  • aiaiAction - 通用 AI 交互
  • aiTap - 点击操作
  • aiHover - 悬停操作
  • aiInput - 输入操作
  • aiKeyboardPress - 键盘操作
  • aiScroll - 滚动操作
  • aiRightClick - 右键点击操作

查询

  • aiAsk - 询问 AI 模型任何问题
  • aiQuery - 从当前页面提取结构化的数据
  • aiNumber - 从当前页面提取数字
  • aiString - 从当前页面提取字符串
  • aiBoolean - 从当前页面提取布尔值

更多 API

  • aiAssert - 断言
  • aiWaitFor - 等待
  • aiLocate - 定位元素
  • runYaml - 执行 YAML 自动化脚本
  • setAIActionContext - 设置 AI 动作上下文,在调用 agent.aiAction() 时,发送给 AI 模型的背景知识
  • evaluateJavaScript - 在页面上下文中执行 JavaScript
  • logScreenshot - 在报告文件中记录当前截图,并添加描述
  • freezePageContext - 冻结页面上下文
  • unfreezePageContext - 解冻页面上下文

除了上述暴露的快捷方法之外,如果还需要调用其它 agent 提供的 API,请使用 agentForPage 获取 PageAgent 实例,使用 PageAgent 调用 API 进行交互:

test('case demo', async ({ agentForPage, page }) => {
  const agent = await agentForPage(page);

  await agent.logScreenshot();
  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,
  logScreenshot,
}) => {
  // 使用 aiInput 输入搜索关键词
  await aiInput('Headphones', '搜索框');

  // 使用 aiTap 点击搜索按钮
  await aiTap('搜索按钮');

  // 等待搜索结果加载
  await aiWaitFor('搜索结果列表已加载', { timeoutMs: 5000 });

  // 使用 aiScroll 滚动到页面底部
  await aiScroll(
    {
      direction: 'down',
      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('界面左侧有类目筛选功能');

  // 使用 logScreenshot 记录当前状态
  await logScreenshot('搜索结果', { content: '耳机搜索的最终结果' });
});

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

Step 4. 运行测试用例

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

Step 5. 查看测试报告

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

扩展自定义交互动作

使用 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.aiAction('点击红色按钮五次');

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

更多