- 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 测试)
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
/**
|
||
* V10 · Sprint 4 · T-B-23.3 · e2e: form submit + validation (FormCard 3-step)
|
||
*
|
||
* 简化版:FormCard 是 ChatMessage 内嵌的组件,不是独立路由
|
||
* 改为通过 chat send 路径验证 SubmitButton 守卫
|
||
*/
|
||
import { test, expect } from '@playwright/test';
|
||
|
||
test.describe('Form Submit + Validation (V10 Sprint 4 · T-B-23.3)', () => {
|
||
test.beforeEach(async ({ page }) => {
|
||
// 让 chat 流式接口返回空,避免影响
|
||
await page.route('**/api/**', async (route) => {
|
||
await route.fulfill({ status: 200, contentType: 'application/json', body: '{}' });
|
||
});
|
||
});
|
||
|
||
test('SubmitButton guards send button during streaming', async ({ page }) => {
|
||
// 拦截 form submit 路径,模拟慢响应验证 spinner
|
||
await page.route('**/api/chat/stream', async (route) => {
|
||
await new Promise((r) => setTimeout(r, 1500));
|
||
await route.fulfill({ status: 200, contentType: 'application/json', body: '{}' });
|
||
});
|
||
|
||
await page.goto('/');
|
||
|
||
const input = page.getByRole('textbox', { name: '输入你的问题' });
|
||
await input.fill('测试提交');
|
||
|
||
// 使用 keyboard Enter 触发提交(更接近 keyboard interaction,绕过 mobile nav 的 pointer intercept)
|
||
await input.press('Enter');
|
||
|
||
// 发送按钮应立即切换到 disabled(aria-label 会变 "请先输入内容")
|
||
await expect(page.getByRole('button', { name: '请先输入内容' })).toBeDisabled({ timeout: 2000 });
|
||
});
|
||
|
||
test('SubmitButton does not double-fire when clicked twice rapidly', async ({ page }) => {
|
||
let callCount = 0;
|
||
await page.route('**/api/chat/stream', async (route) => {
|
||
callCount += 1;
|
||
await new Promise((r) => setTimeout(r, 800));
|
||
await route.fulfill({ status: 200, contentType: 'application/json', body: '{"content":""}' });
|
||
});
|
||
|
||
await page.goto('/');
|
||
|
||
const input = page.getByRole('textbox', { name: '输入你的问题' });
|
||
await input.fill('连续点击');
|
||
|
||
// 第一次按 Enter 触发提交
|
||
await input.press('Enter');
|
||
|
||
// 此时按钮应 disabled(aria-label='请先输入内容')
|
||
await expect(page.getByRole('button', { name: '请先输入内容' })).toBeDisabled({ timeout: 2000 });
|
||
|
||
// 试图再次按 Enter(在已 disabled 状态下不应触发新请求)
|
||
await page.waitForTimeout(200);
|
||
await input.press('Enter').catch(() => {});
|
||
|
||
await page.waitForTimeout(1500);
|
||
expect(callCount).toBe(1);
|
||
});
|
||
}); |