

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# E2E Testing Guide for Claude23本文档记录了在 LobeHub E2E 测试开发中的经验和最佳实践。45Related: [LOBE-2417](https://linear.app/lobehub/issue/LOBE-2417/建立核心产品功能-e2e-测试体验基准线)67## 测试策略:体验驱动的 E2E 测试89### 核心理念1011建立完整的**用户体验链路 E2E 测试**,作为未来变更和重构的**体验基准线**。1213**目的**:1415- 确保核心用户体验在代码变更后不会退化16- 为重构提供安全网,敢于大胆改进代码17- 从用户视角验证功能完整性1819### 产品架构覆盖2021| 模块 | 子功能 | 优先级 | 状态 |22| ---------------- | --------------------------------- | ------ | ---- |23| **Agent** | Builder, 对话,Task | P0 | 🚧 |24| **Agent Group** | Builder, 群聊 | P0 | ⏳ |25| **Page(文稿)** | 侧边栏 CRUD ✅,文档编辑,Copilot | P0 | 🚧 |26| **知识库** | 创建,上传,RAG 对话 | P1 | ⏳ |27| **记忆** | 查看,编辑,关联 | P2 | ⏳ |2829### 标签系统3031```gherkin32@journey # 用户旅程测试(体验基准线)33@smoke # 冒烟测试(快速验证)34@regression # 回归测试3536@P0 # 最高优先级(CI 必跑)37@P1 # 高优先级(Nightly)38@P2 # 中优先级(发版前)3940@agent # Agent 模块41@agent-group # Agent Group 模块42@page # Page 文稿模块43@knowledge # 知识库模块44@memory # 记忆模块45```4647### 执行策略4849```bash50# CI - P0 冒烟测试(每次 PR)51pnpm exec cucumber-js --config cucumber.config.js --tags "@smoke and @P0"5253# Nightly - 所有用户旅程54pnpm exec cucumber-js --config cucumber.config.js --tags "@journey"5556# 发版前 - 完整回归57pnpm exec cucumber-js --config cucumber.config.js --tags "@P0 or @P1"5859# 完整测试60pnpm exec cucumber-js --config cucumber.config.js61```6263### 测试设计原则64651. **按 CRUD + 核心交互覆盖**:每个模块覆盖创建、读取、更新、删除及核心交互流程662. **LLM 响应必须 Mock**:保证测试稳定性和可重复性673. **中文描述场景**:Feature 文件使用中文,贴近产品需求684. **优先级分层**:合理分配 P0/P1/P2,控制 CI 执行时间6970## 目录结构7172```73e2e/74├── src/75│ ├── features/ # Cucumber feature 文件76│ │ ├── journeys/ # 用户旅程(体验基准线)77│ │ │ ├── agent/78│ │ │ │ ├── agent-builder.feature79│ │ │ │ ├── agent-conversation.feature ✅80│ │ │ │ └── agent-task.feature81│ │ │ ├── agent-group/82│ │ │ │ ├── group-builder.feature83│ │ │ │ └── group-chat.feature84│ │ │ ├── page/85│ │ │ │ └── page-crud.feature ✅86│ │ │ ├── knowledge/87│ │ │ │ └── knowledge-rag.feature88│ │ │ └── memory/89│ │ │ └── memory-crud.feature90│ │ ├── smoke/ # 冒烟测试91│ │ │ └── discover/92│ │ └── regression/ # 回归测试93│ ├── steps/ # Step definitions94│ │ ├── agent/ # Agent 相关 steps95│ │ ├── page/ # Page 相关 steps96│ │ ├── common/ # 通用 steps (auth, navigation)97│ │ └── hooks.ts # Before/After hooks98│ ├── mocks/ # Mock 框架99│ │ └── llm/ # LLM Mock (拦截 AI 请求) ✅100│ └── support/ # 测试支持文件101│ └── world.ts # CustomWorld 定义102├── screenshots/ # 失败截图103├── reports/ # 测试报告104├── cucumber.config.js # Cucumber 配置105└── CLAUDE.md # 本文档106```107108## 本地环境启动109110> 详细流程参考 [e2e/docs/local-setup.md](./docs/local-setup.md)111112### 一键启动(推荐)113114使用 TypeScript 脚本自动完成环境设置:115116```bash117# 在项目根目录运行118119# 仅设置数据库(启动 PostgreSQL + 运行迁移)120bun e2e/scripts/setup.ts121122# 设置数据库并启动服务器123bun e2e/scripts/setup.ts --start124125# 完整设置(数据库 + 构建 + 启动服务器)126bun e2e/scripts/setup.ts --build --start127128# 清理环境129bun e2e/scripts/setup.ts --clean130```131132### 脚本选项133134| 选项 | 说明 |135| ---------------- | ---------------------------- |136| `--clean` | 清理现有容器和进程 |137| `--skip-db` | 跳过数据库设置(使用已有的) |138| `--skip-migrate` | 跳过数据库迁移 |139| `--build` | 启动前构建应用 |140| `--start` | 设置完成后启动服务器 |141| `--port <port>` | 服务器端口(默认 3006) |142143**重要提示**:144145- 必须使用 `paradedb/paradedb:latest` 镜像(支持 pgvector 扩展)146- 服务器必须在**项目根目录**启动,不能在 e2e 目录147- S3 环境变量是**必需**的,即使不测试文件上传(脚本已自动处理)148149## 运行测试150151```bash152# 从 e2e 目录运行153cd e2e154155# 运行特定标签的测试156BASE_URL=http://localhost:3006 \157 DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \158 pnpm exec cucumber-js --config cucumber.config.js --tags "@AGENT-CHAT-001"159160# 调试模式(显示浏览器)161HEADLESS=false BASE_URL=http://localhost:3006 \162 DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \163 pnpm exec cucumber-js --config cucumber.config.js --tags "@conversation"164165# 运行所有测试166BASE_URL=http://localhost:3006 \167 DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \168 pnpm exec cucumber-js --config cucumber.config.js169```170171**重要**: 必须显式指定 `--config cucumber.config.js`,否则配置不会被正确加载。172173## LLM Mock 实现174175### 核心原理176177LLM Mock 通过 Playwright 的 `page.route()` 拦截对 `/webapi/chat/openai` 的请求,返回预设的 SSE 流式响应。178179### SSE 响应格式180181LobeHub 使用特定的 SSE 格式,必须严格匹配:182183```typescript184// 1. 初始 data 事件185id: msg_xxx186event: data187data: {"id":"msg_xxx","model":"gpt-4o-mini","role":"assistant","type":"message",...}188189// 2. 文本内容分块(text 事件)190id: msg_xxx191event: text192data: "Hello"193194id: msg_xxx195event: text196data: "! I am"197198// 3. 停止事件199id: msg_xxx200event: stop201data: "end_turn"202203// 4. 使用量统计204id: msg_xxx205event: usage206data: {"totalTokens":100,...}207208// 5. 最终停止209id: msg_xxx210event: stop211data: "message_stop"212```213214### 使用示例215216```typescript217import { llmMockManager, presetResponses } from '../../mocks/llm';218219// 在测试步骤中设置 mock220llmMockManager.setResponse('hello', presetResponses.greeting);221await llmMockManager.setup(this.page);222```223224### 添加自定义响应225226```typescript227// 为特定用户消息设置响应228llmMockManager.setResponse('你好', '你好!我是 Lobe AI,有什么可以帮助你的?');229230// 清除所有自定义响应231llmMockManager.clearResponses();232```233234## 页面元素定位技巧235236### 富文本编辑器 (contenteditable) 输入237238LobeHub 使用 `@lobehub/editor` 作为聊天输入框,是一个 contenteditable 的富文本编辑器。239240**关键点**:2412421. 不能直接用 `locator.fill()` - 对 contenteditable 不生效2432. 需要先 click 容器让编辑器获得焦点2443. 使用 `keyboard.type()` 输入文本245246```typescript247// 正确的输入方式248await chatInputContainer.click();249await this.page.waitForTimeout(500); // 等待焦点250await this.page.keyboard.type(message, { delay: 30 });251await this.page.keyboard.press('Enter'); // 发送252```253254### 添加 data-testid255256为了更可靠的元素定位,可以在组件上添加 `data-testid`:257258```tsx259// src/features/ChatInput/Desktop/index.tsx260<ChatInput261 data-testid="chat-input"262 ...263/>264```265266## 调试技巧267268### 添加步骤日志269270在每个关键步骤添加 console.log,帮助定位问题:271272```typescript273Given('用户进入页面', async function (this: CustomWorld) {274 console.log(' 📍 Step: 导航到首页...');275 await this.page.goto('/');276277 console.log(' 📍 Step: 查找元素...');278 const element = this.page.locator('...');279280 console.log(' ✅ 步骤完成');281});282```283284### 查看失败截图285286测试失败时会自动保存截图到 `e2e/screenshots/` 目录。287288### 非 headless 模式289290设置 `HEADLESS=false` 可以看到浏览器操作:291292```bash293HEADLESS=false pnpm exec cucumber-js --config cucumber.config.js --tags "@smoke"294```295296## 环境变量297298运行测试需要以下环境变量:299300```bash301BASE_URL=http://localhost:3010 # 测试服务器地址302DATABASE_URL=postgresql://... # 数据库连接303DATABASE_DRIVER=node # 数据库驱动304KEY_VAULTS_SECRET=... # 密钥305AUTH_SECRET=... # Auth 密钥306307# 可选:S3 相关(如果测试涉及文件上传)308S3_ACCESS_KEY_ID=e2e-mock-access-key309S3_SECRET_ACCESS_KEY=e2e-mock-secret-key310S3_BUCKET=e2e-mock-bucket311S3_ENDPOINT=https://e2e-mock-s3.localhost312```313314## 清理环境315316测试完成后或需要重置环境时:317318```bash319# 一键清理(推荐)320bun e2e/scripts/setup.ts --clean321```322323或手动清理:324325```bash326# 停止并删除 PostgreSQL 容器327docker stop postgres-e2e && docker rm postgres-e2e328329# 清理端口占用330lsof -ti:3006 | xargs kill -9331lsof -ti:5433 | xargs kill -9332```333334## 常见问题335336### 1. 测试超时 (function timed out)337338**原因**: 元素定位失败或等待时间不足339340**解决**:341342- 检查选择器是否正确343- 增加 timeout 参数344- 添加显式等待 `waitForTimeout()`345346### 2. strict mode violation (多个元素匹配)347348**原因**: 选择器匹配到多个元素(如 desktop/mobile 双组件)349350**解决**:351352- 使用 `.first()` 或 `.nth(n)`353- 使用 `boundingBox()` 过滤可见元素354355### 3. LLM Mock 未生效356357**原因**: 路由拦截设置在页面导航之后358359**解决**: 确保在 `page.goto()` 之前调用 `llmMockManager.setup(page)`360361### 4. 输入框内容为空362363**原因**: contenteditable 编辑器的特殊性364365**解决**:366367- 先 click 容器确保焦点368- 使用 `keyboard.type()` 而非 `fill()`369- 添加适当的等待时间370371## 编写新测试的流程3723731. **创建 Feature 文件** (`src/features/xxx/xxx.feature`)374 - 使用中文描述场景375 - 添加适当的标签 (@journey, @P0, @smoke 等)3763772. **创建 Step Definitions** (`src/steps/xxx/xxx.steps.ts`)378 - 导入必要的 mock 和工具379 - 每个步骤添加日志380 - 处理元素定位的边界情况3813823. **设置 Mock**(如需要)383 - 在 `src/mocks/` 下创建对应的 mock384 - 在步骤中初始化 mock3853864. **调试和验证**387 - 先用 `HEADLESS=false` 运行观察388 - 检查失败截图389 - 确保稳定通过后再提交390
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| lobehub/lobehubAGENTS.md · 82k | AGENTS.md | setupteststylearch+4 | 93/100 | today |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| bagisto/bagistoCLAUDE.md · 28k | CLAUDE.md | setupbuildteststyle+5 | 100/100 | 7 days ago | |
| Adit-Jain-srm/NightmareNetCLAUDE.md · 46 | CLAUDE.md | buildtestlint-formatstyle+6 | 100/100 | 14 days ago | |
| tyrchen/geektime-bootcamp-aiw7/genslides/backend/CLAUDE.md · 230 | CLAUDE.md | testlint-formatstylearch+6 | 100/100 | 9 days ago | |
| nimbalyst/nimbalystpackages/android/CLAUDE.md · 1.5k | CLAUDE.md | setupbuildstylearch+2 | 100/100 | 14 days ago | |
| dotCMS/corecore-web/CLAUDE.md · 949 | CLAUDE.md | teststylearchtesting-strategy+3 | 100/100 | 14 days ago | |
| microsoft/playwrightCLAUDE.md · 95k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 7 days ago | |
| tphakala/birdnet-goCLAUDE.md · 1.6k | CLAUDE.md | buildtestlint-formatstyle+8 | 100/100 | today | |
| dotCMS/coreCLAUDE.md · 949 | CLAUDE.md | setupbuildteststyle+7 | 99/100 | today |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/lobehub-lobehub-e2e-claude)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.