ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

@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/cypresscypress/puppeteer是 Cypress 官方仓库npm/puppeteer子包中提供的公开测试版 npm 插件它通过 Chrome DevTools ProtocolCDP把 Puppeteer 的浏览器 API 引入 Cypress 测试命令让团队可以在既有 Cypress 测试旁使用 PDF 生成、多标签页切换、更底层的浏览器控制等 Puppeteer 能力。阅读完本文你将掌握该插件的进程架构、setup/cy.puppeteer()/retry三件套 API、Node 端消息处理器与浏览器端命令的数据流并能直接复刻仓库中切换新标签页创建新标签页两套可运行示例与完整开发调试命令。插件定位为何需要在 Cypress 中使用 PuppeteerCypress 的 DOM 自动化能力强大但在以下场景中会力不从心由应用代码触发非测试代码window.open打开的新标签页或新窗口通过 Cypress 命令难以触达的浏览器原生行为需要生成 PDF、管理下载、操作 DevTools 协议层能力的高级浏览器控制。cypress/puppeteer正是为此而生它不要求你放弃 Cypress而是把 Puppeteer 连接到Cypress 自己启动的浏览器实例上让你在断言依旧由 Cypress 完成的前提下用 Puppeteer API 弥补 Cypress 的覆盖盲区。该包当前处于public beta仓库内版本号由 semantic-release 管理见 package.jsonREADME 与 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 open与cypress 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发布内容仅包含dist与support两个目录其中support/下预置了已编译的index.js与index.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()的消息传递模型一致。完整调用链如下测试代码调用cy.puppeteer(messageName, ...args)命令名与参数均为字符串/可序列化数据浏览器端支持命令 src/support/index.ts 内部透传执行cy.task(__cypressPuppeteer__, { name, args }, { log: false })Node 端 setup.ts 注册了同名 task校验后通过puppeteer.connect({ browserWSEndpoint: debuggerUrl })连上 Cypress 启动的浏览器再调用onMessage中对应的消息处理器处理器的返回值回到浏览器端作为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.tsundefined 归一化cy.task()在返回undefined时会报错因此 Node 端在处理器无返回值时统一转为null见 setup.ts。API 详解setup(options) —— Cypress 配置端注册在cypress.config.ts的setupNodeEvents(on)中调用用于注册运行 Puppeteer 自动化的消息处理器import { setup } from cypress/puppeteer export default defineConfig({ e2e: { setupNodeEvents (on) { setup({ on, onMessage: { async myMessageHander (browser) { // 利用 Puppeteer browser 实例操作浏览器 }, }, }) }, }, })选项选项必填说明on是setupNodeEvents提供的on事件注册函数onMessage是键为字符串、值为函数的对象详见下节puppeteer否从puppeteer-core导入的 Puppeteer 库实例覆盖插件默认版本setup对参数做了严格校验options 必须是普通对象、on必须是函数、onMessage必须是普通对象否则抛出错误见 setup.ts。当未传puppeteer时默认使用插件依赖的puppeteer-corepackage.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.tsretry(async () { // 若抛错则按间隔重试成功则返回其返回值 }, { timeout: 4000, // 总超时默认 4000ms delayBetweenTries: 200, // 重试间隔默认 200ms })从源码看retry的实现是递归makeAttempt每次尝试抛错就等待delayBetweenTries累计时间达到timeout后抛出Failed retrying after ${timeout}ms: ${err.message}。cy.puppeteer(messageName[, ...args]) —— 测试侧命令messageName必填字符串须与setup的onMessage某个键一致...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.tsimport { 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 retryPromisePage(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) // 找到则返回 pageretry 会原样返回它 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.tsit(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.tsimport { 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.tsit(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.html到page-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脚本底层是 vitestvitest run。仓库内的集成测试文件实际名为 multi-tab.cy.ts运行时可用--spec cypress/e2e/multi-tab.cy.ts精确定位。易错点小结综合文档与源码使用该插件时请留意处理器是 Node 代码onMessage函数体里不能使用 Cypress 命令、cy.*或 DOM API有头 Chrome 137 不可用headed 场景改选 Electron / Chrome for Testing / Chromium需要扩展配合open/headed 模式下主标签回收依赖 Cypress Chrome 扩展禁用会触发明确报错善用retry新标签页加载有竞态先轮询browser.pages()匹配 URL 再交互是最稳的写法关闭与清理示例中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),仅供参考
返回列表