Metabase E2E 测试编写技能:从源码先行分析到 Cypress 用例生成的七阶段工作流 📅 发布时间:2026/9/8 21:35:58 👁 浏览次数: Metabase E2E 测试编写技能从源码先行分析到 Cypress 用例生成的七阶段工作流【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase本文以 Metabase 仓库中.claude/skills/e2e-test-create/下的 E2E 测试编写技能Skill文档为核心完整拆解其代码阅读优先Code-Reading-First的七阶段工作流如何从 React 组件源码提取选择器与用户流、如何启动后端与快照管理、如何按 Metabase Cypress 约定生成用例、以及 Playwright 兜底探查与清理机制。读完后你可以复现 Metabase 官方的 E2E 测试编写流程并理解其每一步背后的 runner 实现与约束来源。1. 技能定位这是一个给 Agent 的 E2E 测试生成规程e2e-test-create是 Metabase 仓库内置的一份 Claude Code 技能定义文件位于 .claude/skills/e2e-test-create/SKILL.md。它不是一段文档式的最佳实践建议而是一份可执行的 Agent 操作规程其 frontmatter 明确声明了运行边界name / description分析 React 组件源码以理解 UI 结构然后生成符合 Metabase 约定的 Cypress E2E 测试只有在读代码 截图调试都不够时才回退到 Playwright MCP 浏览器探查disable-model-invocation: true禁止模型自动调用该技能必须由用户显式触发allowed-tools仅允许Bash、Read、Write、Grep、Glob、Skill和mcp__playwright__*即该技能的全部动作被限定在读代码、跑命令、写文件、浏览器探查四类能力内。整个规程的组织方式是一条严格的单向流水线Phase 0 调研 → Phase 1 代码分析 → Phase 2 启动后端 → Phase 3 生成 Spec → Phase 4 验证 → Phase 5 修复最多 2 次→ Phase 6 Playwright 兜底 → Phase 7 清理。其中最核心的设计原则写在标题里——Code-Reading-First在生成任何测试代码之前必须先分析 React 组件源码来理解 DOM 结构、选择器和用户流浏览器探查永远是最后手段。2. Phase 0 — 调研先读三类共享资产在动笔写任何测试之前技能要求先完成三项调研读现有 helperse2e/support/helpers/下的全部共享辅助函数如restore、signInAs、openOrdersTable等。Metabase 的 E2E 用例几乎不裸写cy.visit()而是复用这些导航助手读表/字段 schema 常量e2e/support/cypress_sample_database中定义了示例数据库的表与字段常量ORDERS、PRODUCTS等。仓库中该模块的实际文件是 cypress_sample_database.js技能文档写作.ts导入时按模块路径不带扩展名引用读实例数据常量e2e/support/cypress_sample_instance_data存放实例级 ID如ORDERS_DASHBOARD_ID、NORMAL_USER_ID对应实际文件 cypress_sample_instance_data.js。这些 ID 是由快照机制生成并回填的而非手填。此外还要用 Glob 扫描e2e/test/scenarios/找到与被测功能最接近的现有 spec精确模仿其模式该目录下按 URL 结构镜像组织如dashboard、data-studio、admin等子目录再用 Glob 定位frontend/src/metabase/下对应功能区的 React 组件。3. Phase 1 — 代码分析从源码中提取选择器与用户流这一阶段的口号是No browser needed — source code has everything不需要浏览器源码里什么都有。具体做七件事定位组件Glob grepfrontend/src/metabase/找被测功能区的组件提取选择器在相关组件中 grepdata-testid记录可见文本读 JSX记下按钮标签、标题、占位符文案记录 aria 属性greparia-label理解用户流读事件处理器onClick、onSubmit、onChange理解交互链路找到 API 调用grepApi.use、fetch、useQuery以及端点定义识别出需要拦截intercept的 API 请求交叉参考现有 spec在同功能区的现有 spec 中复用已验证的选择器与cy.intercept模式。第 6 步与 Phase 3 直接呼应代码分析阶段识别出的 API 调用在生成用例时要用cy.intercept的 stub/wait 模式处理这是 Metabase 约定中API 时序控制的要求见第 5 节。4. Phase 2 — 启动后端版本选择、快照生成与数据恢复4.1 版本与启动命令技能规定默认使用MB_EDITIONoss开源版无需企业版 token、更快只有用户明确要求编写企业版测试时才用MB_EDITIONee。启动命令为MB_EDITIONoss bin/e2e-backend并要求用run_in_background: true后台运行而非符号。bin/e2e-backend会自动探测后端是否已在运行并直接复用。查看 bin/e2e-backend 的实现可以看到这一逻辑脚本先以PORT${MB_JETTY_PORT:-4000}取端口默认4000如果curl -sf http://localhost:$PORT/api/health成功就打印Backend already running — reusing it并直接exit 0否则exec node e2e/runner/start-backend.js。而 e2e/runner/start-backend.js 根据JAR_PATH环境变量决定是runFromJar对预编译 JAR 测试还是runFromSource从源码启动带热重载的 live 后端并在收到 SIGTERM/SIGINT 时调用backend.stop()清理。4.2 快照不要手动生成技能明确警告不要通过跑无关的测试 spec 来手动生成快照。原因是bun test-cypress这个 runner 默认GENERATE_SNAPSHOTS: true会在运行任何 spec 之前自动生成快照Phase 4 通过/e2e-test技能运行测试时如果快照不存在会在首次运行自动补上。这一点在 runner 源码 e2e/runner/run_cypress_local.ts 中得到印证默认选项为{ MB_EDITION: ee, CYPRESS_GUI: true, GENERATE_SNAPSHOTS: true }可被同名环境变量覆盖当GENERATE_SNAPSHOTS为真时会先rm -f e2e/support/cypress_sample_instance_data.json重置缓存再以e2e/support/cypress-snapshots.config.js配置无头运行一次 Cypress——快照的实际生产者就是e2e/snapshot-creators/下的 spec如 default.cy.snap.js 与qa-db.cy.snap.js。此外 runner 还会用docker compose -f ./e2e/test/scenarios/docker-compose.yml up -d拉起测试容器并检查前端 dev server默认端口MB_FRONTEND_DEV_PORT或8080是否在运行未运行则提示先执行bun run build-hot。4.3 恢复干净测试数据启动后端后用 testing API 恢复默认测试数据curl -sf -X POST http://localhost:4000/api/testing/restore/default这条恢复接口在后续 Phase 6 重新探查前也会再次调用保证每次浏览器探查都从相同的数据状态出发。5. Phase 3 — 生成 Cypress SpecMetabase 约定详解Phase 3 的内容通过文档引用./../_shared/cypress-conventions.md指向仓库中的共享约定文件 .claude/skills/_shared/cypress-conventions.md。该文件同时约束编写与评审两个场景是 Metabase E2E 风格的权威来源核心规则如下5.1 文件位置与命名spec 放在e2e/test/scenarios/area/目录结构镜像 URL 结构新 spec 优先用.cy.spec.ts现存大量.cy.spec.js仍然有效——不要在不相关工作里顺手转换旧.jsspecdescribe块命名模式area sub-area feature (#issue-number)。5.2 Helpers 与常量两条铁律所有 helper 通过const { H } cy;访问绝不从e2e/support/helpers直接 import。标准骨架const { H } cy; import { ORDERS_DASHBOARD_ID } from e2e/support/cypress_sample_instance_data; import { ORDERS, ORDERS_ID } from e2e/support/cypress_sample_database; describe(area sub-area feature (#issue-number), () { beforeEach(() { H.restore(); cy.signInAsAdmin(); }); it(should do the primary happy-path thing, () { // test }); });另一条铁律是永远不要硬编码数字 ID——即使是测试自己创建的实体。自增主键不稳定运行中更早的种子步骤可能把下一个 ID从 10 推到 11。正确做法是从创建响应里捕获并复用// Good — 捕获并使用 H.createDashboard({ name: My dashboard }).then(({ body: dashboard }) { cy.visit(/dashboard/${dashboard.id}); }); // Good — alias 拦截从响应里取 id cy.intercept(POST, /api/dashboard).as(createDashboard); // ...触发创建... cy.wait(createDashboard).its(response.body.id).then((id) { /* ... */ }); // Bad — 10 只是当时自增恰好落到的值 cy.visit(/dashboard/10);5.3 选择器优先级与禁止清单优先级从高到低a11y 查询cy.findByRole()/cy.findByLabelText()来自testing-library/cypress——顺带能捕获无障碍回归cy.findByText()——元素有稳定可见文本时使用cy.findByTestId()——对应data-testid其他data-*属性兜底。禁止使用cy.get([data-testid...])必须用findByTestId、CSS class尤其是 styled-components/Mantine 生成的、临时 CSS 属性选择器path[fill...]、XPath。图表测试的 ECharts 例外e2e/support/helpers/e2e-visual-tests-helpers.js 是唯一允许用裸 CSS 属性选择器深入渲染后图表 DOM 的文件——因为 ECharts 渲染的 SVG 没有data-testid且 a11y 面很小。其中的echartsContainer、goalLine、pieSliceWithColor、BoxPlot.*等 helper 是有意的例外写图表断言必须走这些 helper遇到没覆盖的图表模式应往该文件加新 helper而不是在 spec 里内联cy.get(path[fill...])。另外两条选择器细则位置选择器.eq(N)、.first()等只在顺序本身就是断言内容或紧挨着长度断言时使用metabase/no-unsafe-element-filteringlint 规则会对未加长度断言的.last()、负索引.eq()报警文本选择器必须限定作用域——顶层cy.findByText(...)/cy.contains(...)会匹配整个文档是典型的误匹配来源应使用cy.contains([roledialog], Save)、cy.findByRole(dialog).findByText(Save)或within链式限定。5.4within的三条规则within必须链在既有选择器之后someSelector().within(() {...})裸cy.within(...)没有作用域构造上就是错的回调里只有一条命令时不要套within直接链式即可——within只在两条及以上命令共享作用域时才值得不要给within回调命名参数within(($modal) ...)中的参数运行时永远用不到需要 jQuery 对象时应改用.then()。5.5 Setup、等待与时序Setup 走 API 不走 UI用cy.request()或现成 API helper 搭建前置状态UI 只驱动真正被测的那条流程永远不用数字cy.wait(ms)API 时序用先定义cy.intercept()、后触发动作、再cy.wait(alias)的模式DOM 就绪优先.should(be.visible)断言已渲染且用户可见.should(exist)只证明节点在 DOM 中不是就绪检查cy.intercept(POST, /api/dataset).as(dataset); // ...触发动作... cy.wait(dataset);5.6 永不给cy.*返回值赋值cy.*命令是异步入队返回的是chainer而非 DOM 节点/字符串/响应。const button cy.findByRole(...)之后button.click()是一个经典陷阱。正确姿势是.then()内使用解出的值或.as(alias)cy.get(alias)在测试后段引用。如果要给查询起名字方便读用函数而不是 const// Bad — 一次性 chainer之后使用不会重新查询、没有重试 const foo cy.findByText(Foo); foo.click(); // Good — 每次调用都入队一条全新查询带完整重试语义 const foo () cy.findByText(Foo); foo().click();cypress/no-assigning-return-valueslint 规则在 e2e 配置中已按 error 级别启用但 helper 返回值、解构、包装对象等间接形式仍需人工审查。5.7 断言与隔离只断言用户可见状态文本、URL、aria 属性不断言 DOM 结构expect()只出现在cy.then()/cy.wrap()回调里负断言必须配对正断言单独的should(not.exist)在 UI 还没渲染时就会碰巧通过。先断言页面处于预期状态某段文本可见、URL 正确、API 已 settle再断言不该出现的东西不存在对同一父容器的多个文本检查合并成一条断言链.should(contain, Foo).and(contain, Bar).and(not.contain, Baz)一次查询、一份重试预算、原子执行每个it()必须可独立运行不依赖前一个it()的状态状态重置用beforeEach()而不是before()当H.restore()与H.resetTestTable()同时出现时H.restore()必须在前由metabase/no-unordered-test-helpers规则强制。5.8 用cy.log()标注步骤而不是注释cy.log(...)与 JS 注释在源码中同样可读但失败时差距巨大它出现在 Cypress 命令面板、截图和视频的时间点中CI 失败截图能直接告诉你测试当时进行到哪一步而//注释在运行时被剥离、在任何失败产物中都不可见。注意对等的克制cy.log不应复述下一条自解释命令如cy.log(Visit dashboard); H.visitDashboard(id)是噪音应用于阶段标记、非显然的意图、长流程的分节标题。5.9 性能三原则不要把一条流程拆成大量小it()Cypress 单测试开销 框架自身的每测试装配/拆除此代码库经验值约 5–10 秒 你的beforeEachH.restore() 登录 导航又是数秒。拆成 8 个小测试就是 8 倍开销。先问一句只断言几个元素存在/可见的测试几乎总是错放在 E2E 里的单元测试——应移到 Jest React Testing Library甚至直接删除若已有更廉价层的覆盖。隔离不等于一个断言一个测试扩展已有测试优先于新增近重复测试新it()与同describe中某个测试共享 80–90% 的前置和流程、只在结尾分叉时应扩展而非复制每次cy.visit()都很贵Metabase 前端是大 Redux store 的 React 应用冷启动要经历 store 水合、路由引导、settings/权限/用户拉取。同一测试内第二次、第三次cy.visit()是完整的应用重启而非换页。已在应用内时优先点击链接、面包屑、侧栏做应用内导航保留热 storecy.visit()仍是测试首次导航或权限变更等确实需要整页刷新的场景之后的正确选择。6. Phase 4 — 验证必须经由 /e2e-test 技能运行生成 spec 后的验证分两步Grepe2e/support/helpers/确认所有 import 的 helper 都存在必须使用/e2e-test技能运行测试不要直接跑bun test-cypress——/e2e-test技能见 .claude/skills/e2e-test/SKILL.md负责版本选择、快照管理与环境变量。调用形如/e2e-test GREPshould do the thing --spec e2e/test/scenarios/path若创建了多个it()块应逐个运行以隔离失败。/e2e-test技能背后的 runner 约束值得注意均来自该技能文档spec 路径必须经--spec传入因为 runner 把参数交给 Cypress 的 CLI 解析器cypress.cli.parseRunArguments裸位置路径会被静默丢弃然后跑整个套件按测试名过滤用GREP环境变量而非--env grep后者在逗号处会断MB_EDITIONee时需要在 shell 预先导出CYPRESS_MB_ALL_FEATURES_TOKEN、CYPRESS_MB_PRO_SELF_HOSTED_TOKEN等 token且只允许检查是否已设置绝不回显 token 值查耗时不跑测试直接读 e2e/support/timings.json存储每个 spec 的最近 CI 耗时毫秒。此外还有 flaky 检测的压测入口MB_EDITIONoss bin/e2e-stress-test --spec pathE2E_STRESS_RUNSN控制迭代次数默认 5首个失败即停并打印截图路径。7. Phase 5 — 修复失败先榨干 Cypress 自己的输出测试失败时优先从 Cypress 输出修复读取失败截图路径打印在输出的(Screenshots)段下、读取控制台的错误信息与代码框、修复后回到 Phase 4 重跑。最多尝试 2 次两次仍无法定位才进入 Phase 6。8. Phase 6 — Playwright 兜底绕过 CSP、API 登录、增量观察日志只有 Phase 5 两次失败后才进入此阶段且后端已在运行无需重启。步骤是先curl -sf -X POST http://localhost:4000/api/testing/restore/default恢复干净数据然后用browser_run_code执行一段设置代码做两件事——剥离 CSP 响应头Metabase 服务严格 CSP会拦截 dev server 脚本这一步镜像的是 Cypress 侧的chromeWebSecurity: false和通过 API 登录POST /api/session凭据adminmetabase.test / 12341234把返回的 session id 写进metabase.DEVICEcookie再导航到http://localhost:4000并等待networkidle。进入浏览器后的关键纪律是增量维护观察日志每完成一次重要交互立即把观察追加到/tmp/e2e-observations.mdURL、点击的按钮与可见文本/role、data-testid、触发的 API 调用、关键 UI 状态然后再进行下一次交互。对每个页面/流程取一次可访问性快照browser_snapshot、点击交互元素、填表、触发模态框、截关键状态图。探查结束后读回观察日志、用观察到的选择器与行为修复测试、回到 Phase 4 重跑、最后rm -f /tmp/e2e-observations.md清理。9. Phase 7 — 清理只按端口杀不宽泛 pkill所有测试通过或彻底放弃修复之后必须杀掉 4000 端口上的后端lsof -ti:4000 | xargs kill 2/dev/null || true技能特别强调不要用宽泛的pkill模式——机器上可能有跑在其他端口的其他 Metabase 实例且 Phase 2 启动的后端进程不会随 Claude 会话结束而自动退出留着它既浪费资源又会干扰后续会话所以清理是强制项。10. 流程级反模式清单What NOT to do技能在结尾汇总了三条流程级禁令约定级禁令见第 5 节的约定文件不要把 Playwright 当第一步——永远先分析源码不要在阶段之间杀后端——它要在整个流程中保持运行不要臆造选择器——只用源码中找到或浏览器中观察到的选择器。11. 小结为什么这个工作流是代码阅读优先纵观七阶段e2e-test-create的设计逻辑可以归纳为三层防御选择器可信度Phase 1 强制从源码提取data-testid/aria/可见文本Phase 3 的选择器优先级与禁止清单Phase 6 的观察后才允许写选择器规则共同杜绝臆造选择器、状态可信度Phase 2 的快照自动生成与restore/default数据恢复配合永不硬编码 ID的约定保证测试起点可复现、资源与时间成本Phase 4 经由/e2e-test统一管理与 runner 的GENERATE_SNAPSHOTS默认行为衔接第 5.9 节的性能三原则控制冷启动开销Phase 7 的按端口清理防止资源泄漏。对希望在 Metabase 或类似前端重 快照驱动架构的项目里编写 E2E 测试的开发者这套规程与 .claude/skills/_shared/cypress-conventions.md、.claude/skills/e2e-test/SKILL.md 以及 bin/e2e-backend、e2e/runner/run_cypress_local.ts 构成的完整证据链是可以直接借鉴的工程范式。【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考