Chrome 扩展程序
介绍
🌐 Introduction
扩展程序只有在使用持久上下文启动时才能在 Chromium 中工作。自行使用自定义浏览器参数需谨慎,因为某些参数可能会破坏 Playwright 的功能。
🌐 Extensions only work in Chromium when launched with a persistent context. Use custom browser args at your own risk, as some of them may break Playwright functionality.
Google Chrome 和 Microsoft Edge 移除了侧载扩展所需的命令行标志,因此请使用随 Playwright 打包的 Chromium。:::
🌐 Google Chrome and Microsoft Edge removed the command-line flags needed to side-load extensions, so use Chromium that comes bundled with Playwright.
下面的代码片段检索位于 ./my-extension 的 Manifest v3 扩展的 服务工作进程。
🌐 The snippet below retrieves the service worker of a Manifest v3 extension whose source is located in ./my-extension.
注意使用 chromium 通道,这允许以无头模式运行扩展程序。或者,你也可以以有头模式启动浏览器。
🌐 Note the use of the chromium channel that allows to run extensions in headless mode. Alternatively, you can launch the browser in headed mode.
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');
// Test the service worker as you would any other worker.
await browserContext.close();
})();
服务工作者空闲挂起(MV3)
🌐 Service worker idle suspension (MV3)
Chrome MV3 服务工作者在大约 30 秒不活动后会自动暂停,并按需重新启动。当这种情况发生时,Playwright 会保持 同一个 Worker 对象存活——不会发出新的 'serviceworker' 事件。在重启窗口期间发出的新的 evaluate() 调用会被阻塞,直到新的上下文准备好,然后自动恢复:
🌐 Chrome MV3 service workers are automatically suspended after ~30 seconds of inactivity and restarted on demand. When this happens, Playwright keeps the same Worker object alive — no new 'serviceworker' event is emitted. New evaluate() calls issued during the restart window are stalled until the new context is ready and then resume automatically:
const sw = await context.waitForEvent('serviceworker');
// ... SW suspends after 30 s of inactivity and is restarted by the browser ...
// The existing handle is transparent across the restart.
await sw.evaluate(() => sendMessage({ type: 'ping' })); // just works
在暂停的准确时刻已经在进行中的 evaluate() 调用将抛出 "Service worker restarted",这与正在进行中的页面导航的行为相匹配。
测试
🌐 Testing
在运行测试时要加载扩展程序,可以使用测试夹具来设置上下文。你也可以动态获取扩展 ID,并使用它来加载并测试弹出页面,例如。
🌐 To have the extension loaded when running tests you can use a test fixture to set the context. You can also dynamically retrieve the extension id and use it to load and test the popup page for example.
注意使用 chromium 通道,这允许以无头模式运行扩展程序。或者,你也可以以有头模式启动浏览器。
🌐 Note the use of the chromium channel that allows to run extensions in headless mode. Alternatively, you can launch the browser in headed mode.
首先,添加将加载扩展的装置:
🌐 First, add fixtures that will load the extension:
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) => {
// for 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;
然后在测试中使用这些装置:
🌐 Then use these fixtures in a test:
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');
});