Chrome 扩展程序:测试 Service Worker 中止过程

Aga Czyżewska
Aga Czyżewska
Rowan Deysel
Rowan Deysel

这是怎么回事?

从 Manifest V2 到 Manifest V3 的过渡会发生根本性的变化。在 Manifest V2 中,扩展程序位于后台页面中。后台网页用于管理扩展程序与网页之间的通信。Manifest V3 改用 Service Worker。

在这篇博文中,我们深入探讨了测试扩展 Service Worker 的问题。特别是,我们研究了如何确保我们的产品在 Service Worker 被暂停时正常工作。

我们是谁?

eyeo 是一家致力于为用户、浏览器、广告客户和发布商提供平衡且可持续的在线价值交换的公司。我们在全球拥有超过 3 亿广告过滤用户,他们允许展示 Acceptable Ads,即一项独立广告标准,用于确定广告是否可接受且不具干扰性。

我们的 Extension Engine 团队负责提供广告过滤技术,为市场上一些最热门的广告拦截浏览器扩展程序(如 AdBlock 和 Adblock Plus)提供支持,在全球拥有 1.1 亿多用户。此外,我们还以开源库的形式提供这项技术,供其他广告过滤浏览器扩展程序使用。

什么是 Service Worker?

扩展程序 Service Worker 是浏览器扩展程序的中心事件处理脚本。它们在后台独立运行。一般来说,这没什么问题。在新 Service Worker 中,我们可以在后台页面上完成大部分需要执行的操作。但与后台网页相比,存在一些变化:

  • Service Worker 在不使用时终止。这需要我们保留应用状态,而不是依赖于全局变量。这意味着,我们系统的所有入口点都必须准备好在系统初始化之前被调用。
  • 在等待任何异步回调之前,必须先附加事件监听器。已暂停的 Service Worker 仍然可以接收其已订阅的事件。如果事件监听器未在事件循环的第一轮注册,则不会在该事件唤醒 Service Worker 时收到事件。
  • 闲置终止功能可能会在计时器完成之前使计时器遭到中断

Service Worker 何时暂停?

对于 Chrome 119,我们遇到的是 Service Worker 会被暂停:

  • 30 秒后未收到事件或调用扩展程序 API。
  • 如果开发者工具是打开的,或者您使用的是基于 ChromeDriver 的测试库(请参阅功能请求),则永远不会这样做。
  • 如果您在 chrome://serviceworker-internals 中点击 Stop

如需了解最新信息,请参阅 Service Worker 生命周期

为什么测试此问题会带来问题?

理想情况下,如果提供关于“如何高效测试 Service Worker”的官方指导或提供有效测试示例,会很有帮助。在测试 Service Worker 的过程中,我们遇到了一些挑战:

  • 我们的测试扩展程序中有状态。当 Service Worker 停止时,我们会丢失其状态和注册的事件。如何在测试流程中保留数据?
  • 如果 Service Worker 可能在任何时候被暂停,我们需要测试所有功能在中断后是否都能正常工作。
  • 即使我们在测试中引入了一种随机挂起 Service Worker 的机制,浏览器中也没有可用于轻松暂停 Service Worker 的 API。我们已要求 W3C 团队添加此功能,但这是一个持续的讨论。

测试 Service Worker 暂停

我们已尝试过几种在测试期间触发 Service Worker 暂停的方法:

方法 此方法存在的问题
等待任意时长(例如 30 秒) 这会使测试速度缓慢且不可靠,尤其是在运行多项测试时。在使用 WebDriver 时,WebDriver 无法正常工作,因为 WebDriver 使用 Chrome 的开发者工具 API,而且 Service Worker 在打开开发者工具时不会被暂停。即使我们可以绕过它,我们仍然必须检查 Service Worker 是否已暂停,我们没有办法做到这一点。
在 Service Worker 中运行无限循环 根据相关规范,这可能导致终止,具体取决于浏览器如何实现此功能。在这种情况下,Chrome 不会终止 Service Worker,因此我们不能测试 Service Worker 暂停时的情况。
在 Service Worker 中显示一条消息,用于检查其是否已被暂停 发送消息可唤醒 Service Worker。此方法可用于检查 Service Worker 是否处于休眠状态,但它会破坏需要在暂停 Service Worker 后立即执行检查的测试的结果。
使用 chrome.processes.terminate() 终止 Service Worker 进程 扩展程序的 Service Worker 会与扩展程序的其他部分共享进程,因此,使用 chrome.process.terminate() 或 Chrome 的进程管理器 GUI 可终止此进程,而不仅会终止该 Service Worker,还会终止所有扩展程序页面。

最后,我们进行了一项测试,该测试通过让 Selenium WebDriver 打开 chrome://serviceworker-internals/ 并点击 Service Worker 的“stop”按钮,来检查代码如何响应挂起的 Service Worker。

这是目前最好的选择,但目前并不理想,因为我们的 Mocha 测试(在扩展程序页面上运行) 无法自行执行此操作,因此它们需要传回我们的 WebDriver 节点程序。这意味着,这些测试无法仅使用扩展程序运行;必须使用 Selenium WebDriver 触发。

下图说明了我们如何通过不同的流与浏览器 API 进行通信,以及添加“挂起 Service Worker”机制对它的影响。

测试流程示意图
测试 Service Worker 挂起时的工作流。

在挂起 Service Worker(蓝色)的新流程中,我们添加了 Selenium WebDriver 以通过界面“点击”挂起,这会在浏览器 API 中触发操作。

值得一提的是,在 Chrome 错误下,使用 Selenium WebDriver 执行此操作会导致 Service Worker 无法重新启动。此问题已在 Chrome 116 中得到解决,幸运的是,还有一种权宜解决方法:将 Chrome 设置为在每个标签页上自动打开 DevTools,即可让 Service Worker 正确启动。

这是我们在测试时采用的方法,尽管这种方法并不理想,因为点击该按钮可能不是稳定的 API,并且打开开发者工具(对于旧版浏览器)似乎会降低性能。

如何涵盖全部功能?模糊测试

有了用于测试暂停的机制后,我们必须决定如何将其插入到自动化测试套件中。我们在以下环境中运行标准测试:在每次与后台页面交互之前,WebDriver 会点击 chrome://serviceworker-internals/ 页面上的 Stop,从而暂停 Service Worker

模糊测试运行示例
展示当前测试设置的图片。

我们会运行大多数(而非所有)测试,因为挂起机制并不完全稳定,有时还会导致不稳定。此外,在模糊模式下运行所有测试套件需要耗费大量时间。因此,我们没有涵盖所有“类似”情况,而是选择了最关键的路径,在模糊测试模式下进行测试。值得一提的是,在“模糊”模式下运行功能测试意味着我们必须增加测试的超时,因为暂停和重启 Service Worker 需要额外的时间。

这些测试作为粗粒度的首次通过检查,可以突出显示代码失败的许多地方,但不一定能发现 Service Worker 挂起可能导致故障的所有细微变化。

在内部,我们将这类测试称为“模糊测试”。传统上,模糊测试是指在程序中抛出无效输入,并确保其可以合理响应,或者至少不会崩溃。在我们的案例中,“无效输入”是指随时被暂停的 Service Worker,而我们预期的“合理行为”是指广告过滤功能必须继续像以前一样工作。这实际上并不是无效的输入,因为这是 Manifest V3 中的预期行为,但在 Manifest V2 中是无效的,所以它看起来是合理的术语。

摘要

Service Worker 是 Manifest V3 中最大的变化之一(除了 declarativeNetRequest 规则之外)。迁移到 Manifest V3 可能需要对浏览器扩展程序进行很多代码更改,并采用新的测试方法。它还要求具有持久状态的扩展程序的开发者准备其扩展程序,以便妥善处理意外的 Service Worker 暂停。

遗憾的是,目前没有 API 能够以适合我们用例的简单方式处理中止。由于我们想在早期阶段针对挂起机制测试扩展程序代码库的稳健性,因此必须着手解决。其他面临类似挑战的扩展程序开发者可以使用这种权宜解决方法,虽然这种方案在开发和维护阶段非常耗时,但值得一试,因为这样可以确保我们的扩展程序能够在定期暂停 Service Worker 的环境中成功运行。

尽管已经提供了对测试 Service Worker 挂起方法的基本支持,但通过扩展程序内为测试 Service Worker 提供了更好的平台支持,这是我们未来非常希望实现的,因为它可以大大减少我们的测试执行时间和维护工作量。