本指南介绍了如何通过使用 Puppeteer 测试 Service Worker 的终止来构建更强大的扩展程序。准备好随时应对终止情况非常重要,因为这可能会在没有警告的情况下发生,导致 Service Worker 中的任何非持久性状态丢失。因此,在有要处理的事件时,扩展程序必须保存重要状态,并且能够在再次启动后立即处理请求。
前期准备
克隆或下载 chrome-extensions-samples 代码库。
我们将在 /functional-samples/tutorial.terminate-sw/test-extension
中使用该测试扩展程序,该扩展程序会在用户点击按钮时向 Service Worker 发送消息,并在收到响应时向页面添加文本。
您还需要安装 Node.JS,这是 Puppeteer 所基于的运行时。
第 1 步:启动 Node.js 项目
在新目录中创建以下文件。二者一起创建一个新的 Node.js 项目,并提供使用 Jest 作为测试运行程序的 Puppeteer 测试套件的基本结构。如需详细了解此设置,请参阅使用 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-extension
。chrome.runtime.onMessage
的处理程序依赖于在 chrome.runtime.onInstalled
事件的处理程序中设置的状态。因此,当 Service Worker 终止并且对将来的任何消息的响应失败时,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();
}
最后,使用以下代码更新您的测试。现在终止 Service Worker,并再次点击该按钮以检查您是否收到响应。
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
。测试应该会失败,这表示 Service Worker 在终止后没有响应。
第 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
再次运行测试。现在应该会通过。
后续步骤
现在,您可以将相同的方法应用于自己的扩展程序。请考虑以下事项:
- 构建测试套件,以支持无论 Service Worker 是否意外终止时都能正常运行。然后,您可以分别运行这两种模式,以便更清楚地了解导致故障的原因。
- 编写代码以在测试中的随机点终止 Service Worker。 此方法有助于发现可能难以预测的问题。
- 从测试失败中汲取经验,在未来尝试防御性编码。例如,添加 lint 规则以阻止使用全局变量,并尝试将数据移至更持久的状态。