Playwright 自动化任务中的状态确认与幂等设计实践
在使用 Playwright 编写浏览器自动化任务时,一个常见误区是把“按钮点击成功”当成“业务已经完成”。页面跳转、接口受理、后台处理和最终生效是不同状态。如果脚本只负责点击,不读取业务状态,就很难区分真正成功、重复执行和静默失败。
最近改造一套定时任务时,我遇到了几类典型问题:页面已经打开但业务动作没有触发、心跳间隔超限导致进度清零、接口已受理但结果延迟同步,以及调度器把成功退出码误判为失败。本文整理这次改造中的通用做法。
一、把执行和验证拆成两个阶段
自动化流程至少应包含两个独立阶段:
- 执行阶段:打开页面、填写内容或调用业务接口。
- 验证阶段:重新查询任务状态,确认服务端记录已经改变。
执行成功只能说明请求已经发出。验证阶段不应复用执行函数返回的缓存结果,而应通过独立的查询接口或详情页重新读取状态。对于每日任务,可以用业务日期和任务标识组成幂等键;执行前发现已经完成时,直接返回 already_done,避免重复操作。
二、心跳任务要处理旋转令牌
有些需要累计在线时长的业务,会先创建会话,再要求客户端按固定间隔发送心跳。服务端除了检查累计时长,还可能限制相邻心跳的最大间隔,并在每次响应中返回新的会话令牌。
如果客户端始终使用第一次拿到的令牌,令牌中保存的时间信息就可能过期,进而造成进度重置。通用处理方式是:每次心跳后同时更新累计进度和下一次要使用的令牌。
session_id = start_session()["session_id"]
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
time.sleep(heartbeat_interval)
result = heartbeat(session_id)
session_id = result.get("session_id", session_id)
if result.get("completed"):
break
心跳间隔应小于服务端允许的最大间隔,并给页面操作、网络抖动和接口耗时留出余量。例如最大间隔为 15 秒时,可以从 5 秒开始测试。不要通过缩短到毫秒级来追求速度,这只会增加无效请求。
三、区分“已受理”和“已完成”
异步业务常见的结果是:请求返回成功,但任务状态仍然是未完成。这不一定表示失败,也可能只是后台尚未同步。
因此结果模型最好明确区分以下状态:
accepted:业务请求已被受理。confirmed:独立查询确认已经完成。pending:仍在等待后台处理。failed:明确失败,并保存可诊断的错误信息。
对 pending 可以使用有上限的延迟复查,例如按 30 秒、60 秒、120 秒间隔查询。超过时限后保留待确认状态,而不是无限重试,更不能把受理成功直接写成任务完成。
四、单个任务失败不应阻断整批任务
批量自动化中,不同任务通常彼此独立。如果第一个任务登录失效、页面结构变化或接口超时,就直接退出整个循环,后续本来可以成功的任务也会被跳过。
更稳妥的结构是为每个任务单独捕获异常,记录任务名、失败阶段和错误摘要,然后继续执行下一项。批处理结束后再汇总成功、失败和待确认数量。若多个任务共享同一个登录态,登录失效可以作为全局停止条件,避免无意义的连续请求。
五、统一命令行与调度器的结果语义
这次改造还遇到一个很小但隐蔽的问题:命令行函数用 0 表示成功、非零表示失败,调度器却把它当成字典并读取 ok 字段。结果是任务实际成功,面板仍显示失败。
可以统一让内部函数返回结构化结果,只在命令行入口转换退出码;如果暂时保留旧接口,调度器就必须正确处理两种返回类型。无论采用哪种方式,都应通过真实的调度入口做一次端到端验证,而不能只直接调用任务函数。
六、凭证和公开内容需要额外边界
浏览器持久化目录、Cookie、访问令牌和临时密钥都不应写入日志或文章。自动化使用的账号应遵循最小权限原则,涉及完整云账号权限的授权弹窗不能仅凭脚本默认接受。
如果自动化流程会发布公开内容,还应把内容质量作为前置条件:基于真实经验生成,检查事实、代码和敏感信息,并保留可追溯的发布记录。自动化可以减少重复操作,但不应替代内容审核。
总结
可靠的自动化不是简单地让脚本运行结束,而是让每个步骤都有明确、可查询的结果。执行与验证分离、正确维护会话令牌、区分异步状态、隔离单任务失败,并统一调度结果语义,能够显著提高定时任务的可维护性。最后,通过真实入口验证一轮完整流程,往往比增加更多重试更有价值。
- 点赞
- 收藏
- 关注作者
评论(0)