// main-flow.spec.js —— T 归属。L5 主线 1:主流程(TC-E2E-01-*)+ 主线 2:检索模式(TC-E2E-02-*)。 // 真实 BFF 上游:QA 约 37s/次(DISCOVERY.md);LLM 402 → answer 为 fallback 证据列表, // 答案内容断言回退为结构断言(TEST_CASES.md §3.3,依赖充值)。 import { test, expect } from '@playwright/test'; import { tier, openSessionPanel, closeSessionPanel, newSession, submitQuestion, waitForAnswerEnd, cleanupSessionsByTitle, trackConsoleErrors } from './helpers.js'; test.describe('E2E-01 主流程', () => { let pageRef = null; test.afterEach(async () => { if (pageRef) { // 清理双前缀:新建时的 R1 前缀会被首问自动标题覆盖为问题前缀(FR-SESSION-008) await cleanupSessionsByTitle(pageRef, 'R1主流程'); await cleanupSessionsByTitle(pageRef, '水稻SPL基因家族的功能研究'); } }); test('主流程:提问→五相位推进→答案→来源/图谱 tab→刷新恢复', async ({ page }) => { test.setTimeout(300_000); const t = tier(page); const titlePrefix = t === 'desktop' ? 'R1主流程' : 'R1主流程' + (t === 'tablet' ? '-T' : '-M'); pageRef = page; const consoleErrors = []; trackConsoleErrors(page, consoleErrors); await cleanupSessionsByTitle(page, titlePrefix); await cleanupSessionsByTitle(page, '水稻SPL基因家族的功能研究'); // 1. 首屏加载 → 欢迎空态(.empty-state) await page.goto('/'); await expect(page.locator('.app-shell')).toBeVisible(); // 2. 新建会话(桌面左栏 / 平板·手机经抽屉) const listBefore = await page.locator('.session-item').count(); await newSession(page); await expect(page.locator('.session-item')).toHaveCount(listBefore + 1, { timeout: 10_000 }); await closeSessionPanel(page); await expect(page.locator('.empty-state')).toBeVisible(); // 3. 问答模式提问(真实上游 ~37s;240s 上限内) const question = '水稻SPL基因家族的功能研究有哪些关键进展?'; await submitQuestion(page, question, 'ask'); // 4. 用户气泡立即出现 + 助手骨架 pipeline 五相位 waiting await expect(page.locator('.msg.user .bubble').last()).toContainText(question.slice(0, 12), { timeout: 10_000 }); const pipe = page.locator('.msg.assistant .pipeline-card').last(); await expect(pipe).toBeVisible({ timeout: 10_000 }); await expect(pipe.locator('.p-step')).toHaveCount(5); // 5. 五相位推进:检索相位 running(真实上游 92s 内必经);等待终态 await expect(pipe.locator('.p-step.running').first()).toBeVisible({ timeout: 150_000 }); const res = await waitForAnswerEnd(page, { timeout: 240_000 }); expect(res.sawRunning, '流式期间应有相位处于 running').toBe(true); expect(res.done, 'pipeline 徽章终态应为 done').toBe(true); // 6. 终态:pipeline 全部 done、消息含来源块与 text 块(402 fallback:结构断言,依赖充值) await expect(pipe.locator('.p-step')).toHaveCount(5); await expect(pipe.locator('.p-step.done')).toHaveCount(5); const asst = page.locator('.msg.assistant').last(); await expect(asst.locator('.inline-sources .src-chip').first()).toBeVisible({ timeout: 20_000 }); // 揭示动画收尾:点跳过加速。终态判据 = 最终 text 块出现 //(reveal 完成时 stream 块被替换为 text 块,二者互斥;reduced-motion 环境直接定格 text) const skip = asst.locator('.skip-btn'); if (await skip.count()) await skip.click().catch(() => {}); const mdText = asst.locator('.md-text').first(); await expect(mdText).toBeVisible({ timeout: 180_000 }); // 6b. Markdown 渲染硬断言(BUG-R1-06 已修复验证): // 触发条件收紧为真实 markdown 语法——行首列表标记「- 」或成对 **加粗**, // 避免把行内连字符(如 vegetative-to-reproductive)误判为列表 const rawText = await mdText.textContent().catch(() => ''); const listLike = /^- /m.test(rawText); const boldLike = /\*\*[^*]+\*\*/.test(rawText); if (listLike || boldLike) { const html = await mdText.innerHTML().catch(() => ''); const listOk = !listLike || html.includes(' { let pageRef = null; test.afterEach(async () => { if (pageRef) { await cleanupSessionsByTitle(pageRef, 'R1检索'); await cleanupSessionsByTitle(pageRef, '水稻SPL基因家族的功能研究'); } }); test('检索模式:只出来源不出答案块,use_llm=false', async ({ page }) => { test.setTimeout(300_000); const t = tier(page); pageRef = page; await cleanupSessionsByTitle(page, 'R1检索'); await cleanupSessionsByTitle(page, '水稻SPL基因家族的功能研究'); await page.goto('/'); await expect(page.locator('.app-shell')).toBeVisible(); const listBefore = await page.locator('.session-item').count(); await newSession(page); await expect(page.locator('.session-item')).toHaveCount(listBefore + 1); await closeSessionPanel(page); // 检索模式提问 await submitQuestion(page, '水稻SPL基因家族的功能研究有哪些关键进展?', 'search'); // 等待终态(真实 search 单次约 92s) const res = await waitForAnswerEnd(page, { timeout: 240_000 }); expect(res.done, 'pipeline 徽章终态应为 done').toBe(true); // 断言:无 text 答案块;有 sources 块;有 pipeline 卡 const asst = page.locator('.msg.assistant').last(); await expect(asst.locator('.inline-sources .src-chip').first()).toBeVisible({ timeout: 20_000 }); await expect(asst.locator('.md-text')).toHaveCount(0); await expect(asst.locator('.stream-wrap')).toHaveCount(0); }); });