付费自动化里有一种很容易让人着急的场面:按钮已经点了,页面没有结果,余额也暂时没有变化。等待几秒以后,屏幕仍然停在原处。此时最直觉的反应,是再点一次,或者把等待时间从10秒改成1分钟。
但我最近遇到的情况,恰好说明这两种反应都可能把问题推向更危险的方向。真正的提交动作已经发生了,只是平台在提交之后弹出了确认框,执行器没有回答,所以生成流程没有继续。表面看起来像“点击没有生效”,实际上是“点击生效后还缺了一步”。
这件事让我重新意识到,付费自动化最重要的能力,不是遇到无结果时更积极,而是知道什么时候绝对不能再做一次。在可能产生扣费的流程里,没有证据不能证明失败,反而要先假设动作可能已经发生。
按钮点下去以后,事情并没有结束
当时是在带参考图的真实视频生成流程里复现问题。生成按钮只点击了一次,随后页面出现了提交确认对话框。因为执行器的后续逻辑只会等待几种生成证据,比如页面出现“生成中”、余额发生变化,或者出现新的结果,它没有识别这个对话框,更没有代替人点击确认。
于是,平台停在等待用户回答的状态,生成自然没有开始。轮询结束后,系统得到的只是“没有观察到提交证据”。这个结果很谨慎,却容易被人误读成“提交失败”。第1轮复现里没有扣费,反而让这种误读更有诱惑力:既然钱没有少,似乎再点一次也没关系。
可是在另一次真实运行里,网络延迟、页面刷新和余额显示都可能让证据晚一点出现。没有扣费只能说明这一次没有观察到扣费,不能证明重复提交永远安全。如果把一次未观察到证据直接翻译成一次确定失败,自动化就会在最不该冒险的地方替人做决定。
“没有看到结果”只是一个观察结论,不是“可以重新付费提交”的行动许可。
表面问题是弹窗,真正问题是状态边界
一开始,我也容易把它看成一个页面兼容问题:某个弹窗没有被处理,补一个点击就好了。后来把动作顺序拆开,才发现需要解决的不是“多点一个按钮”,而是要重新定义提交之后允许做什么。
提交前,执行器拥有一个明确动作:点击生成。这个动作可能消耗用量,因此只能执行一次。提交后,页面可能直接进入生成,也可能停在需要确认的中间状态,还可能因为网络或平台异常暂时没有显式反馈。这几种状态的共同点是:它们都发生在唯一提交动作之后,不能再回到“重新提交”这个分支。
这就是问题最容易被混淆的地方。确认弹窗不是第二次生成,它是第一次提交的延续;生成证据也不是按钮点击的同义词,而是平台已经接受并推进任务的可观察结果。只有把这几层状态分开,才不会用“没有结果”覆盖掉“已经点击过”的事实。
如果不处理这个边界,会导致两种相反的错误。一种是盲等:弹窗一直在前面,轮询永远拿不到生成证据,最后把本来可以继续的任务判为失败。另一种是盲重试:第一次提交的结果只是延迟,第二次点击却产生重复任务,甚至出现重复扣费。前者损失一次机会,后者可能损失真实用量。
最容易误判的两个捷径
错误捷径一,是把轮询时间简单拉长。等待更久只对“证据会迟到”有效,对“流程正在等待人工确认”无效。弹窗没有被回答时,页面不会因为时间经过就自动完成;延长轮询只是让失败更晚被发现,也让人更难判断究竟卡在哪里。
错误捷径二,是提交无结果就再次点击生成。这种做法看起来能提高成功率,因为第二次点击可能碰巧绕过了第一次遇到的页面状态。但它没有回答最关键的问题:第一次点击到底有没有被平台接收?只要这个问题没有证据,第二次付费动作就是不可逆的猜测。
我选择不增加自动重试,是因为这里的风险不是少跑一次,而是把一次不确定的状态变成两次确定的付费动作。宁可把一个需要人工排查的任务留在失败状态,也不能让执行器用重提交去掩盖证据不足。这个取舍看起来保守,却是付费系统应有的安全边界。
把提交拆成单击、确认和证据
修复后的流程不是把按钮点击改成“点击直到成功”,而是把唯一提交动作拆成3段。第1段是单击:找到可用的生成按钮,只点击一次,并立即记录已经发生过提交尝试。这个记录很重要,因为后续任何异常都不能再把流程送回提交入口。
第2段是短窗口确认探测。提交后不直接进入长时间轮询,而是在一个有限的时间窗口内检查已知的确认弹窗。当前需要识别2类场景:一类是询问是否在当天默认生成全部结果的弹窗,确认按钮是“是”;另一类是提示还有剩余内容待生成的弹窗,继续按钮是“继续生成”。
这里的关键不是“看见按钮就点”,而是按精确文案和弹窗语义定位。每一类弹窗都有自己的应答记账,同一提交生命周期内最多点击一次。这样做可以处理平台要求的确认,又不会因为页面重复渲染、状态抖动或轮询重入而连续点击确认。
第3段才是证据轮询。确认完成后,继续等待“生成中”、余额变化、新结果等可观察证据。观察到证据,才允许进入后续的结果处理;没有观察到证据,即使已经回答过弹窗,也只能 fail-closed。因为确认过弹窗不等于平台已经开始生成,确认动作本身仍然不是成功凭证。
安全的流程不是让每个状态都自动向前走,而是为每个不可逆动作规定唯一入口和证据出口。
测试要证明“不多点一次”
这次暴露问题还有一个原因:原来的单元测试使用了模拟对象,模拟会立即返回提交成功的证据。它验证了后面的轮询逻辑,却没有模拟真实页面在提交后停下来的情况。测试通过,并不代表真实的状态转移完整。
所以新增的状态机测试,重点不只是断言最终成功,而是覆盖4个边界。第1个边界是第1类确认弹窗,确认“是”后继续等待证据;第2个边界是第2类确认弹窗,点击“继续生成”后继续等待证据;第3个边界是没有弹窗,流程直接进入证据轮询,保证原有路径不被破坏;第4个边界是已经应答弹窗,但始终没有生成证据,此时必须失败关闭,且不能重新点击生成。
这4个测试共同约束了一件事:弹窗应答是提交后的补充动作,不是重新提交的机会。测试还要记录点击次数,而不是只检查最终状态。因为如果只断言“最后失败”或“最后成功”,一个隐藏的重复点击也可能被覆盖过去。
这里有一个值得保留的失败场景:确认框处理成功,但平台仍没有证据。它看起来不够圆满,却是最能检验安全边界的测试。一个成熟的自动化流程,不是把所有异常都变成成功,而是能在没有足够证据时停在可解释、可恢复的位置。
下次我会怎么做
下次遇到付费提交后没有结果,我会先问3个问题:唯一提交按钮是否已经点击过,页面是否出现过已知的确认状态,当前是否拿到了生成已经开始的证据。问题顺序不能反过来。只要第1个答案是“是”,就不会再回到提交入口。
然后,我会把流程固定成一条短规则:单击一次,短窗口探测确认;已知弹窗各应答一次;再进入证据轮询;没有证据就 fail-closed。弹窗的文案和按钮可以随着平台变化而更新,但这条顺序不应该被临时的成功压力改写。
我也会在测试里保留“无弹窗”和“应答后仍无证据”这两个场景。前者防止修复只适用于新问题,后者防止修复者为了让测试变绿而偷偷放宽失败边界。真正要留下的不是某个平台的两个按钮名称,而是“确认”和“成功证据”必须分开判断。
这件事让我重新意识到,自动化的可靠性不等于它总能把任务做完。很多时候,可靠性表现为它知道什么时候停止,知道哪些动作不能猜,知道一次点击之后该用观察来换取下一步。在付费流程里,最值得自动化的不是重复尝试,而是把“不确定时不再付费”执行得足够坚定。