Playwright for Python:Chrome 扩展使用指南
Playwright 仅支持在 Chromium 浏览器中加载和测试 Chrome 扩展,且需通过持久化上下文(persistent context)启动。需注意:Google Chrome 和 Microsoft Edge 已移除侧载扩展的命令行标志,建议使用 Playwright 捆绑的 Chromium 浏览器。
核心前提
- 仅支持 Chromium 内核浏览器,Firefox、WebKit 不支持扩展。
- 必须使用
launch_persistent_context启动持久化上下文,普通上下文无法加载扩展。 - 支持 Manifest V2 和 V3 版本扩展,Manifest V3 需通过 Service Worker 管理扩展逻辑。
- 可通过
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)
注意事项
- 扩展兼容性:确保扩展支持 Playwright 捆绑的 Chromium 版本,避免因版本不兼容导致加载失败。
- 无头模式限制:部分依赖页面交互的扩展功能(如弹窗、桌面通知)在无头模式下可能无法正常工作,可切换为有头模式(移除
channel="chromium",添加headless=False)。 - 权限配置:若扩展需要特殊权限(如访问文件、摄像头),需在启动上下文时通过
permissions参数授予。 - 临时目录清理:若
user_data_dir设为固定路径,多次运行可能残留状态,建议测试前清理目录或使用临时目录。
要不要我帮你整理 Chrome 扩展测试问题排查指南,包含扩展加载失败、Service Worker 启动异常、权限不足等常见问题的解决方案?