From c61df18ab97d1a834cc4f33ad7e7f623296ea212 Mon Sep 17 00:00:00 2001 From: yoyoyohamapi <8338436+yoyoyohamapi@users.noreply.github.com> Date: Thu, 3 Apr 2025 02:46:08 +0000 Subject: [PATCH] test(core): chat with collection & tags (#11388) ### TL:DR * AI chat with collection E2E * AI chat with tag E2E > Close BS-3007 --- .../src/blocksuite/ai/chat-panel/index.ts | 8 +- .../components/ai-chat-chips/add-popover.ts | 3 + .../views/nodes/collection/operations.tsx | 1 + .../explorer/views/nodes/tag/operations.tsx | 1 + .../explorer/views/sections/tags/index.tsx | 1 + .../e2e/basic/authority.spec.ts | 2 +- .../e2e/basic/chat.spec.ts | 1 - .../e2e/chat-with/collections.spec.ts | 78 ++++++++++++++++++- .../e2e/chat-with/tags.spec.ts | 60 +++++++++++++- .../e2e/utils/chat-panel-utils.ts | 34 +++++++- .../e2e/utils/editor-utils.ts | 57 ++++++++++++++ .../e2e/utils/test-utils.ts | 10 ++- 12 files changed, 248 insertions(+), 8 deletions(-) diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts index 39d7d02c24..616fd417d0 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts @@ -607,7 +607,13 @@ export class ChatPanel extends SignalWatcher( return html`
-
${isEmbedding ? `Embedding ${done}/${total}` : 'AFFiNE AI'}
+
+ ${isEmbedding + ? html`Embedding ${done}/${total}` + : 'AFFiNE AI'} +
{ AIProvider.toggleGeneralAIOnboarding?.(true); diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts index 58f1519086..d2880fb6ae 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts @@ -254,6 +254,9 @@ export class ChatPanelAddPopover extends SignalWatcher( @property({ attribute: false }) accessor abortController!: AbortController; + @property({ attribute: 'data-testid', reflect: true }) + accessor testId: string = 'ai-search-input'; + @query('.search-input') accessor searchInput!: HTMLInputElement; diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx index 9bad42e4a7..e337389eb7 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/collection/operations.tsx @@ -118,6 +118,7 @@ export const useExplorerCollectionNodeOperations = ( view: ( diff --git a/packages/frontend/core/src/modules/explorer/views/sections/tags/index.tsx b/packages/frontend/core/src/modules/explorer/views/sections/tags/index.tsx index 03b7518ad9..465367008c 100644 --- a/packages/frontend/core/src/modules/explorer/views/sections/tags/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/sections/tags/index.tsx @@ -43,6 +43,7 @@ export const ExplorerTags = () => { return ( { test.beforeEach(async ({ page, utils }) => { - await utils.testUtils.setupTestEnvironment(page); + await utils.testUtils.setupTestEnvironment(page, false); await utils.chatPanel.openChatPanel(page); }); diff --git a/tests/affine-cloud-copilot/e2e/basic/chat.spec.ts b/tests/affine-cloud-copilot/e2e/basic/chat.spec.ts index 0d16e35684..7f0f12697f 100644 --- a/tests/affine-cloud-copilot/e2e/basic/chat.spec.ts +++ b/tests/affine-cloud-copilot/e2e/basic/chat.spec.ts @@ -256,7 +256,6 @@ test.describe('AIBasic/Chat', () => { ]); const { actions } = await utils.chatPanel.getLatestAssistantMessage(page); - await page.pause(); await actions.retry(); await utils.chatPanel.waitForHistory(page, [ diff --git a/tests/affine-cloud-copilot/e2e/chat-with/collections.spec.ts b/tests/affine-cloud-copilot/e2e/chat-with/collections.spec.ts index d52d2bf452..23144d0eb2 100644 --- a/tests/affine-cloud-copilot/e2e/chat-with/collections.spec.ts +++ b/tests/affine-cloud-copilot/e2e/chat-with/collections.spec.ts @@ -1,3 +1,79 @@ +import { loginUser } from '@affine-test/kit/utils/cloud'; +import { expect } from '@playwright/test'; + import { test } from '../base/base-test'; -test.describe('AIChatWith/Collections', () => {}); +test.describe('AIChatWith/Collections', () => { + test.beforeEach(async ({ page, utils }) => { + const user = await utils.testUtils.getUser(); + await loginUser(page, user); + await utils.testUtils.setupTestEnvironment(page); + await utils.chatPanel.openChatPanel(page); + + // Create two collections + await utils.editor.createCollectionAndDoc( + page, + 'Collection 1', + 'EEee is a cute cat' + ); + await utils.editor.createCollectionAndDoc( + page, + 'Collection 2', + 'FFff is a cute dog' + ); + }); + + test('should support chat with collection', async ({ page, utils }) => { + await utils.chatPanel.chatWithCollections(page, ['Collection 1']); + await utils.chatPanel.makeChat(page, 'What is EEee(Use English)'); + await utils.chatPanel.waitForHistory(page, [ + { + role: 'user', + content: 'What is EEee(Use English)', + }, + { + role: 'assistant', + status: 'success', + }, + ]); + + await expect(async () => { + const { content, message } = + await utils.chatPanel.getLatestAssistantMessage(page); + expect(content).toMatch(/EEee.*cat/); + expect(await message.locator('affine-footnote-node').count()).toBe(1); + }).toPass(); + }); + + test('should support chat with multiple collections', async ({ + page, + utils, + }) => { + await utils.chatPanel.chatWithCollections(page, [ + 'Collection 1', + 'Collection 2', + ]); + await utils.chatPanel.makeChat( + page, + 'What is EEee? What is FFff?(Use English)' + ); + await utils.chatPanel.waitForHistory(page, [ + { + role: 'user', + content: 'What is EEee? What is FFff?(Use English)', + }, + { + role: 'assistant', + status: 'success', + }, + ]); + + await expect(async () => { + const { content, message } = + await utils.chatPanel.getLatestAssistantMessage(page); + expect(content).toMatch(/EEee.*cat/); + expect(content).toMatch(/FFff.*dog/); + expect(await message.locator('affine-footnote-node').count()).toBe(2); + }).toPass(); + }); +}); diff --git a/tests/affine-cloud-copilot/e2e/chat-with/tags.spec.ts b/tests/affine-cloud-copilot/e2e/chat-with/tags.spec.ts index 70e3ccd2ac..4d8990aea1 100644 --- a/tests/affine-cloud-copilot/e2e/chat-with/tags.spec.ts +++ b/tests/affine-cloud-copilot/e2e/chat-with/tags.spec.ts @@ -1,3 +1,61 @@ +import { loginUser } from '@affine-test/kit/utils/cloud'; +import { expect } from '@playwright/test'; + import { test } from '../base/base-test'; -test.describe('AIChatWith/tags', () => {}); +test.describe('AIChatWith/tags', () => { + test.beforeEach(async ({ page, utils }) => { + const user = await utils.testUtils.getUser(); + await loginUser(page, user); + await utils.testUtils.setupTestEnvironment(page); + await utils.chatPanel.openChatPanel(page); + await utils.editor.createTagAndDoc(page, 'Tag 1', 'EEee is a cute cat'); + await utils.editor.createTagAndDoc(page, 'Tag 2', 'FFff is a cute dog'); + }); + + test('should support chat with tag', async ({ page, utils }) => { + await utils.chatPanel.chatWithTags(page, ['Tag 1']); + await utils.chatPanel.makeChat(page, 'What is EEee(Use English)'); + await utils.chatPanel.waitForHistory(page, [ + { + role: 'user', + content: 'What is EEee(Use English)', + }, + { + role: 'assistant', + status: 'success', + }, + ]); + await expect(async () => { + const { content, message } = + await utils.chatPanel.getLatestAssistantMessage(page); + expect(content).toMatch(/EEee.*cat/); + expect(await message.locator('affine-footnote-node').count()).toBe(1); + }).toPass(); + }); + + test('should support chat with multiple tags', async ({ page, utils }) => { + await utils.chatPanel.chatWithTags(page, ['Tag 1', 'Tag 2']); + await utils.chatPanel.makeChat( + page, + 'What is EEee? What is FFff?(Use English)' + ); + await utils.chatPanel.waitForHistory(page, [ + { + role: 'user', + content: 'What is EEee? What is FFff?(Use English)', + }, + { + role: 'assistant', + status: 'success', + }, + ]); + await expect(async () => { + const { content, message } = + await utils.chatPanel.getLatestAssistantMessage(page); + expect(content).toMatch(/EEee.*cat/); + expect(content).toMatch(/FFff.*dog/); + expect(await message.locator('affine-footnote-node').count()).toBe(2); + }).toPass(); + }); +}); diff --git a/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts b/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts index 4eec12cf5a..122f66dcab 100644 --- a/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts +++ b/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts @@ -225,7 +225,6 @@ export class ChatPanelUtils { await expect(states.every(state => state === 'finished')).toBe(true); }).toPass({ timeout: 20000 }); - await page.pause(); await this.makeChat(page, text); } @@ -249,6 +248,39 @@ export class ChatPanelUtils { await this.makeChat(page, text); } + public static async chatWithTags(page: Page, tags: string[]) { + for (const tag of tags) { + const withButton = await page.getByTestId('chat-panel-with-button'); + await withButton.click(); + const withMenu = await page.getByTestId('ai-add-popover'); + await withMenu.getByTestId('ai-chat-with-tags').click(); + await withMenu.getByText(tag).click(); + await page.getByTestId('chat-panel-chips').getByText(tag); + } + await this.waitForEmbeddingProgress(page); + } + + public static async chatWithCollections(page: Page, collections: string[]) { + for (const collection of collections) { + const withButton = await page.getByTestId('chat-panel-with-button'); + await withButton.click(); + const withMenu = await page.getByTestId('ai-add-popover'); + await withMenu.getByTestId('ai-chat-with-collections').click(); + await withMenu.getByText(collection).click(); + await page.getByTestId('chat-panel-chips').getByText(collection); + } + await this.waitForEmbeddingProgress(page); + } + + public static async waitForEmbeddingProgress(page: Page) { + await page.getByTestId('chat-panel-embedding-progress').waitFor({ + state: 'visible', + }); + await page.getByTestId('chat-panel-embedding-progress').waitFor({ + state: 'hidden', + }); + } + public static async enableNetworkSearch(page: Page) { const networkSearch = await page.getByTestId('chat-network-search'); if ((await networkSearch.getAttribute('data-active')) === 'false') { diff --git a/tests/affine-cloud-copilot/e2e/utils/editor-utils.ts b/tests/affine-cloud-copilot/e2e/utils/editor-utils.ts index 77394ddd65..14c47721ab 100644 --- a/tests/affine-cloud-copilot/e2e/utils/editor-utils.ts +++ b/tests/affine-cloud-copilot/e2e/utils/editor-utils.ts @@ -209,6 +209,63 @@ export class EditorUtils { ); } + public static async createCollectionAndDoc( + page: Page, + collectionName: string, + docContent: string + ) { + // Create collection + await page.getByTestId('explorer-bar-add-collection-button').click(); + const input = await page.getByTestId('prompt-modal-input'); + await input.focus(); + await input.pressSequentially(collectionName); + await page.getByTestId('prompt-modal-confirm').click(); + const collections = await page.getByTestId('collapsible-section-content'); + const collection = await collections + .getByText(collectionName) + .locator('..'); + + // Create doc + await collection.hover(); + await collection.getByTestId('collection-add-doc-button').click(); + await page.getByTestId('confirm-modal-confirm').click(); + await this.focusToEditor(page); + const texts = docContent.split('\n'); + for (const [index, line] of texts.entries()) { + await page.keyboard.insertText(line); + if (index !== texts.length - 1) { + await page.keyboard.press('Enter'); + } + } + } + + public static async createTagAndDoc( + page: Page, + tagName: string, + docContent: string + ) { + // Create tag + const tags = await page.getByTestId('explorer-tags'); + await tags.getByTestId('explorer-bar-add-favorite-button').click(); + const input = await page.getByTestId('rename-modal-input'); + await input.focus(); + await input.pressSequentially(tagName); + await input.press('Enter'); + const tag = await tags.getByText(tagName).locator('..'); + + // Create doc + await tag.hover(); + await tag.getByTestId('tag-add-doc-button').click(); + await this.focusToEditor(page); + const texts = docContent.split('\n'); + for (const [index, line] of texts.entries()) { + await page.keyboard.insertText(line); + if (index !== texts.length - 1) { + await page.keyboard.press('Enter'); + } + } + } + public static async selectElementInEdgeless(page: Page, elements: string[]) { await page.evaluate( ({ elements }) => { diff --git a/tests/affine-cloud-copilot/e2e/utils/test-utils.ts b/tests/affine-cloud-copilot/e2e/utils/test-utils.ts index 46a30cdfda..2f51a0a9de 100644 --- a/tests/affine-cloud-copilot/e2e/utils/test-utils.ts +++ b/tests/affine-cloud-copilot/e2e/utils/test-utils.ts @@ -1,4 +1,7 @@ -import { createRandomAIUser } from '@affine-test/kit/utils/cloud'; +import { + createRandomAIUser, + enableCloudWorkspace, +} from '@affine-test/kit/utils/cloud'; import { openHomePage, setCoreUrl } from '@affine-test/kit/utils/load-page'; import { clickNewPageButton, @@ -52,10 +55,13 @@ export class TestUtils { }; } - public async setupTestEnvironment(page: Page) { + public async setupTestEnvironment(page: Page, enableCloud: boolean = true) { await openHomePage(page); await clickNewPageButton(page); await waitForEditorLoad(page); + if (enableCloud) { + await enableCloudWorkspace(page); + } } public async createTestWorkspace(page: Page, name: string = 'test') {