首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我让 AI 操作了 8 个内容平台,才发现 UI 里藏着多少「只有人看得懂」的约束

我让 AI 操作了 8 个内容平台,才发现 UI 里藏着多少「只有人看得懂」的约束

原创
作者头像
用户6170966
发布2026-08-30 16:38:36
发布2026-08-30 16:38:36
70
举报

先给结论:图形界面同时干着两件事——交互约束。人用的时候感觉不到区别,因为约束是画给你看的:按钮灰了、选项变浅、提示条弹一下。当你让程序去操作同一个界面时,交互那部分它拿得到,约束那部分它几乎全看不见

于是就出现了这一类最难查的故障:操作返回成功,但什么都没发生。

这篇记录我把八个内容平台的发布流程自动化时踩到的坑。不讲怎么做自动化,讲的是界面到底把哪些信息只编码在了视觉层,以及这对任何「让程序驱动为人设计的系统」的场景都成立的一条原则。


一、最典型的一类:禁用态

某平台的标签选择器,上限是 2 个。加满之后,剩下的候选项会变成浅灰色。

人看到浅灰色就知道点不动了。程序看到的是什么?

代码语言:javascript
复制
// 加满之后,对一个"禁用"的候选项调用
el.click();     // 不抛错,不报警,正常返回

它不抛错、不返回 false、DOM 上也没有 disabled 属性——只是 CSS 把它画浅了,并且点击回调里加了个提前 return。

所以我的代码是这么写的:

代码语言:python
复制
if click_option(page, tag):
    print(f"✅ 标签 {tag} 已添加")

三个标签,三次 。实际只加上了两个。

日志、返回值、异常,三条通道全部显示成功——只有截图能看出问题

正确的判据不在操作端,在状态端

后来改成读平台自己算的那个数:

代码语言:python
复制
# 页面上有一行「你还能添加 N 个标签」
left = read_remaining_quota(page)   # 加之前
click_option(page, tag)
now = read_remaining_quota(page)    # 加之后
ok = now < left                     # 额度真的减少了,才算加上

这个数是平台自己维护的业务状态,不是我对操作结果的推断。

这是整篇最重要的一条:不要用「操作有没有执行」判断「状态有没有改变」。


二、第二类:视觉上存在,DOM 里不存在

另一个平台的发布按钮,截图上清清楚楚一个红色「发布」。但:

代码语言:javascript
复制
// 全页扫描,清洗掉零宽字符和空白后精确匹配
Array.from(document.querySelectorAll('*'))
     .filter(e => clean(e.innerText) === '发布')
// → 只找到侧边栏的「发布笔记」和一个「定时发布」开关,没有那个按钮

排查绕了很久,因为它伪装成好几种常见问题:先怀疑按钮文本里夹了零宽字符(清洗了还是找不到),再怀疑在 iframe 里(只有主文档),再怀疑 DOM 查询被截断了。

最后是用坐标反查一步定位的:

代码语言:javascript
复制
document.elementFromPoint(743, 855)
// → <xhs-publish-btn submit-text="发布" save-text="暂存离开" submit-disabled="false">

按钮文字放在 HTML 属性上,真正的按钮渲染在 closed shadow root 里。 element.shadowRoot 返回 nullquerySelectorAll 穿不进去,innerText 是空串。

所有基于文本的定位——包括各种测试框架的 getByText——在这里结构上不可能成功,不是选择器没写对。

但它照常接收鼠标事件

所以解法是按坐标点。这又带出一个更阴的问题:

我一开始按容器宽度的比例算坐标,落在了按钮右侧的空白上。而 closed shadow root 让 elementFromPoint 无论点容器里哪一处都返回同一个元素——看着像命中了,一切"正常",就是不发布。

后来改成按容器中心的固定偏移算(按钮尺寸是固定的,容器宽度随窗口变),才稳定。

教训:当一个查询无论如何都返回同一个结果时,它就不再有区分能力,也就不能拿来当校验。


三、第三类:同一个界面,两套语义

某平台的文章编辑页,URL 里带着文章 ID,标题和正文都正确加载了原文。页面上有个「导入 md 文件」的入口——发布新文章时我一直用它,因为它能顺带解决标题栏的一个问题。

在编辑页用同样的方式导入、提交,返回的成功页长这样:

代码语言:bash
复制
https://.../creation/success/164183558

而我编辑的那篇文章 ID 是 164152491

它新建了一篇重复文章,原文纹丝不动。

「导入」这个动作在产品语义里始终是「导入成一篇新文章」,跟你当前打开的是不是编辑页无关。而成功页的 URL 格式一模一样——不比对 ID 根本发现不了

后来改成剪贴板粘贴(直接改当前文章的编辑器内容),并加了一道硬校验:

代码语言:python
复制
if returned_id != original_id:
    print(f"🔴 新建了一篇重复文章({returned_id},原文是 {original_id})")
    return 1

同一类问题在另一个平台的变体

编辑已发布内容时,按钮的名字会变:

平台

新建时

编辑时

A

发布

更新

B

发布 → 确定并发布

更新 → 确定并更新

C

发布

保存修改

C 那个最坑:页面上同时还有一个「发布」文本元素(在别处)。我的宽匹配点到了它,返回 True,然后什么也没发生。

判据当时写的是「URL 里的文章 ID 没变」——它确实没变,因为压根没提交。校验条件和失败模式正交,等于没校验。

最后加的规矩是:保存后重新加载页面,回读内容比对。这是唯一不会被骗的判据。


四、第四类:工具本身的副作用

这一类不怪界面,怪我。

某平台的标签框里已经有两个标签 chip,我想清空搜索输入框,写了:

代码语言:python
复制
input.fill("")

结果两个 chip 一起没了

fill 的实现是「聚焦 → 全选 → 替换」。在这类复合组件里,全选的选区包含了 chip,插入空文本等于把它们删掉。而 fill("非空") 就没这个问题——它替换的是选区内容。

更麻烦的是这个 bug 和第一节那个「判据不可靠」叠在一起:判据永远返回 False → 每次都走清空分支 → chip 被删光 → 判据继续 False。表现是「截图里明明有标签,脚本却说一个都没加上」。

两个各自不致命的问题叠在一起,症状会指向一个不存在的第三个原因。


五、还有一类:删除操作的多步确认

删一篇文章,我以为点了「删除」→「确定」就完事了。

页签数字确实变了:已发布 10 → 9。但另一个数字我没看:回收站 0 → 1

删除只是移到了回收站,回收站里还有一步「彻底删除」,弹窗文案是「删除后无法恢复」。

我当时的判据是「页面上还有没有这篇文章的链接」——回收站里的文章链接仍在列表页 DOM 里,所以判据显示"仍在",我以为删除失败了。判据错了两次,方向还相反。

真正可靠的信号是平台自己的计数:全部 12 → 11、回收站 1 → 0。


六、一条通用原则

把上面五类放在一起看,共同点很清楚:

界面把「能不能做」这件事,大量地只编码在了视觉层。

按钮灰了、选项浅了、提示条弹了、菜单项禁用了——人一眼就懂,程序拿到的却是一个正常返回的 click()

所以让程序驱动为人设计的系统时,有一条我认为绕不开的规矩:

永远不要用「操作的返回值」判断「状态是否改变」。要回读状态本身。

具体到实践:

不要

click() 有没有抛错

读平台自己维护的计数、额度、状态标签

看返回的 URL 长得对不对

比对 URL 里的业务 ID 是不是原来那个

看提交后页面有没有跳转

重新加载页面,回读内容

看某个元素在不在

看那个元素能不能真的起作用

最后一条尤其重要:当一个检查无论如何都返回相同结果时,它已经没有区分能力了,留着只会给你虚假的安全感。


七、这件事的适用范围比自动化大得多

我做的只是内容发布这么个小场景,但同样的结构在别处也成立。

当 Agent 开始替人操作各种系统时,它面对的正是同一个问题:过去很多业务约束是靠界面来实施的——某个字段你看不到是因为页面没渲染,某个操作你做不了是因为按钮灰着,某个流程必须走审批是因为页面强制你提交。

界面被绕开之后,这些约束不会自动跟着走。它们要么被显式地表达出来(在接口层、在权限层、在状态机里),要么就变成一堆「调用成功但什么都没发生」的静默失败。

我这一天遇到的每一个坑,本质上都是同一句话:约束没有被表达,只是被画了出来。


小结

症状

真正的原因

可靠的判据

操作返回成功,状态没变

禁用态只在 CSS 里

读平台自己的计数/额度

元素肉眼可见,代码找不到

closed shadow root

坐标反查定位

提交成功,改的却是别的对象

同一界面两套语义

比对返回的业务 ID

截图有内容,脚本说没有

两个 bug 叠加

每个环节独立回读

删除后还在

多步确认(回收站)

看平台的分类计数

如果只记一句:回读状态,别信返回值。


本文的实测来自把八个内容平台的发布流程自动化的过程,工具与踩坑记录由 Code2AI 团队维护。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、最典型的一类:禁用态
    • 正确的判据不在操作端,在状态端
  • 二、第二类:视觉上存在,DOM 里不存在
    • 但它照常接收鼠标事件
  • 三、第三类:同一个界面,两套语义
    • 同一类问题在另一个平台的变体
  • 四、第四类:工具本身的副作用
  • 五、还有一类:删除操作的多步确认
  • 六、一条通用原则
  • 七、这件事的适用范围比自动化大得多
  • 小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档