Cypress Studio 本地开发指南:基于 CYPRESS_LOCAL_STUDIO_PATH 的本地联调、类型同步与 Cypress-in-Cypress 测试
【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress
Cypress 的 Studio 是随 Cypress Cloud 一起交付的云功能,生产环境中其 bundle 在运行时从 Cloud 拉取,而非内置于本地发行版。本篇指南基于仓库内的 guides/studio-development.md 完整梳理 Studio 的本地开发工作流:如何把cypress-services仓库中本地构建的 Studio bundle 接入cypress仓库运行、如何通过yarn gulp downloadStudioTypes同步 Studio 提供的 TypeScript 类型,以及如何用 Cypress-in-Cypress(CIC)加云请求 mock 的方式对 Studio 做端到端测试。读完你可以独立完成 Studio 的本地联调,并理解服务端StudioLifecycleManager在本地模式下跳过的校验与热重载机制。
Studio 的交付模型:Cloud 运行时下发 bundle
理解本地开发流程的前提,是理解 Studio 的交付模型。在生产环境中,Studio bundle 不是打包进 Cypress 发行版的,而是应用启动时从 Cypress Cloud 获取的。对应到源码,这一流程由 StudioLifecycleManager 驱动:
- 先调用
postStudioSession向 Cloud 申请一个 Studio 会话,拿到studioUrl与protocolUrl(见 StudioLifecycleManager.ts#L195-L198); - 用
ensureStudioBundle按 URL 中的 hash 下载并缓存 bundle,随后读取 bundle 内server/index.js,并通过 SHA-256 与 manifest 中的期望值比对来校验完整性(StudioLifecycleManager.ts#L241-L256); - 把脚本交给 StudioManager 的
setup()加载为 Studio server,并准备 Protocol 管理器供 AI 能力使用(StudioLifecycleManager.ts#L258-L308)。
正因为 bundle 来自 Cloud,本地开发时就需要一个开关让服务端改用本地构建的 Studio——这正是CYPRESS_LOCAL_STUDIO_PATH的作用。
本地联调流程:运行在本地构建的 Studio 上
第一步:准备 cypress-services 中的本地 Studio 构建
Studio 的产品代码不在这个仓库,而在cypress-services仓库中(克隆该仓库需要 Cypress 组织成员权限)。准备工作如下:
- 克隆
cypress-services仓库; - 在仓库根目录执行
yarn; - 在
app/packages/studio目录执行yarn watch,开启构建监听; - 如果你同时要针对本地运行的
cypress-services后端开发,先在cypress-services根目录执行yarn dev。
yarn watch的产物目录即cypress-services/app/packages/studio/dist/development,它是下一步环境变量要指向的路径。
第二步:设置环境变量
| 环境变量 | 取值说明 |
|---|---|
CYPRESS_INTERNAL_ENV | 指定 Cloud 环境:staging、production,或指向本地cypress-services实例的development |
CYPRESS_LOCAL_STUDIO_PATH | 指向本地 Studio 构建目录,即cypress-services/app/packages/studio/dist/development;一旦设置,将覆盖(override)来自 Cloud 的 bundle |
需要注意CYPRESS_INTERNAL_ENV是保留变量:CLI 在启动时会校验其取值合法性,非法值会直接报出 “The environment variable with the reserved name 'CYPRESS_INTERNAL_ENV' is set” 错误,而非staging/production之外的合法取值也会打印警告提示(校验逻辑见 cli/lib/cli.ts#L444-L462,行为快照见 cli/test/lib/snapshots/cli.spec.ts.snap)。在 gulp 侧,该变量未设置时默认按production处理(scripts/gulp/tasks/gulpCloudDeliveredTypes.ts#L1)。
第三步:在 cypress 仓库中启动并进入 Studio
- 在
cypress仓库执行yarn和yarn cypress:open; - 在应用中登录 Cypress Cloud;
- 使用一个已启用 Studio 的项目(例如 Cypress (staging) 或 Cypress Internal Org 下为该项目开启了
studio-ai特性的项目); - 打开一个包含 E2E 测试的项目,从某条测试上使用 “Add Commands to Test” 进入 Studio。
本地模式的行为差异(源码视角)
CYPRESS_LOCAL_STUDIO_PATH设置后,服务端行为有几处关键差异,都可在源码中验证:
- 跳过 bundle 下载与完整性校验。
createStudioManager中,若该变量已设置,则直接取studioPath = process.env.CYPRESS_LOCAL_STUDIO_PATH、studioHash = 'local'、manifest = {},不再走postStudioSession/ensureStudioBundle分支(StudioLifecycleManager.ts#L203-L233);StudioManager.setup()里传给 Studio server 的verifyHash回调也会直接返回true,注释明确写道 “If we are running locally, we don't need to verify the signature”(studio.ts#L41-L51)。 - 热重载本地 Studio 代码。
setupWatcher会用 chokidar 监听CYPRESS_LOCAL_STUDIO_PATH/server/index.js的变化,一旦检测到修改(awaitWriteFinish: true),先destroy()旧的 StudioManager,再用相同参数重建,并打印Studio manager reloaded(StudioLifecycleManager.ts#L347-L400)。这就是指南所说 “无需重启应用即可修改本地 Studio 代码”的机制;同时本地模式下 studio-ready 监听器不会被清空,以便重载后再次回调(StudioLifecycleManager.ts#L335-L340)。 - 绕过错误上报。指南原文说明:设置
CYPRESS_LOCAL_STUDIO_PATH或直接从本地克隆运行应用时,错误上报(error reporting)会被绕过,错误改为输出到浏览器或 Node 控制台,方便本地调试时直接看到堆栈。
同步 Studio 类型到代码库
Studio bundle 同时提供了app与server两个方向的类型定义,Cypress 代码库需要把它们纳入来做类型检查。指南给出的命令是:
yarn gulp downloadStudioTypes若希望类型来自本地cypress-services仓库而不是 Cloud,则加上环境变量:
CYPRESS_LOCAL_STUDIO_PATH=<path-to-cypress-services/app/packages/studio/dist/development-directory> yarn gulp downloadStudioTypes从实现看(scripts/gulp/tasks/gulpCloudDeliveredTypes.ts#L100-L112),该任务的行为是:
- 未设置
CYPRESS_LOCAL_STUDIO_PATH时:调用postStudioSession(使用固定演示项目ypt4pf)拿到studioUrl,再通过ensureStudioBundle把 bundle 下载到系统临时目录<tmpdir>/cypress/studio/<hash>(hash 从 URL 末段解析,见 getBundlePath); - 设置了该变量时:跳过网络请求,直接从本地目录拷贝类型文件;
- 类型文件映射(createTypeMappings):
| bundle 内源文件 | 拷贝目标 |
|---|---|
app/types.ts | packages/app/src/studio/studio-app-types.ts |
server/types.ts | packages/types/src/studio/studio-server-types.ts |
同一文件中的downloadPromptTypes任务采用完全相同的机制服务于cy-prompt(对应环境变量CYPRESS_LOCAL_CY_PROMPT_PATH,映射三个类型文件到packages/app、packages/driver、packages/types下的 prompt 目录),可见这是 Cloud 下发 bundle 的类型同步的通用做法(gulpCloudDeliveredTypes.ts#L114-L126)。
测试 Studio
单元/组件测试
支持云 Studio 且位于cypressmonorepo 中的代码(例如上文提到的StudioLifecycleManager、StudioManager),其单元与组件测试和仓库其余代码保持一致的风格与运行方式,具体细节可参考本仓库的 CONTRIBUTING.md。位于cypress-servicesmonorepo 的 Studio 代码,则有自己的伴随单元/组件测试,随代码存放于该仓库内。
Cypress-in-Cypress:launchStudio 辅助函数
用 Cypress 测试 Studio 的入口辅助函数位于 packages/app/cypress/e2e/studio/helper.ts。其中launchStudio按文档描述完成四步(实现见 helper.ts#L27-L66):
- 加载项目——默认是
studiofixture 项目(projectName = 'studio'); - 导航到目标 spec——默认
specName.cy.js; - 进入 Studio:可以是已有测试(点击 runnable 上的
launch-studio按钮),也可以通过createNewTestFromSuite/createNewTestFromSpecHeader参数从套件或 spec 头部 “新建测试” 进入; - 等待 Studio 模式下的测试运行结束。
默认参数之外,helper 还提供cliArgs、shouldLogin、afterLaunch钩子,以及loadProjectAndRunSpec、inputNewTestName、incrementCounter等配套函数,典型用法见 packages/app/cypress/e2e/studio/studio-ui.cy.ts。
这些 CIC 测试默认消费的是Cloud 提供的 Studio bundle。studiofixture 项目使用 canary 性质的 projectId,从而能拿到最新的 Cloud Studio 构建——当前该项目的 cypress.config.js 中projectId为n69px6(与下文 mock URL 中的前缀一致)。如果你希望 CIC 测试改用本地 Studio 构建,把process.env.CYPRESS_LOCAL_STUDIO_PATH设为本地 studio 构建目录即可;Studio 的启用开关位于 packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts。
Mock 云交互:cy.mockNodeCloudRequest
要真正触发 Studio AI,需要 Cloud 侧的可用性接口。测试中通过 mock Node 端发出的 Cloud 请求来模拟,例如声明 “该项目的 testgen 已启用”:
cy.mockNodeCloudRequest({ url: '/studio/testgen/n69px6/enabled', method: 'get', body: { enabled: true }, })该命令定义在 packages/frontend-shared/cypress/support/e2e.ts#L657,最终走到 e2e 插件任务的__internal_mockNodeCloudRequest:它用 nock 拦截https://cloud.cypress.io的对应 method + URL 请求并返回 200 与指定 body(e2ePluginSetup.ts#L489-L501)。
Mock AI 输出:固定 generate 结果
为保证每次运行 AI 调用结果一致,测试同样模拟生成接口:
const aiOutput = 'cy.get(\'button\').should(\'have.text\', \'Increment\')' cy.mockNodeCloudRequest({ url: '/studio/testgen/n69px6/generate', method: 'post', body: { recommendations: [{ content: aiOutput }] }, })需要强调文档中给出的结论:上面两个 helper 拦截的是Node 侧的请求,因此这些 CIC 测试仍然真实覆盖了浏览器与 Node 之间的接口,只是把 “Node → Cloud” 这一段替换成了确定性的桩。
Mock Protocol 全量快照:cy.mockStudioFullSnapshot
还有一个特殊问题:在 Cypress-in-Cypress 的内层 Cypress 中,真实的 Protocol(CDP 协议)无法正常工作。从源码看,__internal_openProject会把process.env.CYPRESS_LOCAL_PROTOCOL_PATH指向一个空操作的 dummy protocol(e2ePluginSetup.ts#L438-L440,dummy 文件为packages/frontend-shared/cypress/fixtures/dummy-protocol.js)。由此,需要人工构造一个“将发给 AI 的 CDP 全量快照”来替代真实采集,即:
cy.mockStudioFullSnapshot({ id: 1, nodeType: 1, nodeName: 'div', localName: 'div', nodeValue: 'div', children: [], shadowRoots: [], })其实现是把快照 JSON 写入环境变量CYPRESS_IN_CYPRESS_MOCK_FULL_SNAPSHOT(e2ePluginSetup.ts#L481-L488),命令挂载点在 packages/frontend-shared/cypress/support/e2e.ts#L656。此外,若需要模拟 Cloud 的流式响应(当前仅 Studio AI 交互使用 SSE 流),插件还提供__internal_mockNodeCloudStreamingRequest,它会以text/event-stream内容类型回放event: chunk/event: end事件流(e2ePluginSetup.ts#L502-L530)。
小结
| 目标 | 关键手段 | 源码/配置佐证 |
|---|---|---|
| 本地运行 Studio 构建 | 设置CYPRESS_LOCAL_STUDIO_PATH指向dist/development,yarn cypress:open | StudioLifecycleManager.ts#L229-L233 |
| 跳过 bundle 校验、热重载 | 本地模式下verifyHash恒真、chokidar 监听server/index.js | studio.ts#L41-L51、StudioLifecycleManager.ts#L379-L399 |
| 同步 app/server 类型 | yarn gulp downloadStudioTypes(可带CYPRESS_LOCAL_STUDIO_PATH走本地) | gulpCloudDeliveredTypes.ts#L100-L112 |
| CIC 进入 Studio | launchStudiohelper +studiofixture 项目(projectIdn69px6) | helper.ts、cypress.config.js |
| 固定 AI 行为 | cy.mockNodeCloudRequest/cy.mockNodeCloudStreamingRequest/cy.mockStudioFullSnapshot | e2ePluginSetup.ts#L481-L530 |
以上流程以当前仓库源码为准;其中cypress-services仓库相关步骤(克隆、yarn watch、yarn dev)依赖该私有仓库的实际目录结构,若其内部结构有调整,CYPRESS_LOCAL_STUDIO_PATH的指向路径需相应修改,但cypress仓库侧的消费逻辑(路径直连、跳过校验、热重载、类型映射)不受影响。
【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考