本节摘要:本节处理固化光谱的中间地带:值得纳入测试体系、又不值得(或不适合)写死成纯代码的任务——断言「页面主体可见」「搜索结果与关键词相关」这类感知性内容。工具是第 5.2 节的 Midscene(约 1.5 万 stars,MIT,中文文档第一梯队):三件套以测试用例的形态进入体系,ai 写操作、aiQuery 抽数据、aiAssert 做感知断言,JS 侧嵌进 Playwright 测试(也可独立脚本),Python 侧对应 pytest 形态(以官方中文文档为准)。AI 断言进 CI 的两条纪律先立住:误判走复核流(感知断言的失败先出报告人工复核,不直接熔断——第 6.1 节口径的 CI 版),报告归档(每次运行的视觉证据进第 8.3 节事件流)。然后是三层混合策略:纯代码冒烟打底(快、稳、零成本)、Midscene 语义用例补盲(感知与语义断言)、关键流程两者并用(结构断言加 aiAssert 双保险)。flake 对比给一张示意表(标注示意值,以各自环境实测为准):自然语言用例的表达力换来略高的不稳定性,靠分层运行节奏把差异控制在可接受面。收在轨迹回放:Midscene 报告作为回归资产的一部分。
第 9.1 节把「值得死板」的任务搬进了纯代码,剩下的任务里有一类处境尴尬:它们属于测试体系(要随每次回归跑),但抗不出纯代码断言——「页面主体内容可见、无报错遮罩」「搜索结果与关键词相关」「上传后的图片预览没有变形」。第 5.2 节的结论在这里直接适用:结构性断言用代码,感知性断言用自然语言,关键流程并用。Midscene 的价值就是把后半句接进测试体系——自然语言写用例,模型执行与判定,测试框架负责运行与报告。
它与第 9.1 节固化管道的关系是补充而非替代:固化管道处理「路径确定」的流程(Playwright 代码),Midscene 桥处理「断言感知」的验证(aiAssert),两者在同一条 CI 流水线上各司其职。
JS 侧嵌进 Playwright 测试(Jest 形态同理,以官方文档为准):
// todo.spec.mjs —— Midscene 语义用例嵌进测试体系(写法示意,以官方文档为准) import { expect, test } from "@playwright/test"; import { PlaywrightAgent } from "@midscene/web/playwright"; test("自然语言添加待办,感知断言与代码断言并用", async ({ page }) => { await page.goto("http://127.0.0.1:8080/demo_todo.html"); const agent = new PlaywrightAgent(page); await agent.ai("在「新待办」输入框填入 买牛奶,点击「添加」按钮"); await agent.aiAssert("列表中包含一条内容为 买牛奶 的待办"); // 感知断言 await expect(page.getByRole("listitem")).toContainText("买牛奶"); // 代码兜底 });
Python 侧的 pytest 形态(包名与断言入口以官方中文文档为准):
# test_todo_ai.py —— Midscene 的 Python 用例形态(写法示意,以官方中文文档为准) from midscene import Agent def test_add_todo() -> None: agent = Agent.from_playwright("http://127.0.0.1:8080/demo_todo.html") agent.ai("在「新待办」输入框填入 回复邮件,点击「添加」按钮") agent.ai_query("提取待办列表的全部条目文本") agent.ai_assert("列表中包含一条内容为 回复邮件 的待办") agent.release()
两侧的分工沿用第 5.2 节的两步走:先在报告与回放工具链成熟的 JS 侧把用例调通(排障快),再按工程栈归属决定落地侧——测试基建在 Node 系就留 JS,团队主栈是 Python 就翻译过去。指令文本与断言语义两侧原样复用(它们本来就是自然语言),翻译成本为零。唯一要双侧各留一份的是夹具层(视口、前置数据、报告路径),这部分进公共配置而不是复制粘贴。
写用例的三条纪律从第 5.2 节原样继承:固定视口(用例默认头部)、断语写可观察事实(「列表包含买牛奶」而非「页面正常」)、指令写用户视角。补充一条 CI 特有的:用例自足——每条用例自己准备数据、自己清理(或接受幂等),CI 的执行顺序不能成为隐含前提。断语改写的对照示例:
| 模糊断语(易抖动) | 改写后(可观察,推荐) |
|---|---|
| 页面看起来正常 | 主体内容可见且无报错遮罩 |
| 加载完成了 | 列表区域渲染出至少一条条目 |
| 排版没乱 | 输入框与「添加」按钮均在视口内可见 |
改写的共同套路:把主观形容换成可数、可见、可判定的陈述——模型判读这类断语的抖动面远小于主观形容,这也是第五节抖动表里「断语边界模糊」一条的主要治理手段。
感知断言的误判概率(第 5.2 节承认的那个代价)在 CI 语境下被放大:CI 的失败信号直接驱动人的注意力,误报多了就是狼来了。两条纪律兜住:
| 纪律 | 做法 | 违反的后果 |
|---|---|---|
| 误判走复核流 | aiAssert 失败先标记「待复核」并出报告,人工确认后才升级为真实缺陷 | 误报骚扰与信任流失 |
| 报告归档 | 每次运行的可视化报告(截图、判定依据)作为证据指针进第 8.3 节事件流 | 复核无据可查,争论没有裁判 |
复核流的工程形态很轻:测试框架的 retry 与软失败标记(soft assertion)都够用,关键是分级——感知断言的失败在告警系统里属于「黄」不是「红」(第 11.2 节告警分级的前置呼应)。纯代码断言维持「红」的待遇:它几乎不误判,失败就该拉响。判定冲突时的仲裁规则也要先写好:代码断言与 aiAssert 一个过一个不过时,以代码断言为准(它判定的是结构事实),aiAssert 的分歧进复核——这条规则免去了每次失败现场争论「听谁的」。
报告归档的形态:报告目录按运行组织(runs 加运行标识),事件流的 evidence 字段存目录路径而非单文件——回放时整个目录一起取,截图、推理、判定依据一次到齐。
| 层 | 工具 | 覆盖 | 运行节奏(示意) | 成本 |
|---|---|---|---|---|
| 冒烟层 | 纯 Playwright | 核心路径结构验证 | 每次提交 | 零 token |
| 语义层 | Midscene 用例 | 感知与语义断言 | 每日定时 | 每用例若干次模型调用 |
| 关键层 | 两者并用 | 支付、权限、发布敏感项 | 发布前 | 双份成本买双保险 |
分层的原则是按失败信号的可靠性排节奏:越可靠的信号跑得越频繁、越靠近开发反馈环;带误判面的信号往后放、频率降低,用复核流消化不确定性。这也是成本的自然分配——模型调用集中在低频高价值的语义层与关键层,高频冒烟保持零成本。层的归属不是永久的:一条语义用例连续一个月零误判,可以提名降层提速;一条冒烟用例开始频繁因改版挂掉,说明它断言的其实是语义(比如「主入口可达」),该升层交给 aiAssert——月度回顾时按第 11.1 节稳定率数据调层,是这套策略保持鲜活的机制。
第 5.2 节说过 Midscene 每次操作产出可回放的中间材料(带定位框截图、推理与动作输出、断言判定依据)。进了测试体系之后,这些报告的定位要升级:它们是回归资产的证据层,与测试代码同等重要地被版本管理与检索。
| 动作 | 做法 | 产出 |
|---|---|---|
| 归档 | 每次运行的报告路径作为 evidence 指针写入第 8.3 节事件流 | 可按 run_id 检索的证据库 |
| 回放 | 失败用例的三步复盘:看报告截图、对照当时快照、查事件时间线 | 一份「当时发生了什么」的叙事 |
| 沉淀 | 月度回顾高频失败的断语,改写清单进用例模板 | 断语口径的持续收敛 |
回放的价值在「失败的历史可解释」:一条语义用例这个月挂了三次,回放三次的报告往往能看出模式(都是懒加载区域、都是视口底部元素)——这类模式正是第 11.1 节 flake 归因的输入。沉淀的价值在「断语口径越用越准」:模糊断语(「页面正常」)在月度回顾里被逐步替换成可观察断语(「主体内容可见且无报错遮罩」),用例集随时间自我改良。
与第 9.1 节回归任务集的关系:任务集是骨架(哪些用例、什么节奏跑),报告库是血肉(每次跑的证据)。只有骨架没有血肉的测试体系,失败时只能靠现场重跑取证——那是最慢的排障路径。
| 指标(示意值,以各自环境实测为准) | 纯 Playwright | Midscene 用例 |
|---|---|---|
| 千次运行抖动率 | 低于百分之一 | 百分之一到百分之五 |
| 主要抖动源 | 时序与数据(第 11.1 节) | 视口渲染差异、断语边界模糊 |
| 平均用例耗时 | 秒级 | 数十秒级 |
| 每次运行边际成本 | 零 | 模型调用费用 |
数字务必当作占位符读:真实数值随页面、模型、网络环境差异极大,这张表的用途是结构对比而非基准——Midscene 用例的抖动源多一类(语义判定的边界),因此控制手段也多一条:断语边界模糊的用例要么改写得更可观察(第 5.2 节坑表),要么降级到更低频层。抖动率的度量与治理统一归第 11.1 节,本章只需带着「示意值、须实测」的清醒。
补充一个常被忽略的联动:三层策略的成本与抖动会互相传导——语义层跑得越频繁,模型调用费用与抖动暴露面越大;把语义层降到每日之后省下的钱,一部分应该回投给第五节的报告归档(证据库越全,月度回顾改良断语的效率越高)。这是一个「频率换质量」的再平衡,团队按自己的告警噪音与预算找平衡点。
| 坑 | 现象 | 排查与处理 |
|---|---|---|
| 感知断言当红色告警 | CI 天天狼来了 | 复核流分级,黄色待复核 |
| 报告不留档 | 复核时靠回忆 | 报告路径进事件流 evidence 字段 |
| 语义层塞进提交钩子 | 提交变慢、费钱 | 语义层走每日定时,冒烟层守提交 |
| 断语模糊不修 | 同用例时过时不过 | 改写成可观察事实,或降层降频 |
| 密钥写进 CI 配置明文 | 流水线日志泄露 | 环境变量或密管注入(第 7.1 节) |
| 报告只存最近一次 | 月度回顾无料可看 | 报告按运行归档进证据库(第五节) |
| 用例互相依赖顺序 | 单跑即失败 | 用例自足纪律(第二节),CI 顺序无关化 |
测试资产就位后,镜头拉回运行时:当任务量大到单个浏览器实例吃不下、账单开始引人注目,就进入了多智能体的领地。下一章讲并发与队列、实例池与速率协调,以及 token 成本的结构与省钱三招。