一个弹窗挡了我两天:阻塞式交互的代价
一个协议更新弹窗,把我的开发环境挡住了整整两天。
不是技术难题,也没有报错——只是每次打开"开发者空间",页面都会弹出一个「协议更新提醒」,勾选框 + 一个「进入」按钮。点勾选框,没反应;点「进入」,页面纹丝不动。于是我以为环境坏了,转头去干别的。
两天后我才发现:环境一直是好的,被挡住的是入口。
一、这件事的技术原因(顺带记一下)
我一开始用的是脚本里的合成点击:
el.dispatchEvent(new MouseEvent('click', { bubbles: true }))
对这种自定义勾选框无效。换成真实输入事件(Playwright 的 click,底层是 CDP 的 Input.dispatchMouseEvent)后,一次就通过了:
await page.click('.eco-checkbox');
await page.click('#protocol-submit-btn');
区别在于事件是否"可信"(isTrusted)。合成事件只是通知监听器"有人点了我",真实事件才会触发浏览器默认行为。很多框架、自定义组件、以及依赖默认行为的交互(勾选、拖拽、文件选择、全屏、剪贴板),只认真实事件。
技术上的结论一句话:自动化里点不动的东西,先怀疑事件类型,再怀疑选择器。
二、更值得说的是产品侧:阻塞式交互的代价
这个弹窗的产品意图完全合理——协议更新了,必须让用户知晓并同意,这是合规要求。但它的实现方式带来了一连串代价,两天里我至少踩了三次:
- 用户以为产品坏了。没有报错、没有提示"请先勾选",只有一个不动的按钮。我第一反应是"环境挂了"。
- 修复者找不到原因。因为页面看起来是"加载完成"的,只是主功能没出现。
- 自动化全线失效。所有依赖"进入环境"的流程(打卡、任务、构建)全部静默失败。
- 合规目的其实没达成。我点了两天「进入」,其实一个字都没读。
三、如果我来改,会做四件事
第一,明确告诉用户缺什么。
点「进入」但没勾选时,不该毫无反应,而应该:
- 把勾选框描红 + 轻微抖动
- 在按钮旁边出现一行字:「请先勾选同意上述协议」
一行提示能省掉用户 10 分钟的困惑。沉默是最贵的错误处理。
第二,把"必须同意"降到最低摩擦。
比如把协议做成可滚动的抽屉,“滚动到底部自动可勾选”;或者提供一个"仅同意核心条款"的选项。合规要的是"用户知情并同意",不是"用户被卡住"。
第三,给强阻塞加"逃生通道"。
任何全屏阻塞层都该有:明确的关闭按钮(哪怕是"暂不更新,退出")、以及说明"不同意的后果是什么"(是功能受限,还是完全不能用)。
用户需要知道代价,才能做出真实的选择。
第四,给合法自动化留白名单。
如果产品里有官方/半官方的自动化场景(企业内部脚本、测试环境、厂商自己的巡检),应该提供:
- 一个稳定的可编程入口(API 或带签名的 URL)
- 或者对已认证的会话记住同意状态,不必每次重建环境都重签
我这次的根因就是:每次创建新容器 = 全新会话 = 重新弹窗,而我的脚本没有任何办法通过它。
四、这件事给我的三个自查问题
做完这次排查,我把这三个问题加进了自己的检查清单:
- "打不开"和"没进去"要分开判断。 页面有内容 ≠ 主流程可用。我现在会专门确认一个"主流程标志物"(比如环境剩余时长、工作区节点),而不是只看页面加载完。
- 静默失败要当成缺陷处理。 如果一次点击没有任何反馈,不要假设"可能成功了"。把它当作失败,去找反馈。
- 阻塞层是自动化的一级风险。 任何模态框、协议页、引导页,都应该被显式识别并处理,而不是等它自己消失。
五、小结
这两天最荒诞的地方在于:挡住我的不是 bug,是一个正确的产品决策,配上了一个不够好的交互实现。
技术上,我记住了一句话:合成事件能骗过监听器,骗不过浏览器。
产品上,我记住了一句话:任何阻塞用户的界面,都必须同时提供"为什么被阻塞"和"怎么解除阻塞"。
否则用户会得出和你预期完全相反的结论——就像我这两天以为的那样:这东西坏了。
- 点赞
- 收藏
- 关注作者
评论(0)