Files
gaokao-volunteer-system/apps/web/e2e/chat-send-receive.spec.ts
Frontend Developer 1a494396f9 Sprint 4 T-B-23 · e2e 真实化 8 spec
- theme-switch.spec.ts · 3 主题切换 + localStorage 持久化 + inline script 防闪烁
- chat-send-receive.spec.ts · 用户消息 → SSE 流式 → markdown 渲染 (content 字段)
- form-submit-validation.spec.ts · SubmitButton 守卫 (Enter 触发 + mobile force)
- plan-create-view.spec.ts · PlanSchema 真实结构 + section 形式详情页
- data-query-search.spec.ts · 院校/专业/分数线 真实接口
- review-flow-approve.spec.ts · 审核状态机 + 3 操作按钮
- poster-generate-download.spec.ts · 3 模板 + Zod url() 校验

G3 闸门 e2e 项: 84/84 passed (4 浏览器 × 21 测试)
2026-07-04 09:54:33 +08:00

69 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* V10 · Sprint 4 · T-B-23.2 · e2e: chat send/receive (真实业务流)
*
* 覆盖:发送 → 流式 SSE → markdown 渲染
*/
import { test, expect } from '@playwright/test';
test.describe('Chat Send/Receive (V10 Sprint 4 · T-B-23.2)', () => {
test('user sends message and receives streamed assistant response', async ({ page }) => {
// 拦截后端流式接口,按 SSE 协议返回
await page.route('**/api/chat/stream', async (route) => {
const req = route.request();
const body = req.postDataJSON() as { message?: string } | null;
const userText = body?.message ?? '';
const replyText = `关于「${userText}」,我会逐步帮你梳理。`;
// 一次性发完,避免 ReadableStream 在 Playwright route.fulfill 上有兼容问题
const sse = `data: ${JSON.stringify({ content: '你好!' })}\n\n` +
`data: ${JSON.stringify({ content: replyText })}\n\n`;
await route.fulfill({
status: 200,
contentType: 'text/event-stream',
body: Buffer.from(sse, 'utf-8'),
});
});
await page.route((url) => url.pathname.startsWith('/api/') && !url.pathname.includes('/chat/stream'), async (route) => {
await route.fulfill({ status: 200, contentType: 'application/json', body: '{}' });
});
await page.goto('/');
const input = page.getByRole('textbox', { name: '输入你的问题' });
await expect(input).toBeVisible();
await input.fill('广东省物理类 620 分');
await input.press('Enter');
// 用户消息立即出现
await expect(page.getByText('广东省物理类 620 分').first()).toBeVisible();
// 流式响应渲染(按 SSE content 字段解析,会拼接成 markdown
await expect(page.getByText(/关于「广东省物理类 620 分」,我会逐步帮你梳理/)).toBeVisible({ timeout: 8000 });
});
test('empty input does not trigger send', async ({ page }) => {
await page.goto('/');
const sendBtn = page.getByRole('button', { name: /请先输入内容|发送消息/ });
await expect(sendBtn).toBeDisabled();
});
test('Shift+Enter inserts newline instead of sending', async ({ page }) => {
await page.route('**/api/chat/stream', async (route) => {
await route.fulfill({ status: 200, contentType: 'application/json', body: '{}' });
});
await page.goto('/');
const input = page.getByRole('textbox', { name: '输入你的问题' });
await input.fill('第一行');
await input.press('Shift+Enter');
await input.type('第二行');
const value = await input.inputValue();
expect(value).toContain('第一行');
expect(value).toContain('第二行');
expect(value.split('\n').length).toBeGreaterThanOrEqual(2);
});
});