Cypress 官方 @cypress/puppeteer 插件:在测试中用 Puppeteer API 驱动多标签页与跨页操作
【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress
@cypress/puppeteer是 Cypress 仓库内的官方实验性插件(public beta),它让你在 Cypress 测试里通过一条cy.puppeteer()命令调用完整的 Puppeteer 浏览器 API,从而处理多标签页、新窗口等 Cypress 命令体系难以直接覆盖的场景。读完本文,你将掌握它的安装与 TypeScript 配置、浏览器兼容性边界、setup/retry/cy.puppeteer()三组 API 的完整用法,以及插件在 Node 侧通过 WebSocket 接管 Cypress 所启动浏览器的底层实现机制,并能复现仓库自带的多标签页示例测试。
安装与 TypeScript 配置
npm / yarn 安装
npm install --save-dev @cypress/puppeteeryarn add --dev @cypress/puppeteer从 package.json 可以看到该包的关键依赖约束:
peerDependencies声明了cypress >= 13.6.0,低于此版本的 Cypress 无法使用本插件;dependencies内置了puppeteer-core(当前为^21.2.1),你无需额外安装即可使用默认版本;- 发布的产物包括
dist与support两个目录(见files字段),其中support目录就是浏览器侧cy.puppeteer()命令的实现。
TypeScript 类型声明
如果你使用 TypeScript,需要在tsconfig.json中加入:
{ "compilerOptions": { "types": ["cypress", "@cypress/puppeteer/support"] } }这条声明对应的类型定义在 support/index.d.ts,它为Cypress.Chainable接口注入了puppeteer(messageName: string, ...args: any[]): Chainable签名,使cy.puppeteer('myHandler')这类调用获得类型提示。
兼容性与浏览器支持边界
使用@cypress/puppeteer前需要满足以下条件:
- Cypress 13.6.0+:插件依赖
after:browser:launch事件获取浏览器的调试端点,该事件从 13.6.0 起提供。src/plugin/setup.ts 中注册该事件失败时会明确提示 "Ensure you are running Cypress >= 13.6.0"; - 仅支持 Chromium 系浏览器,如 Chrome for Testing、Chromium、Electron。从源码看,任务处理入口会检查
cypressBrowser.family !== 'chromium'并直接报错拒绝非 Chromium 系浏览器; - Chrome 品牌浏览器 137+ 的受限:由于 Chrome 移除了
--load-extension标志,标准 Chrome 137 及以上版本在有头模式(cypress open或cypress run --headed)下无法工作,建议使用 Electron、Chrome for Testing 或 Chromium 替代。注意该限制只影响有头模式,在cypress run(无头)模式下任何版本的 Chrome 都正常工作。这一点在 setup.ts 中有硬性拦截:当检测到majorVersion >= 137 && name === 'chrome' && isHeaded时抛出插件错误。
工作机制:浏览器命令如何驱动 Node 侧的 Puppeteer
理解本插件的架构是高效使用它的前提。cy.puppeteer()命令本身运行在浏览器中,但真正的 Puppeteer 自动化运行在 Node 进程中,模式与cy.task()类似:
- 注册阶段:你在
cypress.config.ts的setupNodeEvents中调用setup(),它做两件事——通过on('after:browser:launch')捕获 Cypress 启动浏览器后返回的webSocketDebuggerUrl;再通过on('task', ...)注册一个名为__cypressPuppeteer__的内部任务(见 setup.ts)。 - 调用阶段:浏览器侧的
cy.puppeteer(messageName, ...args)(实现在 src/support/index.ts)只是把消息名和参数序列化为{ name, args },经cy.task('__cypressPuppeteer__')投递到 Node 侧,所以args 必须 JSON 可序列化。 - 执行阶段:Node 侧任务处理函数每次调用都会通过
puppeteer.connect({ browserWSEndpoint: debuggerUrl, defaultViewport: null })连接到 Cypress 启动的那个浏览器,执行你在onMessage中注册的对应处理函数,然后把返回值(或错误)传回测试断言。 - 收尾阶段:处理函数执行完毕后,插件会自动执行
browser.disconnect()断开连接。对于有头 Chromium(Electron 除外),还会先调用activateMainTab把焦点切回 Cypress 主标签页。
关于第 4 步值得展开:src/plugin/activateMainTab.ts 会向 Cypress 主标签页postMessage一条cypress:extension:activate:main:tab消息,由随 Cypress 分发的 Chrome 扩展响应并恢复主标签页的激活状态(超时 2000ms)。Cypress 自身在浏览器内始终维护对主标签页的焦点,如果 Puppeteer 代码把某个新标签页带到前台,这条机制保证了后续 Cypress 命令还能继续正常执行——这也是 Troubleshooting 中"扩展通信"错误的来源。
错误处理同样贯穿整条链路:Node 侧捕获到的异常会被包装为{ __error__: { name, message, stack } }返回,浏览器侧再把它重新抛出为可读的cy.puppeteer() failed with the following error信息(见 setup.ts 与 support/index.ts)。此外,处理函数若返回undefined,由于cy.task()对此会报错,插件会统一转换为null返回。
API 详解
setup(options)(Cypress 配置侧)
用于注册消息处理框架:
setup(options)Options:
on(必填):setupNodeEvents提供的on事件注册函数;onMessage(必填):字符串键到处理函数的对象,详见下文;puppeteer(可选):从puppeteer-core导入的puppeteer实例,用于覆盖插件默认使用的puppeteer-core版本。
onMessage的键即测试中cy.puppeteer(key)调用的目标名。处理函数运行在 Node.js 而非浏览器中,因此函数体内不能使用 Cypress 命令和 DOM API。每个处理函数接收:
browser:连接到 Cypress 所启动浏览器的 PuppeteerBrowser实例;...args:测试中cy.puppeteer()传入的其余参数(已反序列化)。
retry(functionToRetry[, options])(Cypress 配置侧)
Puppeteer 代码常在"目标页面尚未打开/加载完成"的时机运行,retry用于对这类可能初次失败的函数进行重试:
retry(functionToRetry[, options])functionToRetry(必填):若抛错则继续按间隔重试,若不抛错则返回其结果;timeout(可选):总重试时长(毫秒),默认4000;delayBetweenTries(可选):两次尝试之间的等待时间(毫秒),默认200。
默认值与行为可从 src/plugin/retry.ts 确认:超时后抛出Failed retrying after ${timeout}ms: ${err.message}。
cy.puppeteer(messageName[, ...args])(Spec 侧)
cy.puppeteer(messageName[, ...args])messageName(必填):与配置中onMessage的某个键一致;...args(可选):传入处理函数的值,必须 JSON 可序列化。返回值(若有)即为该命令链的产出,可直接接.should()断言。
// spec cy.puppeteer('testNewTab', 'value 1', 42, [true, false]) // Cypress config setup({ on, onMessage: { testNewTab (browser, stringArg, numberArg, arrayOfBooleans) { // stringArg === 'value 1' // numberArg === 42 // arrayOfBooleans[0] === true / arrayOfBooleans[1] === false } } })完整示例:多标签页测试
以下两个示例与仓库内的真实测试 cypress/e2e/multi-tab.cy.ts 及其配套 cypress.config.ts 一致,fixture 页面位于npm/puppeteer/cypress/fixtures/下。示例中虽然使用标签页,但同理适用于窗口——在 Puppeteer 看来标签页与窗口都由Page类封装,本质相同。
示例一:切换到 Cypress 测试打开的新标签页
演示要点:
- 切换到 Cypress 测试动作打开的标签页;
- 用
retry获取新标签页的Page实例; - 通过 Puppeteer 获取页面引用与内容;
- 把内容传回 Cypress 断言。
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') })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 () => { // 浏览器中最终会有两个标签页:Cypress 标签页与新打开的标签页 // 在 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 实例 return page }) // Cypress 始终聚焦主标签页,把目标页面带到前台再操作更稳妥 await page.bringToFront() const paragraph = (await page.waitForSelector('p'))! const paragraphText = await page.evaluate((el) => el.textContent, paragraph) // 清理 ElementHandle 引用 paragraph.dispose() await page.close() // 返回值即 cy.puppeteer() 链的产出 return paragraphText }, }, }) }, }, })示例二:由 Puppeteer 主动创建新标签页
演示要点:
- 向
setup传入自定义的puppeteer实例覆盖默认版本; - 从
cy.puppeteer()向消息处理函数传参; - 通过 Puppeteer 创建新标签页并访问页面;
- 取回内容在 Cypress 中断言。
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') }) })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 来自 spec 中 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() // 返回值即 cy.puppeteer() 链的产出 return paragraphText }, }, }) }, }, })仓库自带的 cypress.config.ts 额外用 Express 在 8000 端口静态托管cypress/目录以提供 fixture 页面;你自己的项目中通常已有baseUrl或 dev server,直接复用即可。
故障排查
错误:Cannot communicate with the Cypress Chrome extension
若命令日志出现 "Cannot communicate with the Cypress Chrome extension. Ensure the extension is enabled when using the Puppeteer plugin.",说明插件无法与 Cypress 扩展通信。该扩展的作用是在 open 模式(有头运行)下,Puppeteer 命令执行后重新激活 Cypress 主标签页。排查方向:
- 如果你在有头模式使用 Chrome 品牌浏览器且版本 137+,请改用 Chrome for Testing 或 Chromium;
- 访问
chrome://extensions/确认 Cypress 扩展在 Cypress 启动的那个 Chrome 实例中已启用; - 若企业安全策略拦截了扩展,请通过扩展 ID
caljajdfkjjjdehjdoimjkkakekklcck放行。
其他从源码可预见的报错
Lost the reference to the browser:通常发生在 Cypress 配置被热重载但浏览器未重新启动时,关闭并重新打开浏览器即可;Could not find message handler with the name ...:cy.puppeteer()传入的名字与onMessage键不匹配,错误信息会列出所有已注册的处理器名;Only browsers in the "Chromium" family are supported:当前浏览器不是 Chromium 系。
构建、测试与验证
本插件目录(npm/puppeteer)内可直接执行以下命令(对应 package.json 的scripts):
yarn build # 编译 TypeScript 到 dist/ yarn watch # 监听并增量重建 yarn cypress:open # 打开 Cypress 交互式测试(即 multi-tab 示例) yarn cypress:run # 一次性运行 Cypress 测试(默认 --browser chrome) yarn test # 运行 vitest 单元测试 yarn test-watch # watch 模式运行单元测试单元测试覆盖三个核心模块:test/unit/setup.spec.ts(参数校验与任务注册行为)、test/unit/retry.spec.ts(默认 4000ms/200ms 的重试语义)、test/unit/activateMainTab.spec.ts(扩展激活消息与超时逻辑)。该插件目前处于 public beta,仓库鼓励使用者反馈以持续改进;版本变更记录见 CHANGELOG.md。
【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考