Cypress Runner 包深度解析职责边界、构建管线与被 packages/app 取代的演进路径【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress导读packages/runner是 Cypress 桌面应用Desktop App中负责测试运行器界面的核心构建包它把测试驱动引擎packages/driver与结果报告器packages/reporter通过 webpack 打包成浏览器可执行的cypress_runner.js同时负责注入到被测应用AUT内部、用于拦截网络与包装定时器的启动脚本。随着 Cypress 将界面迁移到packages/app该包已进入退役维护状态。阅读本文你将掌握 runner 的模块职责、四份 webpack 产物bundle的生成逻辑、AUT 注入脚本的实现细节以及理解其隐式依赖如何在 Nx 构建缓存中正确失效。一、Runner 是什么一个处于退役期的构建型包packages/runner是 Cypress 仓库中的一个旧包官方文档明确说明它已被packages/app取代deprecated in favor ofpackages/app。从代码结构看它不再承担 UI 逻辑的开发而是被保留下来专门处理无法直接迁移的历史包袱其核心价值集中在构建期而非运行期。在仓库目录结构上packages/runner 下主要包含四类内容src/运行器主界面的源码但大部分只是样式文件app.scss、header.scss、iframe.scss等唯一真正有逻辑的是入口src/index.js与src/main.jsx以及unified-runner.tsxinjection/会被注入到 AUTApplication Under Testhead中的脚本按主源/次源cross-origin分为两个入口static/index.html运行器页面的宿主 HTML 模板以模板占位符如{{namespace}}、{{base64Config}}的方式被服务端渲染webpack.config.ts定义该包全部打包产物的构建配置。在架构上它是一个典型的胶水/打包器角色自身几乎不包含业务实现真正的实现分布在被它引用的下游包中。退役前仍需保留的五项职责根据 packages/runner/README.md在被完全移除之前该包还剩五项需要处理的职责恰好对应它存在的全部理由用 webpack 打包packages/reporter与packages/driver一旦这两个包可以被packages/app直接导入例如通过 Vite 的 dev server本项职责即可解除。打包packages/reporter的样式这些样式统一在main.scss中汇总加载。理想情况下reporter 应当自己引入自身样式而非由 runner 代为处理。承载dom.js该文件依赖私有的 webpack loader难以被packages/app使用的 Vite 直接导入一旦dom.js摆脱对 webpack 专用 loader 的依赖就应迁移到packages/app。承载 Cypress Studio Recorder 代码这部分功能在 Cypress 9.x 中标记为实验性experimental不会在 Cypress 10.x 初期进入产品计划后续再回归上游讨论见 cypress-io/cypress issue #22870目前该代码暂存于此且尚未在 app 中被使用。遗留的旧版 Cypress 样式Legacy Cypress styles大部分可以删除属于待清理的历史样式资产。二、构建管线四个 bundle 是如何生成的2.1 构建命令与脚本入口packages/runner的构建命令定义在 packages/runner/package.json 中prebuild: rimraf ./dist, build: node ../../scripts/run-webpack, build-prod: cross-env NODE_ENVproduction yarn build, watch: node ../../scripts/run-webpack --watch --progressbuild通过仓库统一的scripts/run-webpack驱动 webpack产物输出到dist/files: [dist]build-prod会先注入NODE_ENVproduction再执行构建watch以监听模式运行--progress输出构建进度值得注意cypress:open与cypress:run脚本已被改为打印提示并exit 1明确告知测试已迁移到 packages/app——这从包脚本层面印证了 README 中的退役声明。2.2 webpack 配置中的四个产物构建的核心实现在 packages/runner/webpack.config.ts该文件通过getCommonConfig()、getSimpleConfig()与getCopyWebpackPlugin()复用packages/web-config的公共配置最终导出四个配置对象对应四个产物产物文件名入口用途关键配置cypress_runner.jssrc/index.js主运行器 UI bundle含 driver 与 reporter继承commonConfigaliasbluebird/lodash复制 faviconcypress_cross_origin_runner.jssrc/cross-origin.js跨域次源运行器 UI bundle同上injection.jsinjection/main.js注入主源 AUT 的脚本使用getSimpleConfig()mode: productioninjection_cross_origin.jsinjection/cross-origin.js注入跨域 AUT 的脚本同上其中src/index.js的内容极其精简只做两件事import ./main.scss import ./main.jsx也就是导入全部样式并执行main.jsx——把UnifiedRunner对象挂到全局window上import { UnifiedRunner } from ../unified-runner window.UnifiedRunner UnifiedRunner2.3 babel-plugin-prismjs 与资源复制webpack.config.ts中还做了两件环境相关的事为 babel-loader 注入 prismjs 插件查找公共配置中的 babel-loader 规则向其 options.plugins 追加babel-plugin-prismjs支持javascript、typescript、jsx、tsx四种语言高亮并启用line-numbers、line-highlight两个插件css: false表示样式不内联。复制 favicon通过waitUntilIconsBuilt()等待packages/icons构建完成后再读取getPathToFavicon(favicon.ico)用CopyWebpackPlugin将图标复制到dist/。该等待逻辑存在于仓库 scripts/ensure-icons用于规避跨包构建时的竞态条件。2.4 static/index.html 中的加载协议主运行器页面由 packages/runner/static/index.html 提供骨架。它是模板文件运行时由服务端把{{projectName}}、{{namespace}}、{{base64Config}}等占位符替换为实际值关键加载顺序如下link relstylesheet href/{{namespace}}/runner/cypress_runner.css div idapp/div script typetext/javascript src/{{namespace}}/runner/cypress_runner.js/script script typetext/javascript window.__Cypress__ true setTimeout(function () { Runner.start(document.getElementById(app), {{base64Config | safe}}) }, 0) /script可以看到先加载样式与主 bundle随后调用Runner.start(app 容器, base64 编码后的配置)完成启动配置以 base64 字符串传入规避模板转义问题模板过滤器safe。同时设置window.__Cypress__ true作为处于 Cypress 顶层窗口的全局标记。2.5 UnifiedRunner主 bundle 的对外契约bundler 主入口 以 React 18 的createRootAPI 为标准将以下模块聚合为一个UnifiedRunner对象export const UnifiedRunner { CypressJQuery: $Cypress.$, // driver 内部的 jQuery CypressDriver: $Cypress, // packages/driver 整体 shortcuts, // reporter 的键盘快捷键 setReporterDocument, // reporter 文档环境设置 React, MobX, // reporter 的响应式状态库 ReactDOM: { createRoot }, Reporter, // packages/reporter 主组件 }其中Reporter、shortcuts、setReporterDocument直接来自packages/reporter/src$Cypress来自packages/driver。这从源码层面印证了 README 所述将 driver 与 reporter 打包进 dist/cypress_runner.js的事实——unified-runner.tsx就是 webpack 入口src/index.js → src/main.jsx链路中真正的模块汇合点。三、样式汇总main.scss 的 Sass 打包策略packages/runner/src/main.scss 承担了该包打包样式的职责其加载策略非常典型import lib/variables; import lib/mixins; import lib/fonts; import lib/base; // 导入 src 下除 lib 与 main 之外的全部 scss 文件 import ../../runner/src/!(lib)*/**/!(assertions-menu|main).scss; // 汇总 reporter 的样式入口 import ../../reporter/src/main-runner;要点在于首先引入公共基础层变量variables.scss、mixinmixins.scss、字体fonts.scss与基础样式base.scss随后通过 Sass 的 glob 语法与负向匹配把src/下除lib/目录及main、assertions-menu之外的所有组件级样式一次性纳入!(lib)*、!(assertions-menu|main)最后直接跨包引入packages/reporter的样式入口main-runner。这也解释了 README 中Bundles styles for packages/reporter, loaded in main.scss的表述runner 的样式打包本身依赖对 reporter 目录的跨包 Sass 导入属于需要被反向迁移reporter 自引样式的耦合点。四、AUT 注入脚本主源与跨域两份 injection除了界面 bundlerunner 还通过 webpack 产出两类注入脚本它们被packages/proxy注入到被测应用AUT的head中运行。源码注释明确要求此 bundle 必须保持轻量、尽量不引入依赖。4.1 主源注入injection/main.jspackages/runner/injection/main.js 面向主源primary originAUT逻辑依次为获取全局 Cypressconst Cypress window.Cypress parent.Cypress若父窗口缺失 Cypress 则直接抛错提示这是不应发生的 bug补丁 XMLHttpRequest调用patchXmlHttpRequest(window)移除 SRI 属性当Cypress.config(removeSRIAttributes)开启时调用patchElementIntegrity(window)剥离主 AUT 帧中script/link的 SRI子资源完整性属性避免被 proxy 改写后的同源资源因摘要不匹配而被浏览器拦截跨域帧由injection/cross-origin.js另行处理包装定时器createTimers()的封装必须在 AUT 内完成而不是在 driver 中——因为若在顶层帧包装定时器回调里的未捕获错误会被顶层errorhandler 捕获而无法正确归属到 AUT随后通过Cypress.on(app:timers:reset / app:timers:pause)订阅重置与暂停事件通知 driverCypress.action(app:window:before:load, window)。4.2 跨域注入injection/cross-origin.jspackages/runner/injection/cross-origin.js 面向跨域secondary originAUT复杂度更高查找 Cypress 桥findCypress()遍历window.parent.frames找到既暴露Cypress且window.location.origin与当前帧 origin 一致的帧访问跨域帧抛出的SecurityErrorDOMException: Blocked a frame from accessing a cross-origin frame会被捕获并忽略消息回显监听aut:cypress:location消息并通过MessageChannelevent.ports[0]回传当前 URL卸载通知beforeunload时向parent广播cross:origin:before:unload即使对应 spec bridge 尚未创建也要让主 Cypress 实例感知卸载错误上报监听error事件把包含 href、message、stack 的错误数据以cross:origin:aut:throw:error消息投递给window.topCookie 模拟与反检测patchDocumentCookie(cypressConfig.simulatedCookies)打补丁当modifyObstructiveCode开启时把window.frameElement的 getter 改为返回null伪装成未被 iframe 包裹当modifyObstructiveThirdPartyCode开启时同样剥离 SRI 属性网络补丁patchFetch(window)、patchXmlHttpRequest(window)并暴露window.__attachToCypress(Cypress)供 spec bridge 附着后调用该函数会自毁即delete window.__attachToCypress一次性附着若已能找到Cypress Cypress.cy立即执行附着并触发app:window:before:load同时兼顾避免竞争条件race condition。对比两份脚本可以发现设计原则主源直接信任parent.Cypress而跨域场景则通过逐帧探测 postMessage 通信来建立连接这正是 Cypress 支持跨域测试cy.origin的底层注入基础之一。五、隐式依赖与 Nx 构建缓存失效5.1 为什么必须声明隐式依赖packages/runner/README.md 明确列出三类隐式依赖且它们在 packages/runner/package.json 中有双重体现devDependencies: { packages/driver: 0.0.0-development, packages/errors: 0.0.0-development, packages/reporter: 0.0.0-development, ... }, nx: { implicitDependencies: [ packages/driver, packages/reporter, packages/config ] }devDependencies中的packages/driver、packages/reporter等属于常规的包级依赖nx.implicitDependencies额外声明了packages/driver、packages/reporter、packages/config三者与 runner 的源码级耦合。之所以需要这份声明是因为 runner 的构建并不是 import 这三个包编译后的产物而是通过 webpack/Sass 直接把它们的源文件打进dist/cypress_runner.js例如main.scss直接import ../../reporter/src/main-runnerunified-runner.tsx直接import { Reporter } from packages/reporter/src/main。这种越级引用源码的关系任务依赖图project graph无法自动发现。5.2 对构建行为的实际影响在 Nx 中implicitDependencies意味着当packages/driver、packages/reporter、packages/config中任意一个包的源文件发生变化时runner 的构建缓存必须被视为失效并触发重建。否则CI 或本地增量构建可能复用旧的cypress_runner.js导致界面运行的仍是修改前的 driver/reporter 代码——这是一类难以排查的幽灵缓存问题。理解该机制对于在 Cypress monorepo 中改 driver/reporter 代码后为何 runner 会被重新构建非常有帮助。从仓库现状看nx.json与lerna.json并存runner 的这份nx配置正是为了让 Nx 感知这类 webpack 源码打包型依赖而存在的。六、演进方向迁移到 packages/app 的路线图综合 README 与源码可以梳理出 runner 的完整退役路径UI 与组件runner 已不再承载 UI 测试其cypress:open/cypress:run脚本直接报错退出测试已迁至packages/appdriver/reporter 的打包职责待packages/app能以 Vite 直接导入二者后解除——packages/app位于 packages/app它同时包含 Vue 组件src/*.vue、TSX 组件与自身的 Vite 配置正是新一代界面的宿主样式职责main.scss对packages/reporter/src/main-runner的跨包导入应改为 reporter 自引样式dom.js迁移待其摆脱 webpack 专用 loader 后移入packages/appStudio Recorder 与 Legacy 样式前者等待功能回归时机后者大部分直接清理。值得注意的是Runner 在 UI 层甚至已经存在与 app 对齐的接口雏形unified-runner.tsx已采用 React 18createRoot、暴露Reporter/ReactDOM.createRoot等标准对象使主 bundle 能以统一方式被宿主页面挂载这为后续被 app 完全接管铺平了接口层面的道路。七、小结packages/runner是 Cypress monorepo 中一个承上启下的构建型包它以 webpack 为枢纽将 driver、reporter 及其样式汇总为运行器页面可直接加载的cypress_runner.js并提供注入 AUT 的主源/跨域两套启动脚本支撑 Cypress 的网络拦截、定时器控制与跨域测试能力。其退场不是废弃而是能力向packages/app的有序移交。理解该包的职责边界、四产物构建管线、隐式依赖语义与迁移路线能帮助你在修改packages/driver或packages/reporter源码后正确判断为何需要重建 runner阅读 packages/runner/webpack.config.ts 快速定位产物入口通过 packages/runner/injection/main.js 与 injection/cross-origin.js 深入理解 Cypress 对被测应用的注入机制。延伸阅读对运行器注入与驱动细节感兴趣可继续阅读 packages/driver 与 packages/proxy 的 README若关注新一代界面实现则直接进入 packages/app 目录。其中 packages/runner/package.json 的nx.implicitDependencies配置、packages/runner/webpack.config.ts 的四产物定义以及 packages/runner/static/index.html 的加载协议是继续排查运行器相关问题时的三个关键入口。【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考