Skip to content

Commit

Permalink
e2e: add widget chat tests (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
634750802 authored Sep 13, 2024
1 parent 185f2a9 commit cb4ff98
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 43 deletions.
11 changes: 11 additions & 0 deletions e2e/test-html/widget-controlled.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document</title>
</head>
<body>
<script async src="http://127.0.0.1:3000/widget.js" data-api-base="http://127.0.0.1:3000" data-controlled="true"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion e2e/test-html/widget.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
<title>Document</title>
</head>
<body>
<script async data-api-base="http://localhost:3000" src="http://localhost:3000/widget.js"></script>
<script async src="http://127.0.0.1:3000/widget.js"></script>
</body>
</html>
41 changes: 6 additions & 35 deletions e2e/tests/chat.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect, type Page, type Request, test } from '@playwright/test';

const QUESTION = 'What is the content of sample.pdf?';
import { expect, test } from '@playwright/test';
import { getChatRequestPromise, QUESTION, testNewChat } from '../utils/chat';

test.describe('Chat', () => {
test('From Home Page', async ({ page, baseURL }) => {
Expand All @@ -12,15 +11,15 @@ test.describe('Chat', () => {
await page.getByPlaceholder('Input your question here...').fill(QUESTION);

// https://playwright.dev/docs/events#waiting-for-event
const chatRequestPromise = page.waitForRequest(request => request.url() === `${baseURL}/api/v1/chats` && request.method() === 'POST');
const chatRequestPromise = getChatRequestPromise(page, baseURL);
const trigger = page.locator('button', { has: page.locator('svg.lucide-arrow-right') });
await trigger.click();

await expect(trigger).toBeDisabled();
return await chatRequestPromise;
});

await testNewChat(page, chatRequest);
await testNewChat(page, chatRequest, true);
});

test('From Keyboard Shortcut', async ({ page, baseURL }) => {
Expand All @@ -33,39 +32,11 @@ test.describe('Chat', () => {
await page.keyboard.insertText(QUESTION);

// https://playwright.dev/docs/events#waiting-for-event
const chatRequestPromise = page.waitForRequest(request => request.url() === `${baseURL}/api/v1/chats` && request.method() === 'POST');
const chatRequestPromise = getChatRequestPromise(page, baseURL);
await page.keyboard.press('ControlOrMeta+Enter');
return await chatRequestPromise;
});

await testNewChat(page, chatRequest);
await testNewChat(page, chatRequest, true);
});
});

async function testNewChat (page: Page, chatRequest: Request) {
await test.step('Wait page changes', async () => {
await page.waitForURL(/\/c\/.+/);

expect(await page.title()).toContain(QUESTION);
await page.getByRole('heading', { name: QUESTION }).waitFor({ state: 'visible' });
});

const streamText = await test.step('Wait for chat stop', async () => {
const chatResponse = await chatRequest.response();
expect(chatResponse.ok()).toBe(true);

// Feedback button indicates chat ends.
await page.locator('button', { has: page.locator('svg.lucide-message-square-plus') }).waitFor({ state: 'visible' });

return await chatResponse.text();
});

await test.step('Check response text', async () => {
const lastLine = streamText.split('\n').filter(t => !!t.trim()).slice(-1)[0];
expect(lastLine).toMatch(/^2:/);
const message = JSON.parse(lastLine.slice(2))[0].assistant_message;

expect(message.finished_at).toBeTruthy();
expect(message.content.trim().length).toBeGreaterThan(0);
});
}
66 changes: 60 additions & 6 deletions e2e/tests/widget.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,67 @@
import { expect, test } from '@playwright/test';
import { expect, type Locator, type Page, test } from '@playwright/test';
import { getChatRequestPromise, QUESTION, testNewChat } from '../utils/chat';

test('JS Widget', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Ask AI' }).waitFor({ state: 'visible' });
expect(await page.evaluate('window.tidbai')).toMatchObject({ open: false });
expect(await page.evaluate('tidbai')).toMatchObject({ open: false });
});

test('JS Widget from other page', async ({ page }) => {
await page.goto('http://localhost:4001/widget.html');
await page.getByRole('button', { name: 'Ask AI' }).waitFor({ state: 'visible' });
expect(await page.evaluate('window.tidbai')).toMatchObject({ open: false });
test('Embedded JS Widget with trigger button', async ({ page }) => {
const trigger = await test.step('Wait trigger visible and tidbai object ready', async () => {
await page.goto('http://localhost:4001/widget.html');
const trigger = page.getByRole('button', { name: 'Ask AI' });
await trigger.waitFor({ state: 'visible' });
expect(await page.evaluate('tidbai')).toMatchObject({ open: false });
return trigger;
});

const dialog = await test.step('Click and show dialog', async () => {
await trigger.click();

const dialog = page.getByRole('dialog', { name: 'Ask AI' });
await dialog.waitFor({ state: 'visible' });

return dialog;
});

await testWidgetChat(page, dialog);
});

// Used by docs.pingcap.com
test('Embedded JS Widget controlled by js', async ({ page }) => {
await test.step('Wait trigger visible and tidbai object ready', async () => {
await page.goto('http://localhost:4001/widget-controlled.html');
const trigger = page.getByRole('button', { name: 'Ask AI' });
await trigger.waitFor({ state: 'detached' });
expect(await page.evaluate('window.tidbai')).toMatchObject({ open: false });
});

const dialog = await test.step('JS api call and show dialog', async () => {
await page.evaluate('tidbai.open = true');

const dialog = page.getByRole('dialog', { name: 'Ask AI' });
await dialog.waitFor({ state: 'visible' });

return dialog;
});

await testWidgetChat(page, dialog);
});

async function testWidgetChat (page: Page, dialog: Locator) {
await test.step('Fill in question', async () => {
const input = dialog.getByPlaceholder('Input your question here...');
await input.focus();
await input.fill(QUESTION);
});

const chatRequestPromise = await test.step('Trigger ask by press ControlOrMeta+Enter', async () => {
const chatRequestPromise = getChatRequestPromise(page, 'http://127.0.0.1:3000');
await page.keyboard.press('ControlOrMeta+Enter');

return chatRequestPromise;
});

await testNewChat(page, chatRequestPromise, false);
}
36 changes: 36 additions & 0 deletions e2e/utils/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expect, type Page, type Request, test } from '@playwright/test';

export const QUESTION = 'What is the content of sample.pdf?';

export function getChatRequestPromise (page: Page, baseURL: string) {
return page.waitForRequest(request => request.url() === `${baseURL}/api/v1/chats` && request.method() === 'POST');
}

export async function testNewChat (page: Page, chatRequest: Request, validatePageUrlAndTitle: boolean) {
await test.step('Wait page changes', async () => {
if (validatePageUrlAndTitle) {
await page.waitForURL(/\/c\/.+/);
expect(await page.title()).toContain(QUESTION);
}
await page.getByRole('heading', { name: QUESTION }).waitFor({ state: 'visible' });
});

const streamText = await test.step('Wait for chat stop', async () => {
const chatResponse = await chatRequest.response();
expect(chatResponse.ok()).toBe(true);

// Feedback button indicates chat ends.
await page.locator('button', { has: page.locator('svg.lucide-message-square-plus') }).waitFor({ state: 'visible' });

return await chatResponse.text();
});

await test.step('Check response text', async () => {
const lastLine = streamText.split('\n').filter(t => !!t.trim()).slice(-1)[0];
expect(lastLine).toMatch(/^2:/);
const message = JSON.parse(lastLine.slice(2))[0].assistant_message;

expect(message.finished_at).toBeTruthy();
expect(message.content.trim().length).toBeGreaterThan(0);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function originalUrl (request: NextRequest) {
}
}

export { handler as GET, handler as POST, handler as DELETE, handler as HEAD, handler as PUT, handler as PATCH };
export { handler as GET, handler as POST, handler as DELETE, handler as HEAD, handler as PUT, handler as PATCH, handler as OPTIONS };

export const runtime = 'edge';

Expand Down

0 comments on commit cb4ff98

Please sign in to comment.