news 2026/9/8 17:45:33

@cypress/puppeteer 插件解析:在 Cypress 中调用 Puppeteer API 实现多标签页与高级浏览器自动化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
@cypress/puppeteer 插件解析:在 Cypress 中调用 Puppeteer API 实现多标签页与高级浏览器自动化

@cypress/puppeteer 插件解析:在 Cypress 中调用 Puppeteer API 实现多标签页与高级浏览器自动化

【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress

@cypress/puppeteer是 Cypress 官方仓库(npm/puppeteer子包)中提供的公开测试版 npm 插件,它通过 Chrome DevTools Protocol(CDP)把 Puppeteer 的浏览器 API 引入 Cypress 测试命令,让团队可以在既有 Cypress 测试旁使用 PDF 生成、多标签页切换、更底层的浏览器控制等 Puppeteer 能力。阅读完本文,你将掌握该插件的进程架构、setup/cy.puppeteer()/retry三件套 API、Node 端消息处理器与浏览器端命令的数据流,并能直接复刻仓库中"切换新标签页""创建新标签页"两套可运行示例与完整开发调试命令。

插件定位:为何需要在 Cypress 中使用 Puppeteer

Cypress 的 DOM 自动化能力强大,但在以下场景中会力不从心:

  • 由应用代码触发(非测试代码window.open)打开的新标签页或新窗口;
  • 通过 Cypress 命令难以触达的浏览器原生行为;
  • 需要生成 PDF、管理下载、操作 DevTools 协议层能力的高级浏览器控制。

@cypress/puppeteer正是为此而生:它不要求你放弃 Cypress,而是把 Puppeteer 连接到Cypress 自己启动的浏览器实例上,让你在断言依旧由 Cypress 完成的前提下,用 Puppeteer API 弥补 Cypress 的覆盖盲区。

该包当前处于public beta(仓库内版本号由 semantic-release 管理,见 package.json,README 与 AGENTS.md 均明确标注测试版状态),意味着可能出现破坏性变更,反馈由 Cypress 官方维护并跟踪。

运行前提与兼容性

使用前需要满足以下条件(依据 README.md 的 Compatibility 一节与 package.json 的peerDependencies):

条件要求说明
Cypress 版本>= 13.6.0在 package.json 中声明为 peerDependency,同时setup内部依赖after:browser:launch事件注册
浏览器仅支持 Chromium 系Chrome for Testing、Chromium、Electron 均可用;插件依赖 CDP 连接 Cypress 托管的浏览器
Chrome 品牌限制137+ 版本不支持有头模式因 Chrome 移除了--load-extension标志,cypress opencypress run --headed下不可用;headless 的cypress run不受影响

这一限制并非文档说辞,而是被写进了插件运行时逻辑:setup.ts 在after:browser:launch回调中检查majorVersion >= 137 && browser.name === 'chrome' && browser.isHeaded,命中即抛出错误并建议改用 Electron、Chrome for Testing 或 Chromium。

安装与项目骨架

# npm npm install --save-dev @cypress/puppeteer # yarn yarn add --dev @cypress/puppeteer

使用 TypeScript 时,在tsconfig.json中加入类型声明:

{ "compilerOptions": { "types": ["cypress", "@cypress/puppeteer/support"] } }

包结构上,package.json 声明main指向dist/plugin/index.js,发布内容仅包含distsupport两个目录,其中support/下预置了已编译的index.jsindex.d.ts,供支持文件的import '@cypress/puppeteer/support'直接引用。

仓库中该包的源码布局如下(与 AGENTS.md 的 Architecture 一节一致):

npm/puppeteer/ ├── src/ │ ├── plugin/ # Node 端插件代码(在 setupNodeEvents 中注册) │ │ ├── index.ts # 插件主入口,导出 setup 与 retry │ │ ├── setup.ts # 初始化与 Cypress 托管浏览器的 Puppeteer 连接 │ │ ├── activateMainTab.ts # 聚焦/重新激活主浏览器标签 │ │ ├── retry.ts # Puppeteer 操作的重试工具 │ │ └── util.ts # 共享工具(pluginError) │ └── support/ │ └── index.ts # 浏览器端支持命令,注册 cy.puppeteer() ├── support/ # 发布用的支持文件目录(独立于 src/support 编译产物) ├── cypress/ │ ├── e2e/multi-tab.cy.ts # 多标签页端到端测试 │ └── fixtures/{page-1..4}.html # 示例页面 └── test/unit/{setup,retry,activateMainTab}.spec.ts # vitest 单元测试

核心架构:一个浏览器端命令 + 一个 Node 端任务

理解该插件的关键是把握"代码跑在哪儿"。README 明确说明:cy.puppeteer()在浏览器(测试运行页)中执行,但绝大部分 Puppeteer 自动化代码跑在Node 进程(Cypress 配置)里,调用方式与cy.task()的消息传递模型一致。

完整调用链如下:

  1. 测试代码调用cy.puppeteer('messageName', ...args),命令名与参数均为字符串/可序列化数据;
  2. 浏览器端支持命令 src/support/index.ts 内部透传执行cy.task('__cypressPuppeteer__', { name, args }, { log: false })
  3. Node 端 setup.ts 注册了同名 task:校验后通过puppeteer.connect({ browserWSEndpoint: debuggerUrl })连上 Cypress 启动的浏览器,再调用onMessage中对应的消息处理器;
  4. 处理器的返回值回到浏览器端,作为cy.puppeteer()的 yield 值参与 Cypress 断言。

其中debuggerUrl来自after:browser:launch事件中的options.webSocketDebuggerUrl(见 setup.ts),这正是 CDP 连接的端点。因为 Cypress 与 Puppeteer 共享同一个浏览器进程,所以 Puppeteer 操作的新标签页与 Cypress 测试在同一个浏览器会话内。

值得注意的边界行为:

  • 支持命令不是 Cypress 命令链cy.puppeteer()返回的是 Promise-like 值(实际由cy.task派生),可以直接.should(...)断言,但处理器代码运行于 Node,不能在其中调用任何 Cypress 命令或 DOM API;
  • 参数必须可 JSON 序列化cy.puppeteer(messageName, ...args)的 args 会反序列化后透传给处理器;
  • 错误打包:Node 端处理器抛出的错误会被messageHandlerError序列化为{ __error__: { name, message, stack } },浏览器端检测到__error__后重新抛出,保证测试失败信息完整(见 setup.ts 与 src/support/index.ts);
  • undefined 归一化cy.task()在返回undefined时会报错,因此 Node 端在处理器无返回值时统一转为null(见 setup.ts)。

API 详解

setup(options) —— Cypress 配置端注册

cypress.config.tssetupNodeEvents(on)中调用,用于注册运行 Puppeteer 自动化的消息处理器:

import { setup } from '@cypress/puppeteer' export default defineConfig({ e2e: { setupNodeEvents (on) { setup({ on, onMessage: { async myMessageHander (browser) { // 利用 Puppeteer browser 实例操作浏览器 }, }, }) }, }, })

选项:

选项必填说明
onsetupNodeEvents提供的on事件注册函数
onMessage键为字符串、值为函数的对象(详见下节)
puppeteerpuppeteer-core导入的 Puppeteer 库实例,覆盖插件默认版本

setup对参数做了严格校验:options 必须是普通对象、on必须是函数、onMessage必须是普通对象,否则抛出错误(见 setup.ts)。当未传puppeteer时,默认使用插件依赖的puppeteer-core(package.json 声明依赖puppeteer-core ^21.2.1)。

onMessage —— 消息处理器

onMessage对象的键就是测试中cy.puppeteer(key)调用的名字;其值是在 Node.js 中执行的 Puppeteer 代码。处理器接收两个参数:

  • browser:连接到 Cypress 启动浏览器的 Puppeteer Browser 实例;
  • ...args:测试里传给cy.puppeteer()的反序列化参数。
// 测试侧 cy.puppeteer('testNewTab', 'value 1', 42, [true, false]) // 配置侧 setup({ on, onMessage: { testNewTab (browser, stringArg, numberArg, arrayOfBooleans) { // stringArg === 'value 1' // numberArg === 42 // arrayOfBooleans[0] === true / arrayOfBooleans[1] === false } } })

retry(functionToRetry[, options]) —— 重试工具

由于新开标签页从触发到可交互存在时间差,插件提供了重试工具让首次可能失败的操作可靠完成(实现见 retry.ts):

retry(async () => { // 若抛错则按间隔重试,成功则返回其返回值 }, { timeout: 4000, // 总超时,默认 4000ms delayBetweenTries: 200, // 重试间隔,默认 200ms })

从源码看,retry的实现是递归makeAttempt:每次尝试抛错就等待delayBetweenTries,累计时间达到timeout后抛出Failed retrying after ${timeout}ms: ${err.message}

cy.puppeteer(messageName[, ...args]) —— 测试侧命令

  • messageName(必填):字符串,须与setuponMessage某个键一致;
  • ...args(可选):透传给消息处理器的值,必须 JSON 可序列化。

支持文件侧只需一行导入即可获得类型与命令:

// cypress/support/e2e.ts import '@cypress/puppeteer/support'

实战示例:多标签页测试

仓库在 cypress/e2e/multi-tab.cy.ts 和 cypress.config.ts 中给出了可直接运行的完整示例,两个用例恰好覆盖了插件的两大典型用法:接管 Cypress 操作打开的新标签页用 Puppeteer 创建并驱动新标签页。以下代码即来自该仓库,可直接复制使用。

示例一:切换到 Cypress 触发打开的新标签页

该示例演示:切换到测试动作打开的新标签页 → 用retry获取 Page 实例 → 读取页面内容 → 交回 Cypress 断言。

cypress.config.ts

import { defineConfig } from 'cypress' import type { Browser as PuppeteerBrowser, Page } from 'puppeteer-core' import { setup, retry } from '@cypress/puppeteer' export default defineConfig({ e2e: { setupNodeEvents (on) { setup({ on, onMessage: { async switchToTabAndGetContent (browser: PuppeteerBrowser) { // 页面可能尚未打开加载完毕,因此用 retry 轮询查找目标页面 const page = await retry<Promise<Page>>(async () => { // 在 Puppeteer 中,标签页与窗口统一抽象为 Page 实例 const pages = await browser.pages() // 找到我们想交互的那个页面 const page = pages.find((page) => page.url().includes('page-2.html')) // 找不到就抛错,向 retry 表明需要重试 if (!page) throw new Error('Could not find page') // 找到则返回 page,retry 会原样返回它 return page }) // Cypress 会把焦点保持在它自己的标签页上,交互前通常需要把目标页带到前台 await page.bringToFront() const paragraph = (await page.waitForSelector('p'))! const paragraphText = await page.evaluate((el) => el.textContent, paragraph) // 结束前清理元素句柄引用 paragraph.dispose() await page.close() // 返回的文本就是 cy.puppeteer() 在 spec 中 yield 的值 return paragraphText }, }, }) }, }, })

spec.cy.ts

it('switches to a new tab', () => { cy.visit('/cypress/fixtures/page-1.html') cy.get('input').type('Hello from Page 1') cy.get('button').click() // 触发打开新标签页 cy .puppeteer('switchToTabAndGetContent') .should('equal', 'You said: Hello from Page 1') })

示例二:用 Puppeteer 创建新标签页并传参

该示例演示:向插件传入自定义版本的 Puppeteer → 从cy.puppeteer()传参给消息处理器 → 创建新标签页访问 URL → 取回内容断言。

cypress.config.ts

import { defineConfig } from 'cypress' import puppeteer, { Browser as PuppeteerBrowser, Page } from 'puppeteer-core' import { setup, retry } from '@cypress/puppeteer' export default defineConfig({ e2e: { setupNodeEvents (on) { setup({ on, // 传入你自己的 puppeteer 版本来替换插件默认版本 puppeteer, onMessage: { async createTabAndGetContent (browser: PuppeteerBrowser, text: string) { // 在 Cypress 启动的浏览器内创建新标签页 const page = await browser.newPage() // text 来自测试中 cy.puppeteer() 的调用参数 await page.goto(`http://localhost:8000/cypress/fixtures/page-4.html?text=${text}`) const paragraph = (await page.waitForSelector('p'))! const paragraphText = await page.evaluate((el) => el.textContent, paragraph) paragraph.dispose() await page.close() return paragraphText }, }, }) }, }, })

spec.cy.ts

it('creates a new tab', () => { cy.visit('/cypress/fixtures/page-3.html') // 从页面动态取值,再透传给 puppeteer 消息处理器 cy.get('#message').invoke('text').then((message) => { cy .puppeteer('createTabAndGetContent', message) .should('equal', 'I approve this message: Cypress and Puppeteer make a great combo') }) })

提示:Puppeteer 视角下标签页与窗口本质相同,都由 Page 类封装,因此上述两例只需稍加调整即可推广到多窗口场景。

底层机制:主标签页如何"归还"给 Cypress

多标签自动化有一个隐蔽问题:Puppeteer 操作后焦点停留在非 Cypress 标签页上,会影响后续 Cypress 命令。插件用 activateMainTab.ts 解决——每个消息处理器结束后,在特定条件下把主标签页重新激活:

  • 仅面向有头的 Chromium 且非 Electron场景:Electron 没有标签概念,无头浏览器不涉及焦点、旧版 headless 也不运行扩展(对应注释见 setup.ts);
  • 实现上先取browser.pages()的第一个页面,执行page.evaluate注入sendActivationMessage:向windowpostMessage 发送cypress:extension:activate:main:tab,监听 Cypress Chrome 扩展回发的cypress:extension:main:tab:activated消息;
  • 整个过程有ACTIVATION_TIMEOUT = 2000毫秒超时(见 activateMainTab.ts)。

如果激活失败,Node 端会返回知名错误:"Cannot communicate with the Cypress Chrome extension. Ensure the extension is enabled when using the Puppeteer plugin."。这个扩展在 open 模式下对焦点恢复至关重要,排查建议(README Troubleshooting 一节):

  • Chrome 品牌 137+ 请改用 Chrome for Testing / Chromium;
  • 在 Cypress 启动的 Chrome 中访问chrome://extensions/确认扩展已启用;
  • 确保障碍安全策略放行该扩展(扩展 idcaljajdfkjjjdehjdoimjkkakekklcck)。

仓库内的测试与验证方式

插件在仓库内配有两层验证,可作为你自行改造或复刻时的参考:

单元测试(vitest)—— 覆盖三个核心模块:

  • test/unit/setup.spec.ts:验证setup的参数校验、task 注册与错误打包;
  • test/unit/retry.spec.ts:验证重试时机、成功返回与超时抛错;
  • test/unit/activateMainTab.spec.ts:验证主标签激活逻辑。

端到端测试(Cypress 自举)—— cypress/e2e/multi-tab.cy.ts 使用插件自身跑通两个多标签用例,配套 cypress.config.ts 用 express 在localhost:8000托管cypress/fixtures/下的静态页面(page-1.htmlpage-4.html),模拟真实应用交互。

开发命令速查

AGENTS.md 的 Key Commands 一节与 package.json 的 scripts 保持了一致,常用命令如下:

yarn build # rimraf dist + tsc,产物输出到 dist/ yarn watch # rimraf dist + tsc 监听模式(增量重编译) yarn check-ts # TypeScript 类型检查,不输出(tsc --noEmit) yarn lint # ESLint 检查 yarn test -- src/plugin/setup.spec.ts # 运行指定 vitest 单测文件 yarn test -- "src/**/*.spec.ts" # 按 glob 运行匹配的单测 yarn cypress:run -- --spec "cypress/e2e/puppeteer.cy.ts" --browser chrome # 定向运行某个集成测试(需 Chrome)

注意:cypress:run脚本显式传递了--browser chrome(见 package.json),因为插件的 CDP 连接依赖 Chromium 系浏览器;而test脚本底层是 vitest(vitest run)。仓库内的集成测试文件实际名为 multi-tab.cy.ts,运行时可用--spec "cypress/e2e/multi-tab.cy.ts"精确定位。

易错点小结

综合文档与源码,使用该插件时请留意:

  1. 处理器是 Node 代码onMessage函数体里不能使用 Cypress 命令、cy.*或 DOM API;
  2. 有头 Chrome 137+ 不可用:headed 场景改选 Electron / Chrome for Testing / Chromium;
  3. 需要扩展配合:open/headed 模式下主标签回收依赖 Cypress Chrome 扩展,禁用会触发明确报错;
  4. 善用retry:新标签页加载有竞态,先轮询browser.pages()匹配 URL 再交互是最稳的写法;
  5. 关闭与清理:示例中paragraph.dispose()page.close()、Node 端browser.disconnect()共同保证会话与句柄被及时释放。

如需完整的 API 文档与更多细节,可继续查阅仓库内 npm/puppeteer/README.md 与 npm/puppeteer/CHANGELOG.md。

【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 17:45:29

【单片机毕业设计】基于 STM32 的 DHT11 环境感知与 ESP‑01S 无线监控系统设计 基于 STM32 的阈值可配置智能加湿补水报警系统设计(011607)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/8 17:44:53

opencode:开源终端AI编程代理上手与实战指南

最近几周我几乎把所有AI编程Agent轮着用了一遍&#xff0c;从Claude Code到Codex CLI&#xff0c;再到各种IDE插件&#xff0c;最后在终端里停留最久的&#xff0c;反而是opencode。这工具定位特别明确&#xff1a;一个开源的、终端优先的AI编程代理&#xff0c;核心是TypeScri…

作者头像 李华
网站建设 2026/9/8 17:44:37

CLion STM32 printf重定向:用_write替代fputc解决串口无输出

我刚开始从 Keil 转到 CLion 做 STM32 开发时&#xff0c;也踩过这个坑&#xff1a;照着网上教程认认真真重写了 fputc &#xff0c;代码编译通过、下载正常&#xff0c;串口却死活不输出任何东西&#xff0c;甚至程序还直接卡死。后来排查半天才发现&#xff0c;CLion 默认的…

作者头像 李华
网站建设 2026/9/8 17:44:04

从源码构建的免费 Java IDE:IntelliJ IDEA 社区版

从源码构建的免费 Java IDE&#xff1a;IntelliJ IDEA 社区版 【免费下载链接】intellij-community IntelliJ IDEA & IntelliJ Platform 项目地址: https://gitcode.com/GitHub_Trending/in/intellij-community IntelliJ IDEA 社区版是免费的开源 Java IDE。日常写 …

作者头像 李华
网站建设 2026/9/8 17:43:13

RPCS3 加载 FIFA 13 卡 5 分钟?3 处配置改完 30 秒进游戏

RPCS3 加载 FIFA 13 卡 5 分钟&#xff1f;3 处配置改完 30 秒进游戏 【免费下载链接】rpcs3 PlayStation 3 emulator and debugger 项目地址: https://gitcode.com/GitHub_Trending/rp/rpcs3 RPCS3 加载 FIFA 13 卡在加载画面 5 分钟、甚至直接闪退&#xff1f;问题多半…

作者头像 李华
网站建设 2026/9/8 17:43:11

2026年Claude Code插件实战盘点:9款高含金量工具与选型指南

作为一个天天泡在终端里跟 Claude Code 打交道的人&#xff0c;我见过太多人一上来就满世界找插件&#xff0c;看到 GitHub 上哪个仓库 star 多就装哪个&#xff0c;结果插件列表长得能当购物清单用&#xff0c;真正干活的时候反而被插件之间的配置冲突、上下文污染、token 浪费…

作者头像 李华