跳转到主要内容

Chrome 扩展

简介

注意

扩展程序仅在以持久上下文(persistent context)启动时才能在 Chromium 中工作。使用自定义浏览器参数需自行承担风险,因为其中一些可能会破坏 Playwright 的功能。

Google Chrome 和 Microsoft Edge 移除了侧载(side-load)扩展程序所需的命令行标志,因此请使用 Playwright 捆绑的 Chromium。

下面的代码片段获取了源文件位于 ./my-extensionManifest v3 扩展程序的 Service Worker

注意使用了 chromium 通道,它允许在无头模式(headless mode)下运行扩展程序。或者,您也可以以有头模式(headed mode)启动浏览器。

from playwright.sync_api import sync_playwright, Playwright

path_to_extension = "./my-extension"
user_data_dir = "/tmp/test-user-data-dir"


def run(playwright: Playwright):
context = playwright.chromium.launch_persistent_context(
user_data_dir,
channel="chromium",
args=[
f"--disable-extensions-except={path_to_extension}",
f"--load-extension={path_to_extension}",
],
)
if len(context.service_workers) == 0:
service_worker = context.wait_for_event('serviceworker')
else:
service_worker = context.service_workers[0]

# Test the service worker as you would any other worker.
context.close()


with sync_playwright() as playwright:
run(playwright)

Service Worker 空闲挂起 (MV3)

Chrome MV3 Service Worker 在不活动约 30 秒后会自动挂起,并按需重启。当这种情况发生时,Playwright 会保持相同的 Worker 对象存活——不会触发新的 'serviceworker' 事件。在重启窗口期间发出的新的 evaluate() 调用会处于停滞状态,直到新上下文准备就绪,然后自动恢复。

sw = context.wait_for_event('serviceworker')

# ... SW suspends after 30 s of inactivity and is restarted by the browser ...

# The existing handle is transparent across the restart.
sw.evaluate("sendMessage({ type: 'ping' })") # just works
注意

在挂起瞬间已经处于运行中的 evaluate() 调用会抛出 "Service worker restarted" 错误,这与页面导航中途被打断的行为一致。

测试

要在运行测试时加载扩展程序,您可以使用测试夹具(test fixture)来设置上下文。您还可以动态获取扩展程序 ID,并使用它来加载和测试弹出页面(popup page)等。

注意使用了 chromium 通道,它允许在无头模式(headless mode)下运行扩展程序。或者,您也可以以有头模式(headed mode)启动浏览器。

首先,添加将加载扩展程序的夹具

conftest.py
from typing import Generator
from pathlib import Path
from playwright.sync_api import Playwright, BrowserContext
import pytest


@pytest.fixture()
def context(playwright: Playwright) -> Generator[BrowserContext, None, None]:
path_to_extension = Path(__file__).parent.joinpath("my-extension")
context = playwright.chromium.launch_persistent_context(
"",
channel="chromium",
args=[
f"--disable-extensions-except={path_to_extension}",
f"--load-extension={path_to_extension}",
],
)
yield context
context.close()


@pytest.fixture()
def extension_id(context) -> Generator[str, None, None]:
# for manifest v3:
service_worker = context.service_workers[0]
if not service_worker:
service_worker = context.wait_for_event("serviceworker")

extension_id = service_worker.url.split("/")[2]
yield extension_id

然后在测试中使用这些夹具

test_foo.py
from playwright.sync_api import expect, Page


def test_example_test(page: Page) -> None:
page.goto("https://example.com")
expect(page.locator("body")).to_contain_text("Changed by my-extension")


def test_popup_page(page: Page, extension_id: str) -> None:
page.goto(f"chrome-extension://{extension_id}/popup.html")
expect(page.locator("body")).to_have_text("my-extension popup")