跳到主要内容

Playwright for Python:Chrome 扩展使用指南

Playwright 仅支持在 Chromium 浏览器中加载和测试 Chrome 扩展,且需通过持久化上下文(persistent context)启动。需注意:Google Chrome 和 Microsoft Edge 已移除侧载扩展的命令行标志,建议使用 Playwright 捆绑的 Chromium 浏览器。

核心前提

  1. 仅支持 Chromium 内核浏览器,Firefox、WebKit 不支持扩展。
  2. 必须使用 launch_persistent_context 启动持久化上下文,普通上下文无法加载扩展。
  3. 支持 Manifest V2 和 V3 版本扩展,Manifest V3 需通过 Service Worker 管理扩展逻辑。
  4. 可通过 channel="chromium" 启用无头模式加载扩展,或直接以有头模式运行。

快速示例:加载并获取扩展 Service Worker

同步示例

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}",
],
)

# 获取扩展的 Service Worker(Manifest V3 核心)
if len(context.service_workers) == 0:
# 等待 Service Worker 启动
service_worker = context.wait_for_event("serviceworker")
else:
service_worker = context.service_workers[0]

# 测试 Service Worker(如执行扩展逻辑、监听事件等)
print("扩展 Service Worker URL:", service_worker.url)

# 关闭上下文
context.close()

with sync_playwright() as playwright:
run(playwright)

异步示例

import asyncio
from playwright.async_api import async_playwright, Playwright

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

async def run(playwright: Playwright):
# 启动持久化上下文并加载扩展
context = await playwright.chromium.launch_persistent_context(
user_data_dir,
channel="chromium",
args=[
f"--disable-extensions-except={path_to_extension}",
f"--load-extension={path_to_extension}",
],
)

# 获取扩展 Service Worker
if len(context.service_workers) == 0:
service_worker = await context.wait_for_event("serviceworker")
else:
service_worker = context.service_workers[0]

# 扩展测试逻辑
print("扩展 Service Worker 状态:", service_worker.state)

# 关闭上下文
await context.close()

async def main():
async with async_playwright() as playwright:
await run(playwright)

asyncio.run(main())

测试扩展:Pytest fixtures 配置

通过 Pytest 夹具(fixture)统一配置扩展加载,实现测试用例复用,支持测试扩展功能和弹窗页面。

步骤 1:创建 fixtures(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: BrowserContext) -> Generator[str, None, None]:
# 从 Service Worker URL 中提取扩展 ID(Manifest V3)
if len(context.service_workers) == 0:
service_worker = context.wait_for_event("serviceworker")
else:
service_worker = context.service_workers[0]

# 扩展 ID 格式:chrome-extension://{extension_id}/...
extension_id = service_worker.url.split("/")[2]
yield extension_id

步骤 2:编写扩展测试用例(test_foo.py)

from playwright.sync_api import expect, Page

def test_extension_modifies_page(page: Page):
"""测试扩展是否修改目标页面内容"""
page.goto("https://example.com")
# 断言扩展已修改页面(根据扩展实际功能调整)
expect(page.locator("body")).to_contain_text("Changed by my-extension")

def test_extension_popup(page: Page, extension_id: str):
"""测试扩展弹窗页面是否正常加载"""
# 访问扩展弹窗页面(URL 格式固定)
page.goto(f"chrome-extension://{extension_id}/popup.html")
# 断言弹窗内容
expect(page.locator("body")).to_have_text("my-extension popup")

关键配置说明

1. 扩展加载参数

参数用途
--load-extension={path}加载指定路径的扩展(支持多个扩展,用逗号分隔)
--disable-extensions-except={path}仅启用指定扩展,屏蔽浏览器中其他已安装扩展
channel="chromium"启用 Playwright 捆绑的 Chromium 通道,支持无头模式加载扩展

2. 持久化上下文

  • 必须使用 launch_persistent_context 而非 launch,否则扩展无法持久化加载。
  • user_data_dir 用于保存扩展状态(如配置、登录信息),为空字符串时自动生成临时目录。

3. 扩展 ID 获取

  • Manifest V3:从 Service Worker URL 中提取(格式:chrome-extension://{extension_id}/service-worker.js)。
  • Manifest V2:可通过 context.pages 遍历扩展页面,或从扩展管理页面(chrome://extensions)获取。

常见场景实战

场景 1:测试扩展对网页的修改

# 同步
def test_extension_injects_script(page: Page):
page.goto("https://example.com")
# 验证扩展注入的脚本是否生效(如添加自定义 DOM 元素)
expect(page.locator("#extension-injected-element")).to_be_visible()

# 异步
async def test_extension_injects_script(page: Page):
await page.goto("https://example.com")
await expect(page.locator("#extension-injected-element")).to_be_visible()

场景 2:模拟点击扩展图标打开弹窗

# 同步
def test_open_extension_popup(page: Page, extension_id: str):
# 直接访问弹窗页面(替代点击图标,更稳定)
page.goto(f"chrome-extension://{extension_id}/popup.html")
# 操作弹窗中的元素(如输入、点击按钮)
page.locator("input#username").fill("test-user")
page.locator("button#submit").click()
expect(page.locator("#result")).to_have_text("Success")

# 异步
async def test_open_extension_popup(page: Page, extension_id: str):
await page.goto(f"chrome-extension://{extension_id}/popup.html")
await page.locator("input#username").fill("test-user")
await page.locator("button#submit").click()
await expect(page.locator("#result")).to_have_text("Success")

场景 3:调试扩展 Service Worker

# 同步
def test_service_worker_logic(context: BrowserContext):
service_worker = context.service_workers[0]
# 执行 Service Worker 中的函数
result = service_worker.evaluate("() => myExtensionFunction()")
print("扩展函数执行结果:", result)
# 监听 Service Worker 事件
def on_message(message):
print("Service Worker 消息:", message)
service_worker.on("message", on_message)

# 异步
async def test_service_worker_logic(context: BrowserContext):
service_worker = context.service_workers[0]
result = await service_worker.evaluate("() => myExtensionFunction()")
print("扩展函数执行结果:", result)

注意事项

  1. 扩展兼容性:确保扩展支持 Playwright 捆绑的 Chromium 版本,避免因版本不兼容导致加载失败。
  2. 无头模式限制:部分依赖页面交互的扩展功能(如弹窗、桌面通知)在无头模式下可能无法正常工作,可切换为有头模式(移除 channel="chromium",添加 headless=False)。
  3. 权限配置:若扩展需要特殊权限(如访问文件、摄像头),需在启动上下文时通过 permissions 参数授予。
  4. 临时目录清理:若 user_data_dir 设为固定路径,多次运行可能残留状态,建议测试前清理目录或使用临时目录。

要不要我帮你整理 Chrome 扩展测试问题排查指南,包含扩展加载失败、Service Worker 启动异常、权限不足等常见问题的解决方案?