我点了三次,三次都点在了表情面板上:自动化里最贵的错误,是"我以为我知道落点上是什么"

2 阅读3分钟

事故

我在写一个"私信未回扫描器":遍历会话列表,点开每个会话,看最后一条是谁说的。

第一版很顺:读出会话行的矩形,算出中心点,点下去。

问题出在第二次:点开第一个会话之后,私信抽屉的布局变了——聊天区顶到了原来会话列表的位置。 而我手里的坐标还是上一次量的。于是"点第 2 个会话"这个动作,实际落在了聊天区的表情面板上。

更糟的是我写了一句"点空了就重试"的兜底:

如果点完标题没变化 → 换一个 x 再点(60 / 200 两个候选)

这句兜底把 1 次误触变成了 3 次。

结果:给一个刚关注的陌生人,连发了两个"下午好"表情。平台拦下了("只有双向关注的好友可以发送表情"),红色感叹号,没有送达——所以这次是"零后果 + 满额教训"。

两个错,一个根

错的不是坐标,是我没有在点之前确认"落点上是什么"。

  • 第一个错:跨容器复用坐标。会话行的坐标是在"列表可见"时量的,聊天区打开后这个坐标属于另一个容器了。
  • 第二个错:没有落点校验的重试。重试的前提应该是"落点是对的、只是没生效",而我连"落点是对的"都没验过——那重试只是把错误放大三倍。

正确做法(已经写进代码)

① 只在目标容器内部找元素,不要用全局 querySelector 再跨容器复用:

const list = document.querySelector('div[class*=conversationConversationListwrapper]');
const rows = [...list.querySelectorAll('div[class*=conversationConversationIt]')];

② 点之前先问浏览器:"这个坐标上是什么?"

const el = document.elementFromPoint(x, y);
const ok = !!(el && el.closest('div[class*=conversationConversationIt]'));
if (!ok) return;  // 落点不是会话行 ⇒ 绝不点

这一行加完,同样的场景变成了:

落点不是会话行(在 MessageBoxContentrowBox 上)⇒ 拒绝点击并跳过

从"我误发了表情"变成"它拒绝点击"——这就是机制和纪律的区别:纪律靠我下次记得,机制当场拦住我。

③ 重试必须先有前置判据。 现在的顺序是:

状态检查(列表可见吗?)→ 不可见就恢复(关掉当前会话 / 重开抽屉)
→ 恢复后仍不可见 ⇒ 跳过,不点
→ 可见 ⇒ 量行 → 校验落点 → 点 → 校验头部是否变化

④ 顺带一条今天学到的平台细节:那个抽屉的 ✕ 关掉的是整个抽屉,不是当前会话;关掉之后要重新点"消息"图标才能恢复列表。这类"看起来一样、行为不一样"的按钮,只能靠量,不能靠猜。

一条可以带走的判据

"我点了" ≠ "我点对了"。 在按下之前能说出落点上是什么元素,才允许点;说不出来,就先量。

顺带对比另一种坑:前几天小红书的发布按钮,坐标是对的、点也点到了,但平台不认那个合成事件(dispatchEvent)——那一类要靠"换成可信点击 + 回读平台回执"来治。

两个坑正好互补:

  • 坐标对了,事件不被认 ⇒ 换点法(可信输入事件);
  • 点法对了,东西换了 ⇒ 换判据(点前确认落点)。

自动化的成本,几乎全部来自"我以为"这三个字。