1. 概述
1.1 传统自动化测试的挑战
Playwright是一个现代化的端到端测试框架,由微软开发,专门用于Web应用程序的自动化测试。
// 测试登录表单
test('登录表单测试', async ({ page }) => {
// 假设这是你的登录页面
await page.goto('https://example.com/login');
// 填写用户名
await page.fill('input[name="username"]', 'testuser');
// 填写密码
await page.fill('input[name="password"]', 'testpassword');
// 点击登录按钮
await page.click('button[type="submit"]');
// 验证登录成功后的跳转或消息
await expect(page).toHaveURL(/.*dashboard/);
});
传统自动化测试需要指定确切的DOM节点,这样会存在以下问题:
- 深度耦合:测试代码和业务代码的深度耦合,业务代码重构后,测试代码也需要重构
- 学习成本高:编写用例需要学习Playwright的语法并了解业务代码,效率比较低
- 维护成本高:UI变更时需要频繁更新选择器
1.2 AI在测试领域的发展趋势
随着AI技术的发展,测试领域出现了新的解决方案:
- MCP集成:Playwright支持MCP服务器,使用自然语言与大模型交互,无需编写传统的自动化代码
- 自然语言测试:Midscene使用自然语言进行自动化测试,降低了技术门槛
2. Midscene介绍
2.1 什么是Midscene
Midscene.js 是由字节跳动全新开源的 UI 自动化工具,通过引入多模态 AI 推理能力,帮助开发者打破传统 UI 自动化难于编写和维护的困境。
项目地址: https://github.com/web-infra-dev/midscene
2.2 Midscene的优势
- 简单易用:使用自然语言,可以快速编写测试用例
- 松耦合:与业务代码解耦,无需关注具体的代码实现,方便对代码进行重构
- 维护性强:UI变更时测试用例通常无需修改
2.3 Midscene的局限性
- 执行速度较慢:执行速度比传统自动化测试代码调用慢,可以通过一些方式提高运行效率,具体见文档
- 成本考虑:调用模型有一定成本
- 交互限制:目前仅支持点击、拖拽(只在 UI-TARS 模型中支持)、输入、键盘和滚动操作
- 稳定性风险:AI 模型的返回值不是 100% 准确的,需要遵循编写提示词的技巧来提高稳定性
- 验证码限制:无法绕过验证码,有些 LLM 服务会拒绝涉及验证码解决的请求
2.4 预估费用
针对 Midscene.js 样例项目,这是费用消耗数据(gpt-4o-08-06 模型,且未启用 prompting caching):
| 任务 | 分辨率 | Prompt Tokens / 价格 | Completion Tokens / 价格 | 总价 |
|---|---|---|---|---|
| 拆解(Plan)并在 eBay 执行一次搜索 | 1280x800 | 6005 / $0.0150125 | 146 / $0.00146 | $0.02 |
| 提取(Query)eBay 搜索结果的商品信息 | 1280x800 | 9107 / $0.0227675 | 122 / $0.00122 | $0.02 |
引用自: https://zhuanlan.zhihu.com/p/15458924120
2.5 快速上手
2.5.1 环境配置
配置密钥:
OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" # 或任何其他提供商的接入点
OPENAI_API_KEY="sk-xxxxxxxxxxxxxx"
MIDSCENE_MODEL_NAME="qwen-vl-max-latest"
MIDSCENE_USE_QWEN_VL=1
2.5.2 项目初始化
详细集成指南: Midscene 与 Playwright 集成
2.5.3 基础API
1. 执行交互 Action
// 基本用法
await agent.aiAction('在搜索框中输入 "JavaScript",然后点击搜索按钮');
2. 数据提取 Query
// 使用此方法,你可以直接从 UI 提取结构化的数据
const dataA = await agent.aiQuery({
time: '左上角展示的日期和时间,string',
userInfo: '用户信息,{name: string}',
tableFields: '表格的字段名,string[]',
tableDataRecord: '表格中的数据记录,{id: string, [fieldName]: string}[]',
});
3. 断言 Assert
await agent.aiAssert('"Sauce Labs Onesie" 的价格是 7.99');
2.5.4 代码示例演示
test.beforeEach(async ({ page }) => {
await page.goto("https://todomvc.com/examples/react/dist/");
});
test("todo", async ({ ai, aiQuery, aiAssert, aiTap, aiHover }) => {
// .ai - 通用 AI 操作方法
await ai("在任务框 input 输入 今天学习 JS,按回车键");
await ai("在任务框 input 输入 明天学习 Rust,按回车键");
await ai("在任务框 input 输入后天学习 AI,按回车键");
// .aiTap, .aiHover - 即时操作接口
await aiHover('任务列表中的第二项');
await aiTap("第二项任务右边的删除按钮");
await aiTap("第二条任务左边的勾选按钮");
await aiTap("任务列表下面的 completed 状态按钮");
await aiAssert('列表下方有一个区域显示有 "1 item left"');
});
3. 技术实现
3.1 流程图
在playwright环境执行aiAction的流程如下:
Midscene 会根据 Playwright 的截图去调用大模型,从而定位到DOM节点的位置,然后根据用户的指令再去调用模型来生成操作规划,这些操作规划最终会去调用 Playwright 的API。
3.2 模块划分
3.2.1 缓存模块
1. XPath缓存
todolist网址上"What needs to be done?"输入框的dom节点路径,缓存dom节点的XPath。XPath MDN介绍
{
type: 'locate',
prompt: "The input field labeled 'What needs to be done?'",
xpaths: [ '/html/body/section[1]/header[1]/div[1]/input[1]' ]
}
2. 规划缓存
使用 YAML 来定义流程规划。"在任务框 input 输入 今天学习 JS,按回车键"这个提示词需要执行两件事情:
- 输入"今天学习 JS"
- 按回车键
{
type: 'plan',
prompt: '在任务框 input 输入 今天学习 JS,按回车键',
yamlWorkflow: 'tasks:\n' +
' - name: 在任务框 input 输入 今天学习 JS,按回车键\n' +
' flow:\n' +
" - aiInput: ''\n" +
' value: 今天学习 JS\n' +
" locate: The input field labeled 'What needs to be done?'\n" +
" - aiKeyboardPress: ''\n" +
' keyName: Enter\n'
}
3.2.2 AI调用模块
输入:
- 页面截图 - Playwright 捕获的当前页面截图(图像数据)
- 用户指令 - 自然语言指令,如:"在任务框 input 输入 '今天学习 JS',然后按回车键"
- 上下文信息 - 当前页面的状态和历史操作
输出:
// 第一次响应
{
"what_the_user_wants_to_do_next_by_instruction": "在任务框 input 输入 '今天学习 JS',然后按回车键",
"log": "I will use action Input to type '今天学习 JS' into the input field",
"more_actions_needed_by_instruction": true,
"action": {
"type": "Input",
"param": {
"value": "今天学习 JS",
"locate": {
"bbox": [510, 132, 1060, 198],
"prompt": "The input field labeled 'What needs to be done?'"
}
}
}
}
// 第二次响应
{
"what_the_user_wants_to_do_next_by_instruction": "在任务框 input 输入 '今天学习 JS' 后,需要按回车键确认输入",
"log": "I will use action KeyboardPress to press the Enter key to confirm the input",
"more_actions_needed_by_instruction": false,
"action": {
"type": "KeyboardPress",
"param": {
"keyName": "Enter"
}
}
}
3.2.3 调用playwright模块
对应的源码在: packages/web/src/lib/puppeteer/base-page.ts
// 调用playwright的API
page.keyboard.type("今天学习 JS", { delay: 80 })
page.keyboard.press("Enter")
4. 案例实践
4.1 测试通过 magic link 登录
import { test } from "./fixture";
test("通过magic link登录", async ({ page, ai, aiQuery, aiAction, aiAssert, aiTap, aiInput, aiHover }) => {
await page.goto("https://app.creatify.ai/auth/magic-link/b525980660ed4d5cad94cc5169c205d0/?redirect_url=https://app.creatify.ai/home");
await aiAction('等待10秒钟,直到页面加载完成')
await aiTap('AI Video Ads')
await aiAssert("页面上写着Share your product link to generate a video");
});
4.2 测试未登录态使用 url to video
import { test } from "./fixture";
test("未登录态使用 url to video", async ({ page, ai, aiQuery, aiAction, aiAssert, aiTap, aiInput, aiHover }) => {
await page.goto("https://app.creatify.ai/tool/link-to-video");
await aiTap("Try some links");
await aiTap("Analyze URL");
await aiAssert("出现登录弹窗")
});
5. 调试指南
Playwright 提供了可视化的调试界面,可以帮助我们更好地调试 Midscene 测试:
# 启动 UI 模式
npx playwright test --ui
