方法论与洞察

UI 自动化的固定坐标,必须绑一条可机检的前提断言 · v1

一句话律:硬编码坐标都有隐含前提(面板是关的 / 窗口是最大化的 / 输入框是空的)。前提不成立时坐标不会报错,它会落在别的东西上并照常执行。 所以坐标不能单独存在,必须和一条能当场校验的断言绑在一起。

为什么它比”坐标写错”危险一个量级

坐标写错是响亮的失败——点了个空白处,什么都没发生,你立刻知道。

前提不成立是安静的成功——点击、输入、Ctrl+A+Delete 全都正常执行了,只是执行在别的控件上。脚本一路绿灯跑完,产物参数正常,你要么很久以后才发现,要么根本发现不了。

实测最贵的一次:右侧文档面板开着时,输入框坐标 (3670,990) 实际落在文档编辑器里,一次 Ctrl+A + Delete 把一份 18KB 的 PLAN.md 清成了 101 字节。脚本没有任何异常输出。

实证

Codex 教程录屏项目,两批素材各自独立复现:

事故前提是什么前提不成立时坐标落在哪
PLAN.md 被清空 18KB→101B右侧文档面板已关闭文档编辑器
同根因第二次:确认语被打进 PLAN.md同上同上
整段一声不响退出,什么都没打印Codex 在录制屏上最大化窗口外的桌面背景(读到深色 → 判成”AI 还在跑”)
段 13 连着两次没跑起来输入框是空的输入框有内容时发送键从浅灰变深,和”停止方块”一样暗
”找不到窗口”窗口没被最小化过最小化窗口 GetWindowRect 只有 160×28,被按最小尺寸过滤的枚举漏掉
侧栏项目条目点错行侧栏滚动在顶部别的项目

共同点:没有一条是坐标本身算错了。

操作规则

  1. 每个坐标常量旁边写死它的前提,并把前提做成可机检的断言,不是注释里的一句提醒。注释拦不住三周后的自己。

  2. 断言写在动作之前ensure_layout() 必须先于 clear_box(),顺序反了就是清空文档——这条是用一份被删的文件换来的。

  3. 断言优先选位置无关的像素特征。本项目用”按钮区域最暗像素”一个量同时判三件事:

    • < 250:按钮确实在这个坐标上(面板开着的话这里是纯白)
    • < 100:按钮是停止方块(AI 在跑)
    • ≈ 140:按钮是箭头(空闲)

    好判据的标准是跟视图无关。试过”和参考图比对”,会因为视图切换(新建任务页 ↔ 对话页)产生难以解释的误判。

  4. 破坏性操作前必须过断言——Ctrl+A + Delete、拖放、点”确认”/“删除”/“提交”。非破坏性动作可以只在开头过一次。

  5. 窗口类前提要校验矩形,不能只发一条消息就当成功ShowWindow(SW_RESTORE) 不保证回到最大化:窗口如果中途被人手动动过,restore 出来的是普通尺寸,所有基于”最大化”的坐标同时失效。发完 SW_MAXIMIZE 再读回 rect 比一遍。

  6. 应用状态机不要靠单次点击推断。本例要开一个挂在指定项目上的空白任务:只点侧栏项目条目 → 进它已有的对话;只点”新建任务” → 项目是空的。正解是三步组合(新建任务 → 选项目 → 搜索过滤到一条 → 点它),并用像素校验结果。

边界

同族

验证状态

⭐⭐ 二次验证

两次跨不同项目、不同界面版面独立复现。尚未在录屏以外的域验证,第三次验证应当来自别的 UI 自动化场景。

关联文档

类型/洞察律主题/自动化主题/UI自动化工具/humancast