深度解析 Cypress 浏览器自动化扩展 @packages/extension:MV2/MV3 双版本架构与构建调试指南
【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress
本文聚焦 Cypress monorepo 中的packages/extension包——一个在 Cypress 测试运行时被 Chrome 与 Firefox 加载的 WebExtension。它从"扩展层"而非调试协议层自动化浏览器,补足 CDP 与 WebDriver BiDi 覆盖不到的 API(如 Firefox 的 Cookie/下载事件与浏览器状态清理、Chrome 主标签页跟踪与激活)。读完本文,你将理解 MV2/MV3 两套扩展"为什么存在且分工不同"、各自的源码工作流、构建产物与测试体系,并掌握在真实浏览器里加载与调试该扩展的完整步骤。
为什么 Cypress 需要自己的浏览器扩展
Cypress 通过@packages/server启动浏览器并驱动测试,大多数自动化能力来自底层调试协议;但协议并非万能。packages/extension/AGENTS.md明确给出了这个包的存在理由:
The WebExtension loaded by Chrome and Firefox during Cypress test runs. It automates the browser at the extension level, reaching APIs that CDP and BiDi don't cover.
即:扩展以浏览器扩展 API(WebExtension API)为杠杆,触及 CDP(Chrome DevTools Protocol)和 BiDi(WebDriver BiDi)覆盖不到的领域——例如 Firefox 环境下基于browser.cookies/browser.downloads的 Cookie 与下载事件推送、调用browser.browsingData.remove清理浏览器状态;Chrome 环境下跟踪主 Cypress 标签页并把用户拉回该标签页。扩展与@packages/server、@packages/socket、@packages/icons三个包产生依赖(详见下文"集成点")。
核心架构:不是一套代码的两个构建,而是两种截然不同的职责
该文档特别强调了一个容易误解的架构事实——AGENTS.md 写道:
The two bundles are not two builds of the same thing — each is loaded by exactly one browser and does a different job.
app/v2/与app/v3/不是"同一功能的 V2/V3 双版本"(Chrome MV2 迁移到 MV3 那样),而是各自只被一个浏览器加载、各做各的工作:
| 维度 | app/v2/(Manifest V2) | app/v3/(Manifest V3) |
|---|---|---|
| 目标浏览器 | 仅 Firefox | 仅 Chrome |
| 加载方 | @packages/server的 Firefox 启动逻辑(经utils.writeExtension写入) | @packages/server的 Chrome 启动逻辑(经getPathToV3Extension取得路径) |
| 后端通信 | 经socket.io(实际是@packages/socket的浏览器端 client)回连 Cypress 服务端 | 无 socket 连接,仅通过扩展内部消息(content script ↔ service worker) |
| 核心职责 | 推送 Cookie 变更事件、下载创建/完成/取消事件;处理reset:browser:state(BiDi 刻意将此职责委托给扩展) | 记录主 Cypress 标签页 URL,并在需要时把该标签页重新激活到前台,供@cypress/puppeteer使用 |
| 运行时载体 | MV2 background page(background.js) | MV3 service worker(service-worker.js)+ content script |
V2:Firefox 专用的"事件推送 + 状态重置"扩展
V2 扩展在 Firefox 里承担两类自动化任务,源码可见于 app/v2/background.ts。
连接与服务端通信。client.ts 从@packages/socket/browser/client引入client并以transports: ['websocket']建立连接;background.ts 建立ws后监听两类服务端消息:
automation:request:按消息名分发,其中唯一已注册的处理是reset:browser:state→ 调用扩展侧的resetBrowserState,未注册消息则回传错误结构(__error/__stack/__name)。automation:config:触发checkIfFirefox判定(依赖browser.runtime.getBrowserInfo),随后注册事件监听。
Cookie 事件推送。listenToCookieChanges使用browser.cookies.onChanged.addListener,当 Cookie 变化的cause不是overwrite时,通过automation:push:request向服务端推送change:cookie事件(background.ts)。
下载事件推送。listenToDownloads只在 Firefox 下启用(注释明确"非 Firefox 浏览器走 CDP"),分别监听:
browser.downloads.onCreated→ 推送create:download(携带 id、filePath、mime、url);browser.downloads.onChanged→ 根据下载状态推送complete:download或canceled:download(background.ts)。
浏览器状态重置。resetBrowserState调用browser.browsingData.remove({}, {...})一次性清理cache、cookies、downloads、formData、history、indexedDB、localStorage、passwords、pluginData、serviceWorkers共十类数据(background.ts),源码注释指出 Chrome 走 CDP automation,且 Firefox 不支持fileSystems/serverBoundCertificates这两类数据清理。
对应地,v2/manifest.json 为 MV2 清单,声明了cookies、browsingData、downloads权限与<all_urls>主机权限,通过applications.gecko.id = "automation-extension@cypress.io"固定 Firefox 扩展 ID,background.scripts指定background.js,chrome_url_overrides.newtab指向newtab.html。init.ts负责在扩展页面加载时把 background page 连接到 Cypress 服务端。
V3:Chrome 专用的"标签页跟踪 + 激活"扩展
V3 扩展解决的是另一类问题:让 Cypress 主标签页在测试过程中保持/恢复前台。它不建立 socket 连接,而是采用 MV3 标准的content script ↔ service worker 消息协作。
Manifest 骨架。v3/manifest.json 为 MV3 清单:权限仅tabs与storage,background.service_worker指向service-worker.js,content_scripts匹配http://*/*、https://*/*、<all_urls>并注入content.js。相比 V2,它刻意不申请 cookies/downloads 等敏感权限,职责更轻。
Service worker 逻辑。service-worker.ts 顶部注释点明了其运行约束:service worker 拥有扩展 API,但无法直接访问网页内容。核心实现为两段消息处理(service-worker.ts):
url:changed:把最新的主标签页 URL 写入chrome.storage.local(mostRecentUrl),完成"标签页跟踪";activate:main:tab:从 storage 读取mostRecentUrl,在chrome.tabs.query({})结果中匹配包含该 URL 的标签页,调用chrome.tabs.update(tab.id, { active: true })将其置前,并通过main:tab:activated回执确认。
一个值得注意的实现细节是:激活标签页时特意不抢系统焦点——源码注释明确写道 "this brings the main Cypress tab to the front of any other tabs without Chrome stealing focus from other running apps"(service-worker.ts),并且把失败日志通过console.log输出(仅在开发者检查 service worker 时可见,避免污染用户控制台)。
Content script 桥接。content.ts 运行在网页上下文,它通过扩展端口(port.onMessage,参见 content.ts)与 service worker 通信,把"网页侧需要激活主标签页"的请求转交给拥有tabsAPI 的 service worker。
扩展 UI 与主题资产
除自动化逻辑外,该包还包含少量扩展界面资源:newtab.html/newtab.css(覆盖新建标签页)、popup.html/popup.css(工具栏弹窗),以及theme/目录(浏览器主题清单与theme_frame.png等资源)。V2/V3 的 manifest 都将browser_action/action的default_popup指向popup.html,图标则由@packages/icons提供的各尺寸 PNG(16/19/38/48/128)构成。
工程化:gulp 编排的三种产物与双工具链
packages/extension/package.json与 README.md 展示了清晰的构建拓扑——扩展包并非单一构建流程,而是"app + lib"三种产物,全部由 gulpfile.ts 编排:
| 产物 | 内容 | 工具链 | 输出 |
|---|---|---|---|
app(v2) | MV2 扩展(Firefox) | webpack-cli(webpack.config.mjs)打包app/v2 | 输出background.js |
app(v3) | MV3 扩展(Chrome) | tsc -p tsconfig.app.v3.json直接编译 | ESM 产物,可在浏览器原生运行(无外部依赖) |
lib | @packages/extension的main入口:查找/加载扩展的 Node 侧工具方法 | tsc -p tsconfig.lib.json转译 | CommonJS(在 Node 上下文消费) |
各脚本在 package.json 中均有对应:build委托给gulp build,build:v2走 webpack-cli,build:v3与build:lib各走各自的 tsc project,clean走gulp clean。编译后的app-dist、lib-dist目录与theme一并列入包的files发布清单。
常用命令速查
以下命令均在 monorepo 根目录执行:
# 构建 V2 + V3 两个扩展 bundle 与 lib yarn workspace @packages/extension build # 监听模式:构建后由 chokidar 监听 app 目录变动并自动重构建 yarn workspace @packages/extension watch # 单元测试(vitest run) yarn workspace @packages/extension test # 监听运行测试 yarn workspace @packages/extension test-watch # 调试模式运行测试(inspect-brk、不并行、无超时) yarn workspace @packages/extension test-debug # 运行单个测试文件 yarn workspace @packages/extension test -- <path-to-spec> # 按 glob 匹配运行测试 yarn workspace @packages/extension test -- "<glob-pattern>" # 类型检查 yarn workspace @packages/extension check-ts一个常见的坑:该包与@packages/electron一样,安装后必须显式执行yarn build,postinstall脚本只打印提醒'@packages/extension needs: yarn build'而不会真正构建。
测试体系
测试由 Vitest 驱动(vitest.config.ts),分为单元测试与集成测试两层:
- 单元测试:test/unit/extension.spec.ts,覆盖
lib层查找/加载扩展的工具方法; - V2 集成测试:test/integration/v2/background.spec.ts,验证 Firefox background page 的消息分发、Cookie/下载事件推送与
reset:browser:state; - V3 集成测试:test/integration/v3/service-worker.spec.ts 与 content.spec.ts,验证 service worker 的标签页跟踪/激活协议以及 content script 桥接;
- 共享辅助代码位于 test/helpers/background.js。
在真实浏览器中加载与调试扩展
在 Chrome 中调试 V3(service worker)
V3 扩展的 background 逻辑运行在 service worker 中,需要打开 service worker 的控制台才能看到日志与断点。步骤如下(整理自 README.md):
- 打开 Chrome,进入
chrome://extensions; - 勾选右上角Developer Mode(开发者模式);
- 点击左上角Load unpacked extension...(加载已解压的扩展程序);
- 选择packages/extension/app-dist/v3目录;
- 在扩展卡片中点击service worker(Inspect views 下的 service worker)以调试
service-worker.js; - 每次改动
manifest.json后点击Reload(⌘R)使其生效。
service-worker.ts 源码注释补充了另一种调试入口:在新标签页打开chrome://inspect→ 左侧选择 Service Workers → 点击 inspect。若 reload 不生效,可能需要重启 Chrome 后再次在chrome://extensions重载扩展。这是 MV3 service worker 生命周期带来的已知调试体验。
在 Firefox 中调试 V2(background page)
Firefox 的 V2 扩展通过 Cypress 测试运行加载,调试步骤同样来自 README.md:
- 通过
cypress open启动并让 Cypress 拉起 Firefox; - 在 Firefox 中打开新标签页,导航到
about:debugging; - 点击左侧导航的This Firefox,在Temporary Extensions(临时扩展)下找到名为Cypress的扩展;
- 点击inspect,会弹出独立的调试窗口;
- 关闭
about:debugging标签页; - 在弹出的调试窗口中切到Debugger标签页,即可看到
background.js; - 按需设置断点并观察变量。
集成点与依赖关系
文档在"Integration Points"一节明确了该包在 monorepo 中的位置:
- 运行时依赖
@packages/socket:承担浏览器端 ↔ Cypress 服务端的通信(V2 经 socket 传输自动化事件); - 依赖
@packages/icons:提供扩展图标资源; - 被
@packages/server消费:server 在浏览器启动参数中注入构建好的扩展——Firefox 经utils.writeExtension写入扩展、Chrome 经getPathToV3Extension取扩展路径(详见 packages/extension/AGENTS.md)。
与此对应,package.json 的nx.implicitDependencies声明了@packages/server与@packages/socket,意味着这两个包任一发生变化都会在 CI 中触发 extension 的重构;lib产物以 CommonJS 形态被 Node 侧消费,也解释了为什么它使用独立于浏览器 bundle 的编译管线。
附:开发中的注意事项(Gotchas)
从 AGENTS.md 与 README.md 可提炼出几条对贡献者影响实际的约束:
- 安装依赖后必须执行
yarn workspace @packages/extension build(或整仓yarn build),postinstall仅打印提醒; - V2 与 V3 的构建工具链不同(webpack vs 直接
tsc),需要修改app构建逻辑时分别查看 webpack.config.mjs、tsconfig.app.v2.json/tsconfig.app.v3.json与 gulp 任务; - 两条扩展各对应一个浏览器,修改行为前先确认修改目标属于 Firefox 的协议补足(V2)还是 Chrome 的标签页管理(V3),避免在错误的 bundle 中实现功能。
【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考