Skip to content

介绍如何在持久化上下文中加载和测试 Chrome 扩展。

Google Chrome 和 Microsoft Edge 已经移除了侧载扩展所需的命令行标志,因此请使用 Playwright 随附的 Chromium。

下面的代码片段会获取一个 Manifest v3 扩展的 service worker。该扩展的源代码位于 ./my-extension

请注意这里使用了 chromium channel,它允许在无头模式下运行扩展。也可以改为以有头模式启动浏览器。

const { chromium } = require('playwright');
(async () => {
const pathToExtension = require('path').join(__dirname, 'my-extension');
const userDataDir = '/tmp/test-user-data-dir';
const browserContext = await chromium.launchPersistentContext(userDataDir, {
channel: 'chromium',
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`
]
});
let [serviceWorker] = browserContext.serviceWorkers();
if (!serviceWorker)
serviceWorker = await browserContext.waitForEvent('serviceworker');
// 像测试任何其他 worker 一样测试这个 service worker。
await browserContext.close();
})();

Chrome MV3 service worker 会在大约 30 秒无活动后自动挂起,并在需要时重新启动。发生这种情况时,Playwright 会保持同一个 Worker 对象处于可用状态,不会触发新的 'serviceworker' 事件。

在重启窗口期间发起的新的 evaluate() 调用会暂停,直到新的上下文准备就绪,然后自动继续执行:

const sw = await context.waitForEvent('serviceworker');
// ... SW 在 30 秒无活动后挂起,并由浏览器重新启动 ...
// 现有句柄可以透明地跨越重启过程。
await sw.evaluate(() => sendMessage({ type: 'ping' })); // 可以正常工作

为了在运行测试时加载扩展,可以使用测试 fixture 来设置上下文。也可以动态获取扩展 ID,并使用它加载和测试扩展的弹出页面等。

请注意这里使用了 chromium channel,它允许在无头模式下运行扩展。也可以改为以有头模式启动浏览器。

首先,添加用于加载扩展的 fixture:

fixtures.ts
import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
context: async ({ }, use) => {
const pathToExtension = path.join(__dirname, 'my-extension');
const context = await chromium.launchPersistentContext('', {
channel: 'chromium',
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});
await use(context);
await context.close();
},
extensionId: async ({ context }, use) => {
// 对于 manifest v3:
let [serviceWorker] = context.serviceWorkers();
if (!serviceWorker)
serviceWorker = await context.waitForEvent('serviceworker');
const extensionId = serviceWorker.url().split('/')[2];
await use(extensionId);
},
});
export const expect = test.expect;

然后在测试中使用这些 fixture:

import { test, expect } from './fixtures';
test('example test', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.locator('body')).toHaveText('Changed by my-extension');
});
test('popup page', async ({ page, extensionId }) => {
await page.goto(`chrome-extension://${extensionId}/popup.html`);
await expect(page.locator('body')).toHaveText('my-extension popup');
});
-
0:000:00