UI 自动化探索系列
建议按顺序阅读:
| 篇序 | 文章 | 主题 |
|---|---|---|
| ① | 用 AI 降低 iOS 客户端 UI 自动化测试难度(实践复盘) | AI 辅助写出线性脚本(交互 / .steps / Shell) |
| ② | 启发式 UI 自动化:从线性剧本到每步读屏决策(实践复盘) | 启发式回归:每步读屏再决策 |
本篇为第 ② 篇。
与前文的关系
上一篇 用 AI 降低 iOS 客户端 UI 自动化测试难度(实践复盘) 解决:怎么把测试流程写成可执行的 AXe 脚本 (交互 / .steps / Shell)。
本篇是递进:脚本有了之后,跑起来时界面经常和写脚本那天不一样------下面先说实践中遇到的干扰,再说我们怎么改执行模型。
实践背景:页面并不总是「干净的主流程」
按上一篇写好 Shell 剧本,在 Debug 包 / 日常联调 里做回归时,常见情况是:核心操作路径没变,但屏幕上多出了剧本没写过的层。典型干扰包括:
- Debug 弹窗:内泄漏弹窗等,挡住目标按钮或底部 Tab
- 运营弹窗:活动蒙层、付费引导、我知道了 / Got it 等
- 系统权限:麦克风、跟踪等 Alert
- 气泡 / 动画遮盖:悬浮提示、短暂 toast,改变可点区域或误导坐标点击
- 数据相关 UI:列表为空、缺少某种类型的内容等
线性剧本的默认假设是:执行到第 N 步时,用户仍停在写脚本时那个页面。一旦上述层出现,常见后果是:
- 该点的按钮点不到(被挡住)→ 脚本反复点同一坐标 → 死循环
- 误点到蒙层或误入别的页面 → 后续步骤全部偏离
- 只能在
flow_ui_common.sh里不断 补 if/grep,每遇一种新弹窗改一次脚本
这类 UI 不是测试目标本身 ,但在测试环境几乎不可避免。我们需要的是:先看当前屏幕长什么样,再决定点哪里------而不是盲按剧本的下一行。
核心变化:从「写死剧本」到「每步读屏再决策」
针对上面的问题,本篇引入 axe_ai Heuristic:
- 以前(线性剧本) :
第 1 步点 A → 第 2 步点 B → 第 3 步点 C,顺序写死在脚本里 - 现在(启发式) :每一步 先
describe-ui读当前屏 → 规则判断「该关弹窗 / 该返回 / 该继续主流程」→ 只执行一个动作 → 下一步再重新读屏
换句话说:不是多写几条 if 补丁,而是把 「下一步点哪」从剧本里拆出来,交给读屏 + 优先级规则。
| 维度 | 上一篇(AI + AXe 写脚本) | 本篇(axe_ai Heuristic) |
|---|---|---|
| 编排 | 步骤顺序事先写好,按行执行 | Observe → Plan → Act 循环 |
| 遇弹窗 / 误导航 | bash if/grep 补丁 |
优先级规则树(弹窗 > 主流程 > 导航) |
| AI 何时参与 | 写脚本、修脚本 | 探索路径并沉淀为 Python 规则;运行期不调模型 |
| 运行期 | 按剧本逐步执行 | 每步重读屏,只执行已沉淀规则 |
| 留证 / 卡住时 | 多为终端日志;卡住常等人按 Enter 继续 | 每步截图 + run.jsonl;屏+动作连续相同 → 自动中止 |
| 瞬态 UI | 单张截图,一闪而过的提示易错过 | 关键操作后并行连续截图 |
| 更适合 | 快速出第一版脚本、固定验收清单 | 多入口冒烟、回归留证、干扰多的环境 |
还要额外解决的几件事:
- 未知遮盖层无法事先枚举
- 多入口分支(如场景 A / B / C)难用 bash 维护
- 卡住时缺少每步截图与结构化日志
- 弹窗关闭始终优先于主流程操作
二者关系:先用 AI+AXe 摸清路径 → 沉淀为规则 → 用启发式层做可重复、可分支的回归。
补充:run.jsonl 是什么?
启发式回归每跑一步,会在输出目录(如 /tmp/axe_ai_run/)里追加一行 JSON ,文件叫 run.jsonl(JSON Lines 格式:一行 = 一步,不是把整个日志写进一个大数组)。
每一行把当步三件事记在一起:
- observe --- 执行前 读屏:截图路径(
step_XX.png)、当前屏labels、无障碍树摘要 - planner --- 决策 :为何点这一步(
reasoning)、具体动作(如tap_label+OK、type文案) - exec_ok --- 结果:这一步 AXe 是否执行成功
可把它理解成自动化的黑匣子 :脚本卡住时,不用重新跑一遍,打开 run.jsonl 看第几步、当时屏幕上有什么、规则打算点什么。比 Shell 剧本只在终端打印日志更易复盘,也方便交给 Agent 对照截图续排。
工作原理
运行期:Observe → Plan → Act
暂时无法在飞书文档外展示此内容
运行期主循环如上图:每步 先读屏,再由规则 Planner 选一个动作执行,并写入 run.jsonl;不调大模型(模型仅在开发期改规则,见下节 callout)。
下表是各阶段职责对照:
| 阶段 | 做什么 | 调 AXe | 调 LLM |
|---|---|---|---|
| Observe | describe-ui 压成 label 列表 + 截图 |
✅ | ❌ |
| Plan | HeuristicPlanner 按优先级猜下一步 |
❌ | ❌ |
| Act | tap_label / type / 坐标兜底 |
✅ | ❌ |
「启发式」指什么
这里的 Heuristic(启发式) 不是机器学习,也不是运行时调用大模型。可以把它理解成:把有经验的测试同学「看一眼屏幕再决定下一步」的做法,写成一条条 if-else 规则。
特点是 够用、便宜、不保证万无一失 :不穷举 App 所有页面状态,只根据当前屏幕上能读到的文字和按钮,猜一个最可能有效的动作;猜错了再由人补规则。
和「精确剧本」的对照:
| 精确剧本 | 启发式规则 |
|---|---|
| 事先定义好每一步点哪里 | 每一步先看屏再决定 |
| 界面必须和写脚本时一致 | 允许弹窗、误点等干扰,优先处理 |
| 一步走错后面全错 | 下一步重新读屏,有机会自我纠正 |
常用的几类「经验法则」:
- 模糊认页 --- 不依赖内部页面代号,而是看屏幕上是否出现某些特征文字(如「OK」「返回」「提交」),据此判断「现在大概在哪个界面」。
- 优先级链 --- 多条规则排好顺序:先关挡屏的弹窗 → 再处理误进的页面(点返回)→ 再走主流程。先匹配到的先执行,不回头试别的。
- 动作降级 --- 优先按按钮上的文字 点击(稳定);若无障碍树里没有该按钮,再退而求其次用屏幕坐标点(脆弱,仅作兜底)。
- 记住已做过的步骤 --- 日志里记录「是否已经关过弹窗、是否已经提交过」,避免在同一步上打转。
- 有界搜索 --- 列表里一时找不到目标条目时,滑动有限次数;仍没有则换备用策略(例如先创建一条测试数据),而不是无限滑到底。
- 近似完成 --- 有的提示(如「处理中」)一闪而过,不必死等文字出现;提交动作执行后,结合连续截图判断是否已进入预期状态。
上述规则由开发者在 Cursor 里探索、验证后写入
planner.py等文件;跑回归时只执行这些规则,不再问模型。
AI 在哪介入
开发期 / 排障期 :Cursor Agent 看截图、run.jsonl、describe-ui → 改 planner.py / scenario_nav.py。
运行期 :只跑已沉淀的规则,不再调用模型 。axe_ai 的「ai」强调 Agent 友好产物(截图 + jsonl),不是脚本内置 LLM。
优势
- 执行模型不同:从「按行号跑剧本」变为「每步读屏再决策」,弹窗、误导航、数据缺失时不易连锁失败。
- 抗扰动:弹窗优先关闭、误进子页面自动返回、右下角悬浮按钮连点无效则中止。
- 可复盘 :每步
step_XX.png+run.jsonl,失败可交给 Agent 对照日志续排。 - 数据自愈:列表缺数据时可走备用策略(如先造一条测试内容),不硬依赖线上数据。
- 与 Shell 共存 :底层仍是 AXe;弹窗处理逻辑与
flow_ui_common.sh对齐。
劣势
- 不完备:启发式可能误判页面;新页面要补规则,不能开箱即用。
- 维护规则本身有成本:本质是把探索经验整理成 Python 规则,路径越多文件越大。
- 仍绑模拟器 + AXe:真机、内嵌网页细粒度控件问题与前文相同。
- 坐标兜底仍脆弱:多机型要比例或 helper 动态算点。
演进方向
| 阶段 | 方向 |
|---|---|
| 近期 | 对更多需求 分别用 启发式 与 传统 Shell 剧本 做 UI 自动化验证,横向比对效果、成本与可维护性;把跑通的路径沉淀为更多脚本或规则。仍主要靠人在 Cursor 里探索、改规则。 |
| 中期 | 单次回归运行中 :启发式某步发现现有规则推不动(规划不出合理动作,或执行后仍卡在同一界面)→ 动态调用大模型 读截图与 run.jsonl → 补本步决策,并自动修补脚本规则 ,使本次或下次同类场景能继续。侧重 跑的时候自救 + 当场/跑完补规则。 |
| 长期 | 不依赖每次跑测时的临时救场 ,而是建设固定流水线:CI 定期跑启发式冒烟 → 失败步骤自动打包产物(截图、jsonl、日志)→ Agent 批量生成规则变更(如 PR) → 人审或自动合入 → 规则库随版本迭代;失败则阻断合流 。模型主要服务 离线、批量、可审计的规则演进 ,而不是每条回归都在线调 API。侧重 组织级闭环 + 门禁。 |
中期 vs 长期(一句话) :中期是「这一次跑挂了,模型帮忙修规则让能跑通 」;长期是「跑挂了多少次、挂在哪里,系统自动汇总并改仓库里的规则,并纳入 CI 常态门禁」。