@lobehub/lobehub 2.0.0-next.188 → 2.0.0-next.189

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/e2e/CLAUDE.md +109 -2
  4. package/e2e/docs/llm-mock.md +68 -0
  5. package/e2e/docs/local-setup.md +354 -0
  6. package/e2e/docs/testing-tips.md +94 -0
  7. package/e2e/src/features/journeys/agent/agent-conversation.feature +0 -32
  8. package/e2e/src/mocks/llm/index.ts +6 -6
  9. package/e2e/src/steps/agent/conversation.steps.ts +3 -471
  10. package/package.json +2 -2
  11. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
  12. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
  13. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
  14. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
  15. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
  16. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
  17. package/src/app/[variants]/(main)/community/(detail)/assistant/features/Sidebar/ActionButton/AddAgent.tsx +47 -27
  18. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +4 -3
  19. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
  20. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
  21. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
  22. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
  23. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
  24. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
  25. package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/TopicSelector.tsx +18 -20
  26. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +19 -25
  27. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +21 -26
  28. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/Actions.tsx +4 -13
  29. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +3 -3
  30. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Actions.tsx +4 -13
  31. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +8 -15
  32. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +3 -3
  33. package/src/app/[variants]/(main)/home/_layout/Header/components/AddButton.tsx +3 -4
  34. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Actions.tsx +4 -13
  35. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +13 -20
  36. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +3 -3
  37. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/Actions.tsx +4 -13
  38. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/index.tsx +16 -23
  39. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/useDropdownMenu.tsx +3 -3
  40. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +4 -6
  41. package/src/features/AgentBuilder/TopicSelector.tsx +18 -17
  42. package/src/features/Conversation/ChatItem/style.ts +7 -0
  43. package/src/features/Conversation/Messages/Assistant/Actions/Error.tsx +1 -3
  44. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +37 -16
  45. package/src/features/Conversation/Messages/AssistantGroup/Actions/index.tsx +36 -17
  46. package/src/features/Conversation/Messages/Supervisor/Actions/index.tsx +36 -17
  47. package/src/features/Conversation/Messages/Task/Actions/Error.tsx +1 -3
  48. package/src/features/Conversation/Messages/Task/Actions/index.tsx +31 -15
  49. package/src/features/Conversation/Messages/User/Actions/index.tsx +1 -1
  50. package/src/features/Conversation/Messages/index.tsx +8 -59
  51. package/src/features/Conversation/components/ShareMessageModal/index.tsx +1 -1
  52. package/src/features/Conversation/hooks/useChatItemContextMenu.tsx +313 -83
  53. package/src/features/NavPanel/components/NavItem.tsx +33 -3
  54. package/src/features/PageEditor/Copilot/TopicSelector/Actions.tsx +6 -14
  55. package/src/features/PageEditor/Copilot/TopicSelector/TopicItem.tsx +1 -0
  56. package/src/features/PageEditor/Copilot/TopicSelector/useDropdownMenu.tsx +6 -3
  57. package/src/features/ResourceManager/components/Explorer/ItemDropdown/DropdownMenu.tsx +12 -35
  58. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -8
  59. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +162 -160
  60. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +16 -8
  61. package/src/features/ResourceManager/components/Explorer/ToolBar/ActionIconWithChevron.tsx +4 -3
  62. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +6 -12
  63. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +8 -8
  64. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +8 -11
  65. package/src/features/ResourceManager/components/Tree/index.tsx +121 -122
  66. package/src/layout/GlobalProvider/index.tsx +5 -2
  67. package/src/styles/global.ts +6 -0
  68. package/src/features/Conversation/components/ContextMenu.tsx +0 -418
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.189](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.188...v2.0.0-next.189)
6
+
7
+ <sup>Released on **2026-01-01**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Migrate to new DropdownMenuV2 and showContextMenu API.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Migrate to new DropdownMenuV2 and showContextMenu API, closes [#11079](https://github.com/lobehub/lobe-chat/issues/11079) ([04cfc0e](https://github.com/lobehub/lobe-chat/commit/04cfc0e))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.188](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.187...v2.0.0-next.188)
6
31
 
7
32
  <sup>Released on **2026-01-01**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Migrate to new DropdownMenuV2 and showContextMenu API."
6
+ ]
7
+ },
8
+ "date": "2026-01-01",
9
+ "version": "2.0.0-next.189"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/e2e/CLAUDE.md CHANGED
@@ -104,6 +104,62 @@ e2e/
104
104
  └── CLAUDE.md # 本文档
105
105
  ```
106
106
 
107
+ ## 本地环境启动
108
+
109
+ > 详细流程参考 [e2e/docs/local-setup.md](./docs/local-setup.md)
110
+
111
+ ### 快速启动流程
112
+
113
+ ```bash
114
+ # Step 1: 清理环境
115
+ docker stop postgres-e2e 2> /dev/null; docker rm postgres-e2e 2> /dev/null
116
+ lsof -ti:3006 | xargs kill -9 2> /dev/null
117
+ lsof -ti:5433 | xargs kill -9 2> /dev/null
118
+
119
+ # Step 2: 启动数据库(使用 paradedb 镜像,支持 pgvector)
120
+ docker run -d --name postgres-e2e \
121
+ -e POSTGRES_PASSWORD=postgres \
122
+ -p 5433:5432 \
123
+ paradedb/paradedb:latest
124
+
125
+ # 等待数据库就绪
126
+ until docker exec postgres-e2e pg_isready; do sleep 2; done
127
+
128
+ # Step 3: 运行数据库迁移(项目根目录)
129
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
130
+ DATABASE_DRIVER=node \
131
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
132
+ bun run db:migrate
133
+
134
+ # Step 4: 构建应用(首次或代码变更后)
135
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
136
+ DATABASE_DRIVER=node \
137
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
138
+ BETTER_AUTH_SECRET=e2e-test-secret-key-for-better-auth-32chars! \
139
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH=1 \
140
+ SKIP_LINT=1 \
141
+ bun run build
142
+
143
+ # Step 5: 启动服务器(必须在项目根目录运行!)
144
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
145
+ DATABASE_DRIVER=node \
146
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
147
+ BETTER_AUTH_SECRET=e2e-test-secret-key-for-better-auth-32chars! \
148
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH=1 \
149
+ NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION=0 \
150
+ S3_ACCESS_KEY_ID=e2e-mock-access-key \
151
+ S3_SECRET_ACCESS_KEY=e2e-mock-secret-key \
152
+ S3_BUCKET=e2e-mock-bucket \
153
+ S3_ENDPOINT=https://e2e-mock-s3.localhost \
154
+ bunx next start -p 3006
155
+ ```
156
+
157
+ **重要提示**:
158
+
159
+ - 必须使用 `paradedb/paradedb:latest` 镜像(支持 pgvector 扩展)
160
+ - 服务器必须在**项目根目录**启动,不能在 e2e 目录
161
+ - S3 环境变量是**必需**的,即使不测试文件上传
162
+
107
163
  ## 运行测试
108
164
 
109
165
  ```bash
@@ -111,12 +167,19 @@ e2e/
111
167
  cd e2e
112
168
 
113
169
  # 运行特定标签的测试
114
- HEADLESS=false BASE_URL=http://localhost:3010 \
170
+ BASE_URL=http://localhost:3006 \
115
171
  DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
116
172
  pnpm exec cucumber-js --config cucumber.config.js --tags "@AGENT-CHAT-001"
117
173
 
174
+ # 调试模式(显示浏览器)
175
+ HEADLESS=false BASE_URL=http://localhost:3006 \
176
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
177
+ pnpm exec cucumber-js --config cucumber.config.js --tags "@conversation"
178
+
118
179
  # 运行所有测试
119
- pnpm exec cucumber-js --config cucumber.config.js
180
+ BASE_URL=http://localhost:3006 \
181
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
182
+ pnpm exec cucumber-js --config cucumber.config.js
120
183
  ```
121
184
 
122
185
  **重要**: 必须显式指定 `--config cucumber.config.js`,否则配置不会被正确加载。
@@ -263,6 +326,50 @@ S3_BUCKET=e2e-mock-bucket
263
326
  S3_ENDPOINT=https://e2e-mock-s3.localhost
264
327
  ```
265
328
 
329
+ ## 清理环境
330
+
331
+ 测试完成后或需要重置环境时,执行以下清理操作:
332
+
333
+ ### 停止服务器
334
+
335
+ ```bash
336
+ # 查找并停止占用端口的进程
337
+ lsof -ti:3006 | xargs kill -9 2> /dev/null
338
+ lsof -ti:3010 | xargs kill -9 2> /dev/null
339
+ ```
340
+
341
+ ### 停止 Docker 容器
342
+
343
+ ```bash
344
+ # 停止并删除 PostgreSQL 容器
345
+ docker stop postgres-e2e 2> /dev/null
346
+ docker rm postgres-e2e 2> /dev/null
347
+ ```
348
+
349
+ ### 一键清理(推荐)
350
+
351
+ ```bash
352
+ # 清理所有 E2E 相关进程和容器
353
+ docker stop postgres-e2e 2> /dev/null
354
+ docker rm postgres-e2e 2> /dev/null
355
+ lsof -ti:3006 | xargs kill -9 2> /dev/null
356
+ lsof -ti:3010 | xargs kill -9 2> /dev/null
357
+ lsof -ti:5433 | xargs kill -9 2> /dev/null
358
+ echo "Cleanup done"
359
+ ```
360
+
361
+ ### 清理端口占用
362
+
363
+ 如果遇到端口被占用的错误,可以清理特定端口:
364
+
365
+ ```bash
366
+ # 清理 Next.js 服务器端口
367
+ lsof -ti:3006 | xargs kill -9
368
+
369
+ # 清理 PostgreSQL 端口
370
+ lsof -ti:5433 | xargs kill -9
371
+ ```
372
+
266
373
  ## 常见问题
267
374
 
268
375
  ### 1. 测试超时 (function timed out)
@@ -0,0 +1,68 @@
1
+ # LLM Mock 实现
2
+
3
+ ## 核心原理
4
+
5
+ LLM Mock 通过 Playwright 的 `page.route()` 拦截对 `/webapi/chat/openai` 的请求,返回预设的 SSE 流式响应。
6
+
7
+ ## SSE 响应格式
8
+
9
+ LobeHub 使用特定的 SSE 格式,必须严格匹配:
10
+
11
+ ```
12
+ // 1. 初始 data 事件
13
+ id: msg_xxx
14
+ event: data
15
+ data: {"id":"msg_xxx","model":"gpt-4o-mini","role":"assistant","type":"message",...}
16
+
17
+ // 2. 文本内容分块(text 事件)
18
+ id: msg_xxx
19
+ event: text
20
+ data: "Hello"
21
+
22
+ id: msg_xxx
23
+ event: text
24
+ data: "! I am"
25
+
26
+ // 3. 停止事件
27
+ id: msg_xxx
28
+ event: stop
29
+ data: "end_turn"
30
+
31
+ // 4. 使用量统计
32
+ id: msg_xxx
33
+ event: usage
34
+ data: {"totalTokens":100,...}
35
+
36
+ // 5. 最终停止
37
+ id: msg_xxx
38
+ event: stop
39
+ data: "message_stop"
40
+ ```
41
+
42
+ ## 使用示例
43
+
44
+ ```typescript
45
+ import { llmMockManager, presetResponses } from '../../mocks/llm';
46
+
47
+ // 在测试步骤中设置 mock
48
+ llmMockManager.setResponse('hello', presetResponses.greeting);
49
+ await llmMockManager.setup(this.page);
50
+ ```
51
+
52
+ ## 添加自定义响应
53
+
54
+ ```typescript
55
+ // 为特定用户消息设置响应
56
+ llmMockManager.setResponse('你好', '你好!我是 Lobe AI,有什么可以帮助你的?');
57
+
58
+ // 清除所有自定义响应
59
+ llmMockManager.clearResponses();
60
+ ```
61
+
62
+ ## 常见问题
63
+
64
+ ### LLM Mock 未生效
65
+
66
+ **原因**: 路由拦截设置在页面导航之后
67
+
68
+ **解决**: 确保在 `page.goto()` 之前调用 `llmMockManager.setup(page)`
@@ -0,0 +1,354 @@
1
+ # 本地运行 E2E 测试
2
+
3
+ ## 前置要求
4
+
5
+ - Docker Desktop 已安装并**正在运行**
6
+ - Node.js 18+
7
+ - pnpm 已安装
8
+ - 项目已 `pnpm install`
9
+
10
+ ## 完整启动流程
11
+
12
+ ### Step 0: 环境清理(重要!)
13
+
14
+ 每次运行测试前,建议先清理环境,避免残留状态导致问题。
15
+
16
+ ```bash
17
+ # 0.1 确保 Docker Desktop 正在运行
18
+ # 如果未运行,请先启动 Docker Desktop
19
+
20
+ # 0.2 清理旧的 PostgreSQL 容器
21
+ docker stop postgres-e2e 2> /dev/null
22
+ docker rm postgres-e2e 2> /dev/null
23
+
24
+ # 0.3 清理占用的端口
25
+ lsof -ti:3006 | xargs kill -9 2> /dev/null # Next.js 服务器端口
26
+ lsof -ti:5433 | xargs kill -9 2> /dev/null # PostgreSQL 端口
27
+ ```
28
+
29
+ ### Step 1: 启动数据库
30
+
31
+ ```bash
32
+ # 启动 PostgreSQL (端口 5433)
33
+ docker run -d --name postgres-e2e \
34
+ -e POSTGRES_PASSWORD=postgres \
35
+ -p 5433:5432 \
36
+ paradedb/paradedb:latest
37
+
38
+ # 等待数据库就绪
39
+ until docker exec postgres-e2e pg_isready; do sleep 2; done
40
+ echo "PostgreSQL is ready!"
41
+ ```
42
+
43
+ ### Step 2: 运行数据库迁移
44
+
45
+ ```bash
46
+ # 在项目根目录运行
47
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
48
+ DATABASE_DRIVER=node \
49
+ bun run db:migrate
50
+ ```
51
+
52
+ ### Step 3: 构建应用(首次或代码变更后)
53
+
54
+ ```bash
55
+ # 在项目根目录运行
56
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
57
+ DATABASE_DRIVER=node \
58
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
59
+ BETTER_AUTH_SECRET=e2e-test-secret-key-for-better-auth-32chars! \
60
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH=1 \
61
+ SKIP_LINT=1 \
62
+ bun run build
63
+ ```
64
+
65
+ ### Step 4: 启动应用服务器
66
+
67
+ **重要**: 必须在**项目根目录**运行,不能在 e2e 目录运行!
68
+
69
+ ```bash
70
+ # 在项目根目录运行(注意:不是 e2e 目录)
71
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
72
+ DATABASE_DRIVER=node \
73
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
74
+ BETTER_AUTH_SECRET=e2e-test-secret-key-for-better-auth-32chars! \
75
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH=1 \
76
+ NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION=0 \
77
+ S3_ACCESS_KEY_ID=e2e-mock-access-key \
78
+ S3_SECRET_ACCESS_KEY=e2e-mock-secret-key \
79
+ S3_BUCKET=e2e-mock-bucket \
80
+ S3_ENDPOINT=https://e2e-mock-s3.localhost \
81
+ bunx next start -p 3006
82
+ ```
83
+
84
+ ### Step 5: 等待服务器就绪
85
+
86
+ ```bash
87
+ # 在另一个终端运行,确认服务器已启动
88
+ until curl -s http://localhost:3006 > /dev/null; do
89
+ sleep 2
90
+ echo "Waiting..."
91
+ done
92
+ echo "Server is ready!"
93
+ ```
94
+
95
+ ### Step 6: 运行测试
96
+
97
+ ```bash
98
+ # 在 e2e 目录运行测试
99
+ cd e2e
100
+
101
+ # 运行特定标签(默认无头模式)
102
+ BASE_URL=http://localhost:3006 \
103
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
104
+ pnpm exec cucumber-js --config cucumber.config.js --tags "@conversation"
105
+
106
+ # 运行所有测试
107
+ BASE_URL=http://localhost:3006 \
108
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
109
+ pnpm exec cucumber-js --config cucumber.config.js
110
+
111
+ # 调试模式(显示浏览器,观察执行过程)
112
+ HEADLESS=false \
113
+ BASE_URL=http://localhost:3006 \
114
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
115
+ pnpm exec cucumber-js --config cucumber.config.js --tags "@conversation"
116
+ ```
117
+
118
+ ## 一键启动脚本
119
+
120
+ ### 完整初始化(首次运行或需要重建)
121
+
122
+ 在项目根目录创建 `e2e-init.sh`:
123
+
124
+ ```bash
125
+ #!/bin/bash
126
+ set -e
127
+
128
+ echo "🧹 Step 0: Cleaning up..."
129
+ docker stop postgres-e2e 2> /dev/null || true
130
+ docker rm postgres-e2e 2> /dev/null || true
131
+ lsof -ti:3006 | xargs kill -9 2> /dev/null || true
132
+ lsof -ti:5433 | xargs kill -9 2> /dev/null || true
133
+
134
+ echo "🐘 Step 1: Starting PostgreSQL..."
135
+ docker run -d --name postgres-e2e \
136
+ -e POSTGRES_PASSWORD=postgres \
137
+ -p 5433:5432 \
138
+ paradedb/paradedb:latest
139
+
140
+ echo "⏳ Waiting for PostgreSQL..."
141
+ until docker exec postgres-e2e pg_isready 2> /dev/null; do sleep 2; done
142
+ echo "✅ PostgreSQL is ready!"
143
+
144
+ echo "🔄 Step 2: Running migrations..."
145
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
146
+ DATABASE_DRIVER=node \
147
+ bun run db:migrate
148
+
149
+ echo "🔨 Step 3: Building application..."
150
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
151
+ DATABASE_DRIVER=node \
152
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
153
+ BETTER_AUTH_SECRET=e2e-test-secret-key-for-better-auth-32chars! \
154
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH=1 \
155
+ SKIP_LINT=1 \
156
+ bun run build
157
+
158
+ echo "✅ Initialization complete! Now run e2e-start.sh to start the server."
159
+ ```
160
+
161
+ ### 快速启动服务器
162
+
163
+ 在项目根目录创建 `e2e-start.sh`:
164
+
165
+ ```bash
166
+ #!/bin/bash
167
+ set -e
168
+
169
+ echo "🧹 Cleaning up ports..."
170
+ lsof -ti:3006 | xargs kill -9 2> /dev/null || true
171
+
172
+ echo "🚀 Starting Next.js server on port 3006..."
173
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
174
+ DATABASE_DRIVER=node \
175
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
176
+ BETTER_AUTH_SECRET=e2e-test-secret-key-for-better-auth-32chars! \
177
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH=1 \
178
+ NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION=0 \
179
+ S3_ACCESS_KEY_ID=e2e-mock-access-key \
180
+ S3_SECRET_ACCESS_KEY=e2e-mock-secret-key \
181
+ S3_BUCKET=e2e-mock-bucket \
182
+ S3_ENDPOINT=https://e2e-mock-s3.localhost \
183
+ bunx next start -p 3006
184
+ ```
185
+
186
+ ### 运行测试
187
+
188
+ 在 e2e 目录创建 `run-test.sh`:
189
+
190
+ ```bash
191
+ #!/bin/bash
192
+
193
+ # 默认参数
194
+ TAGS="${1:-@journey}"
195
+ HEADLESS="${HEADLESS:-true}" # 默认无头模式
196
+
197
+ echo "🧪 Running E2E tests with tags: $TAGS"
198
+ echo " Headless: $HEADLESS"
199
+
200
+ HEADLESS=$HEADLESS \
201
+ BASE_URL=http://localhost:3006 \
202
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
203
+ pnpm exec cucumber-js --config cucumber.config.js --tags "$TAGS"
204
+ ```
205
+
206
+ 使用方式:
207
+
208
+ ```bash
209
+ # 运行特定标签(默认无头模式)
210
+ ./run-test.sh "@conversation"
211
+
212
+ # 调试模式(显示浏览器)
213
+ HEADLESS=false ./run-test.sh "@conversation"
214
+ ```
215
+
216
+ ## 快速启动(假设数据库和构建已完成)
217
+
218
+ ```bash
219
+ # Terminal 1: 启动服务器(项目根目录)
220
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
221
+ DATABASE_DRIVER=node \
222
+ KEY_VAULTS_SECRET=LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s= \
223
+ BETTER_AUTH_SECRET=e2e-test-secret-key-for-better-auth-32chars! \
224
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH=1 \
225
+ NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION=0 \
226
+ S3_ACCESS_KEY_ID=e2e-mock-access-key \
227
+ S3_SECRET_ACCESS_KEY=e2e-mock-secret-key \
228
+ S3_BUCKET=e2e-mock-bucket \
229
+ S3_ENDPOINT=https://e2e-mock-s3.localhost \
230
+ bunx next start -p 3006
231
+
232
+ # Terminal 2: 运行测试(e2e 目录,默认无头模式)
233
+ cd e2e
234
+ BASE_URL=http://localhost:3006 \
235
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
236
+ pnpm exec cucumber-js --config cucumber.config.js --tags "@conversation"
237
+
238
+ # 调试模式(显示浏览器)
239
+ HEADLESS=false BASE_URL=http://localhost:3006 \
240
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres \
241
+ pnpm exec cucumber-js --config cucumber.config.js --tags "@conversation"
242
+ ```
243
+
244
+ ## 环境变量参考
245
+
246
+ ### 测试运行时环境变量
247
+
248
+ | 变量 | 值 | 说明 |
249
+ | -------------- | -------------------------------------------------------- | --------------------------------------------------- |
250
+ | `BASE_URL` | `http://localhost:3006` | 测试服务器地址 |
251
+ | `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5433/postgres` | 数据库连接 |
252
+ | `HEADLESS` | `true`(默认)/`false` | 是否无头模式运行浏览器,设为 `false` 可观察执行过程 |
253
+
254
+ ### 服务器启动环境变量(全部必需)
255
+
256
+ | 变量 | 值 | 说明 |
257
+ | ------------------------------------- | -------------------------------------------------------- | ---------------- |
258
+ | `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5433/postgres` | 数据库连接 |
259
+ | `DATABASE_DRIVER` | `node` | 数据库驱动 |
260
+ | `KEY_VAULTS_SECRET` | `LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=` | 密钥保险库密钥 |
261
+ | `BETTER_AUTH_SECRET` | `e2e-test-secret-key-for-better-auth-32chars!` | 认证密钥 |
262
+ | `NEXT_PUBLIC_ENABLE_BETTER_AUTH` | `1` | 启用 Better Auth |
263
+ | `NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION` | `0` | 禁用邮箱验证 |
264
+
265
+ ### S3 Mock 变量(必需!)
266
+
267
+ | 变量 | 值 |
268
+ | ---------------------- | ------------------------------- |
269
+ | `S3_ACCESS_KEY_ID` | `e2e-mock-access-key` |
270
+ | `S3_SECRET_ACCESS_KEY` | `e2e-mock-secret-key` |
271
+ | `S3_BUCKET` | `e2e-mock-bucket` |
272
+ | `S3_ENDPOINT` | `https://e2e-mock-s3.localhost` |
273
+
274
+ **注意**: S3 环境变量是**必需**的,即使不测试文件上传功能。缺少这些变量会导致发送消息时报错 "S3 environment variables are not set completely"。
275
+
276
+ ## 常见问题排查
277
+
278
+ ### Docker daemon is not running
279
+
280
+ **症状**: `Cannot connect to the Docker daemon`
281
+
282
+ **解决**: 启动 Docker Desktop 应用
283
+
284
+ ### PostgreSQL 容器已存在
285
+
286
+ **症状**: `docker: Error response from daemon: Conflict. The container name "/postgres-e2e" is already in use`
287
+
288
+ **解决**:
289
+
290
+ ```bash
291
+ docker stop postgres-e2e
292
+ docker rm postgres-e2e
293
+ ```
294
+
295
+ ### S3 environment variables are not set completely
296
+
297
+ **原因**: 服务器启动时缺少 S3 环境变量
298
+
299
+ **解决**: 启动服务器时必须设置所有 S3 mock 变量
300
+
301
+ ### Cannot find module './src/libs/next/config/define-config'
302
+
303
+ **原因**: 在 e2e 目录下运行 `next start`
304
+
305
+ **解决**: 必须在**项目根目录**运行 `bunx next start`,不能在 e2e 目录运行
306
+
307
+ ### EADDRINUSE: address already in use
308
+
309
+ **原因**: 端口被占用
310
+
311
+ **解决**:
312
+
313
+ ```bash
314
+ # 查找并杀掉占用端口的进程
315
+ lsof -ti:3006 | xargs kill -9
316
+ lsof -ti:5433 | xargs kill -9
317
+ ```
318
+
319
+ ### BeforeAll hook errored: net::ERR_CONNECTION_REFUSED
320
+
321
+ **原因**: 服务器未启动或未就绪
322
+
323
+ **解决**:
324
+
325
+ 1. 确认服务器已启动:`curl http://localhost:3006`
326
+ 2. 确认 `BASE_URL` 环境变量设置正确
327
+ 3. 等待服务器完全就绪后再运行测试
328
+
329
+ ### 测试超时或不稳定
330
+
331
+ **可能原因**:
332
+
333
+ 1. 网络延迟
334
+ 2. 服务器响应慢
335
+ 3. 元素定位问题
336
+
337
+ **解决**:
338
+
339
+ 1. 使用 `HEADLESS=false` 观察测试执行过程
340
+ 2. 检查 `screenshots/` 目录中的失败截图
341
+ 3. 增加等待时间或使用更稳定的定位器
342
+
343
+ ## 清理环境
344
+
345
+ 测试完成后,清理环境:
346
+
347
+ ```bash
348
+ # 停止服务器
349
+ lsof -ti:3006 | xargs kill -9
350
+
351
+ # 停止并删除 PostgreSQL 容器
352
+ docker stop postgres-e2e
353
+ docker rm postgres-e2e
354
+ ```
@@ -0,0 +1,94 @@
1
+ # 测试技巧
2
+
3
+ ## 页面元素定位
4
+
5
+ ### 富文本编辑器 (contenteditable) 输入
6
+
7
+ LobeHub 使用 `@lobehub/editor` 作为聊天输入框,是一个 contenteditable 的富文本编辑器。
8
+
9
+ **关键点**:
10
+
11
+ 1. 不能直接用 `locator.fill()` - 对 contenteditable 不生效
12
+ 2. 需要先 click 容器让编辑器获得焦点
13
+ 3. 使用 `keyboard.type()` 输入文本
14
+
15
+ ```typescript
16
+ // 正确的输入方式
17
+ await chatInputContainer.click();
18
+ await this.page.waitForTimeout(500); // 等待焦点
19
+ await this.page.keyboard.type(message, { delay: 30 });
20
+ await this.page.keyboard.press('Enter'); // 发送
21
+ ```
22
+
23
+ ### 添加 data-testid
24
+
25
+ 为了更可靠的元素定位,可以在组件上添加 `data-testid`:
26
+
27
+ ```tsx
28
+ // src/features/ChatInput/Desktop/index.tsx
29
+ <ChatInput
30
+ data-testid="chat-input"
31
+ ...
32
+ />
33
+ ```
34
+
35
+ ## 调试技巧
36
+
37
+ ### 添加步骤日志
38
+
39
+ 在每个关键步骤添加 console.log,帮助定位问题:
40
+
41
+ ```typescript
42
+ Given('用户进入页面', async function (this: CustomWorld) {
43
+ console.log(' 📍 Step: 导航到首页...');
44
+ await this.page.goto('/');
45
+
46
+ console.log(' 📍 Step: 查找元素...');
47
+ const element = this.page.locator('...');
48
+
49
+ console.log(' ✅ 步骤完成');
50
+ });
51
+ ```
52
+
53
+ ### 查看失败截图
54
+
55
+ 测试失败时会自动保存截图到 `e2e/screenshots/` 目录。
56
+
57
+ ### 非 headless 模式
58
+
59
+ 设置 `HEADLESS=false` 可以看到浏览器操作:
60
+
61
+ ```bash
62
+ HEADLESS=false pnpm exec cucumber-js --config cucumber.config.js --tags "@smoke"
63
+ ```
64
+
65
+ ## 常见问题
66
+
67
+ ### 测试超时 (function timed out)
68
+
69
+ **原因**: 元素定位失败或等待时间不足
70
+
71
+ **解决**:
72
+
73
+ - 检查选择器是否正确
74
+ - 增加 timeout 参数
75
+ - 添加显式等待 `waitForTimeout()`
76
+
77
+ ### strict mode violation (多个元素匹配)
78
+
79
+ **原因**: 选择器匹配到多个元素(如 desktop/mobile 双组件)
80
+
81
+ **解决**:
82
+
83
+ - 使用 `.first()` 或 `.nth(n)`
84
+ - 使用 `boundingBox()` 过滤可见元素
85
+
86
+ ### 输入框内容为空
87
+
88
+ **原因**: contenteditable 编辑器的特殊性
89
+
90
+ **解决**:
91
+
92
+ - 先 click 容器确保焦点
93
+ - 使用 `keyboard.type()` 而非 `fill()`
94
+ - 添加适当的等待时间