Qwen3vl+Midscene+Playwright自动化AI测试框架搭建流程(win11本地安装)
一、环境准备
Windows 11(其他 Windows 版本同理)
安装Node.js:v20+(不建议使用18及以下版本)
安装npm(20及以后的node.js正常已经集成了npm)
二、项目初始化
在Windows系统中创建项目根目录,如:D:/Midscene,并初始化
新建Midscene根目录,如:D:/Midscene
进入Midscene根目录中,打开命令行执行初始化命令:npm init -y
三、安装核心依赖
在Midscene根目录中,打开命令行执行以下安装命令:
npm install @playwright/test @midscene/web playwright
npm install node-autoit-koffi dotenv
npx playwright install chromium
其中node-autoit-koffi插件用于操作弹框等特殊控件,需要同步下载客户端工具autoit-v3,解压到Midscene根目录下即可
四、配置 Playwright
创建 playwright.config.js,在调试阶段建议把报告中的截图、视频和异常信息都设为开启,后续跑成熟了可以改为失败后展示
javascript
// playwright.config.js
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
testDir: './testcase',
timeout: 1800000, // 超时时间默认为30分钟
retries: 0,
use: {
headless: false,
launchOptions: {
args: [
'--start-maximized', // 启动时最大化窗口
'--disable-features=ExternalProtocolDialog',// 关闭外部协议弹窗
'--disable-infobars', // 禁用“Chrome 正受到自动化测试软件控制”的提示栏
'--disable-notifications', // 禁用网页通知弹窗
'--disable-popup-blocking', // 禁用弹出窗口拦截
],
},
viewport: null,
screenshot: 'always',// only-on-failure 仅在失败时截图
video: 'always',// retain-on-failure 仅在失败时保留视频
trace: 'on', // on-first-retry 仅在第一次重试时启用跟踪
},
reporter: [
['html', { outputFolder: 'playwright-report' }],
['line'],
],
});
五、配置环境变量(Qwen3-VL 模型)
在项目根目录创建 .env 文件(建议把MIDSCENE_MODEL_TEMPERATURE随机性调低,避免AI天马行空的乱思考):
# 阿里云线上模型地址
MIDSCENE_MODEL_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
MIDSCENE_MODEL_API_KEY="sk-xxxxxxx"
MIDSCENE_MODEL_NAME="qwen3-vl-plus"
MIDSCENE_MODEL_FAMILY="qwen3-vl"
MIDSCENE_USE_QWEN_VL=1
MIDSCENE_PLANNING_MAX_ATTEMPTS=1
# 调整模型参数(最大token数、降低随机性、重试间隔时间)
MIDSCENE_MODEL_MAX_TOKENS=4096
MIDSCENE_MODEL_TEMPERATURE=0.1
MIDSCENE_MODEL_RETRY_INTERVAL=10000
# 测试地址信息
TEST_BASE_URL="https://www.abc.com/login" #登录地址
TEST_USERNAME=abcdef
TEST_PASSWORD=Abc111
六、编写钉钉登录、日期处理等辅助工具类
Midscene根目录下创建utils文件夹,新建autoLoginDingtalk.js、date.js两个工具类,其中autoLoginDingtalk.js用于弹框识别和操作
// utils/autoLoginDingtalk.js
import * as autoit from 'node-autoit-koffi';
async function autoLoginDingtalk({ page, agent, data }) {
await agent.aiAct('点击页面右侧登录区域中的【点击图标直接登录】文字,页面跳转到认证页');
await page.waitForTimeout(3000);
await agent.aiAct('点击认证页中位于二维码右侧的用户头像');
await page.waitForTimeout(3000);
await autoit.init();
// 等待【登录弹窗】出现(前端类匹配)
const authCheckDialog = await autoit.winGetHandle("[CLASS:CUICef2ProjectWnd]");
if (!authCheckDialog) {
throw new Error("未检测到钉钉登录弹窗");
}
// 激活窗口,状态设为1(正常显示),并等待窗口稳定
await autoit.winSetStateByHandle(authCheckDialog, 5);
await autoit.sleep(500); // 等待窗口稳定
const ctrlHandle = await autoit.controlGetHandle(authCheckDialog, "[CLASS:Chrome_RenderWidgetHostHWND; INSTANCE:1]");
console.log('✅ 获取到钉钉登录弹框句柄: '+ctrlHandle);
await autoit.controlClickByHandle(authCheckDialog, ctrlHandle, "left", 1, 200, 320);
await autoit.sleep(500);
console.log('✅ 已点击【登录】按钮');
await page.waitForTimeout(6000);
}
module.exports = { autoLoginDingtalk };
注:需要通过之前下载的autoit-v3工具包中的Au3Info_x64.exe程序点击finder tool拖拽到需要探查的按钮上即可获取定位信息,用于winGetHandle、controlGetHandle、controlClickByHandle方法入参。

date.js用于生成日、周、月、年等格式信息
// utils/date.js
function formatDate(date, format = 'YYYY-MM-DD') {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const week = String(getWeekNumber(date)).padStart(2, '0');
switch (format) {
case 'YYYY-MM-DD':
return `${year}-${month}-${day}`;
case 'YYYY-WW':
return `${year}-${week}`;
case 'YYYY-MM':
return `${year}-${month}`;
case 'YYYY':
return `${year}`;
default:
return `${year}-${month}-${day}`;
}
}
function getWeekNumber(date = new Date()) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
module.exports = { formatDate };
工具类可以根据需要自行修改或新增,并在测试用例文件中通过如下方式引入:
const { formatDate } = require('../utils/date.js');
const { autoLoginDingtalk } = require('../utils/autoLoginDingtalk.js');
七、编写第一个测试用例
Midscene根目录下创建testcase文件夹,新建test001.spec.js,注意这里调用的是本地浏览器,进而可以比较真实的模拟本机上的浏览器的配置,如果确实需要调用playwright安装时自带的chromium,直接去掉下方代码注释中写明了用于本地 Chrome的配置项
// testcase/test001.spec.js
import { test } from '@playwright/test';
import { chromium } from 'playwright';
import { PlaywrightAgent } from '@midscene/web/playwright';
import 'dotenv/config';
import path from 'path';
import { log } from 'console';
const { formatDate } = require('../utils/date.js');
const { autoLoginDingtalk } = require('../utils/autoLoginDingtalk.js');
const loginUsername = process.env.TEST_USERNAME;
const loginPassword = process.env.TEST_PASSWORD;
const baseURL = process.env.TEST_BASE_URL;
const testDataList = [
// {
// name: 'UI自动化-生产指标分析-集团-月度-对比分析',
// OrgLevel: '集团',
// dateType: '月',
// analysisType: '对比分析',
// },
{
name: 'UI自动化-生产指标分析-集团-周度-对比分析',
OrgLevel: '集团',
dateType: '周',
analysisType: '对比分析',
}
];
test.describe('UI自动化-生产指标分析-001', () => {
let agent;
let context; // 保存 persistent context
let page; // 保存手动创建的 page
test.beforeEach(async () => {
// 使用项目下的独立目录(确保没有被其他 Chrome 占用)
const userDataDir = path.join(process.cwd(), 'chrome-profile-test');
// 2. 启动持久化上下文(直接使用本地安装的 Chrome)
context = await chromium.launchPersistentContext(userDataDir, {
headless: false,
channel: 'chrome', // 关键:使用本地 Chrome,而不是 Playwright 自带的 Chromium
args: [
'--start-maximized', // 启动时最大化窗口
'--disable-features=ExternalProtocolDialog', // 禁用外部协议弹窗
'--disable-infobars',
'--disable-notifications',
'--disable-external-protocol-handling',
],
});
// 3. 获取或创建页面
page = context.pages()[0] || await context.newPage();
await page.goto(baseURL);
// 4. 创建 Midscene Agent(传入手动创建的 page)
agent = new PlaywrightAgent(page, {
waitForNetworkIdleTimeout: 10000,
cache: { strategy: 'read-write', id: 'pre-establish-cache' },
});
});
test.afterEach(async () => {
if (agent) await agent.destroy();
if (context) await context.close(); // 关闭持久化上下文
});
testDataList.forEach((data) => {
test(data.name, async () => {
console.info(data.name + ' - 测试开始');
await autoLoginDingtalk({ page,agent, data });
await agent.aiAct('鼠标悬停在【XX】菜单上');
await page.waitForTimeout(2000);
await agent.aiAct('在弹出的下拉菜单中点击【生产指标分析】');
await page.waitForTimeout(2000);
await agent.aiAct('鼠标悬停在【XX】菜单下方的【XX】子菜单上');
await page.waitForTimeout(2000);
await agent.aiAct('在弹出菜单中点击第1列的第3个菜单【XX】');
await page.waitForTimeout(5000);
await agent.aiAssert('检查左侧架构树:【XX】单选框默认选中【XX】,当前架构为【XX】');
const day = formatDate(new Date(), 'YYYY-MM-DD');
const week = formatDate(new Date(), 'YYYY-WW');
const month = formatDate(new Date(), 'YYYY-MM');
log('当前日期:', day, '当前周:', week, '当前月:', month);
await agent.aiAssert('检查右侧图表区域顶部控件,从左到右依次为:日周月选项卡(默认为日)、时间选择器(默认为'+ day +'的前一天)、下拉菜单1(默认为三天平均),下拉菜单2(默认为全部)、其他筛选');
log('顶部控件验证通过!');
await agent.aiAssert('检查第1幅图表:左上角标题为【XX】');
log('第1幅图表验证通过!');
switch (data.dateType) {
case '日':
await agent.aiAct('切换【日周月选项卡】的时间格式为日,并在右侧【时间选择器】上选择起止时间均为'+day+'日的前一天');
break;
case '周':
await agent.aiAct('切换【日周月选项卡】的时间格式为周,并在右侧【时间选择器】上选择起止时间均为'+week+'周的前一周');
break;
case '月':
await agent.aiAct('切换【日周月选项卡】的时间格式为月,并在右侧【时间选择器】上选择起止时间均为'+month+'月的前一月');
break;
}
console.info(data.name + ' - 测试通过!');
});
});
});
截止到这一步,项目整体目录和文件已经全部创建完成,结构如下图所示,框里的是主体文件

八、运行测试
在根目录下打开命令行执行
npx playwright test test001.spec.js
如果一切配置正确的话,命令行会输出部分日志如下:
Running 2 tests using 1 worker UI自动化-生产指标分析001 - 测试开始 ✅ 获取到钉钉登录弹框句柄: 1444844 ✅ 已点击【登录】按钮 当前日期: 2026-06-24 当前周: 2026-26 当前月: 2026-06 顶部控件验证通过! 第1幅图表验证通过! UI自动化-生产指标分析001 - 测试通过!
UI自动化-生产指标分析002 - 测试开始 ✅ 获取到钉钉登录弹框句柄: 6885268 ✅ 已点击【登录】按钮 当前日期: 2026-06-24 当前周: 2026-26 当前月: 2026-06 顶部控件验证通过! 第1幅图表验证通过! UI自动化-生产指标分析-002- 测试通过!
2 passed (4.5m)
To open last HTML report run:
npx playwright show-report
九、测试报告
测试报告在测试完成后由playwright自动生成并打开html格式文档
10、运行成本优化
1、Midscene 自带 AI 缓存,可在配置文件中开启
2、大模型云端添加 Prompt Caching
更多推荐


所有评论(0)