Chromium / ChromeDriver 完全指南 / 10 - 浏览器扩展
10 - 浏览器扩展
在自动化脚本中加载、测试和调试浏览器扩展,掌握 Manifest V3 扩展的自动化能力。
10.1 浏览器扩展基础
浏览器扩展 (Browser Extension) 是运行在浏览器中的小程序,可以修改网页行为、添加 UI 元素、拦截网络请求等。
Manifest 版本
| 版本 | Chrome 支持 | 说明 |
|---|---|---|
| Manifest V2 (MV2) | 2024 年起逐步淘汰 | Service Worker 替代 Background Page |
| Manifest V3 (MV3) | Chrome 88+ (推荐) | 安全性更高,权限更细,性能更好 |
扩展结构
my-extension/
├── manifest.json # 扩展配置 (必需)
├── background.js # Service Worker (MV3) / Background Page (MV2)
├── content.js # Content Script (注入到网页)
├── popup.html # 弹出窗口 UI
├── popup.js
├── options.html # 设置页面
├── icons/
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
└── _locales/ # 国际化
├── zh_CN/
│ └── messages.json
└── en/
└── messages.json
MV3 manifest.json 示例
{
"manifest_version": 3,
"name": "我的扩展",
"version": "1.0.0",
"description": "一个示例扩展",
"permissions": ["activeTab", "storage", "scripting"],
"host_permissions": ["https://*.example.com/*"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_end"
}
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png"
}
}
}
10.2 Selenium 加载扩展
加载 CRX 文件
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 加载 .crx 扩展文件
options.add_extension("/path/to/extension.crx")
# 加载多个扩展
options.add_extension("/path/to/ext1.crx")
options.add_extension("/path/to/ext2.crx")
# 注意: --headless (旧版) 不支持扩展加载
# --headless=new 支持扩展加载
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
加载解压扩展目录
options = Options()
# 加载解压的扩展目录 (开发阶段常用)
options.add_argument("--load-extension=/path/to/my-extension")
# 加载多个扩展
options.add_argument(
"--load-extension=/path/to/ext1,/path/to/ext2"
)
# 禁用其他扩展 (排除干扰)
options.add_argument("--disable-extensions-except=/path/to/my-extension")
driver = webdriver.Chrome(options=options)
等待扩展加载
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def wait_for_extension(driver, extension_id, timeout=10):
"""等待扩展加载完成"""
# 扩展页面可通过 chrome-extension://ID/ 访问
driver.get(f"chrome-extension://{extension_id}/popup.html")
WebDriverWait(driver, timeout).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
# 获取扩展 ID
# 方法 1: 手动查看 chrome://extensions
# 方法 2: 通过 CDP 获取
extensions_info = driver.execute_cdp_cmd("Management.getAll", {})
for ext in extensions_info:
print(f"ID: {ext['id']}, Name: {ext['name']}")
10.3 Playwright 加载扩展
const { chromium } = require('playwright');
const path = require('path');
(async () => {
const extensionPath = path.resolve(__dirname, './my-extension');
const context = await chromium.launchPersistentContext('', {
headless: false, // Playwright 扩展加载需要有头模式
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
],
});
// 等待扩展背景页面
const background = context.serviceWorkers()[0] ||
context.waitForEvent('serviceworker');
const page = await context.newPage();
await page.goto('https://example.com');
// ... 测试
await context.close();
})();
Playwright — 测试扩展弹出窗口
test('扩展弹出窗口工作正常', async ({ context }) => {
// 获取扩展 ID
let [background] = context.serviceWorkers();
if (!background) {
background = await context.waitForEvent('serviceworker');
}
const extensionId = background.url().split('/')[2];
// 打开扩展弹出页面
const page = await context.newPage();
await page.goto(`chrome-extension://${extensionId}/popup.html`);
// 测试弹出窗口内容
await expect(page.locator('h1')).toHaveText('我的扩展');
await page.getByRole('button', { name: '启用' }).click();
await expect(page.locator('.status')).toHaveText('已启用');
});
10.4 常用扩展自动化场景
10.4.1 广告拦截器测试
def test_adblock():
options = Options()
options.add_extension("/path/to/adblock.crx")
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
driver.get("https://example-news-site.com")
# 验证广告被拦截
ads = driver.find_elements(By.CSS_SELECTOR, ".ad-banner, .ad-container")
assert len(ads) == 0, f"仍有 {len(ads)} 个广告元素未被拦截"
# 检查网络请求 (通过 CDP)
logs = driver.get_log("performance")
ad_requests = [
log for log in logs
if "doubleclick.net" in str(log) or "googleads" in str(log)
]
assert len(ad_requests) == 0, "仍有广告网络请求"
driver.quit()
10.4.2 VPN/代理扩展
def test_with_vpn_extension():
options = Options()
options.add_extension("/path/to/vpn-extension.crx")
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
# 等待 VPN 连接
time.sleep(5)
# 验证 IP 已改变
driver.get("https://api.ipify.org?format=json")
ip_data = json.loads(driver.find_element(By.TAG_NAME, "body").text)
print(f"当前 IP: {ip_data['ip']}")
driver.quit()
10.4.3 密码管理器扩展
def test_with_password_manager():
options = Options()
options.add_argument("--load-extension=/path/to/password-manager")
options.add_argument("--headless=new")
# 加载已有的用户配置 (包含已保存的密码)
options.add_argument("--user-data-dir=/path/to/profile")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com/login")
# 密码管理器可能自动填充
time.sleep(3)
username = driver.find_element(By.ID, "username")
if username.get_attribute("value"):
print("密码管理器已自动填充用户名")
driver.quit()
10.4.4 自定义扩展测试
def test_custom_extension():
"""测试自定义开发的扩展"""
options = Options()
options.add_argument("--load-extension=./my-extension")
options.add_argument("--disable-extensions-except=./my-extension")
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
driver.get("https://target-website.com")
# 验证扩展 Content Script 注入
result = driver.execute_script(
"return document.querySelector('#my-extension-injected-element') !== null"
)
assert result, "扩展未正确注入 Content Script"
# 验证扩展修改了页面样式
bg_color = driver.execute_script(
"return window.getComputedStyle(document.body).backgroundColor"
)
assert bg_color == "rgb(255, 255, 255)", f"扩展未正确设置背景色: {bg_color}"
driver.quit()
10.5 扩展开发与调试
连接到扩展的 Background Script
# 启动 Chrome 并开启远程调试
options = Options()
options.add_argument("--load-extension=./my-extension")
options.add_argument("--remote-debugging-port=9222")
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
# 通过 CDP 连接到扩展的 background context
# 在 DevTools 中访问 chrome://inspect 可查看扩展
调试 Content Script
// 在 content.js 中添加调试日志
console.log('[MyExtension] Content script loaded on:', window.location.href);
// 使用 debugger 语句 (DevTools 打开时会暂停)
// debugger;
// 监听来自 background 的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('[MyExtension] Received message:', message);
if (message.action === 'getData') {
sendResponse({ data: document.title });
}
});
使用 Selenium 测试扩展通信
def test_extension_messaging():
"""测试扩展内部通信"""
options = Options()
options.add_argument("--load-extension=./my-extension")
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
# 通过执行脚本触发扩展逻辑
result = driver.execute_script("""
return new Promise((resolve) => {
chrome.runtime.sendMessage(
{ action: 'getStatus' },
(response) => resolve(response)
);
});
""")
assert result["active"] == True
driver.quit()
10.6 管理扩展权限
常用权限
| 权限 | 说明 | 风险等级 |
|---|---|---|
activeTab | 访问当前活动标签 | 低 |
storage | 使用 chrome.storage API | 低 |
tabs | 访问标签页信息 | 中 |
cookies | 读写 Cookies | 高 |
webRequest | 拦截/修改网络请求 | 高 |
scripting | 注入脚本 | 中 |
notifications | 显示桌面通知 | 低 |
<all_urls> | 访问所有网站 | 最高 |
安全测试清单
def audit_extension_permissions(extension_path):
"""审计扩展权限"""
manifest_path = f"{extension_path}/manifest.json"
with open(manifest_path) as f:
manifest = json.load(f)
permissions = manifest.get("permissions", [])
host_permissions = manifest.get("host_permissions", [])
high_risk = ["cookies", "webRequest", "webRequestBlocking", "<all_urls>"]
print("=== 扩展权限审计 ===")
print(f"扩展名: {manifest.get('name')}")
print(f"版本: {manifest.get('version')}")
print(f"Manifest 版本: {manifest.get('manifest_version')}")
print(f"\n权限 ({len(permissions)}):")
for p in permissions:
risk = "⚠️ 高" if p in high_risk else "✅ 低"
print(f" {risk} - {p}")
print(f"\n主机权限 ({len(host_permissions)}):")
for h in host_permissions:
print(f" 🔗 {h}")
return permissions, host_permissions
10.7 扩展性能测试
def test_extension_performance():
"""测试扩展对页面性能的影响"""
# 无扩展基准测试
options_no_ext = Options()
options_no_ext.add_argument("--headless=new")
driver = webdriver.Chrome(options=options_no_ext)
driver.get("https://example.com")
baseline = driver.execute_script("""
const perf = performance.getEntriesByType('navigation')[0];
return {
domContentLoaded: perf.domContentLoadedEventEnd - perf.startTime,
loadComplete: perf.loadEventEnd - perf.startTime,
};
""")
driver.quit()
# 有扩展对比测试
options_with_ext = Options()
options_with_ext.add_argument("--load-extension=./my-extension")
options_with_ext.add_argument("--headless=new")
driver = webdriver.Chrome(options_with_ext)
driver.get("https://example.com")
with_ext = driver.execute_script("""
const perf = performance.getEntriesByType('navigation')[0];
return {
domContentLoaded: perf.domContentLoadedEventEnd - perf.startTime,
loadComplete: perf.loadEventEnd - perf.startTime,
};
""")
driver.quit()
# 比较
dom_overhead = with_ext["domContentLoaded"] - baseline["domContentLoaded"]
load_overhead = with_ext["loadComplete"] - baseline["loadComplete"]
print(f"DOM ContentLoaded 开销: {dom_overhead:.0f}ms")
print(f"页面完全加载开销: {load_overhead:.0f}ms")
# 断言扩展对性能的影响不超过 500ms
assert load_overhead < 500, f"扩展性能开销过大: {load_overhead:.0f}ms"
10.8 要点回顾
| 要点 | 说明 |
|---|---|
| MV3 是标准 | 新扩展应使用 Manifest V3 |
--load-extension 加载开发扩展 | 不需要打包为 CRX |
--headless=new 支持扩展 | 旧版无头不支持 |
需要 launchPersistentContext | Playwright 加载扩展需要持久化上下文 |
| 权限审计很重要 | 高权限扩展可能带来安全风险 |
| 性能影响需测量 | 扩展会增加页面加载时间 |
10.9 注意事项
⚠️ MV2 淘汰: Chrome 正在逐步淘汰 Manifest V2,新扩展开发必须使用 MV3。
⚠️ 有头模式要求: 某些扩展功能(如弹出窗口、通知)在无头模式下可能不可用,调试时使用有头模式。
⚠️ 扩展 ID 不固定: 解压扩展的 ID 在每次加载时可能变化,开发阶段使用
--load-extension+--disable-extensions-except确保 ID 稳定。⚠️ CDP 与扩展共存: 使用 CDP 命令时注意不要与扩展的 Content Script 产生冲突。
10.10 扩展阅读
| 资源 | 链接 |
|---|---|
| Chrome 扩展开发文档 | https://developer.chrome.com/docs/extensions/ |
| Manifest V3 迁移指南 | https://developer.chrome.com/docs/extensions/migrating/ |
| Playwright 扩展测试 | https://playwright.dev/docs/chrome-extensions |
| Selenium 扩展加载 | https://www.selenium.dev/documentation/webdriver/browsers/chrome/ |
| Chrome Extensions API | https://developer.chrome.com/docs/extensions/reference/ |