177 lines
7.1 KiB
Markdown
177 lines
7.1 KiB
Markdown
---
|
||
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/<path>`
|
||
如果你创建了多个 `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 中引用的惯例文件。
|