--- name: e2e-test-create description: > 分析 React 组件源代码以理解 UI 结构, 然后按照 Metabase 惯例生成符合习惯的 Cypress E2E 测试。 仅在代码阅读和截图调试不足以解决问题时,才回退使用 Playwright MCP 浏览器探索。 disable-model-invocation: true allowed-tools: - Bash - Read - Write - Grep - Glob - Skill - "mcp__playwright__*" --- # 代码阅读优先 → 生成 Cypress 测试(Metabase) 你正在为 **Metabase** 代码库编写 Cypress E2E 测试。 在生成任何测试代码之前,你**必须**分析 React 组件源代码, 以理解 DOM 结构、选择器和用户流程。 ## 阶段 0 — 调研 1. 在编写任何代码之前,先阅读现有辅助函数: - `e2e/support/helpers/` — 所有共享辅助函数(restore、signInAs、openOrdersTable 等) - `e2e/support/cypress_sample_database.ts` — 表/字段模式常量(ORDERS、PRODUCTS 等) - `e2e/support/cypress_sample_instance_data.ts` — 实例特定 ID(ORDERS_DASHBOARD_ID、NORMAL_USER_ID 等) 2. 使用 Glob 搜索 `e2e/test/scenarios/`,找到与待测试区域最接近的已有 spec 文件。 研究其模式——严格匹配它们。 3. 使用 Glob 搜索 `frontend/src/metabase/`,找到该功能区域对应的 React 组件。 ## 阶段 1 — 代码分析 阅读 React 组件源代码以理解 DOM 结构。不需要浏览器——源代码包含所有信息。 1. **查找相关组件**:使用 Glob 和 grep 搜索 `frontend/src/metabase/` 中该功能区域的代码。 2. **提取选择器**:在相关组件中 grep 搜索 `data-testid`。 3. **记录可见文本**:阅读组件的 JSX,获取按钮标签、标题、占位符文本。 4. **记录 aria 属性**:在相关组件中 grep 搜索 `aria-label`。 5. **理解用户流程**:阅读事件处理函数(onClick、onSubmit、onChange)以理解交互过程。 6. **查找 API 调用**:grep 搜索 `Api.use`、`fetch`、`useQuery`、端点定义,识别需要拦截的 API 调用。 7. **与已有 spec 交叉对照**:找到同一区域内的已有 spec,重用其经过验证的选择器和 `cy.intercept` 模式。 ## 阶段 2 — 启动后端 默认使用 `MB_EDITION=oss`。仅当用户明确要求编写企业版测试时,才使用 `MB_EDITION=ee`。 使用 `run_in_background: true`(而不是 `&`)启动后端。 `bin/e2e-backend` 会自动检测后端是否已在运行并复用。 ```bash MB_EDITION=oss bin/e2e-backend ``` 不要通过运行不相关的测试 spec 来手动生成快照。 `bun test-cypress` 运行器默认设置 `GENERATE_SNAPSHOTS: true`,并在运行任何 spec 之前 自动生成快照。通过 `/e2e-test` skill 在阶段 4 运行测试时, 如果快照尚不存在,将在首次运行时生成。 恢复干净的测试数据: ```bash curl -sf -X POST http://localhost:4000/api/testing/restore/default ``` ## 阶段 3 — 生成 Cypress Spec 遵循 Metabase Cypress 惯例: @./../_shared/cypress-conventions.md 在代码分析中识别出 API 调用后,使用上面展示的拦截模式对它们进行桩代码替换或等待。 ## 阶段 4 — 验证 生成 spec 后: 1. 检查所有导入的辅助函数是否存在(在 `e2e/support/helpers/` 中 grep 搜索)。 2. **你必须使用 `/e2e-test` skill** 来运行测试——不要直接运行 `bun test-cypress`。 `/e2e-test` skill 处理版本选择、快照管理和正确的环境变量设置。 `/e2e-test GREP="should do the thing" --spec e2e/test/scenarios/` 如果你创建了多个 `it()` 块,请逐一运行每个块以隔离失败。 ## 阶段 5 — 修复失败(最多尝试 2 次) 测试失败时,**首先尝试根据 Cypress 输出修复**: 1. 查看失败截图(路径打印在 `(Screenshots)` 下方)。 2. 从控制台输出中查看错误消息和代码帧。 3. 修复测试并重新运行(回到阶段 4,步骤 2)。 如果在 2 次尝试后仍无法诊断问题,则进入阶段 6。 ## 阶段 6 — Playwright 回退 仅在阶段 5 中经过 2 次修复尝试失败后才能进入此阶段。后端已在运行。 恢复干净的测试数据: ```bash curl -sf -X POST http://localhost:4000/api/testing/restore/default ``` **绕过 CSP** 头(在导航之前执行——Metabase 提供了严格的 CSP,会阻止开发服务器脚本)。 使用 `browser_run_code` 进行设置: ```js async (page) => { // 移除 CSP 头以便页面加载(镜像 Cypress 的 chromeWebSecurity: false) await page.context().route('**/*', async (route) => { const response = await route.fetch(); const headers = { ...response.headers() }; delete headers['content-security-policy']; delete headers['content-security-policy-report-only']; await route.fulfill({ response, headers }); }); // 通过 API 登录 const response = await page.request.post('http://localhost:4000/api/session', { data: { username: 'admin@metabase.test', password: '12341234' } }); const session = await response.json(); await page.context().addCookies([{ name: 'metabase.DEVICE', value: session.id, domain: 'localhost', path: '/' }]); await page.goto('http://localhost:4000'); await page.waitForLoadState('networkidle'); return 'signed in'; } ``` **逐步维护观察日志。** 每次进行重要的 Playwright 交互后, 立即将观察到的内容追加到临时文件中,然后再进行下一次交互: ```bash cat >> /tmp/e2e-observations.md << 'OBSERVATION' ## [页面/流程名称] - URL: /question/notebook#... - 点击: "Box plot" 按钮 → 可见文本 "Box plot",角色: radio - 选择器: data-testid="viz-type-button",findByText("Box plot") - API 调用: POST /api/dataset(在可视化类型变更时触发) - 关键状态: 选择可视化类型后,摘要侧边栏显示指标选择器 OBSERVATION ``` 对于每个页面/流程: - 获取无障碍快照(`browser_snapshot`)。 - 点击交互式元素、填写表单、触发弹窗。 - **每执行一步后立即追加到观察日志**:URL、可见文本、aria 标签、`data-testid` 属性、API 调用。 - 对关键状态进行截图。 探索完成后: 1. 回读观察日志:`cat /tmp/e2e-observations.md` 2. 使用观察到的选择器和行为修复测试。 3. 重新运行测试(回到阶段 4)。 4. 清理:`rm -f /tmp/e2e-observations.md` ## 阶段 7 — 清理 所有测试通过后(或放弃修复失败后),**始终杀死端口 4000 上的后端**: ```bash lsof -ti:4000 | xargs kill 2>/dev/null || true ``` 不要使用宽泛的 `pkill` 模式——可能还有其他 Metabase 实例运行在不同的端口上。 阶段 2 启动的后端进程在 Claude 会话结束时**不会**被自动终止。 让其持续运行会浪费资源,并可能干扰将来的会话。务必清理。 ## 禁止事项(工作流程) - 不要将 Playwright 作为第一步——始终先分析源代码。 - 不要在阶段之间杀死后端——它应在整个过程中保持运行。 - 不要编造你在源代码中未找到或在浏览器中未观察到的选择器。 关于惯例层面的「禁止事项」(选择器、等待、辅助函数等),请参见阶段 3 中引用的惯例文件。