@lobehub/lobehub 2.0.0-next.267 → 2.0.0-next.268
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.
- package/.cursor/rules/microcopy-cn.mdc +75 -63
- package/.cursor/rules/microcopy-en.mdc +4 -8
- package/CHANGELOG.md +25 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/apps/desktop/src/main/locales/default/common.ts +2 -2
- package/changelog/v1.json +5 -0
- package/docs/development/database-schema.dbml +4 -0
- package/e2e/CLAUDE.md +9 -8
- package/e2e/cucumber.config.js +1 -0
- package/e2e/src/features/page/README.md +118 -0
- package/e2e/src/features/page/crud.feature +62 -0
- package/e2e/src/features/page/editor-content.feature +93 -0
- package/e2e/src/features/page/editor-meta.feature +60 -0
- package/e2e/src/steps/agent/conversation.steps.ts +4 -4
- package/e2e/src/steps/home/sidebarAgent.steps.ts +91 -94
- package/e2e/src/steps/home/sidebarGroup.steps.ts +4 -4
- package/e2e/src/steps/hooks.ts +2 -0
- package/e2e/src/steps/page/editor-content.steps.ts +344 -0
- package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
- package/e2e/src/steps/page/page-crud.steps.ts +363 -0
- package/e2e/src/support/world.ts +12 -0
- package/locales/ar/file.json +2 -0
- package/locales/bg-BG/file.json +2 -0
- package/locales/de-DE/file.json +2 -0
- package/locales/en-US/auth.json +1 -1
- package/locales/en-US/file.json +2 -0
- package/locales/en-US/metadata.json +2 -2
- package/locales/es-ES/file.json +2 -0
- package/locales/fa-IR/file.json +2 -0
- package/locales/fr-FR/file.json +2 -0
- package/locales/it-IT/file.json +2 -0
- package/locales/ja-JP/file.json +2 -0
- package/locales/ko-KR/file.json +2 -0
- package/locales/nl-NL/file.json +2 -0
- package/locales/pl-PL/file.json +2 -0
- package/locales/pt-BR/file.json +2 -0
- package/locales/ru-RU/file.json +2 -0
- package/locales/tr-TR/file.json +2 -0
- package/locales/vi-VN/file.json +2 -0
- package/locales/zh-CN/file.json +2 -0
- package/locales/zh-TW/file.json +2 -0
- package/package.json +1 -1
- package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
- package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
- package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
- package/packages/const/src/settings/group.ts +0 -10
- package/packages/database/migrations/0068_update_group_data.sql +4 -0
- package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
- package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
- package/packages/database/src/models/knowledgeBase.ts +67 -3
- package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
- package/packages/database/src/repositories/agentGroup/index.ts +4 -9
- package/packages/database/src/repositories/knowledge/index.ts +3 -3
- package/packages/database/src/schemas/chatGroup.ts +4 -3
- package/packages/database/src/types/chatGroup.ts +0 -7
- package/packages/types/src/agentGroup/index.ts +30 -9
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
- package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
- package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
- package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
- package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
- package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
- package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
- package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
- package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
- package/src/components/DragUpload/index.tsx +24 -27
- package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
- package/src/features/CommandMenu/useCommandMenu.ts +4 -14
- package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
- package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
- package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
- package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
- package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
- package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
- package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
- package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
- package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
- package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
- package/src/features/ResourceManager/index.tsx +3 -0
- package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
- package/src/locales/default/auth.ts +1 -1
- package/src/locales/default/file.ts +2 -0
- package/src/locales/default/metadata.ts +2 -2
- package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
- package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
- package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
- package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
- package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
- package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
- package/src/server/modules/AgentRuntime/types.ts +21 -21
- package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
- package/src/server/routers/lambda/agentGroup.ts +10 -12
- package/src/server/services/document/index.ts +1 -0
- package/src/store/agentGroup/slices/curd.test.ts +4 -4
- package/src/store/file/slices/fileManager/action.ts +12 -4
- package/src/store/home/slices/homeInput/action.ts +0 -3
- package/src/store/session/slices/session/action.ts +5 -9
- package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
- package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
- package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
- package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
- package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
- package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
- package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
- package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
- package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
- package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
- package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
- package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
- package/src/features/GroupChatSettings/index.ts +0 -16
- package/src/features/GroupChatSettings/store/action.ts +0 -105
- package/src/features/GroupChatSettings/store/index.ts +0 -18
- package/src/features/GroupChatSettings/store/initialState.ts +0 -23
- package/src/features/GroupChatSettings/store/selectors.ts +0 -13
- package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
- /package/src/features/ResourceManager/components/{Tree → LibraryHierarchy}/TreeSkeleton.tsx +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
@journey @P0 @page
|
|
2
|
+
Feature: Page 编辑器富文本编辑
|
|
3
|
+
|
|
4
|
+
作为用户,我希望能够使用富文本编辑器编写内容,
|
|
5
|
+
以便创建格式丰富的文档
|
|
6
|
+
|
|
7
|
+
Background:
|
|
8
|
+
Given 用户已登录系统
|
|
9
|
+
|
|
10
|
+
# ============================================
|
|
11
|
+
# 基础文本编辑
|
|
12
|
+
# ============================================
|
|
13
|
+
|
|
14
|
+
@PAGE-CONTENT-001
|
|
15
|
+
Scenario: 输入基础文本内容
|
|
16
|
+
Given 用户打开一个文稿编辑器
|
|
17
|
+
When 用户点击编辑器内容区域
|
|
18
|
+
And 用户输入文本 "这是一段测试内容"
|
|
19
|
+
Then 编辑器应该显示输入的文本
|
|
20
|
+
|
|
21
|
+
@PAGE-CONTENT-002 @P1
|
|
22
|
+
Scenario: 编辑已有内容
|
|
23
|
+
Given 用户打开一个文稿编辑器
|
|
24
|
+
When 用户在编辑器中输入内容 "原始内容"
|
|
25
|
+
And 用户选中所有内容
|
|
26
|
+
And 用户输入文本 "修改后的内容"
|
|
27
|
+
Then 编辑器应该显示 "修改后的内容"
|
|
28
|
+
|
|
29
|
+
# ============================================
|
|
30
|
+
# 斜杠命令
|
|
31
|
+
# ============================================
|
|
32
|
+
|
|
33
|
+
@PAGE-SLASH-001 @P1
|
|
34
|
+
Scenario: 使用斜杠命令打开菜单
|
|
35
|
+
Given 用户打开一个文稿编辑器
|
|
36
|
+
When 用户点击编辑器内容区域
|
|
37
|
+
And 用户输入斜杠 "/"
|
|
38
|
+
Then 应该显示斜杠命令菜单
|
|
39
|
+
|
|
40
|
+
@PAGE-SLASH-002 @P1
|
|
41
|
+
Scenario: 通过斜杠命令插入一级标题
|
|
42
|
+
Given 用户打开一个文稿编辑器
|
|
43
|
+
When 用户点击编辑器内容区域
|
|
44
|
+
And 用户输入斜杠命令 "/h1"
|
|
45
|
+
And 用户按下 Enter 键
|
|
46
|
+
And 用户输入文本 "一级标题内容"
|
|
47
|
+
Then 编辑器应该包含一级标题
|
|
48
|
+
|
|
49
|
+
@PAGE-SLASH-003 @P1
|
|
50
|
+
Scenario: 通过斜杠命令插入无序列表
|
|
51
|
+
Given 用户打开一个文稿编辑器
|
|
52
|
+
When 用户点击编辑器内容区域
|
|
53
|
+
And 用户输入斜杠命令 "/ul"
|
|
54
|
+
And 用户按下 Enter 键
|
|
55
|
+
And 用户输入文本 "列表项一"
|
|
56
|
+
Then 编辑器应该包含无序列表
|
|
57
|
+
|
|
58
|
+
@PAGE-SLASH-004 @P2
|
|
59
|
+
Scenario: 通过斜杠命令插入任务列表
|
|
60
|
+
Given 用户打开一个文稿编辑器
|
|
61
|
+
When 用户点击编辑器内容区域
|
|
62
|
+
And 用户输入斜杠命令 "/tl"
|
|
63
|
+
And 用户按下 Enter 键
|
|
64
|
+
And 用户输入文本 "待办事项"
|
|
65
|
+
Then 编辑器应该包含任务列表
|
|
66
|
+
|
|
67
|
+
@PAGE-SLASH-005 @P2 @skip
|
|
68
|
+
Scenario: 通过斜杠命令插入代码块
|
|
69
|
+
Given 用户打开一个文稿编辑器
|
|
70
|
+
When 用户点击编辑器内容区域
|
|
71
|
+
And 用户输入斜杠命令 "/codeblock"
|
|
72
|
+
And 用户按下 Enter 键
|
|
73
|
+
Then 编辑器应该包含代码块
|
|
74
|
+
|
|
75
|
+
# ============================================
|
|
76
|
+
# 文本格式化
|
|
77
|
+
# ============================================
|
|
78
|
+
|
|
79
|
+
@PAGE-FORMAT-001 @P1
|
|
80
|
+
Scenario: 使用快捷键加粗文本
|
|
81
|
+
Given 用户打开一个文稿编辑器
|
|
82
|
+
When 用户在编辑器中输入内容 "加粗文本"
|
|
83
|
+
And 用户选中所有内容
|
|
84
|
+
And 用户按下快捷键 "Meta+B"
|
|
85
|
+
Then 选中的文本应该被加粗
|
|
86
|
+
|
|
87
|
+
@PAGE-FORMAT-002 @P2
|
|
88
|
+
Scenario: 使用快捷键斜体文本
|
|
89
|
+
Given 用户打开一个文稿编辑器
|
|
90
|
+
When 用户在编辑器中输入内容 "斜体文本"
|
|
91
|
+
And 用户选中所有内容
|
|
92
|
+
And 用户按下快捷键 "Meta+I"
|
|
93
|
+
Then 选中的文本应该变为斜体
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@journey @P0 @page
|
|
2
|
+
Feature: Page 编辑器元数据编辑
|
|
3
|
+
|
|
4
|
+
作为用户,我希望能够编辑文稿的标题和图标,
|
|
5
|
+
以便更好地组织和识别我的文档
|
|
6
|
+
|
|
7
|
+
Background:
|
|
8
|
+
Given 用户已登录系统
|
|
9
|
+
|
|
10
|
+
# ============================================
|
|
11
|
+
# 标题编辑
|
|
12
|
+
# ============================================
|
|
13
|
+
|
|
14
|
+
@PAGE-TITLE-001
|
|
15
|
+
Scenario: 编辑文稿标题
|
|
16
|
+
Given 用户打开一个文稿编辑器
|
|
17
|
+
When 用户点击标题输入框
|
|
18
|
+
And 用户输入标题 "我的测试文稿"
|
|
19
|
+
And 用户按下 Enter 键
|
|
20
|
+
Then 文稿标题应该更新为 "我的测试文稿"
|
|
21
|
+
|
|
22
|
+
@PAGE-TITLE-002 @P1
|
|
23
|
+
Scenario: 编辑标题后点击其他区域保存
|
|
24
|
+
Given 用户打开一个文稿编辑器
|
|
25
|
+
When 用户点击标题输入框
|
|
26
|
+
And 用户输入标题 "Click Away Title"
|
|
27
|
+
And 用户点击编辑器内容区域
|
|
28
|
+
Then 文稿标题应该更新为 "Click Away Title"
|
|
29
|
+
|
|
30
|
+
@PAGE-TITLE-003 @P1
|
|
31
|
+
Scenario: 清空标题后显示占位符
|
|
32
|
+
Given 用户打开一个文稿编辑器
|
|
33
|
+
When 用户点击标题输入框
|
|
34
|
+
And 用户清空标题内容
|
|
35
|
+
Then 应该显示标题占位符
|
|
36
|
+
|
|
37
|
+
# ============================================
|
|
38
|
+
# Emoji 图标
|
|
39
|
+
# ============================================
|
|
40
|
+
|
|
41
|
+
@PAGE-EMOJI-001 @P1
|
|
42
|
+
Scenario: 为文稿添加 Emoji 图标
|
|
43
|
+
Given 用户打开一个文稿编辑器
|
|
44
|
+
When 用户点击选择图标按钮
|
|
45
|
+
And 用户选择一个 Emoji
|
|
46
|
+
Then 文稿应该显示所选的 Emoji 图标
|
|
47
|
+
|
|
48
|
+
@PAGE-EMOJI-002 @P1
|
|
49
|
+
Scenario: 更换文稿的 Emoji 图标
|
|
50
|
+
Given 用户打开一个带有 Emoji 的文稿
|
|
51
|
+
When 用户点击已有的 Emoji 图标
|
|
52
|
+
And 用户选择另一个 Emoji
|
|
53
|
+
Then 文稿图标应该更新为新的 Emoji
|
|
54
|
+
|
|
55
|
+
@PAGE-EMOJI-003 @P2
|
|
56
|
+
Scenario: 删除文稿的 Emoji 图标
|
|
57
|
+
Given 用户打开一个带有 Emoji 的文稿
|
|
58
|
+
When 用户点击已有的 Emoji 图标
|
|
59
|
+
And 用户点击删除图标按钮
|
|
60
|
+
Then 文稿不应该显示 Emoji 图标
|
|
@@ -7,7 +7,7 @@ import { Given, Then, When } from '@cucumber/cucumber';
|
|
|
7
7
|
import { expect } from '@playwright/test';
|
|
8
8
|
|
|
9
9
|
import { llmMockManager, presetResponses } from '../../mocks/llm';
|
|
10
|
-
import { CustomWorld } from '../../support/world';
|
|
10
|
+
import { CustomWorld, WAIT_TIMEOUT } from '../../support/world';
|
|
11
11
|
|
|
12
12
|
// ============================================
|
|
13
13
|
// Given Steps
|
|
@@ -29,19 +29,19 @@ Given('用户进入 Lobe AI 对话页面', async function (this: CustomWorld) {
|
|
|
29
29
|
console.log(' 📍 Step: 导航到首页...');
|
|
30
30
|
// Navigate to home page first
|
|
31
31
|
await this.page.goto('/');
|
|
32
|
-
await this.page.waitForLoadState('networkidle', { timeout:
|
|
32
|
+
await this.page.waitForLoadState('networkidle', { timeout: WAIT_TIMEOUT });
|
|
33
33
|
|
|
34
34
|
console.log(' 📍 Step: 查找 Lobe AI...');
|
|
35
35
|
// Find and click on "Lobe AI" agent in the sidebar/home
|
|
36
36
|
const lobeAIAgent = this.page.locator('text=Lobe AI').first();
|
|
37
|
-
await expect(lobeAIAgent).toBeVisible({ timeout:
|
|
37
|
+
await expect(lobeAIAgent).toBeVisible({ timeout: WAIT_TIMEOUT });
|
|
38
38
|
|
|
39
39
|
console.log(' 📍 Step: 点击 Lobe AI...');
|
|
40
40
|
await lobeAIAgent.click();
|
|
41
41
|
|
|
42
42
|
console.log(' 📍 Step: 等待聊天界面加载...');
|
|
43
43
|
// Wait for the chat interface to be ready
|
|
44
|
-
await this.page.waitForLoadState('networkidle', { timeout:
|
|
44
|
+
await this.page.waitForLoadState('networkidle', { timeout: WAIT_TIMEOUT });
|
|
45
45
|
|
|
46
46
|
console.log(' 📍 Step: 查找输入框...');
|
|
47
47
|
// The input is a rich text editor with contenteditable
|
|
@@ -10,7 +10,86 @@ import { Given, Then, When } from '@cucumber/cucumber';
|
|
|
10
10
|
import { expect } from '@playwright/test';
|
|
11
11
|
|
|
12
12
|
import { TEST_USER } from '../../support/seedTestUser';
|
|
13
|
-
import { CustomWorld } from '../../support/world';
|
|
13
|
+
import { CustomWorld, WAIT_TIMEOUT } from '../../support/world';
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Helper Functions
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
async function inputNewName(
|
|
20
|
+
this: CustomWorld,
|
|
21
|
+
newName: string,
|
|
22
|
+
pressEnter: boolean,
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
await this.page.waitForTimeout(300);
|
|
25
|
+
|
|
26
|
+
// Try to find the popover input
|
|
27
|
+
const popoverInputSelectors = [
|
|
28
|
+
'.ant-popover-inner input',
|
|
29
|
+
'.ant-popover-content input',
|
|
30
|
+
'.ant-popover input',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
let renameInput = null;
|
|
34
|
+
|
|
35
|
+
for (const selector of popoverInputSelectors) {
|
|
36
|
+
try {
|
|
37
|
+
const locator = this.page.locator(selector).first();
|
|
38
|
+
await locator.waitFor({ state: 'visible', timeout: 2000 });
|
|
39
|
+
renameInput = locator;
|
|
40
|
+
break;
|
|
41
|
+
} catch {
|
|
42
|
+
// Try next selector
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!renameInput) {
|
|
47
|
+
// Fallback: find any visible input
|
|
48
|
+
const allInputs = this.page.locator('input:visible');
|
|
49
|
+
const count = await allInputs.count();
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < count; i++) {
|
|
52
|
+
const input = allInputs.nth(i);
|
|
53
|
+
const placeholder = (await input.getAttribute('placeholder').catch(() => '')) || '';
|
|
54
|
+
if (placeholder.includes('Search') || placeholder.includes('搜索')) continue;
|
|
55
|
+
|
|
56
|
+
const isInPopover = await input.evaluate((el) => {
|
|
57
|
+
return el.closest('.ant-popover') !== null || el.closest('[class*="popover"]') !== null;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (isInPopover || count <= 2) {
|
|
61
|
+
renameInput = input;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (renameInput) {
|
|
68
|
+
await renameInput.click();
|
|
69
|
+
await renameInput.clear();
|
|
70
|
+
await renameInput.fill(newName);
|
|
71
|
+
|
|
72
|
+
if (pressEnter) {
|
|
73
|
+
await renameInput.press('Enter');
|
|
74
|
+
} else {
|
|
75
|
+
await this.page.click('body', { position: { x: 10, y: 10 } });
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// Keyboard fallback
|
|
79
|
+
await this.page.keyboard.press('Meta+A');
|
|
80
|
+
await this.page.waitForTimeout(50);
|
|
81
|
+
await this.page.keyboard.type(newName, { delay: 20 });
|
|
82
|
+
|
|
83
|
+
if (pressEnter) {
|
|
84
|
+
await this.page.keyboard.press('Enter');
|
|
85
|
+
} else {
|
|
86
|
+
await this.page.click('body', { position: { x: 10, y: 10 } });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await this.page.waitForTimeout(1000);
|
|
91
|
+
console.log(` ✅ 已输入新名称 "${newName}"`);
|
|
92
|
+
}
|
|
14
93
|
|
|
15
94
|
/**
|
|
16
95
|
* Create a test agent directly in database
|
|
@@ -60,7 +139,7 @@ Given('用户在 Home 页面有一个 Agent', async function (this: CustomWorld)
|
|
|
60
139
|
console.log(' 📍 Step: 查找新创建的 Agent...');
|
|
61
140
|
// Look for the newly created agent in the sidebar by its specific ID
|
|
62
141
|
const agentItem = this.page.locator(`a[href="/agent/${agentId}"]`).first();
|
|
63
|
-
await expect(agentItem).toBeVisible({ timeout:
|
|
142
|
+
await expect(agentItem).toBeVisible({ timeout: WAIT_TIMEOUT });
|
|
64
143
|
|
|
65
144
|
// Store agent reference for later use
|
|
66
145
|
const agentLabel = await agentItem.getAttribute('aria-label');
|
|
@@ -81,7 +160,7 @@ Given('该 Agent 未被置顶', async function (this: CustomWorld) {
|
|
|
81
160
|
// Unpin it first
|
|
82
161
|
await targetItem.click({ button: 'right' });
|
|
83
162
|
await this.page.waitForTimeout(300);
|
|
84
|
-
const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|
|
|
163
|
+
const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|unpin/i });
|
|
85
164
|
if ((await unpinOption.count()) > 0) {
|
|
86
165
|
await unpinOption.click();
|
|
87
166
|
await this.page.waitForTimeout(500);
|
|
@@ -103,7 +182,7 @@ Given('该 Agent 已被置顶', async function (this: CustomWorld) {
|
|
|
103
182
|
// Pin it first
|
|
104
183
|
await targetItem.click({ button: 'right' });
|
|
105
184
|
await this.page.waitForTimeout(300);
|
|
106
|
-
const pinOption = this.page.getByRole('menuitem', { name: /置顶|
|
|
185
|
+
const pinOption = this.page.getByRole('menuitem', { name: /置顶|pin/i });
|
|
107
186
|
if ((await pinOption.count()) > 0) {
|
|
108
187
|
await pinOption.click();
|
|
109
188
|
await this.page.waitForTimeout(500);
|
|
@@ -179,7 +258,7 @@ When('用户点击更多操作按钮', async function (this: CustomWorld) {
|
|
|
179
258
|
When('用户在菜单中选择重命名', async function (this: CustomWorld) {
|
|
180
259
|
console.log(' 📍 Step: 选择重命名选项...');
|
|
181
260
|
|
|
182
|
-
const renameOption = this.page.getByRole('menuitem', { name: /^(
|
|
261
|
+
const renameOption = this.page.getByRole('menuitem', { name: /^(rename|重命名)$/i });
|
|
183
262
|
await expect(renameOption).toBeVisible({ timeout: 5000 });
|
|
184
263
|
await renameOption.click();
|
|
185
264
|
await this.page.waitForTimeout(500);
|
|
@@ -190,7 +269,7 @@ When('用户在菜单中选择重命名', async function (this: CustomWorld) {
|
|
|
190
269
|
When('用户在菜单中选择置顶', async function (this: CustomWorld) {
|
|
191
270
|
console.log(' 📍 Step: 选择置顶选项...');
|
|
192
271
|
|
|
193
|
-
const pinOption = this.page.getByRole('menuitem', { name: /^(
|
|
272
|
+
const pinOption = this.page.getByRole('menuitem', { name: /^(pin|置顶)$/i });
|
|
194
273
|
await expect(pinOption).toBeVisible({ timeout: 5000 });
|
|
195
274
|
await pinOption.click();
|
|
196
275
|
await this.page.waitForTimeout(500);
|
|
@@ -201,7 +280,7 @@ When('用户在菜单中选择置顶', async function (this: CustomWorld) {
|
|
|
201
280
|
When('用户在菜单中选择取消置顶', async function (this: CustomWorld) {
|
|
202
281
|
console.log(' 📍 Step: 选择取消置顶选项...');
|
|
203
282
|
|
|
204
|
-
const unpinOption = this.page.getByRole('menuitem', { name: /^(
|
|
283
|
+
const unpinOption = this.page.getByRole('menuitem', { name: /^(unpin|取消置顶)$/i });
|
|
205
284
|
await expect(unpinOption).toBeVisible({ timeout: 5000 });
|
|
206
285
|
await unpinOption.click();
|
|
207
286
|
await this.page.waitForTimeout(500);
|
|
@@ -212,7 +291,7 @@ When('用户在菜单中选择取消置顶', async function (this: CustomWorld)
|
|
|
212
291
|
When('用户在菜单中选择删除', async function (this: CustomWorld) {
|
|
213
292
|
console.log(' 📍 Step: 选择删除选项...');
|
|
214
293
|
|
|
215
|
-
const deleteOption = this.page.getByRole('menuitem', { name: /^(
|
|
294
|
+
const deleteOption = this.page.getByRole('menuitem', { name: /^(delete|删除)$/i });
|
|
216
295
|
await expect(deleteOption).toBeVisible({ timeout: 5000 });
|
|
217
296
|
await deleteOption.click();
|
|
218
297
|
await this.page.waitForTimeout(300);
|
|
@@ -236,13 +315,10 @@ When('用户输入新的名称 {string}', async function (this: CustomWorld, new
|
|
|
236
315
|
await inputNewName.call(this, newName, false);
|
|
237
316
|
});
|
|
238
317
|
|
|
239
|
-
When(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
await inputNewName.call(this, newName, true);
|
|
244
|
-
},
|
|
245
|
-
);
|
|
318
|
+
When('用户输入新的名称 {string} 并按 Enter', async function (this: CustomWorld, newName: string) {
|
|
319
|
+
console.log(` 📍 Step: 输入新名称 "${newName}" 并按 Enter...`);
|
|
320
|
+
await inputNewName.call(this, newName, true);
|
|
321
|
+
});
|
|
246
322
|
|
|
247
323
|
// ============================================
|
|
248
324
|
// Then Steps
|
|
@@ -292,82 +368,3 @@ Then('Agent 应该从列表中移除', async function (this: CustomWorld) {
|
|
|
292
368
|
|
|
293
369
|
console.log(' ✅ Agent 已从列表中移除');
|
|
294
370
|
});
|
|
295
|
-
|
|
296
|
-
// ============================================
|
|
297
|
-
// Helper Functions
|
|
298
|
-
// ============================================
|
|
299
|
-
|
|
300
|
-
async function inputNewName(
|
|
301
|
-
this: CustomWorld,
|
|
302
|
-
newName: string,
|
|
303
|
-
pressEnter: boolean,
|
|
304
|
-
): Promise<void> {
|
|
305
|
-
await this.page.waitForTimeout(300);
|
|
306
|
-
|
|
307
|
-
// Try to find the popover input
|
|
308
|
-
const popoverInputSelectors = [
|
|
309
|
-
'.ant-popover-inner input',
|
|
310
|
-
'.ant-popover-content input',
|
|
311
|
-
'.ant-popover input',
|
|
312
|
-
];
|
|
313
|
-
|
|
314
|
-
let renameInput = null;
|
|
315
|
-
|
|
316
|
-
for (const selector of popoverInputSelectors) {
|
|
317
|
-
try {
|
|
318
|
-
const locator = this.page.locator(selector).first();
|
|
319
|
-
await locator.waitFor({ state: 'visible', timeout: 2000 });
|
|
320
|
-
renameInput = locator;
|
|
321
|
-
break;
|
|
322
|
-
} catch {
|
|
323
|
-
// Try next selector
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (!renameInput) {
|
|
328
|
-
// Fallback: find any visible input
|
|
329
|
-
const allInputs = this.page.locator('input:visible');
|
|
330
|
-
const count = await allInputs.count();
|
|
331
|
-
|
|
332
|
-
for (let i = 0; i < count; i++) {
|
|
333
|
-
const input = allInputs.nth(i);
|
|
334
|
-
const placeholder = (await input.getAttribute('placeholder').catch(() => '')) || '';
|
|
335
|
-
if (placeholder.includes('Search') || placeholder.includes('搜索')) continue;
|
|
336
|
-
|
|
337
|
-
const isInPopover = await input.evaluate((el) => {
|
|
338
|
-
return el.closest('.ant-popover') !== null || el.closest('[class*="popover"]') !== null;
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
if (isInPopover || count <= 2) {
|
|
342
|
-
renameInput = input;
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (renameInput) {
|
|
349
|
-
await renameInput.click();
|
|
350
|
-
await renameInput.clear();
|
|
351
|
-
await renameInput.fill(newName);
|
|
352
|
-
|
|
353
|
-
if (pressEnter) {
|
|
354
|
-
await renameInput.press('Enter');
|
|
355
|
-
} else {
|
|
356
|
-
await this.page.click('body', { position: { x: 10, y: 10 } });
|
|
357
|
-
}
|
|
358
|
-
} else {
|
|
359
|
-
// Keyboard fallback
|
|
360
|
-
await this.page.keyboard.press('Meta+A');
|
|
361
|
-
await this.page.waitForTimeout(50);
|
|
362
|
-
await this.page.keyboard.type(newName, { delay: 20 });
|
|
363
|
-
|
|
364
|
-
if (pressEnter) {
|
|
365
|
-
await this.page.keyboard.press('Enter');
|
|
366
|
-
} else {
|
|
367
|
-
await this.page.click('body', { position: { x: 10, y: 10 } });
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
await this.page.waitForTimeout(1000);
|
|
372
|
-
console.log(` ✅ 已输入新名称 "${newName}"`);
|
|
373
|
-
}
|
|
@@ -10,7 +10,7 @@ import { Given, Then, When } from '@cucumber/cucumber';
|
|
|
10
10
|
import { expect } from '@playwright/test';
|
|
11
11
|
|
|
12
12
|
import { TEST_USER } from '../../support/seedTestUser';
|
|
13
|
-
import { CustomWorld } from '../../support/world';
|
|
13
|
+
import { CustomWorld, WAIT_TIMEOUT } from '../../support/world';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Create a test chat group directly in database
|
|
@@ -58,7 +58,7 @@ Given('用户在 Home 页面有一个 Agent Group', async function (this: Custom
|
|
|
58
58
|
|
|
59
59
|
console.log(' 📍 Step: 查找新创建的 Agent Group...');
|
|
60
60
|
const groupItem = this.page.locator(`a[href="/group/${groupId}"]`).first();
|
|
61
|
-
await expect(groupItem).toBeVisible({ timeout:
|
|
61
|
+
await expect(groupItem).toBeVisible({ timeout: WAIT_TIMEOUT });
|
|
62
62
|
|
|
63
63
|
const groupLabel = await groupItem.getAttribute('aria-label');
|
|
64
64
|
this.testContext.targetItemId = groupLabel || groupId;
|
|
@@ -76,7 +76,7 @@ Given('该 Agent Group 未被置顶', async function (this: CustomWorld) {
|
|
|
76
76
|
if ((await pinIcon.count()) > 0) {
|
|
77
77
|
await targetItem.click({ button: 'right' });
|
|
78
78
|
await this.page.waitForTimeout(300);
|
|
79
|
-
const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|
|
|
79
|
+
const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|unpin/i });
|
|
80
80
|
if ((await unpinOption.count()) > 0) {
|
|
81
81
|
await unpinOption.click();
|
|
82
82
|
await this.page.waitForTimeout(500);
|
|
@@ -95,7 +95,7 @@ Given('该 Agent Group 已被置顶', async function (this: CustomWorld) {
|
|
|
95
95
|
if ((await pinIcon.count()) === 0) {
|
|
96
96
|
await targetItem.click({ button: 'right' });
|
|
97
97
|
await this.page.waitForTimeout(300);
|
|
98
|
-
const pinOption = this.page.getByRole('menuitem', { name: /置顶|
|
|
98
|
+
const pinOption = this.page.getByRole('menuitem', { name: /置顶|pin/i });
|
|
99
99
|
if ((await pinOption.count()) > 0) {
|
|
100
100
|
await pinOption.click();
|
|
101
101
|
await this.page.waitForTimeout(500);
|
package/e2e/src/steps/hooks.ts
CHANGED
|
@@ -85,6 +85,7 @@ Before(async function (this: CustomWorld, { pickle }) {
|
|
|
85
85
|
tag.name.startsWith('@COMMUNITY-') ||
|
|
86
86
|
tag.name.startsWith('@AGENT-') ||
|
|
87
87
|
tag.name.startsWith('@HOME-') ||
|
|
88
|
+
tag.name.startsWith('@PAGE-') ||
|
|
88
89
|
tag.name.startsWith('@ROUTES-'),
|
|
89
90
|
);
|
|
90
91
|
console.log(`\n📝 Running: ${pickle.name}${testId ? ` (${testId.name.replace('@', '')})` : ''}`);
|
|
@@ -106,6 +107,7 @@ After(async function (this: CustomWorld, { pickle, result }) {
|
|
|
106
107
|
tag.name.startsWith('@COMMUNITY-') ||
|
|
107
108
|
tag.name.startsWith('@AGENT-') ||
|
|
108
109
|
tag.name.startsWith('@HOME-') ||
|
|
110
|
+
tag.name.startsWith('@PAGE-') ||
|
|
109
111
|
tag.name.startsWith('@ROUTES-'),
|
|
110
112
|
)
|
|
111
113
|
?.name.replace('@', '');
|