test(core): split and enhance copilot e2e tests (#11007)

### TL;DR

Split and enhance copilot e2e tests.

### What Changed

#### Tests Structure

The e2e tests are organized into the following categories:

1. **Basic Tests (`/basic`)**: Tests for verifying core AI capabilities including feature onboarding, authorization workflows, and basic chat interactions.
2. **Chat Interaction Tests (`/chat-with`)**: Tests for verifying the AI's interaction with various ​object types, such as attachments, images, text content, Edgeless elements, etc.
3. **AI Action Tests (`/ai-action`)**: Tests for verifying the AI's actions, such as text translation, gramma correction, etc.
4. **Insertion Tests (`/insertion`)**: Tests for verifying answer insertion functionality.

#### Tests Writing

Writing a copilot test cases is easier and clear

e.g.
```ts
test('support chat with specified doc', async ({ page, utils }) => {
  // Initialize the doc
  await focusDocTitle(page);
  await page.keyboard.insertText('Test Doc');
  await page.keyboard.press('Enter');
  await page.keyboard.insertText('EEee is a cute cat');

  await utils.chatPanel.chatWithDoc(page, 'Test Doc');

  await utils.chatPanel.makeChat(page, 'What is EEee?');
  await utils.chatPanel.waitForHistory(page, [
    {
      role: 'user',
      content: 'What is EEee?',
    },
    {
      role: 'assistant',
      status: 'success',
    },
  ]);

  const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
  expect(content).toMatch(/EEee/);
});
```

#### Summary

||Cases|
|------|----|
|Before|19||
|After|151||

> Close BS-2769
This commit is contained in:
yoyoyohamapi
2025-03-29 03:41:09 +00:00
parent a709ed2ef1
commit 317d3e7ea6
94 changed files with 4898 additions and 1225 deletions

View File

@@ -0,0 +1,89 @@
import { loginUser } from '@affine-test/kit/utils/cloud';
import { expect } from '@playwright/test';
import { test } from '../base/base-test';
test.describe('AIChatWith/Attachments', () => {
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);
});
test('support chat with attachment', async ({ page, utils }) => {
const textContent = 'EEee is a cute cat';
const buffer = Buffer.from(textContent);
await utils.chatPanel.chatWithAttachments(
page,
[
{
name: 'test.txt',
mimeType: 'text/plain',
buffer: buffer,
},
],
'What is EEee?'
);
await utils.chatPanel.waitForHistory(page, [
{
role: 'user',
content: 'What is EEee?',
},
{
role: 'assistant',
status: 'success',
},
]);
await expect(async () => {
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
expect(content).toMatch(/EEee/);
}).toPass({ timeout: 10000 });
});
test('support chat with multiple attachments', async ({ page, utils }) => {
const textContent1 = 'EEee is a cute cat';
const textContent2 = 'FFff is a cute dog';
const buffer1 = Buffer.from(textContent1);
const buffer2 = Buffer.from(textContent2);
await utils.chatPanel.chatWithAttachments(
page,
[
{
name: 'document1.txt',
mimeType: 'text/plain',
buffer: buffer1,
},
{
name: 'document2.txt',
mimeType: 'text/plain',
buffer: buffer2,
},
],
'What is EEee? What is FFff?'
);
await utils.chatPanel.waitForHistory(page, [
{
role: 'user',
content: 'What is EEee? What is FFff?',
},
{
role: 'assistant',
status: 'success',
},
]);
await expect(async () => {
const { content, message } =
await utils.chatPanel.getLatestAssistantMessage(page);
expect(content).toMatch(/EEee/);
expect(content).toMatch(/FFff/);
expect(await message.locator('affine-footnote-node').count()).toBe(2);
}).toPass({ timeout: 20000 });
});
});

View File

@@ -0,0 +1,3 @@
import { test } from '../base/base-test';
test.describe('AIChatWith/Collections', () => {});

View File

@@ -0,0 +1,82 @@
import { loginUser } from '@affine-test/kit/utils/cloud';
import { focusDocTitle } from '@affine-test/kit/utils/editor';
import {
clickNewPageButton,
waitForEditorLoad,
} from '@affine-test/kit/utils/page-logic';
import { expect } from '@playwright/test';
import { test } from '../base/base-test';
test.describe('AIChatWith/Doc', () => {
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);
});
test('support chat with specified doc', async ({ page, utils }) => {
// Initialize the doc
await focusDocTitle(page);
await page.keyboard.insertText('Test Doc');
await page.keyboard.press('Enter');
await page.keyboard.insertText('EEee is a cute cat');
await utils.chatPanel.chatWithDoc(page, 'Test Doc');
await utils.chatPanel.makeChat(page, 'What is EEee?');
await utils.chatPanel.waitForHistory(page, [
{
role: 'user',
content: 'What is EEee?',
},
{
role: 'assistant',
status: 'success',
},
]);
await expect(async () => {
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
expect(content).toMatch(/EEee/);
}).toPass({ timeout: 10000 });
});
test('support chat with specified docs', async ({ page, utils }) => {
// Initialize the doc 1
await focusDocTitle(page);
await page.keyboard.insertText('Test Doc1');
await page.keyboard.press('Enter');
await page.keyboard.insertText('EEee is a cute cat');
// Initialize the doc 2
await clickNewPageButton(page);
await waitForEditorLoad(page);
await focusDocTitle(page);
await page.keyboard.insertText('Test Doc2');
await page.keyboard.press('Enter');
await page.keyboard.insertText('FFff is a cute dog');
await utils.chatPanel.chatWithDoc(page, 'Test Doc1');
await utils.chatPanel.chatWithDoc(page, 'Test Doc2');
await utils.chatPanel.makeChat(page, 'What is EEee? What is FFff?');
await utils.chatPanel.waitForHistory(page, [
{
role: 'user',
content: 'What is EEee? What is FFff?',
},
{
role: 'assistant',
status: 'success',
},
]);
await expect(async () => {
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
expect(content).toMatch(/EEee/);
expect(content).toMatch(/FFff/);
}).toPass({ timeout: 10000 });
});
});

View File

@@ -0,0 +1,52 @@
import { loginUser } from '@affine-test/kit/utils/cloud';
import type { EdgelessRootBlockComponent } from '@blocksuite/affine/blocks/root';
import type { GfxModel } from '@blocksuite/std/gfx';
import { expect } from '@playwright/test';
import { test } from '../base/base-test';
test.describe('AIChatWith/EdgelessMindMap', () => {
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);
});
test('should support replace mindmap with the regenerated one', async ({
page,
utils,
}) => {
let id: string;
const { regenerateMindMap } = await utils.editor.askAIWithEdgeless(
page,
async () => {
id = await utils.editor.createMindmap(page);
},
async () => {
const { id: rootId } = await utils.editor.getMindMapNode(page, id!, [
0,
]);
await utils.editor.selectElementInEdgeless(page, [rootId]);
}
);
const { answer } = await regenerateMindMap();
await expect(answer.locator('mini-mindmap-preview')).toBeVisible();
const replace = answer.getByTestId('answer-replace');
await replace.click();
// Expect original mindmap to be replaced
const mindmaps = await page.evaluate(() => {
const edgelessBlock = document.querySelector(
'affine-edgeless-root'
) as EdgelessRootBlockComponent;
const mindmaps = edgelessBlock?.gfx.gfxElements
.filter((el: GfxModel) => 'type' in el && el.type === 'mindmap')
.map((el: GfxModel) => el.id);
return mindmaps;
});
expect(mindmaps).toHaveLength(1);
expect(mindmaps?.[0]).not.toBe(id!);
});
});

View File

@@ -0,0 +1,32 @@
import { loginUser } from '@affine-test/kit/utils/cloud';
import { expect } from '@playwright/test';
import { test } from '../base/base-test';
test.describe('AIChatWith/EdgelessNoteBlock', () => {
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);
});
test('should support insert a new note block below the current', async ({
page,
utils,
}) => {
const { translate } = await utils.editor.askAIWithEdgeless(
page,
async () => {
await utils.editor.createEdgelessNote(page, 'Apple');
}
);
const { answer } = await translate('German');
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
const insertBelow = answer.getByTestId('answer-insert-below');
await insertBelow.click();
await expect(page.locator('affine-edgeless-note').nth(1)).toHaveText(
/Apfel/
);
});
});

View File

@@ -0,0 +1,3 @@
import { test } from '../base/base-test';
test.describe('AIChatWith/EdgelessShape', () => {});

View File

@@ -0,0 +1,32 @@
import { loginUser } from '@affine-test/kit/utils/cloud';
import { expect } from '@playwright/test';
import { test } from '../base/base-test';
test.describe('AIChatWith/EdgelessTextBlock', () => {
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);
});
test('should support insert answer below the current text', async ({
page,
utils,
}) => {
const { translate } = await utils.editor.askAIWithEdgeless(
page,
async () => {
await utils.editor.createEdgelessText(page, 'Apple');
}
);
const { answer } = await translate('German');
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
const insertBelow = answer.getByTestId('answer-insert-below');
await insertBelow.click();
await expect(page.locator('affine-edgeless-text')).toHaveText(
/Apple[\s\S]*Apfel/
);
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
import { test } from '../base/base-test';
test.describe('AIChatWith/tags', () => {});

View File

@@ -0,0 +1,130 @@
import { loginUser } from '@affine-test/kit/utils/cloud';
import { expect } from '@playwright/test';
import { test } from '../base/base-test';
test.describe('AIChatWith/Text', () => {
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);
});
test('should support stop generating', async ({ page, utils }) => {
await utils.editor.askAIWithText(page, 'Appel');
await page.getByTestId('action-fix-grammar').click();
await expect(page.getByTestId('ai-generating')).toBeVisible();
const stop = await page.getByTestId('ai-stop');
await stop.click();
await expect(page.getByTestId('ai-generating')).not.toBeVisible();
});
test('should support copy answer', async ({ page, utils }) => {
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
const { answer } = await translate('German');
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
const copy = answer.getByTestId('answer-copy-button');
await copy.click();
await expect(answer.getByTestId('answer-copied')).toBeVisible();
const clipboardText = await page.evaluate(() =>
navigator.clipboard.readText()
);
expect(clipboardText).toBe('Apfel');
});
test('should support insert below', async ({ page, utils }) => {
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
const { answer } = await translate('German');
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
const insertBelow = answer.getByTestId('answer-insert-below');
await insertBelow.click();
const content = await utils.editor.getEditorContent(page);
expect(content).toBe('Apple\nApfel');
});
test('should support insert above', async ({ page, utils }) => {
const { generateHeadings } = await utils.editor.askAIWithText(
page,
'AFFiNE'
);
const { answer } = await generateHeadings();
await answer.locator('h1').isVisible();
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
const insertAbove = answer.getByTestId('answer-insert-above');
await insertAbove.click();
const content = await utils.editor.getEditorContent(page);
expect(content).toBe('AFFiNE\nAFFiNE');
});
test('should support replace selection', async ({ page, utils }) => {
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
const { answer } = await translate('German');
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
const replace = answer.getByTestId('answer-replace');
await replace.click();
const content = await utils.editor.getEditorContent(page);
expect(content).toBe('Apfel');
});
test('should support continue in chat', async ({ page, utils }) => {
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
const { answer } = await translate('German');
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
const continueInChat = answer.getByTestId('answer-continue-in-chat');
await continueInChat.click();
const chatPanelInput = await page.getByTestId('chat-panel-input-container');
const quote = await chatPanelInput.getByTestId('chat-selection-quote');
await expect(quote).toHaveText(/Apple/, { timeout: 10000 });
});
test('should support regenerate', async ({ page, utils }) => {
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
const { answer } = await translate('German');
const regenerate = answer.getByTestId('answer-regenerate');
await regenerate.click();
const content = await utils.editor.getEditorContent(page);
expect(content).toBe('Apple');
});
test('should show error when request failed', async ({ page, utils }) => {
await page.route('**/graphql', route => route.abort('failed'));
await utils.editor.askAIWithText(page, 'Appel');
await page.getByTestId('action-fix-spelling').click();
await expect(page.getByTestId('ai-error')).toBeVisible();
});
test('should support retry when error', async ({ page, utils }) => {
await page.route('**/graphql', route => route.abort('failed'));
await utils.editor.askAIWithText(page, 'Appel');
await page.getByTestId('action-fix-spelling').click();
const aiPanelContainer = await page.getByTestId('ai-panel-container');
await page.route('**/graphql', route => route.continue());
await aiPanelContainer.getByTestId('error-retry').click();
const answer = await utils.editor.waitForAiAnswer(page);
await expect(answer).toHaveText(/Apple/, { timeout: 10000 });
});
test('should support discard', async ({ page, utils }) => {
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
const { answer } = await translate('German');
const discard = answer.getByTestId('answer-discard');
await discard.click();
await expect(answer).not.toBeVisible();
const content = await utils.editor.getEditorContent(page);
expect(content).toBe('Apple');
});
test('should support discard when click outside', async ({ page, utils }) => {
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
const { answer } = await translate('German');
await page.mouse.click(0, 0);
await expect(page.getByText('Discard the AI result')).toBeVisible();
await page.getByTestId('confirm-modal-confirm').click();
await expect(answer).not.toBeVisible();
const content = await utils.editor.getEditorContent(page);
expect(content).toBe('Apple');
});
});