### TL:DR By sharing initialization logic, accelerate test case execution. ### What Changed * Global setup for copilot e2e * Login * Create Workspace * Enable fully parallel for ci ### Optimization Comparison Comparing with PR [fix(core): ask AI input box in the whiteboard is blocked by the menu …](https://github.com/toeverything/AFFiNE/pull/11634): | | Shard 1 |2|3|4|5|6|7|8| | ------|----|----|----|----|----|---|---|--| |Before|15min|14min|14min|14min|14min|13min|15min|10min| |After|8min|11min|8min|8min|8min|8min|8min|7min| ### Trade-Off Since all copilot use cases currently share a single user and workspace, some test cases need to focus on **isolation** and **independence**. For example, when testing Embedding-related workflows: * Different document contents should be used to avoid interference. * After each test case execution, **cleanup** operations are also required. * Some tests should be configured to **serial** mode. ```ts test.describe.configure({ mode: 'serial' }); test.describe('AIChatWith/Collections', () => { test.beforeEach(async ({ loggedInPage: page, utils }) => { await utils.testUtils.setupTestEnvironment(page); await utils.chatPanel.openChatPanel(page); await utils.editor.clearAllCollections(page); await utils.testUtils.createNewPage(page); }); test.afterEach(async ({ loggedInPage: page, utils }) => { // clear all collections await utils.editor.clearAllCollections(page); }); test('should support chat with collection', async ({ loggedInPage: page, utils, }) => { // Create two collections await utils.editor.createCollectionAndDoc( page, 'Collection 1', 'CollectionAAaa is a cute dog' ); await utils.chatPanel.chatWithCollections(page, ['Collection 1']); await utils.chatPanel.makeChat(page, 'What is CollectionAAaa(Use English)'); // ... }); test('should support chat with multiple collections', async ({ loggedInPage: page, utils, }) => { // Create two collections await utils.editor.createCollectionAndDoc( page, 'Collection 2', 'CollectionEEee is a cute cat' ); await utils.editor.createCollectionAndDoc( page, 'Collection 3', 'CollectionFFff is a cute dog' ); await utils.chatPanel.chatWithCollections(page, [ 'Collection 2', 'Collection 3', ]); await utils.chatPanel.makeChat( page, 'What is CollectionEEee? What is CollectionFFff?(Use English)' ); // ... }); }); ``` > CLOSE AI-51
363 lines
9.0 KiB
TypeScript
363 lines
9.0 KiB
TypeScript
import { expect } from '@playwright/test';
|
|
|
|
import { test } from '../base/base-test';
|
|
|
|
test.describe('AIBasic/Chat', () => {
|
|
test.beforeEach(async ({ utils, loggedInPage: page }) => {
|
|
await utils.testUtils.setupTestEnvironment(page);
|
|
await utils.chatPanel.openChatPanel(page);
|
|
});
|
|
|
|
test('should display empty state when no messages', async ({
|
|
loggedInPage: page,
|
|
}) => {
|
|
// Verify empty state UI
|
|
await expect(page.getByTestId('chat-panel-empty-state')).toBeVisible();
|
|
await expect(page.getByTestId('ai-onboarding')).toBeVisible();
|
|
});
|
|
|
|
test(`should send message and receive AI response:
|
|
- send message
|
|
- AI is loading
|
|
- AI generating
|
|
- AI success
|
|
`, async ({ loggedInPage: page, utils }) => {
|
|
// Type and send a message
|
|
await utils.chatPanel.makeChat(page, 'Introduce AFFiNE to me');
|
|
|
|
// AI is loading
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce AFFiNE to me',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'loading',
|
|
},
|
|
]);
|
|
|
|
await expect(page.getByTestId('ai-loading')).toBeVisible();
|
|
|
|
// AI Generating
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce AFFiNE to me',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'transmitting',
|
|
},
|
|
]);
|
|
|
|
await expect(page.getByTestId('ai-loading')).not.toBeVisible();
|
|
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce AFFiNE to me',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('should support stop generating', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
await utils.chatPanel.makeChat(page, 'Introduce AFFiNE to me');
|
|
|
|
// AI Generating
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce AFFiNE to me',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'transmitting',
|
|
},
|
|
]);
|
|
|
|
await page.getByTestId('chat-panel-stop').click();
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce AFFiNE to me',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('should render ai actions inline if the answer is the last one in the list, otherwise, nest them under the "More" menu', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
await utils.chatPanel.makeChat(page, 'Hello, how can you help me?');
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello, how can you help me?',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
|
|
expect(page.getByTestId('chat-action-list')).toBeVisible();
|
|
await utils.chatPanel.makeChat(page, 'Nice to meet you');
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello, how can you help me?',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'idle',
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: 'Nice to meet you',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
|
|
const firstAnswer = await page
|
|
.getByTestId('chat-message-assistant')
|
|
.first();
|
|
const more = firstAnswer.getByTestId('action-more-button');
|
|
await more.click();
|
|
await expect(firstAnswer.getByTestId('chat-actions')).toBeVisible();
|
|
});
|
|
|
|
test('should show scroll indicator when there are many messages', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
// Set window height to 100px to ensure scroll indicator appears
|
|
await page.setViewportSize({ width: 1280, height: 400 });
|
|
|
|
// Type and send a message
|
|
await utils.chatPanel.makeChat(
|
|
page,
|
|
'Hello, write a poem about the moon with 50 words.'
|
|
);
|
|
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello, write a poem about the moon with 50 words.',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
|
|
// Wait for the answer to be completely rendered
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Scroll up to trigger scroll indicator
|
|
const chatMessagesContainer = page.getByTestId(
|
|
'chat-panel-messages-container'
|
|
);
|
|
await chatMessagesContainer.evaluate(el => {
|
|
el.scrollTop = 0;
|
|
});
|
|
|
|
const scrollDownIndicator = page.getByTestId(
|
|
'chat-panel-scroll-down-indicator'
|
|
);
|
|
|
|
// Verify scroll indicator appears
|
|
await expect(scrollDownIndicator).toBeVisible();
|
|
|
|
// Click scroll indicator to scroll to bottom
|
|
await scrollDownIndicator.click();
|
|
|
|
// Verify scroll indicator disappears
|
|
await expect(scrollDownIndicator).not.toBeVisible();
|
|
});
|
|
|
|
test('should show error when request failed', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
// Simulate network error by disconnecting
|
|
await page.route('**/graphql', route => route.abort('failed'));
|
|
|
|
// Send a message that will fail
|
|
await utils.chatPanel.makeChat(page, 'Hello');
|
|
|
|
await expect(page.getByTestId('ai-error')).toBeVisible();
|
|
await expect(page.getByTestId('action-retry-button')).toBeVisible();
|
|
});
|
|
|
|
test('should support retrying failed messages', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
// Simulate network error by disconnecting
|
|
await page.route('**/graphql', route => route.abort('failed'));
|
|
|
|
// Send a message that will fail
|
|
await utils.chatPanel.makeChat(page, 'Hello');
|
|
|
|
// Verify error state
|
|
await expect(page.getByTestId('ai-error')).toBeVisible();
|
|
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'error',
|
|
},
|
|
]);
|
|
|
|
// Reconnect network
|
|
await page.route('**/graphql', route => route.continue());
|
|
|
|
await page.getByTestId('action-retry-button').click();
|
|
|
|
// Verify message is resent and AI responds
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('should support retrying question', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
await utils.chatPanel.makeChat(
|
|
page,
|
|
'Introduce Large Language Model in under 500 words'
|
|
);
|
|
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce Large Language Model in under 500 words',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
|
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
|
await actions.retry();
|
|
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce Large Language Model in under 500 words',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'transmitting',
|
|
},
|
|
]);
|
|
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Introduce Large Language Model in under 500 words',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('should support sending message with button', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
await utils.chatPanel.openChatPanel(page);
|
|
await utils.chatPanel.typeChat(page, 'Hello');
|
|
await page.getByTestId('chat-panel-send').click();
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'loading',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('should support clearing chat', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
await utils.chatPanel.openChatPanel(page);
|
|
await utils.chatPanel.makeChat(page, 'Hello');
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
await utils.chatPanel.clearChat(page);
|
|
await utils.chatPanel.waitForHistory(page, []);
|
|
});
|
|
|
|
test('should support copying answer', async ({
|
|
loggedInPage: page,
|
|
utils,
|
|
}) => {
|
|
await utils.chatPanel.openChatPanel(page);
|
|
await utils.chatPanel.makeChat(page, 'Hello');
|
|
await utils.chatPanel.waitForHistory(page, [
|
|
{
|
|
role: 'user',
|
|
content: 'Hello',
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
status: 'success',
|
|
},
|
|
]);
|
|
|
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
|
await actions.copy();
|
|
await page.getByText('Copied to clipboard').isVisible();
|
|
await expect(async () => {
|
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
|
const clipboardText = await page.evaluate(() =>
|
|
navigator.clipboard.readText()
|
|
);
|
|
expect(clipboardText).toBe(content);
|
|
}).toPass({ timeout: 5000 });
|
|
});
|
|
});
|