方法论与洞察

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,被按最小尺寸过滤的枚举漏掉
侧栏项目条目点错行侧栏滚动在顶部别的项目

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

三验补充(2026-08-13):前提还有两个更隐蔽的形态

上表列的前提全是版面类的(面板开合 / 窗口最大化 / 输入框为空 / 侧栏滚动)。第三批(Codex 教程操作部分 26 段重录)暴露出两个新形态,它们比版面类更难想到:

新形态事故前提是什么
窗口身份find_any("ChatGPT") 抓到「ChatGPT - Google Chrome」,差点对着浏览器录整集句柄指向的确实是目标进程
应用视图316 第三段录成「常规设置」;随后三次 UI 探索连续失败应用停在主界面,而不是设置页/全屏模态

两条对应的解法已写进操作规则 7、8。

操作规则

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

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

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

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

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

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

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

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

  7. 窗口一律按进程名认,不要按标题或类名(三验新增)。Electron 应用(ChatGPT 桌面端、VS Code、Discord…)类名清一色 Chrome_WidgetWin_1;标题由页面内容决定,随时会和别的窗口撞。用 GetWindowThreadProcessId + QueryFullProcessImageNameW 取 exe 名比对。

  8. 每条动作前把应用收敛到已知视图(三验新增)。别假设它还停在上一段结束时的样子——尤其设置页这种”占满整屏且跨脚本残留”的状态。写一个 ensure_main_view() 放在所有坐标动作之前。

  9. 判据要能区分”没找到”和”找错了”(三验新增)。本例空输入框判据应为 0.0019,实测 0.99329(浏览器那块区域是深色),一眼就知道是抓错了窗口。如果判据只有”有没有内容”,会误判成”输入框有残留”,接着去清空一个浏览器页面——又是一次 PLAN.md 事故

  10. 判据的方向要先想清楚:是”变化”还是”增加”?(三验新增)实测一次翻车:用”对话区暗像素增加”判断收到回复,结果发送后读数从 .00521 降到 .00056——新会话的大标题字号很大,暗像素比一句「2 + 2 = 4.」还多。素材本身是好的,白重录一条。凡是”内容变多 → 某指标变大”的直觉,都要先用两端实测标定一次。

边界

同族

验证状态

⭐⭐⭐ 三次验证

三次跨不同项目、不同界面版面独立复现。仍未离开录屏域——三次都来自同一条工具链(humancast + OBS)。真正的跨域验证应当来自别的 UI 自动化场景(浏览器插件、移动端、桌面测试框架)。

另:二验时写下的”最小化窗口枚举不到”这一条,三验时被同一个人在同一套工具上再次踩中(清屏函数把目标窗口自己也最小化了)。已知坑写进文档不等于不会再踩——真正拦住它的是断言,不是记忆。

关联文档

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