使用 Puppeteer 测试 Service Worker 的终止情况

本指南介绍了如何通过测试服务构建更强大的扩展程序 使用 Puppeteer 终止工作器。已做好随时处理账号终止的准备 时间很重要,因为这可能会在没有警告的情况下 发生,导致 Service Worker。因此,扩展程序必须保存重要的状态和 如果存在以下情况,则能够在再次启动后立即处理请求: 事件进行处理。

前期准备

克隆或下载 chrome-extensions-samples 代码库。 我们将在 /functional-samples/tutorial.terminate-sw/test-extension:用于发送消息 向 Service Worker 发送一个按钮并将文本添加到页面 如果收到响应。

您还需要安装 Node.JS,这是 Puppeteer 的构建运行时。

第 1 步:启动 Node.js 项目

在新目录中创建以下文件。它们共同创造了 Node.js 项目并提供 Puppeteer 测试套件的基本结构 使用 Jest 作为测试运行程序。如需详细了解此设置,请参阅使用 Puppeteer 测试 Chrome 扩展程序

package.json

{
  "name": "puppeteer-demo",
  "version": "1.0",
  "dependencies": {
    "jest": "^29.7.0",
    "puppeteer": "^22.1.0"
  },
  "scripts": {
    "start": "jest ."
  },
  "devDependencies": {
    "@jest/globals": "^29.7.0"
  }
}

index.test.js:

const puppeteer = require('puppeteer');

const SAMPLES_REPO_PATH = 'PATH_TO_SAMPLES_REPOSITORY';
const EXTENSION_PATH = `${SAMPLES_REPO_PATH}/functional-samples/tutorial.terminate-sw/test-extension`;
const EXTENSION_ID = 'gjgkofgpcmpfpggbgjgdfaaifcmoklbl';

let browser;

beforeEach(async () => {
  browser = await puppeteer.launch({
    // Set to 'new' to hide Chrome if running as part of an automated build.
    headless: false,
    args: [
      `--disable-extensions-except=${EXTENSION_PATH}`,
      `--load-extension=${EXTENSION_PATH}`
    ]
  });
});

afterEach(async () => {
  await browser.close();
  browser = undefined;
});

请注意,我们的测试会从示例代码库加载 test-extensionchrome.runtime.onMessage 的处理脚本依赖于 chrome.runtime.onInstalled 事件处理脚本中设置的状态。因此,当服务工件终止时,data 的内容将丢失,并且无法响应任何未来的消息。我们会在编写测试后解决此问题。

service-worker-broken.js:

let data;

chrome.runtime.onInstalled.addListener(() => {
  data = { version: chrome.runtime.getManifest().version };
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  sendResponse(data.version);
});

第 2 步:安装依赖项

运行 npm install 以安装所需的依赖项。

第 3 步:编写基本测试

将以下测试添加到 index.test.js 的底部。开始测试 页面中,点击按钮元素并等待响应 从 Service Worker 加载。

test('can message service worker', async () => {
  const page = await browser.newPage();
  await page.goto(`chrome-extension://${EXTENSION_ID}/page.html`);

  // Message without terminating service worker
  await page.click('button');
  await page.waitForSelector('#response-0');
});

您可以使用 npm start 运行测试,并且应该会看到测试成功完成。

第 4 步:终止 Service Worker

添加以下可终止 Service Worker 的辅助函数:

/**
 * Stops the service worker associated with a given extension ID. This is done
 * by creating a new Chrome DevTools Protocol session, finding the target ID
 * associated with the worker and running the Target.closeTarget command.
 *
 * @param {Page} browser Browser instance
 * @param {string} extensionId Extension ID of worker to terminate
 */
async function stopServiceWorker(browser, extensionId) {
  const host = `chrome-extension://${extensionId}`;

  const target = await browser.waitForTarget((t) => {
    return t.type() === 'service_worker' && t.url().startsWith(host);
  });

  const worker = await target.worker();
  await worker.close();
}

最后,使用以下代码更新您的测试。现在,终止服务工作器,然后再次点击该按钮,检查您是否收到了响应。

test('can message service worker when terminated', async () => {
  const page = await browser.newPage();
  await page.goto(`chrome-extension://${EXTENSION_ID}/page.html`);

  // Message without terminating service worker
  await page.click('button');
  await page.waitForSelector('#response-0');

  // Terminate service worker
  await stopServiceWorker(page, EXTENSION_ID);

  // Try to send another message
  await page.click('button');
  await page.waitForSelector('#response-1');
});

第 5 步:运行测试

运行 npm start。您的测试应该会失败,这表示服务工件在终止后未响应。

第 6 步:修复 Service Worker

接下来,通过取消对临时状态的依赖来修复 Service Worker。更新 测试扩展程序以使用存储在 代码库中的 service-worker-fixed.js

service-worker-fixed.js

chrome.runtime.onInstalled.addListener(() => {
  chrome.storage.local.set({ version: chrome.runtime.getManifest().version });
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  chrome.storage.local.get('version').then((data) => {
    sendResponse(data.version);
  });
  return true;
});

在这里,我们将版本保存到 chrome.storage.local,而不是全局变量 在 Service Worker 的生命周期之间保留状态。由于只能异步访问存储空间,因此我们还会从 onMessage 监听器返回 true,以确保 sendResponse 回调保持活跃状态。

第 7 步:再次运行测试

使用 npm start 再次运行测试。现在它应该会通过。

后续步骤

现在,您可以将相同的方法应用于自己的扩展程序。请考虑以下事项:

  • 构建测试套件,以支持在有或没有意外服务 worker 终止的情况下运行。然后,您可以单独运行这两种模式, 以及导致失败的原因。
  • 编写代码以在测试中的随机点终止 Service Worker。 这是一种发现可能难以预测的问题的好方法。
  • 从测试失败中汲取经验,并在将来尝试进行防御性编码。例如,添加 lint 规则以禁止使用全局变量,并尝试将数据移至更持久的状态。