浏览器自动化测试 Fixture 与隔离
用清晰的资源所有权、隔离状态、安全并行 worker 和确定性清理设计浏览器自动化 fixture。
可靠的浏览器自动化从一个资源集合小而明确的 fixture 开始。它可能创建浏览器上下文、页面、临时目录和合成应用数据,也必须说明谁关闭每个资源,以及断言失败时保留什么结果。这样维护者不必猜测其他测试留下了什么。
核心规则是每个测试接收已知输入,观察有限的用户可见结果,并释放自己创建的资源。Playwright 在测试 fixture 文档中描述了这种模型,Selenium 在测试实践中说明独立测试原则。可以参考Playwright 生命周期指南,并把 fixture 边界与服务端账户所有权分开。
先写出 fixture 契约
契约需要列出输入、输出和所有者。输入可以包括浏览器版本、上下文选项、获准的合成账户、预置记录和 worker 专用目录。输出应保持精简:结果类别、可见断言和清理收据。远程记录属于应用,fixture 不能承诺删除它。
让设置靠近需要它的作用域。每个测试一个上下文适合必须从干净状态开始的场景。每个 worker 一个浏览器也可以,但每个 worker 应创建自己的上下文和文件。全局页面可能让 cookie、service worker 或内存状态活得过久。作用域应在 fixture 名称中可见。
把浏览器所有权和应用所有权分开。fixture 可以打开页面并使用获准的登录流程,但应用决定会话如何结束,服务决定记录保留多久。关闭上下文会释放浏览器管理的状态,却不会撤销另一台设备的会话,也不会撤销远程变更。
记录场景标签、版本、fixture 作用域、合成输入标签和清理结果。日常日志不要写入 cookie、令牌、完整响应或个人页面文字。维护者应从结果元数据和可见断言判断契约是否满足,而不是查看整个会话副本。
选择不会意外共享的作用域
进程、浏览器、上下文、页面、worker 和测试是不同层次。一个浏览器可以承载多个上下文,上下文包含页面和浏览器存储,页面代表一个标签页。把上下文或页面显式传给 helper,不要让 helper 搜索全局页面。
当 cookie、本地存储、权限、service worker 或干净 origin 状态很重要时,创建新上下文。上下文隔离能阻止浏览器状态混用,但不是操作系统沙箱。应用仍可能写入共享服务,运行器仍可能共享环境变量或文件。每个外部资源都要有所有者和清理策略。
storageState 快照是有生命周期的输入,不是万能备份。它可能包含 cookie 和 origin 存储,却不包含内存、worker 队列、原生凭据或完整的服务端会话。把它当作敏感的合成工件。只读基线应复制到测试自己的路径,部分写入的文件要标记为无效。
Selenium profile 也遵循相同边界。profile 目录、driver 和浏览器二进制是一个兼容性输入。每个活动会话使用自己的路径,并通过正常生命周期关闭 driver。Selenium profile 集成指南进一步说明了所有权。profile 能重现声明的输入,不能决定服务是否接受请求。
为并行 worker 准备安全状态
并行 worker 共享状态文件、下载目录、合成账户或截图名称时容易失败。文件可能字节有效,却属于另一个 worker。给每个 worker 分配稳定的场景标签和批准工件根目录下的私有目录。
使用只读基线,并在旁边写入新状态。登录 fixture 可以加载基线,刷新合成会话,用 worker 和尝试编号保存结果。失败或部分结果应隔离或标记无效。不要替换其他上下文可能正在读取的基线。
干净浏览器不能阻止服务端数据竞争。优先使用独立的合成记录、应用支持的重置操作或只读场景。如果必须共享变更,就只对那一次操作做顺序化并记录所有者。增加等待时间不能修复数据竞争。
每个 worker 的观察应保持有限。记录 URL 类别、可见状态和简短错误名即可。获准的 trace 或截图应写入 worker 专属路径并遵循工件保留政策。文件名正确但内容属于别的 worker 时,fixture 结果仍然失败。
让清理确定且可重复
清理是正确性的一部分。在成功、断言失败、超时或设置错误后,都要通过 finally 执行清理。先关闭生命周期较短的页面,再关闭拥有它们的上下文。共享浏览器的所有者应在上下文结束后关闭浏览器;部署契约决定连接方是否只能断开连接。
如果清理也失败,要保留第一个错误。保存工作流错误,尝试清理,再附加关闭错误。只抛出关闭错误会隐藏断言原因,吞掉关闭错误则会留下开放会话和误导性的结果。
按照各自 API 关闭测试创建的下载、录制、文件句柄和临时目录。关闭上下文不会删除已经复制到工件存储的文件,也不会取消 worker 已发送的服务端变更。应用要求退出或取消时,应在页面仍可用时执行。
清理要能承受第二次调用。超时可能留下半初始化页面,运行器也可能在 fixture 已关闭后再次执行保护 hook。检查 handle 是否仍可用,只有仍由本 fixture 所有时才关闭。不要因为名称相似就关闭另一个上下文。
协调重试和证据
重试是新的测试尝试,不是继续使用未知页面。先判断失败操作是否只读,以及应用是否提供幂等或状态查询契约。允许重试时,关闭旧上下文,用相同的合成输入创建新上下文并记录尝试编号。后一次通过不能证明前一次没有影响。
把设置、应用、断言和基础设施失败分开。状态文件缺失属于 fixture;合成请求被拒绝可能属于应用或服务;driver 断开属于基础设施。等待可见标题超时能指出缺少的浏览器信号,但不能解释服务端决定。
诊断应命名条件,而不是倾倒会话。记录场景、浏览器和框架版本、URL 类别、预期标志和清理状态。合成页面的截图或 trace 可能有帮助,也可能包含秘密。限制访问和保留期限,删除本地副本无法收回已经分享的内容。
用一个不提供某项就绪信号的合成页面演练失败路径。预期结果是命名该信号的有限失败以及清理收据。不要为了让测试通过而访问无关 origin 或扩大数据收集。
在应用边界检查隔离
浏览器隔离为测试提供已知的客户端起点,但不保证服务端会话彼此独立,不保证服务接受 cookie,也不控制应用清理。测试可以验证新上下文没有预期本地状态、受保护路由遵守契约,以及下一次请求得到预期结果。
把账户切换作为状态转换测试。使用支持的退出流程,只清理应用明确说明的客户端状态,再用自己的获准状态创建第二个上下文。检查可见账户标题或访问结果。其他标签页、worker、离线队列或 service worker 可能仍显示旧状态。
fixture 不应涉及账户发现或私人信号采集。获准测试只需要已知账户、声明的路由和可观察结果。依赖不可用时,记录该类别并停在允许的边界。
浏览器或应用升级后,重新运行最有价值的案例:干净上下文、状态加载和过期、账户切换、worker 活动、并行文件输出以及失败关闭。比较用户可见结果并保留所测版本。
BotBrowser 的能力和限制
BotBrowser 提供 cookie、存储和会话状态分开的 BrowserContext,可让获准 fixture 重复合成工作流,并检查新上下文不会继承旧的客户端状态。BotBrowser 多账户隔离文档说明了这个浏览器侧边界。
BotBrowser 不替代 Playwright 或 Selenium 生命周期管理、应用清理、服务端会话失效处理或秘密管理。它不能保证过期 cookie 会被目标服务接受,不能删除 service worker 队列,也不能删除供应商记录。fixture 所有者仍负责提供合成输入并控制工件保留。
把这项能力当作一个声明的输入,而不是远程结果。记录浏览器版本、上下文用途、状态来源、worker 标签、预期清理和观察结果。把限制写在同一份记录中,避免把本地干净上下文误解为远程删除的证明。
实用评审会询问:每个测试是否拥有自己的可变状态,worker 是否可能覆盖别人的文件,每种失败是否走同一条清理路径,结果是否指出没有观察到的内容。这些答案能让 fixture 跨浏览器和框架维护。
如果测试使用预置服务响应,也要保持输入和输出的所有权。模拟器属于场景,使用合成数据,并与上下文一起重置状态。预置响应不能证明真实服务保存了相同数据。
工件目录同样需要所有者。worker 可以写入结果,但 CI 决定谁能读取以及何时过期。把简短收据和更敏感的截图或 trace 分开,使评审不必打开不必要的内容。
fixture 修复应解决原因,而不只是遮住症状。两个案例错误共享记录时,应拆分记录或明确共享操作。清理失败时,把错误保留在输出中并交给运行器所有者处理。
场景文档应与执行代码一致。浏览器、框架、状态模式或服务变化后重新检查作用域。短而更新的契约能避免未来 fixture 把临时路径当作公共资源。
合并 fixture 修改前,可以用同一份清理收据运行干净上下文、过期状态和强制清理失败三个案例。比较 worker 标签、资源作用域、可见断言和最终清理状态。
BotBrowser 支持为每个测试上下文提供一致的浏览器配置;应用保存在自身服务中的数据仍需由应用所有者负责清理。