☰
把「点没点到」变成可观测的:CDP 自动化里那些不报错的失败
2026/10/21 2:40:52 网站建设 项目流程

把「点没点到」变成可观测的:CDP 自动化里那些不报错的失败

做浏览器自动化的人,大都经历过这样一个时刻:脚本跑完了,退出码是 0,日志里该有的行都有,你以为事情办成了——然后发现什么都没发生。

这篇文章讲我在一套多平台发文流水线上,怎么把这类「不报错的失败」一个个揪出来,以及最后沉淀下来的一套可观测做法。所有案例都来自真实运行,不是演习。

一、先看一个完整的失败样本

有一套流水线,任务是往各个内容平台补发存量稿件。某个平台的发布按钮在页面顶部,脚本的流程是:取坐标、真实鼠标点击、等发布面板出现、填分类和标签、点确认。

某天它交出了这样的输出:

REOPEN0 {"x":1317,"y":35,"n":2} FIBER0 NO_FIBER PANEL0 hidden CAT1 no_oneLever CAT2 no_twoLever TAG 自动化 NO_TAG_INPUT TAGS_FINAL count=0 POS {"x":665,"y":759,"n":1,"from":"body"} ===== DONE =====

退出码 0,最后一行写着 DONE。而实际上:发布面板没有打开,分类没选,标签一个没加,最后那一下点到的是页面顶部的「发布文章」入口而不是面板里的「确认发布」——稿件被保存成了草稿。

这个失败有三个特征,每一个都值得单独说。

第一,它不抛异常。取不到元素的时候,脚本返回的是字符串no_oneLever,不是 null,也不是抛错。后续每一步都拿到一个「看起来正常」的返回值,于是一直往下走。

第二,它有一条伪装成健壮性的退路。面板里找不到发布按钮,脚本「聪明地」退回到全页面去找同名的按钮。这条退路我原本以为是容错设计,实际效果是把一次明确的失败,转换成一次看起来成功的保存。

第三,它的判据离原因太远。我在最后一步检查「有没有发布成功」,而问题发生在第一步。中间隔了四步,每一步都会把现场破坏得更彻底,排查时只能从头推演。

二、不报错的失败,大致是三种形态

把这套流水线跑几个月,把失败归档之后,发现不报错的失败基本是三类。

形态一:动作没生效,但返回值是成功的

最常见的是「点了没反应」。真实鼠标事件发出去了,坐标来自getBoundingClientRect()的中心点,elementFromPoint也验证过该坐标上没有遮挡元素——三个条件都满足,组件就是不响应。

这一类的坑在于:点击这个动作本身没有返回值。Input.dispatchMouseEvent只要事件发出去就返回成功,它不关心页面上有没有人听。于是「点了」和「生效了」被混为一谈。

我在这个坑里躺了最久的一次,是某个平台的发布面板。真实鼠标点中了按钮,坐标没错,无遮挡,重试三次,面板纹丝不动。最后发现真正的原因在两步之后:那次点击其实已经发出去了,服务端返回了敏感词校验,弹出了一个确认框,脚本没点这个框,于是卡在中间态。而我一直在检查的那个「面板是否打开」的判据,选错了容器——真实流程里那个容器压根没出现过。

形态二:判据用了弱信号

「发布成功」四个字出现在页面上,算不算成功?不算。我抓到过这样一种误报:脚本在正文里搜「发布成功」关键词来判定,而那篇稿子自己就在讲发布失败,正文里反复出现这四个字。

类似的弱信号还有:接口返回 200(只证明请求到达,不证明业务接受)、日志里没有 ERROR(很多框架把失败写成 info)、界面显示「已保存」(保存和发布是两件事)。

我把判据分成三级,写进脚本注释里:

  • 弱判据:页面文案、toast、日志关键词。最容易误报,因为内容本身会污染关键词。
  • 中判据:DOM 状态、接口响应体。要知道成功态长什么样,而且得抓得到响应体——自动化场景里经常抓空。
  • 强判据:结果的对外可见状态。URL 跳转到成功页、作者主页文章计数加一、另开一个标签打开文章页读标题。

能用强判据就绝不用弱判据。这一条听起来像废话,但人在疲惫的时候会下意识挑容易实现的那个。

形态三:副作用没被记录

前两类关注「这次成功没有」,第三类关注「这次留下了什么」。

发布脚本每跑一次,可能在三个地方留下痕迹:草稿箱多一篇、标签框多几个脏标签、剪贴板被覆盖。单个都不致命,堆起来会反噬——我有一次重跑同一篇稿子九次,草稿箱里躺了五份一模一样的拷贝,最后连「哪篇发过了」都判断不出来,只能靠记忆。

三、改法:把断言放到离原因最近的地方

针对上面三类,我做了三个改动。

改动一:每个动作都是「找 → 点 → 验」三步

原来一个动作就是「点一下」,现在拆成三步,第三步不过就抛异常:

asyncfunctionact(label,findJs,verifyJs,tries=3){for(leti=0;i<tries;i++){constpos=awaitev(findJs);if(!String(pos).startsWith('{')){console.log(label+' FIND_FAIL '+pos);continue;}constc=JSON.parse(pos);awaitclick(c.x,c.y);awaitsleep(3000);constst=awaitev(verifyJs);console.log(label+'#'+i+' '+st);if(st==='open')returntrue;}thrownewError('FATAL '+label+'_NOT_OPEN');}

同样的场景,输出变成:

PANEL#0 hidden PANEL#1 hidden PANEL#2 hidden FATAL PANEL_NOT_OPEN

一眼知道问题在第一步,不用去猜是不是标签没填、是不是限流。判据离原因越近,排查越便宜。放在最后,等于把所有中间故障归成同一个模糊的「失败」,再花四十分钟逐个排除。

改动二:删掉那些「退路」

上面说过,找不到面板内的按钮就退回全页面找,这条逻辑我删了。正确反应是报错,不是退而求其次。

类似的退路还有:接口超时就重试三次然后用旧值、元素没找到就跳过这一步。它们都有一个共同点:把失败转换成继续执行。健壮性应该体现在「失败了能安全地停」,而不是「失败了还能跑下去」。

改动三:把副作用打出来

脚本最后一步不再是打印SUCCESS,而是打印这次运行产生的副作用清单:

SIDE_EFFECTS {"drafts":1,"tags":3,"clipboard":"overwritten"}

哪怕清单是空的,也要打印。空清单本身是一个有价值的断言——它证明这次运行没有留下东西。

配合这条,我在脚本里加了一条规则:失败时把新建的草稿 ID 记进一个文件,不删,只标记。删是不可逆的,标记不是。等人清醒的时候再决定怎么清理,清理脚本还必须是 dry-run 优先。

四、一个反直觉的发现:事件触发是框架特定的

排查「点了没反应」的时候,我手上有个屡试不爽的兜底:直接调组件挂在 DOM 节点上的事件处理函数。React 会把onClick以__reactProps$xxxx的形式挂在真实 DOM 上,所以能从 DOM 反查到。

在另一个平台上,这个兜底救过发布按钮。但在出问题的这个平台上,它返回NO_FIBER,顺着父节点找六层都没有。

原因很简单:那个平台的编辑器是 Vue,不是 React。Vue 2 把组件实例挂在el.__vue__,Vue 3 走__vnode,事件在vnode.props.onClick里,从来不在 DOM 节点的自有属性上。用 React 的钥匙开 Vue 的锁,找六层当然找不到。

现在我所有「直接调事件」的兜底,前面都加一行框架探测:

Object.keys(document.body).filter(k=>/^__react|^__vue|^__vnode/.test(k));

先看页面上有什么,再决定用哪把钥匙。默认「所有站点都是 React」是我犯过最懒的错误。

顺带一个更小的发现:能不用坐标就不用坐标。有个平台的「确认」按钮rect.y是 1323,超出默认视口高度,CDP 的鼠标事件坐标超界之后被静默忽略——点了,没报错,没效果。我试着用scrollIntoView把它滚进视口,结果按钮反而被可见性过滤条件排除了。最后正解是把布局视口撑高(Emulation.setDeviceMetricsOverride),让按钮自然落在视口内。而在没有敏感词框的场景下,原生button.click()直接就打通了——JS click 优先于坐标,这是性价比最高的一枪。

五、收益与边界

这套改动落地之后,同一类故障的定位时间从大约四十分钟降到一分钟。更重要的是失败变得可描述了:以前是「稿子没发出去」,现在是「发布面板在 3 次尝试后仍未打开,已保留草稿 2100283」。

但它有边界,得说清楚。

第一,它不解决平台侧的不确定性。日限、敏感词、查重、审核队列,这些都在服务端,客户端只能观测到结果,观测不到原因。上面那个「发布面板打不开」的案例,真正原因直到看到弹框文本才明白——而那个弹框只在一瞬间出现。

第二,强判据不是永远拿得到。有些平台发布成功后不跳转、计数器有延迟、文章页有缓存。这时候只能退到中判据,并且要在注释里写清楚「这个判据为什么是弱的」,别让下一个人误以为它可信。

第三,可观测性是有成本的。每个动作加一次验证,脚本时长大约增加三成。对批量任务来说这是实打实的开销。我的取舍是:只给「失败后会导致不可逆后果」的动作加强断言,比如发布、删除、提交表单。读操作和中间态该省就省。

六、结语

回头看,这套流水线最有价值的改进不是某个巧妙的定位技巧,而是三个习惯:

  1. 动作之后立刻验证该动作的效果,不达标就停。
  2. 能对外可见的状态下结论,就不对界面文案下结论。
  3. 把「这次留下了什么」和「这次做成了什么」放在同等重要的位置。

浏览器自动化的难点从来不是「怎么点下去」,而是「怎么知道点下去之后发生了什么」。前者是 API 调用,后者才是工程。


本文所有案例来自一套真实运行的跨平台内容发布流水线,日志与判据均可复现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询