Midscene.js框架(5):测试报告系统与调试技巧
本篇将深入讲解 Midscene.js 的可视化报告系统与调试技巧重点探索如何通过双报告协同快速定位 AI 自动化测试的失败根因。前言一、Midscene 可视化报告体系1.1 报告存储结构1.2 报告配置选项1.3 报告内部结构1.4 手动记录截图到报告二、双报告协同机制2.1 测试框架报告2.2 Midscene 可视化报告2.3 协同定位流程2.4 自动关联两份报告2.5 报告合并工具三、通过报告定位失败根因3.1 场景一AI 规划错误3.2 场景二元素定位偏移3.3 场景三动作执行时序问题3.4 场景四AI 断言误判3.5 场景五缓存导致的幽灵失败四、高级调试技巧4.1 LLM 使用指标监控4.2 实时 LLM 调用回调4.3 页面上下文冻结4.4 日志内容提取4.5 截图缩放优化4.6 Deep Locate 精确定位4.7 任务中止与超时控制4.8 Chrome 扩展 Playground 调试总结前言AI 驱动的 UI 自动化带来一个全新的调试挑战当测试失败时到底哪一步出了问题是 AI 规划错了元素定位偏了还是页面压根没加载出来传统 UI 自动化Selenium / Playwright的调试手段主要依赖日志和截图但在 AI 自动化场景中每一步操作都涉及模型推理失败的原因可能隐藏在AI 规划阶段模型误解了自然语言指令生成了错误的操作计划元素定位阶段模型在截图中找到了错误的元素或定位坐标偏移动作执行阶段操作正确但页面响应异常如弹窗遮挡、网络延迟断言判断阶段AI 对页面状态的理解与预期不一致Midscene.js 针对这些痛点设计了可视化报告系统——每一轮 AI 推理、每一次元素定位、每一个动作执行都会被完整记录下来附带截图、耗时、模型输出等上下文信息。配合测试框架自身的报告构成了强大的双报告协同调试体系。在 Midscene.js 测试框架如 Pytest / Playwright Test的架构中测试执行后会同时产生两份报告两份报告互为补充测试框架报告告诉你哪条用例挂了Midscene 报告告诉你这条用例的哪一步、哪个 AI 决策出了问题。这种协同关系正是高效调试的核心。一、Midscene 可视化报告体系1.1 报告存储结构每次执行后Midscene 会在当前工作目录下生成midscene_run目录1.2 报告配置选项Midscene Agent 提供了完整的报告控制选项适用于代码模式和 YAML 模式输出格式对比重要提示html-and-external-assets格式不能通过file://协议直接打开浏览器 CORS 限制需启动本地服务器# Node.js npx serve # Python python -m http.server然后通过http://localhost:3000访问。代码中配置报告// Playwright Agent 中配置 const agent new PuppeteerAgent(page, { generateReport: true, reportFileName: checkout-flow-test, outputFormat: single-html, groupName: E2E Checkout Suite, groupDescription: Complete checkout flow with payment validation, autoPrintReportMsg: true, persistExecutionDump: true, // 额外保存 JSON dump 用于深度分析 });# YAML 脚本中配置 agent: generateReport: true reportFileName: smoke-test-report groupName: Smoke Test groupDescription: Daily smoke test for core user paths1.3 报告内部结构打开 Midscene 生成的 HTML 报告你会看到完整的执行时间线。以 GitHub 注册表单填写任务为例报告结构如下报告头部 ├── 任务名称Sign up for Github, pass form validation ├── 总耗时、模型信息、缓存命中情况 │ 执行步骤时间线 ├── Action: Sign up for Github... ← 用户指令 │ ├── Plan: Open the GitHub signup page... ← AI 规划6.95s │ ├── Locate: [截图 元素标注] ← 元素定位6.18s │ ├── Tap: [点击坐标记录] ← 动作执行2.08s │ │ │ ├── Plan: Enter a valid email... ← AI 规划5.03s │ ├── Locate: [截图 元素标注] ← 元素定位6.60s │ ├── Input: userexample.com ← 动作执行1.45s │ │ │ ├── Plan: Enter a strong password... ← AI 规划5.33s │ ├── Locate: [截图 元素标注] ← 元素定位5.18s │ ├── Input: ******** ← 动作执行1.35s │ │ │ ├── ... │ │ │ └── Print_Assert_Result: ✅ All fields valid ← 断言结果0.99s │ 报告尾部 ├── Token 使用统计按意图、按模型分类 ├── 缓存命中统计 └── 每步截图缩略图导航每个步骤的关键信息1.4 手动记录截图到报告除了自动记录每步操作你还可以通过recordToReport()手动插入截图标记点// 简单记录自动截图并添加标题和描述 await agent.recordToReport(Login page loaded, { content: User A is about to log in }); // 多截图对比手动提供截图不自动截图 const beforeSubmit await page.screenshot({ encoding: base64 }); await agent.aiTap(submit button); const afterSubmit await page.screenshot({ encoding: base64 }); await agent.recordToReport(Submit comparison, { content: Compare state before and after submit, screenshots: [ { base64: data:image/png;base64,${beforeSubmit}, description: Before submit }, { base64: data:image/png;base64,${afterSubmit}, description: After submit }, ], });这在以下场景特别有用关键节点标记在复杂流程中标记重要状态变化前后对比记录操作前后的页面差异外部截图整合将非 Midscene 操作的截图纳入统一报告二、双报告协同机制2.1 测试框架报告以 Pytest 为例测试框架报告关注的是用例级别的执行结果pytest tests/ --htmlreports/report.html --self-contained-html -v生成的 Pytest HTML 报告包含2.2 Midscene 可视化报告Midscene 报告关注的是AI 操作级别的执行细节每一步 AI 推理的输入和输出每一次元素定位的截图和坐标每一个动作执行的耗时和结果模型 token 消耗统计缓存命中情况2.3 协同定位流程当一条用例失败时标准的调试流程如下┌──────────────────────────────────────────────────────────────┐ │ Step 1: 查看测试框架报告 │ │ │ │ 确认哪条用例失败、失败在哪一行、assert 断言信息是什么 │ │ → 得到用例名称、失败行号、失败原因概要 │ │ │ ├──────────────────────────────────────────────────────────────┤ │ Step 2: 打开 Midscene 报告 │ │ │ │ 在终端输出中找到 Midscene 报告路径 │ │ Midscene - report file updated: ./midscene_run/report/xxx.html│ │ 用浏览器打开该 HTML 文件 │ │ → 得到完整的 AI 操作时间线 │ │ │ ├──────────────────────────────────────────────────────────────┤ │ Step 3: 定位失败步骤 │ │ │ │ 在 Midscene 报告中按时间线找到与失败用例对应的操作段 │ │ 重点关注 │ │ ├── Plan 步骤AI 规划是否正确理解了指令 │ │ ├── Locate 步骤截图标注的元素是否是正确的目标 │ │ ├── Action 步骤动作执行是否成功有无异常 │ │ └── Assert 步骤AI 断言结果与预期是否一致 │ │ → 得到具体失败的步骤和原因 │ │ │ ├──────────────────────────────────────────────────────────────┤ │ Step 4: 分析根因并修复 │ │ │ │ 根据失败步骤的类型采取不同策略 │ │ ├── AI 规划错误 → 优化自然语言指令或改用结构化 API │ │ ├── 元素定位错误 → 启用 deepLocate或调整元素描述 │ │ ├── 动作执行失败 → 增加 waitAfterAction或处理弹窗 │ │ └── 断言判断错误 → 调整断言描述或增加 aiWaitFor 等待 │ │ │ └──────────────────────────────────────────────────────────────┘2.4 自动关联两份报告为了让调试流程更顺畅可以在测试框架的失败处理钩子中自动打印 Midscene 报告路径# conftest.py (Pytest) import pytest pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: agent item.funcargs.get(agent) page item.funcargs.get(page) print(f\n{*60}) print(f❌ 测试失败: {item.name}) print(f{*60}) if page: screenshot_path freports/failure_{item.name}.png page.screenshot(pathscreenshot_path) print(f 失败截图: {screenshot_path}) if agent: report_path agent.finish() print(f Midscene 可视化报告: {report_path}) print(f 用浏览器打开上述路径查看 AI 操作详情) print(f{*60}\n)这样每次测试失败时终端会直接输出两份报告的路径无需手动查找。2.5 报告合并工具当测试套件中运行了多条用例每条用例都会生成独立的 Midscene 报告。使用ReportMergingTool可以将多个报告合并为一个统一报告import { ReportMergingTool } from midscene/core/report; const mergingTool new ReportMergingTool(); // 在每条用例结束后追加报告 mergingTool.append({ reportFilePath: agent.reportFile as string, reportAttributes: { testId: checkout-test-001, testTitle: Complete checkout with credit card, testDescription: End-to-end checkout flow validation, testDuration: 45000, testStatus: passed, // passed | failed | timedOut | skipped | interrupted }, }); mergingTool.append({ reportFilePath: agent2.reportFile as string, reportAttributes: { testId: checkout-test-002, testTitle: Checkout with PayPal, testDuration: 32000, testStatus: failed, }, }); // 合并为单个 HTML 报告 const mergedPath mergingTool.mergeReports(checkout-suite-summary, { rmOriginalReports: false, // 保留原始报告 overwrite: true, // 覆盖已存在的合并报告 }); console.log(合并报告: ${mergedPath}); // → midscene_run/report/checkout-suite-summary.html合并后的报告包含所有用例的执行详情和状态概览非常适合 CI/CD 场景下的统一查看。三、通过报告定位失败根因3.1 场景一AI 规划错误现象aiAct(点击购物车图标并进入结算页面)执行后页面跳转到了错误的地方。报告分析打开 Midscene 报告找到对应的 Action 步骤查看 Plan 子步骤Action: Input iPhone and search ├── Plan: (0.5s) ├── Locate: search input box (5.2s) ├── Input: iPhone (1.3s) │ └── 截图显示输入框内容为 iPhone但搜索按钮仍处于禁用状态 ← ⚠️ ├── Plan: (0.4s) ├── Locate: search button (4.1s) ├── Tap: search button (1.0s) │ └── 截图显示按钮被点击但搜索未触发因为输入未完成验证 ← ❌ └── ...根因AI 将购物车误解为心愿单Wishlist导致规划了错误的操作。修复方案优化指令描述aiAct(Click the Cart icon (with a shopping bag symbol) in the top right navigation或改用即时动作await agent.aiTap(the Cart icon in the top navigation bar)3.2 场景二元素定位偏移现象aiTap(the first product in the list)点击了错误的商品。报告分析查看报告中的 Locate 步骤截图上会标注 AI 识别到的元素位置Locate: the first product in the list ├── 截图显示AI 标注了列表中第二个商品的区域 ← ❌ 定位偏移 ├── 坐标: (450, 320) └── 耗时: 6.18s根因列表顶部有一个广告 bannerAI 将其视为第一个商品。修复方案启用deepLocate进行精确定位await agent.aiTap(the first product in the list, { deepLocate: true });或调整描述排除广告区域await agent.aiTap(the first product card below the banner);3.3 场景三动作执行时序问题现象aiInput输入文本后立即aiTap(search button)但搜索结果为空。报告分析查看报告时间线关注每步耗时和截图Action: Input iPhone and search ├── Plan: (0.5s) ├── Locate: search input box (5.2s) ├── Input: iPhone (1.3s) │ └── 截图显示输入框内容为 iPhone但搜索按钮仍处于禁用状态 ← ⚠️ ├── Plan: (0.4s) ├── Locate: search button (4.1s) ├── Tap: search button (1.0s) │ └── 截图显示按钮被点击但搜索未触发因为输入未完成验证 ← ❌ └── ...根因输入完成后搜索按钮需要时间激活点击过早导致搜索未触发。修复方案增加aiWaitFor等待按钮激活await agent.aiInput(search box, iPhone); await agent.aiWaitFor(the search button is enabled and clickable, { timeoutMs: 5000 }); await agent.aiTap(search button);或增大waitAfterActionconst agent new PuppeteerAgent(page, { waitAfterAction: 1000, // 默认 300ms增大到 1s });3.4 场景四AI 断言误判现象aiAssert(the cart shows 3 items)通过了但实际购物车有 5 件商品。报告分析查看报告中的 Assert 步骤Print_Assert_Result: ✅ the cart shows 3 items ├── 截图显示购物车图标上的数字是 5 ← ❌ AI 看到了 5 但断言通过 ├── AI 输出: The cart badge shows number 3 ← ❌ 模型视觉理解错误 └── 耗时: 0.99s根因视觉模型对小数字的识别存在偏差将 5 误读为 3。修复方案改用aiQuery提取具体数值并用 Python/JS 断言const count await agent.aiNumber(the number on the cart badge); assert(count 3, Expected 3 items, but got ${count});或使用aiString提取文本后验证const cartText await agent.aiString(the text on the cart badge); assert(cartText.includes(3));3.5 场景五缓存导致的幽灵失败现象用例在本地一直通过但在 CI 上偶发失败。报告分析对比本地和 CI 的 Midscene 报告本地报告 ├── Locate: the login button │ └── cache hit — XPath: /html/body/div[2]/button[1] ← 缓存命中 │ └── 耗时: 0.3s极快 CI 报告 ├── Locate: the login button │ └── cache miss — falling back to AI model ← 缓存未命中 │ └── AI 定位到 /html/body/div[3]/button[1] ← ❌ DOM 结构变化 │ └── 耗时: 6.2s根因CI 环境的页面 DOM 结构与本地不同如 A/B 测试、不同分支代码导致缓存 XPath 失效AI 重新定位时找到了错误元素。修复方案将缓存文件提交到代码仓库确保 CI 和本地使用相同的缓存或在 CI 中禁用缓存始终使用 AI 实时定位const agent new PuppeteerAgent(page, { cache: false, // CI 环境禁用缓存 });设置缓存调试日志export DEBUGmidscene:cache:*四、高级调试技巧4.1 LLM 使用指标监控Midscene Agent 提供metrics属性可以实时获取 LLM 调用的 token 使用情况await agent.aiAct(search for headphones and add the first one to cart); const usage agent.metrics; console.log( LLM 使用统计 ); console.log(总 Token: ${usage.totalTokens}); console.log(Prompt Token: ${usage.totalPromptTokens}); console.log(Completion Token: ${usage.totalCompletionTokens}); console.log(缓存命中 Token: ${usage.totalCachedInput}); console.log(总耗时: ${usage.totalTimeCostMs}ms); console.log(调用次数: ${usage.calls}); console.log(\n 按意图分类 ); for (const [intent, bucket] of Object.entries(usage.byIntent)) { console.log(${intent}: ${bucket.totalTokens} tokens, ${bucket.calls} calls); } // planning: 1200 tokens, 3 calls // insight: 800 tokens, 2 calls console.log(\n 按模型分类 ); for (const [model, bucket] of Object.entries(usage.byModel)) { console.log(${model}: ${bucket.totalTokens} tokens); }调试价值成本追踪计算每条用例的模型调用成本性能分析识别 token 消耗异常的步骤模型对比比较多模型策略下不同模型的贡献4.2 实时 LLM 调用回调通过onLLMUsage回调实现实时监控可对接 Langfuse 等可观测性平台const agent new PuppeteerAgent(page, { onLLMUsage: (usage) { console.log([${usage.intent}] ${usage.model} → ${usage.total_tokens} tokens); // 对接 Langfuse // langfuse.event({ name: usage.intent, value: usage.total_tokens }); }, });4.3 页面上下文冻结当需要对同一页面状态执行多次aiQuery时使用freezePageContext避免重复截图// 冻结页面上下文复用同一截图 await agent.freezePageContext(); // 并发执行多个查询共享同一页面快照 const [username, password, loginBtn] await Promise.all([ agent.aiQuery(the value in the username input), agent.aiQuery(the value in the password input), agent.aiLocate(the login button), ]); // 解冻恢复实时页面状态 await agent.unfreezePageContext();注意冻结期间不要执行交互操作如aiTap否则 AI 会基于过期截图做决策。报告中冻结操作会显示 标记。4.4 日志内容提取通过_unstableLogContent()获取完整的执行日志不稳定 API结构可能变化await agent.aiAct(fill the registration form); const logContent agent._unstableLogContent(); console.log(logContent); // 输出包含每步的 AI 输入、模型原始响应、解析结果、执行状态等4.5 截图缩放优化通过screenshotShrinkFactor减少截图尺寸降低 token 消耗const agent new PuppeteerAgent(page, { screenshotShrinkFactor: 2, // 宽高减半面积减少到 1/4 });缩放系数面积变化Token 节省适用场景1默认不变无日常使用21/4~40%移动设备、简单页面31/9~60%极端节省场景可能影响 AI 理解建议移动端设为 2Web 端保持 1。不建议超过 3否则截图过于模糊影响 AI 元素识别。4.6 Deep Locate 精确定位当目标元素较小或与周围元素难以区分时启用deepLocate进行两阶段精确定位// 普通定位单次 AI 调用 await agent.aiTap(the small close button on the popup); // 深度定位两次 AI 调用更精确 await agent.aiTap(the small close button on the popup, { deepLocate: true });模式AI 调用次数耗时准确率适用场景普通定位1快标准大多数场景Deep Locate2约 2 倍更高小元素、密集列表、相似元素4.7 任务中止与超时控制通过abortSignal实现超时控制和用户取消const controller new AbortController(); // 30 秒超时 setTimeout(() controller.abort(timeout), 30000); try { await agent.aiAct(complete the complex checkout process, { abortSignal: controller.signal, }); } catch (e) { if (e.message.includes(abort)) { console.log(任务超时中止查看报告了解执行到哪一步); // 打开 Midscene 报告查看已执行的步骤 } }4.8 Chrome 扩展 Playground 调试Midscene 提供 Chrome 扩展可以在任何网页上即时测试 AI 指令无需编写代码从 Chrome Web Store 安装 Midscene 扩展配置模型 API Key环境变量打开目标网页在右侧侧边栏使用以下功能Act测试aiAct指令查看 AI 规划和执行过程Query测试aiQuery数据提取验证返回格式Assert测试aiAssert断言查看判断结果Tap测试aiTap即时点击验证元素定位调试技巧在编写正式测试脚本前先用 Chrome 扩展在目标页面上验证 AI 指令的有效性确认 AI 能正确定位元素和执行操作后再将指令写入脚本。这能大幅减少后期调试成本。总结调试速查表症状首先检查修复方向AI 执行了错误操作报告中 Plan 步骤的输出优化自然语言指令或改用结构化 API点击了错误元素报告中 Locate 步骤的截图标注启用deepLocate调整元素描述操作成功但结果不对报告中 Action 步骤的时序和截图增加aiWaitFor或waitAfterAction断言结果不可靠报告中 Assert 步骤的 AI 输出改用aiQuery 编程断言本地通过 CI 失败报告中缓存命中状态对比提交缓存文件或 CI 禁用缓存执行太慢agent.metrics的 token 和耗时启用缓存、增大screenshotShrinkFactor成本过高metrics.byIntent和byModel多模型策略、缓存、截图缩放