- git mv 保留历史:ui/(Vue3+Vite)、sdk/(BFF 中间层)
- 路径语义修正:db_path/figures_cache 改相对 sdk/(BFF cwd),
历史库 sdk/backend/data 迁移至 sdk/data/(会话/图片缓存无损)
- 引用修复 ~15 处:config.py、deploy/*.sh、vitest/playwright 配置、
单测导入路径、docs、README、gitignore、.env.example
- 骨架:agent/{qa,methodology,legacy-rag,vendor}、knowledge/manifest.yaml、
tooling/{compile,kb,crypto,assemble,audit,tools,scripts} + README
- 门禁验证:sdk pytest 125 通过、ui vitest 158 通过、
生产 BFF 脚本重启四上游全绿 + 静态托管 + 会话库无损
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 KiB
TEST_CASES.md —— 需求测试用例与追溯矩阵
版本 1.0 | 作者 T(测试 agent)| 用例唯一来源:
PRD.md(81 FR);请求/响应依据:API.md;分层与轮次纪律:TEST_PLAN.md;上游事实:DISCOVERY.md本波(M0)交付:用例清单 + 双向追溯矩阵 + 执行说明 + E2E 骨架(2 条冒烟)。全量执行从 M2 集成后开始,按 TEST_PLAN §5 轮次纪律。 覆盖判据:每个 FR 至少 1 条 TC,且每条 TC 至少映射 1 个 FR(双向完整 = 主门禁 100%)。
1. 用例清单
1.1 编号与通用约定
- 编号:
TC-<模块>-<序号>;E2E 主线场景:TC-E2E-<主线号>-<视口>(视口 M=Mobile 390×844 / T=Tablet 768×1024 / D=Desktop 1440×900)。 - 测试层:L1 后端单元(pytest -m unit,A 组自测)|L2 后端接口(pytest -m api,respx mock 上游,T 主写)|L2s 上游 smoke(
bash tests/smoke_upstreams.sh)|L3 前端单元(vitest)|L4 前端组件(vitest + @vue/test-utils)|L5 端到端(Playwright,三档视口)|L6 验收(PM 人工)。 - 优先级:P0 = 主流程/数据正确性/安全;P1 = 重要功能与降级;P2 = 视觉细节/辅助路径。
- 可断言性要求:预期列均为可观察结果(元素存在/计数/请求体字段/HTTP 状态/存储值),无「检查是否正确」类断言。
- 「依赖充值」标注:上游 QA 当前 LLM 402 降级(DISCOVERY.md §6.1),凡断言涉及自然语言答案内容的用例均标注「依赖充值」——充值前只能验证结构(answer 字段存在、非空或为 fallback 证据列表、text 块已渲染),充值后验证完整断言。检索/证据/图谱/来源类断言不受 402 影响。
- 时序说明:流水线用例中的「pipeline 块状态依次 running→done」属 UI 状态推断,若无专门组件埋点,用例从渲染的相位卡/pipeline 卡状态文本断言;帧时序由 mock
sse_frames.json保证(与 TEST_CASES 无关的 fixture 细节以 PM 的 mock 为准)。
1.2 SHELL 应用外壳(FR-SHELL-001~007)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-SHELL-001 | FR-SHELL-001 | 桌面三栏网格精确尺寸 | Desktop 1440×900;AppShell 已挂载 | 1. 打开应用;2. 计算 .app-shell 的 grid-template-columns 与 rows 实际值(getComputedStyle);3. 计算 .session-panel/.chat-panel/.bench-panel 宽度 |
columns = 260px minmax(0,1fr) 380px;rows = 48px minmax(0,1fr) 28px;三栏实际宽度 260/800/380(1440 下);body computed overflow:hidden |
L5-D | P0 |
| TC-SHELL-002 | FR-SHELL-001 | 缩放下无横向滚动条 | Desktop 初始 1440×900 | 1. 将视口改为 1280×900、再 1440×900;2. 检查 document.documentElement.scrollWidth 与 clientWidth |
两档下 scrollWidth ≤ clientWidth;body 不出现横向滚动条 |
L5-D | P1 |
| TC-SHELL-003 | FR-SHELL-002 | 顶栏结构:Logo/标题/徽章/主题按钮 | 已有一个活动会话;浅色主题 | 1. 打开应用;2. 断言 .top-bar 高 48px;3. 检查左 Logo 圆点与「海古纪 PlantAI」文字(gradient text);4. 检查中部会话标题与状态徽章;5. 检查右侧主题切换按钮 |
.top-bar offsetHeight=48;logo 文字存在且带 background-clip:text 渐变;中部标题=当前会话 title;主题按钮可点 |
L5-D | P1 |
| TC-SHELL-004 | FR-SHELL-002 | 顶栏无会话时显示「就绪」 | 数据库无任何会话 | 打开应用 | 顶栏中部徽章文本=「就绪」(labels.js 对应键) | L4 | P2 |
| TC-SHELL-005 | FR-SHELL-002 | 顶栏长标题省略 | 存在 title 为 60+ 字符的会话 | 1. 打开该会话;2. 检查顶栏标题元素 computed style | 标题 text-overflow=ellipsis;不换行(white-space:nowrap);单行渲染 | L5-D | P2 |
| TC-SHELL-006 | FR-SHELL-003 | 状态栏静态结构 | AppShell 已挂载;/api/health、/api/qa/info 正常 |
1. 检查 .status-bar 高 28px;2. 检查左 4 个 .agent-dot-item;3. 检查右侧版本/会话数/健康标记 |
高=28px;agent 点数量=4;右侧文本含 PlantAI RAG v0.1 与 QA 版本号(来自 /api/qa/info 的 version,如 1.1.73-assertion-anchored) |
L5-D | P1 |
| TC-SHELL-007 | FR-SHELL-003 | 问答进行中状态栏四点联动 | 会话空闲;mock normal(SSE 回放) | 1. 发起提问;2. 在 SSE 进行中读取四个 agent 点的状态类 | 相位推进到 X 时第 X 个点激活(如 planning 时第 1 点 pulse);progress 全部结束后四点定格(成功态) | L5-D | P1 |
| TC-SHELL-008 | FR-SHELL-004 | 左栏折叠为 64px 图标轨 | Desktop 1440×900;左栏展开 | 1. 点击左栏折叠按钮;2. 检查 .session-panel 宽度与图标按钮(3 个 34×34);3. 刷新页面;4. 再次检查 |
折叠后宽度=64px;图标按钮=3 个;过渡 300ms;刷新后仍折叠 | L5-D | P1 |
| TC-SHELL-009 | FR-SHELL-004 | 左栏折叠状态持久化 | 同上 | 1. 折叠;2. 检查 localStorage['pa-side-collapsed'] |
localStorage 值为折叠标记;展开后标记被清除/反转 | L5-D | P2 |
| TC-SHELL-010 | FR-SHELL-005 | 右栏折叠保留手柄条 | Desktop 1440×900;右栏展开 | 1. 点击右栏折叠;2. 检查手柄条尺寸与可见性;3. 点击手柄展开;4. 检查 localStorage['pa-bench-collapsed'] |
折叠后 .bench-panel 隐藏但手柄条 28px 可见可点;点击后恢复展开;刷新后保持折叠态 |
L5-D | P1 |
| TC-SHELL-011 | FR-SHELL-006 | Aurora 背景不遮挡交互 | 任意视口;AppShell 已挂载 | 1. 检查 aurora 层 computed style;2. 点击顶栏主题按钮、会话列表项、作曲器输入框(逐个执行且均生效) | aurora 层 position:fixed、z-index:0、pointer-events:none;上述交互全部可操作 |
L5-D | P1 |
| TC-SHELL-012 | FR-SHELL-006 | 浅色主题 aurora 衰减 | 已切浅色 | 检查 aurora 层或根元素的 --pa-aurora-opacity |
computed 值=.08(UI_SPEC §1.2 令牌值) |
L4 | P2 |
| TC-SHELL-013 | FR-SHELL-007 | 首屏初始化顺序与并行请求 | 深色主题已存 localStorage;无会话 | 1. 打开应用;2. 网络面板记录启动期请求 | documentElement.dataset.theme 在应用挂载前已写入(断言:无白色闪屏——深色下首帧背景即深色;实现断言:main.js 在 createApp 前写 dataset);启动期存在 GET /api/sessions、/api/health、/api/qa/info 三个请求且并行(无串行依赖) |
L5-D + L3 | P0 |
| TC-SHELL-014 | FR-SHELL-007 | 首屏接口失败不阻塞渲染 | mock 对 /api/health 返回 500(或 BFF 停止) |
打开应用 | 页面仍渲染 AppShell 与欢迎空态;健康标记显示降级/离线态;无未捕获异常 | L5-D(mock error 场景) | P1 |
| TC-SHELL-015 | FR-SHELL-007 | 无会话显示欢迎空态 | 数据库无会话 | 打开应用 | 对话区显示欢迎空态(渐变菱形 + 标题 + 3 张建议卡) | L5-D | P1 |
1.3 THEME 主题与无障碍(FR-THEME-001~006)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-THEME-001 | FR-THEME-001 | 深色默认主题 | 首次打开(无 pa-theme 存储) | 1. 打开应用;2. 检查 documentElement.dataset.theme 与 --pa-canvas computed 值 |
theme='dark';canvas=#0A0A10(rgb(10,10,16)) | L5-D | P0 |
| TC-THEME-002 | FR-THEME-001 | 浅色主题逐组件走查 | 已切浅色 | 逐个检查顶栏、会话项、气泡、作曲器、bench 面板的计算样式 | 无失色:各元素背景/文字均来自浅色令牌(canvas=#F6F8FC,surface-1=#FFFFFF);无深色令牌残留(如 #0A0A10 不再出现于背景) | L5-D(或 L6 人工走查) | P0 |
| TC-THEME-003 | FR-THEME-002 | 主题切换即时生效 | 默认深色 | 1. 点击顶栏主题按钮;2. 立即检查 documentElement.dataset.theme 与 localStorage;3. 刷新后复查 |
dataset.theme 即时='light';localStorage['pa-theme']='light';刷新后仍为 light;再切回同理 |
L5-D | P0 |
| TC-THEME-004 | FR-THEME-002 | 主题图标旋转动画 | 同上 | 切换主题时检查图标 transform | 图标日/月互换且 rotate 180°(transition 400ms) | L5-D | P2 |
| TC-THEME-005 | FR-THEME-003 | 浅色 glow 衰减 | 已切浅色 | 检查根元素 --pa-glow-opacity computed 值 |
值=.35(深色下=1) | L4 | P2 |
| TC-THEME-006 | FR-THEME-004 | 动效降级(reduced motion) | 系统「减少动态效果」开启;Playwright 以 reducedMotion:'reduce' 启动 context |
1. 打开应用;2. 检查 aurora/状态点/按钮的 animation 计算样式;3. 发起一次问答 | @media (prefers-reduced-motion: reduce) 生效:所有元素 animation-duration=0 或 animation 为 none;running 状态以静态符号(实心点 + 文字徽章)可辨认,无 pulse/shimmer/spin 运动 |
L5-D(reducedMotion context) | P1 |
| TC-THEME-007 | FR-THEME-005 | 焦点环样式 | 任意视口 | Tab 键聚焦顶栏主题按钮与作曲器 textarea | :focus-visible 输出 2px rgba(94,106,210,.5) 焦点环(outline-offset 1px) |
L5-D | P1 |
| TC-THEME-008 | FR-THEME-005 | 纯键盘完成主流程 | 空数据库;mock normal | 只用 Tab/Enter/Esc 完成:新建会话 → 聚焦输入 → 输入问题 → ⌘/Ctrl+Enter 发送 → Tab 切换工作台 tab → Esc 关闭面板 | 每一步可达且生效;Tab 顺序符合视觉顺序(顶栏 → 会话列表 → 对话 → 作曲器 → 工作台) | L5-D(键盘驱动) | P0 |
| TC-THEME-009 | FR-THEME-006 | 对比度达标(深色) | 深色主题 | 抽检 .app-shell 内 3 处文本(正文 ink、muted、subtle)的计算颜色与 canvas 背景,按 WCAG 公式计算对比度 |
ink/canvas ≥15:1;ink-muted/canvas ≥4.5:1;ink-subtle 仅用于非关键信息 | L5-D + L3(工具函数断言,可脚本化) | P1 |
| TC-THEME-010 | FR-THEME-006 | 对比度达标(浅色) | 浅色主题 | 同 TC-THEME-009 步骤,浅色令牌 | ink(#16182A)/canvas(#F6F8FC) ≈ 计算值 ≥ 深色同等标准;ink-muted/canvas ≥4.5:1 | L5-D | P1 |
1.4 SESSION 会话管理(FR-SESSION-001~009)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-SESSION-001 | FR-SESSION-001 | 列表按 updated_at 倒序 | 数据库 ≥3 个会话,updated_at 互异(fixtures 保证) | 1. GET /api/sessions;2. 前端渲染列表;3. 依次读取条目标题 |
条目顺序 = updated_at 从新到旧 | L2(fixture 会话数据)+ L4 | P0 |
| TC-SESSION-002 | FR-SESSION-001 | 分组「今天/昨天/更早」 | ≥3 个会话,时间分别落在今天/昨天/2 天前 | 渲染列表 | 三个组标签(11px/500/字距0.6px)按序出现,组内条目归属正确;组内按时间倒序 | L4(clock mock) | P1 |
| TC-SESSION-003 | FR-SESSION-001 | 空列表引导文案 | 数据库无会话 | 渲染列表 | 显示引导文案(「暂无会话」类 labels 文案);不渲染列表条目 | L4 | P2 |
| TC-SESSION-004 | FR-SESSION-002 | 状态点四态区分 | 4 个会话分别 idle/running/done/error | 渲染列表 | 形状可区分:idle=空心、running=空心+脉冲动画、done=实心 success 色、error=实心 error 色;running 有 animation,其余无 | L4 | P1 |
| TC-SESSION-005 | FR-SESSION-003 | 新建会话 | 任意初始状态 | 1. 点击「新建会话」按钮;2. 观察网络请求与界面 | 发出 POST /api/sessions;列表新增一条「新会话」;切换为活动会话;对话区显示欢迎空态;不产生空消息(GET 详情 messages 为空数组) |
L5-D + L2 | P0 |
| TC-SESSION-006 | FR-SESSION-003 | 新建连点不重复 | 同上 | 快速连点新建按钮 2 次 | 产生 2 个独立会话(列表+2,两条 POST);均切到最后创建的一个 | L5-D | P1 |
| TC-SESSION-007 | FR-SESSION-004 | 选择会话加载历史 | 会话含 text/table/sources/graph 块消息 | 点击该会话条目 | 发出 GET /api/sessions/{id};加载中显示骨架屏;完成后消息按块类型渲染:markdown 文本、表格(md-table 样式)、来源、图谱块均出现 |
L5-D(mock 会话详情含全块) | P0 |
| TC-SESSION-008 | FR-SESSION-004 | 切换会话中断进行中流 | 会话 A 流式进行中;会话 B 存在 | 点击会话 B | 会话 A 的 SSE 连接 abort(Network 面板连接关闭);会话 A 状态置 error;切回会话 A 后其消息标注「已中断」 | L5-D(mock slow/正常流) | P0 |
| TC-SESSION-009 | FR-SESSION-005 | 搜索过滤(中文/英文/大小写) | ≥3 个会话,标题含中英文混排 | 1. 输入中文关键词;2. 输入英文小写;3. 输入英文大写;4. 输入无匹配词;5. 清空 | 匹配为 title 大小写不敏感子串;无匹配显示「无匹配会话」;清空恢复全量;过滤期间无网络请求 | L5-D + L4 | P1 |
| TC-SESSION-010 | FR-SESSION-006 | 重命名提交 | 活动会话 | 1. 双击标题进入编辑;2. 输入新名;3. 回车 | PATCH /api/sessions/{id} 请求体 {title:新名};列表与顶栏标题同步更新 |
L5-D + L2 | P1 |
| TC-SESSION-011 | FR-SESSION-006 | 重命名取消与空标题 | 同上 | 1. 编辑中按 Esc;2. 再次编辑并清空标题后回车 | Esc 放弃改动;空标题提交按钮禁用或提交被拒,不发出 PATCH 空 title | L5-D | P2 |
| TC-SESSION-012 | FR-SESSION-007 | 删除需二次确认 | 2 个会话 | 1. 右键/菜单点删除;2. 出现确认弹层时点取消 | 未删除(列表不变、无 DELETE 请求) | L5-D | P1 |
| TC-SESSION-013 | FR-SESSION-007 | 删除当前会话切换 | 2 个会话;选中其中一个 | 删除并确认 | DELETE /api/sessions/{id} 204;当前会话切换到剩余最新一个;刷新列表后已删会话不再出现 |
L5-D + L2 | P0 |
| TC-SESSION-014 | FR-SESSION-007 | 删除最后一个会话显示空态 | 仅 1 个会话 | 删除并确认 | 列表空;对话区显示欢迎空态 | L5-D | P1 |
| TC-SESSION-015 | FR-SESSION-007 | 级联删除消息 | 会话含 2 条消息;BFF 真实 | 删除会话后 GET /api/sessions/{id} |
返回 404(NOT_FOUND 信封);SQLite 中该会话消息行一并删除(L2 断言数据库) | L2 | P0 |
| TC-SESSION-016 | FR-SESSION-008 | 首问自动标题 | 新建会话 | 1. 提问(问题 40+ 字符);2. 检查 PATCH 请求与列表标题 | PATCH 请求体 title = 问题前 30 字符 + 省略号;列表标题同步变化 | L5-D + L2 | P0 |
| TC-SESSION-017 | FR-SESSION-008 | 手动改名后不覆盖 | 会话 meta.title_manual=true(改过名) | 1. 再次提问;2. 检查是否发出 title PATCH | 不发出改 title 的 PATCH;标题保持手动值 | L2(会话固定数据)+ L4 | P1 |
| TC-SESSION-018 | FR-SESSION-009 | 活动态高亮 | ≥2 个会话 | 1. 选中会话 A;2. 检查其 computed style;3. 悬停另一项 B 对比 | A 有 2px 渐变左竖条 + g-to 8% 底色 + 内描边;与悬停态(B)可区分 | L5-D | P2 |
1.5 CHAT 对话与问答(FR-CHAT-001~015)
1.5.1 基础渲染
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-CHAT-001 | FR-CHAT-001 | 欢迎空态元素与建议卡 | 无消息 | 打开应用 | 显示 46px 渐变菱形(✧)+ 渐变标题「海古纪 PlantAI 知识工作台」+ 副标题 + 3 张建议卡 | L5-D | P1 |
| TC-CHAT-002 | FR-CHAT-001 | 建议卡点击即提问 | 无消息;mock normal | 点击第 1 张建议卡 | 卡片文本进入作曲器并直接发送;出现用户气泡(内容=卡片文本)与助手骨架;发出 POST /api/qa/chat/stream |
L5-D | P0 |
| TC-CHAT-003 | FR-CHAT-002 | 用户气泡样式与长文本换行 | 任意 | 发送 300 字无空格长文本 | 气泡右对齐,宽度 ≤ 父容器 70%;文本换行且不溢出(scrollWidth ≤ 气泡宽度) | L5-D | P2 |
| TC-CHAT-004 | FR-CHAT-003 | 助手消息头部与悬停竖线 | 已有助手消息 | 1. 检查头部(24px 渐变头像 ✦ + 「PlantAI 智能体」+ 时间);2. 悬停消息左缘 | 头部三要素齐全;左缘 2px 竖线 hover 时出现渐变 | L5-D | P2 |
| TC-CHAT-005 | FR-CHAT-003 | 多块混排顺序 | 消息 blocks 为 [pipeline,text,table,sources,graph](mock 会话详情) | 渲染该消息 | 各块按 blocks 数组顺序渲染;无块丢失 | L5-D | P1 |
| TC-CHAT-006 | FR-CHAT-004 | Markdown 表格样式 | 消息含表格块(mock 含 md-table 内容的 text 块) | 渲染 | 表格带 md-table 类:thead 2px 渐变下边框;数据格 mono 字体(表头为「说明」的列保持比例字体) | L5-D | P1 |
| TC-CHAT-007 | FR-CHAT-004 | 脚本注入被消毒 | 向问答返回的 answer 注入 <script>window.__pwned=1</script> 与 <img src=x onerror=...>(mock fixture 或 BFF 层构造) |
渲染该答案 | window.__pwned 为 undefined;DOM 中不存在 script 标签;onerror 未执行;内容经 DOMPurify 后 v-html |
L5-D(注入 fixture)+ L3(sanitize 单测) | P0 |
| TC-CHAT-008 | FR-CHAT-014 | 流式自动滚动到底 | mock normal;消息区可滚动 | 发起提问 | 流式更新期间滚动条持续贴近底部(scrollTop+clientHeight ≥ scrollHeight−10) | L5-D | P1 |
| TC-CHAT-009 | FR-CHAT-014 | 上滚暂停自动滚动 | 流式进行中 | 向上滚动 ≥100px | 不再自动贴底;出现「回到最新」按钮 | L5-D | P1 |
| TC-CHAT-010 | FR-CHAT-014 | 「回到最新」恢复滚动 | 上滚暂停状态 | 点击「回到最新」按钮 | 滚动回底部;恢复自动滚动;按钮消失 | L5-D | P1 |
| TC-CHAT-011 | FR-CHAT-015 | 内联引用 chip 跳转高亮 | 答案含引用标记(citation 编号)与对应来源 | 点击引用 chip | bench 切换到「文献来源」tab 并滚动定位到对应条目;该条目高亮 2s 后消失;chip 编号与列表条目序号一致 | L5-D | P1 |
1.5.2 作曲器
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-CHAT-012 | FR-CHAT-005 | 输入框高度自适应 | 桌面 | 1. 输入 1 行文本测基线(min-height 46px);2. 输入 10 行文本 | 基线 ≥46px;随内容增高,最大 160px 后内部滚动(scrollHeight > clientHeight) | L5-D | P2 |
| TC-CHAT-013 | FR-CHAT-006 | 三模式切换 | 桌面;mock normal | 依次点击 问答/方案/检索 pill | 激活 pill 渐变底+白字+青光;非激活态可区分;切换即生效 | L5-D | P0 |
| TC-CHAT-014 | FR-CHAT-006 | 问答模式请求体 | mock 正常;问答模式 | 提问并抓取 POST /api/qa/chat/stream 请求体 |
body.use_llm=true;question 为输入文本;无 native_chain 字段(或为 modes.js 定义默认) | L5-D + L3(modes.js 参数集断言) | P0 |
| TC-CHAT-015 | FR-CHAT-006 | 方案模式请求体 | 方案模式 | 同上 | body.use_llm=true;native_chain_enabled=true;native_chain_max_hops=3;native_chain_top_k=5;端点仍为 /api/qa/chat/stream |
L5-D + L3 | P0 |
| TC-CHAT-016 | FR-CHAT-006 | 检索模式请求与响应 | 检索模式 | 1. 提问;2. 观察端点为 /api/qa/search;3. 等待完成;4. 检查消息块 |
body.use_llm=false;响应 answer 为空;消息只含 sources(+pipeline)块,无 text 答案块 | L5-D + L2 | P0 |
| TC-CHAT-017 | FR-CHAT-007 | 快捷键发送 | 桌面 | 输入文字后按 ⌘/Ctrl+Enter | 发送;出现用户气泡 | L5-D | P0 |
| TC-CHAT-018 | FR-CHAT-007 | Enter 换行 | 桌面 | 输入文字后按 Enter(非组合键) | 不发送;textarea 内换行(行数+1) | L5-D | P1 |
| TC-CHAT-019 | FR-CHAT-007 | 中英文输入法快捷键 | IME 输入法激活(模拟 composition 事件) | composition 期间按 Enter | 不触发发送(组合输入确认才触发) | L3(composition 事件模拟) | P1 |
| TC-CHAT-020 | FR-CHAT-007 | 空输入与忙碌禁用 | 流式进行中 | 1. 空输入点发送;2. 流式中点发送 | 按钮 disabled(opacity .45);无新请求;底部提示文案显示快捷键说明 | L5-D | P1 |
| TC-CHAT-021 | FR-CHAT-011 | 停止流式 | mock slow(长流) | 1. 提问;2. 流式中点击「停止」 | 发送按钮已变为「停止」;点击后 SSE 连接关闭(Network 断言);消息置 error 并标注「已停止」;已收到的块保留 | L5-D | P0 |
| TC-CHAT-022 | FR-CHAT-011 | 停止后无悬挂请求 | BFF 真实 + 上游 QA | 流式中停止;随后检查 BFF 日志 | 前端 abort 后 BFF 取消上游请求(BFF 侧无悬挂 upstream 连接;日志有取消记录) | L2(respx 断言 abort)+ L5 | P1 |
1.5.3 流式主流程(FR-CHAT-008)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-CHAT-023 | FR-CHAT-008 | 主流程-用户消息立即落库 | mock normal;新会话 | 发送问题 | 用户气泡立即出现(不等后端);发出 POST /api/sessions/{id}/messages,body={role:user,text};落库成功 |
L5-D + L2 | P0 |
| TC-CHAT-024 | FR-CHAT-008 | 主流程-会话置 running 与骨架 | 同上 | 发送后观察 | 会话状态置 running(左栏状态点脉冲动画);底栏 4 agent 点复位;出现助手骨架消息(status=streaming)含 pipeline 块,五相位全 waiting | L5-D | P0 |
| TC-CHAT-025 | FR-CHAT-008 | 主流程-progress 相位推进与阶段卡 | mock normal(sse_frames 12 帧) | 提问后观察 | 每帧 progress:pipeline 对应相位 running、此前相位 done;工作台「检索过程」新增阶段卡,数量=progress 帧数(11 个 stage 帧);阶段卡 label 为中文(如「已识别实体」) | L5-D | P0 |
| TC-CHAT-026 | FR-CHAT-008 | 主流程-complete 生成三块 | mock normal;提问完成 | 等待 complete | 消息含 text 块(揭示动画结束为最终文本)、sources 块、graph 块;pipeline 五相位全部 done;消息状态 done;会话状态 done | L5-D | P0 |
| TC-CHAT-027 | FR-CHAT-008 | 主流程-答案内容(依赖充值) | mock normal(mock 答案为固定文本);真实上游需充值 | 断言答案文本 | mock 下:答案文本与 mock fixture 一致(可断言内容);真实上游:answer 为自然语言答案(依赖充值,当前 402 下为 fallback 证据列表) | L5-D / L6 | P1 |
| TC-CHAT-028 | FR-CHAT-008 | 主流程-检查点持久化节奏 | BFF 真实;流 ≥3s | 提问并抓取 PATCH 请求 | 流式中每约 3s 发出 PATCH /api/sessions/{id}/messages/{mid}(块检查点);终态再发一次终态 PATCH(done/error) |
L2 + L5 | P0 |
| TC-CHAT-029 | FR-CHAT-008 | 主流程-长任务持续反馈(240s) | mock slow(长流,或上游真实 ~92s) | 提问并计时观察 | 240s 内任一时刻页面均有视觉反馈(相位卡 shimmer/已耗时计数);无静止超过 5s 的状态(5s 间隔采样断言有动画元素或文本变化) | L5-D(slow 场景) | P0 |
| TC-CHAT-030 | FR-CHAT-008 | 网络中断重试按钮 | 流式中断网(playwright context.setOffline(true)) | 1. 流式中断网;2. 观察消息状态;3. 恢复网络;4. 观察是否自动重连;5. 点「重试」 | 消息置 error,文案「连接中断」;出现「重试」按钮;不自动重连(恢复网络后无新请求,需手动点击);点重试后重新发起同问题请求 | L5-D(offline 模拟) | P0 |
| TC-CHAT-031 | FR-CHAT-008 | 刷新恢复中断会话 | 流式进行中刷新页面 | 刷新后检查该会话 | 显示最后检查点内容;标注「已中断」;会话状态 error;不自动重试 | L5-D | P0 |
| TC-CHAT-032 | FR-CHAT-008 | 同一会话并发提问防护 | 流式进行中 | 观察发送按钮 | 按钮 disabled;无法发送第二个问题 | L5-D | P1 |
| TC-CHAT-033 | FR-CHAT-008 | 同会话提问互相独立(无上下文记忆) | 同一会话已完成一次提问 | 第二次提问并抓取请求体 | 第二次请求只含当前 question,无历史消息拼接(max_rounds:1 语义);UI 有「每次提问独立检索」提示文案 | L2 + L5 | P1 |
1.5.4 流水线卡与揭示动画(FR-CHAT-009 / FR-CHAT-010)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-CHAT-034 | FR-CHAT-009 | 五相位视觉与颜色 | 流式中与完成后分别观察 pipeline 卡 | 检查五个 p-step 的颜色令牌与状态字形 | 相位色:规划#C0A8DD、检索#9FBBE0、证据#9FC9A2、生成#DFA88F、完成#C08532;状态字形 ✓done/●running/◌waiting;running 相位带 11px 旋转 spinner;卡头「⚙ 检索流水线」+ 整体状态徽章(语义色) | L5-D | P0 |
| TC-CHAT-035 | FR-CHAT-009 | 相位与语义色不混用 | 流式中出现错误(mock error 场景) | 检查 pipeline 卡 | 相位点颜色只随相位走(五相位色);整体成败徽章只用语义色(success/error)——两者不互换 | L5-D | P1 |
| TC-CHAT-036 | FR-CHAT-010 | 揭示动画与跳过 | mock normal;答案长度已知 | 1. 观察 complete 后答案揭示;2. 点击「跳过」 | 揭示速度 30ms/字符,尾部 8×15px 青色闪烁光标 + 3px shimmer 进度线;点「跳过」后立即显示全文,reveal 元素移除 | L5-D | P1 |
| TC-CHAT-037 | FR-CHAT-010 | 长答案 8s 上限 | mock 构造 >2000 字符答案 | 测量揭示总时长 | 速度自动加快,总时长 ≤8s | L5-D | P2 |
1.5.5 错误与重试(FR-CHAT-012)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-CHAT-038 | FR-CHAT-012 | 引用校验失败重试 | BFF 对 invalid_llm_citation 已自动重试一次仍失败(L2 构造) | 观察响应与界面 | 前端展示「引用校验失败,请重试」(INVALID_CITATION 信封 message)并提供重试按钮 | L2(respx 断言 BFF 重试一次)+ L5 | P0 |
| TC-CHAT-039 | FR-CHAT-012 | 其他错误展示 message | mock scenario=error | 提问 | 消息置 error;展示 error.message 文案(上游错误中文文案);提供重试按钮;已收到块保留 | L5-D(error 场景) | P0 |
| TC-CHAT-040 | FR-CHAT-012 | 重试同位置替换 | 错误后 | 点击重试 | 用相同问题与模式重新发起;不新增用户消息(消息列表长度不变,同一条助手消息位置更新) | L5-D | P0 |
| TC-CHAT-041 | FR-CHAT-012 | SSE error/timeout 事件 | mock 构造 event:error 与 event:timeout 帧 | 提问 | error 帧→展示 error.message;timeout 帧→文案「检索超时」;两者均置消息 error 并保留已收到块 | L5-D(定制 mock 流) | P1 |
1.5.6 持久化与恢复(FR-CHAT-013)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-CHAT-042 | FR-CHAT-013 | 块联合完整落库 | BFF 真实;完成一次问答 | 检查 SQLite 该消息行 | blocks 含 text/table/sources/graph/pipeline 等全部完成块;无 stream 块(瞬态已转 text);raw 字段保存完整归一化响应 | L2 | P0 |
| TC-CHAT-043 | FR-CHAT-013 | 刷新后恢复全部块 | 完成一次含表格/来源/图谱的问答 | 刷新页面并打开该会话 | GET /api/sessions/{id} 后全部块正确重渲染(表格、来源、图谱可见);无数据丢失 |
L5-D | P0 |
1.6 RETRIEVAL 检索过程(FR-RETRIEVAL-001~006)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-RETRIEVAL-001 | FR-RETRIEVAL-001 | 阶段卡与 progress 帧一一对应 | mock normal;完成一次问答 | 统计工作台 Tab1 卡片数与 Network 中 progress 帧数 | 卡片数 = progress 帧数(sse_frames 11 个 stage 帧 → 11 张卡);每卡含相位色图标 + 阶段中文名 + 状态点 + 耗时 mono | L5-D | P0 |
| TC-RETRIEVAL-002 | FR-RETRIEVAL-001 | 阶段卡展开体参数块 | 已有一张阶段卡 | 展开卡头 | 展开体显示 details 参数块(mono)+ 摘要行(如 entities 列表) | L5-D | P2 |
| TC-RETRIEVAL-003 | FR-RETRIEVAL-002 | 相位筛选 | 一次问答后存在 4 个相位卡片 | 依次点击 全部/规划/检索/证据/生成 chip | 激活 chip 实心 #5E6AD2 底 + 白字;筛选后仅显示对应相位卡片(计数=对应相位帧数) | L5-D | P1 |
| TC-RETRIEVAL-004 | FR-RETRIEVAL-003 | 展开折叠与默认态 | 流式中(running 卡)与完成后各观察一次 | 1. 流式中看卡默认态;2. 完成后看默认态;3. 点击头部 | 运行中的卡默认展开;完成后自动折叠;点击切换展开/折叠;chevron 旋转 90°;展开体 fade-slide 200ms;各卡状态互相独立 | L5-D | P1 |
| TC-RETRIEVAL-005 | FR-RETRIEVAL-004 | 运行中效果 | 流式中 | 检查 running 卡 | 头部 shimmer 扫光(1.8s 循环)+ 状态点脉冲;完成后动效停止(animation none/移除) | L5-D | P2 |
| TC-RETRIEVAL-006 | FR-RETRIEVAL-005 | 检索计划展示与无 plan 不显示 | mock normal(fixture 含 retrieval_plan)与无 plan 的响应各一次 | 检查列表顶部 | 含 retrieval_plan 时顶部只读信息块展示 plan;无 plan 时该区域不渲染(无空框);另展示 /api/qa/info 的 12 段 pipeline 静态说明 |
L5-D | P1 |
| TC-RETRIEVAL-007 | FR-RETRIEVAL-006 | 空态 | 未提问 | 打开工作台 Tab1 | 显示「发起提问后,这里会实时展示检索过程」;无假卡片 | L5-D | P2 |
1.7 SOURCE 文献来源(FR-SOURCE-001~006)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-SOURCE-001 | FR-SOURCE-001 | 来源列表条目数与元素 | mock normal 完成问答(source_count=4) | 打开工作台 Tab2 | 条目数 = 4(与响应 source_count 一致);每条:序号 + 标题(超长省略)+ mono 元信息行(PMID:xxx / DOI:xxx / 分数)+ 证据片段(默认 2 行截断) | L5-D | P0 |
| TC-SOURCE-002 | FR-SOURCE-001 | 证据片段展开 | 有一条来源 | 点击片段 | 展开显示完整 snippet;再点收起 | L5-D | P2 |
| TC-SOURCE-003 | FR-SOURCE-002 | 字段缺失不渲染占位 | mock 构造 source 缺 pmid/pmcid/doi/section 的组合 | 渲染列表 | 缺失字段位置不显示占位符;页面文本中不含 undefined、null 字样 |
L5-D | P0 |
| TC-SOURCE-004 | FR-SOURCE-003 | 外链优先级 | 4 个 source:a 有 url;b 无 url 有 doi;c 无 url/doi 有 pmid;d 无 url/doi/pmid 有 pmcid;e 全无 | 检查各条目链接 | a 链=url;b=https://doi.org/{doi};c=PubMed PMID 链接;d=PMC 链接;e 无链接图标;点击新标签打开且带 rel="noopener" |
L5-D | P1 |
| TC-SOURCE-005 | FR-SOURCE-004 | 证据链分组与展开 | 响应 chains 含 ≥2 个 chain_id | 打开证据链区 | 按 chain_id 分组显示;组徽章显示跳数;展开后逐跳列表 源实体 —[关系]→ 目标实体,每跳附 paper_id/PMID;顺序与 hops 数组一致 |
L5-D | P1 |
| TC-SOURCE-006 | FR-SOURCE-005 | 会话级聚合 | 同一会话完成 2 次问答(相同 paper 出现多次) | 切到「本会话全部」 | 发出 GET /api/sessions/{id}/sources;按 paper_id 去重;每条 count = 该 paper 累计出现次数;去重后条数正确 |
L5-D + L2 | P1 |
| TC-SOURCE-007 | FR-SOURCE-006 | 空态 | mock scenario=empty 完成问答(source_count=0) | 打开工作台 Tab2 | 显示「本次检索未命中文献」;无空列表框(无 .ft-item 元素) | L5-D(empty 场景) | P1 |
1.8 EXTRACT 提取任务(FR-EXTRACT-001~006)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-EXTRACT-001 | FR-EXTRACT-001 | 任务列表与派生百分比 | mock 任务列表(含 queued/running/completed/failed 各一) | 打开工作台 Tab3 | 发出 GET /api/extraction/jobs;列表与响应一致;百分比派生正确:queued=5、running(35/75 按 stage)、completed=100、failed 无进度条且显示错误态;100% 为纯 success 色且无 sweep 扫光 |
L5-D + L2(BFF 派生断言) | P0 |
| TC-EXTRACT-002 | FR-EXTRACT-002 | 任务详情 | 已有一个任务 | 点击展开 | GET /api/extraction/jobs/{id};显示 status、message(中文进度)、stats 统计字段(stage/species/figure_table 计数) |
L5-D | P1 |
| TC-EXTRACT-003 | FR-EXTRACT-002 | 未完成任务 plan 不显示 | 一个 running 任务 | 展开详情 | plan 为 null 时不显示 plan 区块且无报错 | L5-D | P1 |
| TC-EXTRACT-004 | FR-EXTRACT-003 | 运行日志 | mock 正常 | 打开运行日志 | GET /api/extraction/run-log;mono 等宽展示;长日志在最大高度内滚动(scrollHeight > clientHeight 时出现内部滚动);自动贴底 |
L5-D | P2 |
| TC-EXTRACT-005 | FR-EXTRACT-004 | 上传 PDF 成功 | 构造 ≤50MB PDF;mock 上传成功 | 附件按钮 → 选 PDF → 上传 | POST /api/extraction/upload(multipart,Content-Type 含 multipart/form-data);显示上传进度;成功返回 job_id 并刷新列表(出现新任务) |
L5-D | P1 |
| TC-EXTRACT-006 | FR-EXTRACT-004 | 非 PDF 被拒 | 一个 .txt 文件 | 尝试上传 | 被拒绝(前端拦截或 422);不发出上传请求(前端拦截时)或返回 VALIDATION 信封 | L5-D + L2 | P1 |
| TC-EXTRACT-007 | FR-EXTRACT-004 | 超 50MB 被拒 | 一个 >50MB PDF | 尝试上传 | 被拒绝;提示超出大小限制;不发出上传请求 | L5-D | P2 |
| TC-EXTRACT-008 | FR-EXTRACT-004 | 上游失败错误文案 | mock 返回 502(MinerU 不可用) | 上传 | 展示上游 message 的明确错误文案(非堆栈);任务列表/详情为失败态 | L5-D | P1 |
| TC-EXTRACT-009 | FR-EXTRACT-005 | 轮询节奏 | 列表含一个 running 任务 | 观察 12s 内网络请求 | 每 5s 轮询一次 GET /api/extraction/jobs;全部终态后轮询停止(再观察 12s 无新请求) |
L5-D | P1 |
| TC-EXTRACT-010 | FR-EXTRACT-005 | 切 tab 暂停轮询 | running 任务存在;切换到其他 tab | 观察网络 | 离开 Tab3 期间无 jobs 轮询请求;切回恢复 | L5-D | P2 |
| TC-EXTRACT-011 | FR-EXTRACT-006 | 离线降级 | mock 对 extraction 返回 503(degraded) | 打开 Tab3 | 显示离线提示与重试按钮;其他 tab(检索/来源/图谱)不受影响可正常使用 | L5-D(degraded 场景) | P1 |
1.9 KG 知识图谱(FR-KG-001~008)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-KG-001 | FR-KG-001 | 答案图谱渲染(SVG 力导向) | mock normal 完成问答(graph 块 6 节点 4 边) | 打开工作台 Tab4 | 渲染手写 SVG 力导向图:节点圆点+标签;边为渐变线(#5E6AD2→#00D4FF)带关系名;节点与答案引用实体一致(与 graph 块节点 label 一致) | L5-D | P0 |
| TC-KG-002 | FR-KG-001 | 节点 >50 降级列表 | mock 构造 graph 块 >50 节点 | 打开 Tab4 | 自动降级为列表视图并提示;不渲染 SVG 画布 | L5-D | P1 |
| TC-KG-003 | FR-KG-002 | 节点着色与图例 | graph 块含 ≥2 种实体 type | 检查节点颜色与图例 | 按 type 着色(品牌+辅助紫+五相位色板);底部图例存在且与节点颜色一一对应;同 type 在会话内颜色稳定(两次问答同一 type 颜色相同) | L5-D | P2 |
| TC-KG-004 | FR-KG-003 | 节点详情面板 | graph 块有节点;Neo4j 不可用(mock 无 kg 或 503) | 点击一个节点 | 侧滑详情面板显示实体属性(消息内已有属性);Neo4j 不可用时仍显示(不报错、不空白) | L5-D | P1 |
| TC-KG-005 | FR-KG-003 | Neo4j 可用时拉全量属性 | Neo4j 可用(真实上游或 mock) | 点击节点 | 面板内额外数据来自 GET /api/kg/entities/{name}(发出该请求并渲染返回 properties) |
L5-D | P2 |
| TC-KG-006 | FR-KG-004 | 实体搜索(精确/前缀) | mock normal | 1. 输入「OsSPL」;2. 回车 | GET /api/kg/entities?q=OsSPL;结果显示(精确或前缀匹配项);响应 <1s(断言用时) |
L5-D + L2 | P0 |
| TC-KG-007 | FR-KG-004 | 实体搜索选中渲染邻域 | 上一步结果 | 点击第一个结果 | 渲染该实体邻域(调 neighborhood 接口或 mock 数据);画布更新为该实体为中心 | L5-D | P1 |
| TC-KG-008 | FR-KG-005 | 邻域深度切换 | mock normal | 1. 深度选 1(默认)观察请求;2. 深度选 2 观察 | 请求 depth 参数分别为 1/2;渲染节点数随之变化;深度 2 时显示「较慢」提示文案(两跳实测约 13s) | L5-D + L2 | P1 |
| TC-KG-009 | FR-KG-005 | 节点上限 200 截断 | mock 构造 >200 节点邻域 | 深度 2 展开 | 渲染节点数 ≤200;每跳按 paper_count/support_count 降序截断(L2 断言 BFF 截断逻辑);超限有提示不卡死 | L2 + L5 | P1 |
| TC-KG-010 | FR-KG-006 | 缩放平移(桌面) | 画布有图 | 滚轮缩放;拖拽平移;双击 | 缩放范围 0.3×–3×;平移生效;双击复位到初始视图 | L5-D | P1 |
| TC-KG-011 | FR-KG-006 | 触摸缩放平移(移动端) | Mobile 视口;画布有图 | 双指捏合;单指拖拽 | 双指缩放与单指拖拽均可用且流畅(缩放比例改变;拖拽后画布位移) | L5-M(touch 模拟) | P1 |
| TC-KG-012 | FR-KG-007 | Neo4j 不可用降级 | mock kg 接口返回 503(或 BFF 断开 Neo4j) | 打开 Tab4 | 搜索框与邻域功能禁用并提示离线;答案图谱照常渲染;tab 不白屏 | L5-D | P1 |
| TC-KG-013 | FR-KG-008 | 空态 | 本次回答无 graph 数据(mock empty) | 打开 Tab4 | 提示「本次检索未产生图谱关系」+ 引导使用实体搜索;不显示空画布 | L5-D(empty 场景) | P2 |
1.10 RESP 响应式(FR-RESP-001~013)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-RESP-001 | FR-RESP-001 | Desktop ≥1280 完整三栏 | Desktop 1440×900 | 检查布局 | 三栏 260/1fr/380 可见(同 TC-SHELL-001) | L5-D | P0 |
| TC-RESP-002 | FR-RESP-002 | Laptop 1024–1279 左栏自动折叠 | 视口 1200×900 | 检查布局 | 三栏保留;左栏宽=64px(自动折叠,无需点击);右栏=340px | L5-D(1200×900) | P1 |
| TC-RESP-003 | FR-RESP-003 | Tablet 左抽屉 | Tablet 768×1024 | 1. 检查布局(对话单栏铺满);2. 点顶栏汉堡 | 左栏默认隐藏;点汉堡后左抽屉 260px 滑入 + scrim 遮罩;顶栏有汉堡按钮;右栏为右抽屉(顶栏工作台按钮带来源数角标);点 scrim 或 Esc 关闭;动效 ease-out 300ms | L5-T | P0 |
| TC-RESP-004 | FR-RESP-004 | Mobile 单栏与工作台覆盖层 | Mobile 390×844 | 1. 检查布局;2. 点顶栏工作台按钮 | 顶栏=汉堡+标题+工作台按钮;对话铺满;作曲器吸底;工作台打开为全屏覆盖层+顶部 tab 条;底部状态栏隐藏(不可见);四个 agent 点合并为顶栏一枚聚合状态点;点 scrim 或 Esc 关闭 | L5-M | P0 |
| TC-RESP-005 | FR-RESP-005 | viewport 与安全区 | Mobile(iPhone 视口) | 检查 meta 与 computed 值 | <meta viewport> 含 viewport-fit=cover;作曲器 padding-bottom = env(safe-area-inset-bottom);顶栏 padding-top = env(safe-area-inset-top)(computed 值>0) |
L5-M | P1 |
| TC-RESP-006 | FR-RESP-006 | iOS 输入放大规避 | Mobile 视口(<768px) | 检查 textarea computed font-size | font-size=16px;未使用 maximum-scale=1(meta 内容断言) | L5-M | P0 |
| TC-RESP-007 | FR-RESP-007 | 悬停态触摸等价 | Mobile 视口;有来源条目与助手消息 | 检查来源外链图标、助手消息左缘线、会话条目样式 | 外链图标常驻可见;助手消息左缘线常驻;会话条目无 hover 底色依赖(点击反馈存在);@media (hover:hover) 包裹 hover 样式(L3 样式断言) |
L5-M | P1 |
| TC-RESP-008 | FR-RESP-008 | 触控目标 44px | Mobile 视口 | 检查会话条目、bench tab、模式 pill、图标按钮高度 | min-height ≥44px(computed) | L5-M | P1 |
| TC-RESP-009 | FR-RESP-009 | 100dvh 视口高度 | Mobile 视口 | 检查应用根容器高度 | 使用 100dvh(computed 解析为实际视口高度);存在 100vh 回退声明(L3 样式断言) | L5-M | P2 |
| TC-RESP-010 | FR-RESP-010 | 虚拟键盘适配 | Mobile 视口;visualViewport 可模拟 | 聚焦 textarea(模拟键盘弹出压缩 visualViewport) | 消息区高度压缩并保持贴底;作曲器始终可见(boundingBox 在视口内) | L5-M | P1 |
| TC-RESP-011 | FR-RESP-011 | 表格横向滚动 | Mobile 视口;消息含宽表格 | 检查 md-table 容器 | 表格外层有横向滚动容器;容器 scrollWidth > clientWidth 时可横滑查看完整内容;字体字号未被压缩(computed font-size ≥ 桌面值) | L5-M | P1 |
| TC-RESP-012 | FR-RESP-012 | 移动端性能降级 | Mobile 视口 | 检查 aurora 与图谱上限 | aurora 模糊半径降低或 --pa-aurora-opacity:0;图谱节点数上限=30(构造 >30 节点 graph 块时渲染 ≤30 或降级列表) |
L5-M | P2 |
| TC-RESP-013 | FR-RESP-013 | 手势(可选 N/A) | Mobile 视口 | 左缘右滑 | 若实现:右滑开会话抽屉、右滑关工作台覆盖层可用;未实现不算缺陷,本条 N/A 记录 | L5-M(标记 N/A) | P2 |
1.11 HEALTH 健康与降级(FR-HEALTH-001~005)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-HEALTH-001 | FR-HEALTH-001 | 健康轮询 30s | 打开应用 | 观察 65s 内 GET /api/health 请求 |
挂载后立即一次;此后每 30s 一次(65s 内共 3 次) | L5-D | P1 |
| TC-HEALTH-002 | FR-HEALTH-001 | 轮询失败不弹窗 | mock /api/health 持续 500 |
打开应用 | 无错误弹窗/未捕获异常;状态栏健康标记显示降级态 | L5-D | P1 |
| TC-HEALTH-003 | FR-HEALTH-001 | 上游恢复 30s 内解禁 | mock 先 503 后恢复正常(场景切换) | 1. 观察禁用态;2. 切回正常场景;3. ≤30s 内观察 | 上游恢复后 ≤30s(一个轮询周期内)对应功能自动解禁,无需手动刷新 | L5-D | P1 |
| TC-HEALTH-004 | FR-HEALTH-002 | QA 不可用降级 | mock scenario=degraded(/api/qa/* 503) | 打开应用 | 作曲器禁用并显示「知识问答服务当前不可用」;历史会话仍可浏览(点击会话可看历史消息);无错误弹窗 | L5-D(degraded 场景) | P0 |
| TC-HEALTH-005 | FR-HEALTH-002 | QA 降级信封语义 | mock degraded | 检查响应结构 | HTTP 503 + {"ok":false,"error":{"code":"UPSTREAM_DOWN","message":"…","degraded":true}};前端按 degraded 处理(横幅/离线态)而非错误弹窗 |
L2 + L5 | P0 |
| TC-HEALTH-006 | FR-HEALTH-003 | Neo4j 不可用降级 | mock kg 503 | 打开 Tab4 | 搜索/邻域禁用并提示离线;答案图谱照常渲染(同 TC-KG-012) | L5-D | P1 |
| TC-HEALTH-007 | FR-HEALTH-004 | 抽取不可用降级 | mock extraction 503 | 打开 Tab3 | 仅 Tab3 降级(离线提示+重试按钮);其他 tab 正常(同 TC-EXTRACT-011) | L5-D | P1 |
| TC-HEALTH-008 | FR-HEALTH-005 | 网络中断提示与不自动重连 | 流式中断网(同 TC-CHAT-030) | 1. 断网;2. 恢复网络观察 | 提示「连接中断」+ 重试按钮;不自动重连;无重复 LLM 调用(Network 无自动新请求) | L5-D | P0 |
1.12 非功能要求(PRD §14)对照用例
| 编号 | 对应要求 | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-NFR-001 | 首屏可交互 | 桌面;mock normal | 打开应用并计时(performance.timing / Playwright 计时) | 桌面 <2s 可交互(首屏渲染+会话列表加载完成);手机 <3s | L5-D / L5-M | P1 | |
| TC-NFR-002 | 长任务反馈 | mock slow(长流) | 提问并采样 | 240s 内无静止 >5s 状态(同 TC-CHAT-029) | L5-D | P0 | |
| TC-NFR-003 | Neo4j 凭据不下发 | 打开应用并抓取全部网络响应与 JS bundle 文本 | 浏览器侧任何响应/源码中不含 Neo4j 密码(<neo4j-password-in-.env> 等)与 bolt:// URI | L5-D + L3(bundle 扫描) | P0 | ||
| TC-NFR-004 | 零外网请求 | 打开应用并抓取全部网络请求 | 所有请求 host 均为 127.0.0.1(或部署域);无 fonts.googleapis.com / CDN 域名请求 | L5-D | P1 | ||
| TC-NFR-005 | 浏览器兼容基线 | Chrome 110+/Firefox 110+/Safari 16+ | Playwright 三浏览器各跑冒烟 2 条 | 冒烟通过(页面加载 + AppShell 存在) | L5(chromium 常驻;firefox/webkit 每轮按需) | P1 |
1.13 E2E 主线场景(TEST_PLAN §6 九条主线 × 三档视口)
编号规则 TC-E2E-<主线号>-<视口>,M=Mobile(390×844)|T=Tablet(768×1024)|D=Desktop(1440×900)。每条为一条用例,步骤与预期为该视口下的完整流程断言。全量用例在后续轮次落为 spec 文件;本波仅实现 TC-E2E-SMOKE-01/02(§1.14)。
| 编号 | 主线 | 视口 | 步骤 | 预期结果 | 优先级 |
|---|---|---|---|---|---|
| TC-E2E-01-M | 1 主流程 | M | 新建会话 → 问答模式提问 → 观察流水线五相位推进 → 等待答案揭示(含表格)→ 来源 tab 有数据 → 图谱 tab 渲染 → 刷新后历史恢复 | 五相位按序推进(waiting→running→done);答案揭示并含 md-table;Tab2 条目数=source_count;Tab4 SVG 图渲染;刷新后该会话消息完整恢复 | P0 |
| TC-E2E-01-T | 1 主流程 | T | 同 TC-E2E-01-M(左栏经抽屉进入会话) | 同 TC-E2E-01-M;另断言左/右抽屉开合正常 | P0 |
| TC-E2E-01-D | 1 主流程 | D | 同 TC-E2E-01-M | 同 TC-E2E-01-M | P0 |
| TC-E2E-02-M | 2 检索模式 | M | 检索模式提问 → 等待完成 → 检查消息块 | 无 text 答案块;仅来源数据;请求体 use_llm=false 且端点 /api/qa/search |
P0 |
| TC-E2E-02-T | 2 检索模式 | T | 同 TC-E2E-02-M | 同 TC-E2E-02-M | P0 |
| TC-E2E-02-D | 2 检索模式 | D | 同 TC-E2E-02-M | 同 TC-E2E-02-M | P0 |
| TC-E2E-03-M | 3 错误与重试 | M | scenario=error 下提问 → 观察错误;scenario=degraded 下观察作曲器 | error:错误文案展示+重试按钮可用(点击后重新发起);degraded:作曲器禁用+「知识问答服务当前不可用」提示 | P0 |
| TC-E2E-03-T | 3 错误与重试 | T | 同 TC-E2E-03-M | 同 TC-E2E-03-M | P0 |
| TC-E2E-03-D | 3 错误与重试 | D | 同 TC-E2E-03-M | 同 TC-E2E-03-M | P0 |
| TC-E2E-04-M | 4 会话管理 | M | 新建/切换/重命名/删除/搜索过滤/观察分组 | 每步按 SESSION 用例断言:新建+1、切换加载历史、重命名同步顶栏、删除需确认且列表-1、搜索实时过滤、分组标签正确 | P0 |
| TC-E2E-04-T | 4 会话管理 | T | 同 TC-E2E-04-M | 同 TC-E2E-04-M | P0 |
| TC-E2E-04-D | 4 会话管理 | D | 同 TC-E2E-04-M | 同 TC-E2E-04-M | P0 |
| TC-E2E-05-M | 5 中断 | M | 流式中途切会话 → 切回原会话 | 原会话置 error 且标注「已中断」;已收到块保留;无悬挂流 | P0 |
| TC-E2E-05-T | 5 中断 | T | 同 TC-E2E-05-M | 同 TC-E2E-05-M | P0 |
| TC-E2E-05-D | 5 中断 | D | 同 TC-E2E-05-M | 同 TC-E2E-05-M | P0 |
| TC-E2E-06-M | 6 主题 | M | 深浅切换 → 刷新 | 切换即时生效(dataset.theme 与 localStorage);刷新后保持 | P0 |
| TC-E2E-06-T | 6 主题 | T | 同 TC-E2E-06-M | 同 TC-E2E-06-M | P0 |
| TC-E2E-06-D | 6 主题 | D | 同 TC-E2E-06-M | 同 TC-E2E-06-M | P0 |
| TC-E2E-07-M | 7 图谱 | M | 实体搜索 → 选中渲染邻域 → 节点详情 → 双指缩放 + 单指拖拽 | 搜索结果渲染;邻域画布更新;详情面板打开;触摸缩放/拖拽生效 | P1 |
| TC-E2E-07-T | 7 图谱 | T | 同 TC-E2E-07-M(触摸为单指拖拽+双指捏合) | 同 TC-E2E-07-M | P1 |
| TC-E2E-07-D | 7 图谱 | D | 实体搜索 → 邻域 → 详情 → 滚轮缩放/拖拽平移/双击复位 | 同 TC-E2E-07-M(桌面交互) | P1 |
| TC-E2E-08-M | 8 抽取 | M | 任务列表 → 详情展开 → 运行日志;mock 下上传 PDF | 列表渲染+百分比正确;详情字段齐全;日志滚动;上传成功返回 job_id 并刷新列表 | P1 |
| TC-E2E-08-T | 8 抽取 | T | 同 TC-E2E-08-M | 同 TC-E2E-08-M | P1 |
| TC-E2E-08-D | 8 抽取 | D | 同 TC-E2E-08-M | 同 TC-E2E-08-M | P1 |
| TC-E2E-09-M | 9 响应式专项 | M | 布局断言(FR-RESP-004 全项)+ 移动端专项:安全区、16px 输入、44px 触控、表格横滚、键盘适配 | 顶栏=汉堡+标题+工作台按钮;作曲器吸底;工作台全屏覆盖层+tab 条;底栏隐藏;聚合状态点存在;safe-area padding 生效;textarea 16px;触控目标 ≥44px;宽表格横滚;聚焦时作曲器可见 | P0 |
| TC-E2E-09-T | 9 响应式专项 | T | 布局断言(FR-RESP-003 全项:抽屉/scrim/角标) | 单栏铺满;左右抽屉开合+scrim+Esc 关闭;汉堡与工作台按钮带来源数角标 | P0 |
| TC-E2E-09-D | 9 响应式专项 | D | 布局断言(FR-RESP-001/002:1280 边界与 1200 折叠) | 1440 完整三栏;1200 左栏自动折叠 64px 右栏 340px | P0 |
1.14 E2E 冒烟骨架用例(本波已实现)
| 编号 | 对应 FR | 标题 | 前置条件 | 步骤 | 预期结果 | 测试层 | 优先级 |
|---|---|---|---|---|---|---|---|
| TC-E2E-SMOKE-01 | FR-SHELL-007(页面加载) | 页面加载冒烟 | dev server 5180 可用;三档视口各跑一次 | 打开 / |
title 含「海古纪 PlantAI」;#app 可见且内容非空 |
L5-M/T/D | P0 |
| TC-E2E-SMOKE-02 | FR-SHELL-001/003/FR-RESP-004(壳层;移动端底栏隐藏断言) | AppShell 存在冒烟 | 同上 | 打开 / |
存在 .app-shell 时:shell/top-bar 可见且无占位符;status-bar 在 ≥768px 视口可见、移动端隐藏(FR-RESP-004);M0 占位期(无 .app-shell)回退:.app-shell-placeholder 可见 |
L5-M/T/D | P0 |
2. 追溯矩阵
2.1 统计总览
| 项 | 值 |
|---|---|
| FR 总数 | 81 |
| 已覆盖 FR | 81 |
| 未覆盖清单 | (空) |
| TC 总数 | 179 |
| 孤儿 TC(无 FR 映射) | 0 |
| 主门禁(FR→TC 且 TC→FR 双向完整) | 81/81 = 100% |
2.2 FR→TC 矩阵(每个 FR 列出其全部 TC)
| FR | 用例 |
|---|---|
| FR-SHELL-001 | TC-SHELL-001, TC-SHELL-002, TC-E2E-SMOKE-02, TC-E2E-09-D |
| FR-SHELL-002 | TC-SHELL-003, TC-SHELL-004, TC-SHELL-005 |
| FR-SHELL-003 | TC-SHELL-006, TC-SHELL-007, TC-E2E-SMOKE-02 |
| FR-SHELL-004 | TC-SHELL-008, TC-SHELL-009 |
| FR-SHELL-005 | TC-SHELL-010 |
| FR-SHELL-006 | TC-SHELL-011, TC-SHELL-012 |
| FR-SHELL-007 | TC-SHELL-013, TC-SHELL-014, TC-SHELL-015, TC-E2E-SMOKE-01 |
| FR-THEME-001 | TC-THEME-001, TC-THEME-002 |
| FR-THEME-002 | TC-THEME-003, TC-THEME-004, TC-E2E-06-M, TC-E2E-06-T, TC-E2E-06-D |
| FR-THEME-003 | TC-THEME-005 |
| FR-THEME-004 | TC-THEME-006 |
| FR-THEME-005 | TC-THEME-007, TC-THEME-008 |
| FR-THEME-006 | TC-THEME-009, TC-THEME-010 |
| FR-SESSION-001 | TC-SESSION-001, TC-SESSION-002, TC-SESSION-003, TC-E2E-04-M, TC-E2E-04-T, TC-E2E-04-D |
| FR-SESSION-002 | TC-SESSION-004 |
| FR-SESSION-003 | TC-SESSION-005, TC-SESSION-006, TC-E2E-04-M, TC-E2E-04-T, TC-E2E-04-D |
| FR-SESSION-004 | TC-SESSION-007, TC-SESSION-008, TC-E2E-04-M, TC-E2E-04-T, TC-E2E-04-D |
| FR-SESSION-005 | TC-SESSION-009, TC-E2E-04-M, TC-E2E-04-T, TC-E2E-04-D |
| FR-SESSION-006 | TC-SESSION-010, TC-SESSION-011, TC-E2E-04-M, TC-E2E-04-T, TC-E2E-04-D |
| FR-SESSION-007 | TC-SESSION-012, TC-SESSION-013, TC-SESSION-014, TC-SESSION-015, TC-E2E-04-M, TC-E2E-04-T, TC-E2E-04-D |
| FR-SESSION-008 | TC-SESSION-016, TC-SESSION-017 |
| FR-SESSION-009 | TC-SESSION-018 |
| FR-CHAT-001 | TC-CHAT-001, TC-CHAT-002 |
| FR-CHAT-002 | TC-CHAT-003 |
| FR-CHAT-003 | TC-CHAT-004, TC-CHAT-005 |
| FR-CHAT-004 | TC-CHAT-006, TC-CHAT-007 |
| FR-CHAT-005 | TC-CHAT-012 |
| FR-CHAT-006 | TC-CHAT-013, TC-CHAT-014, TC-CHAT-015, TC-CHAT-016, TC-E2E-02-M, TC-E2E-02-T, TC-E2E-02-D |
| FR-CHAT-007 | TC-CHAT-017, TC-CHAT-018, TC-CHAT-019, TC-CHAT-020 |
| FR-CHAT-008 | TC-CHAT-023, TC-CHAT-024, TC-CHAT-025, TC-CHAT-026, TC-CHAT-027, TC-CHAT-028, TC-CHAT-029, TC-CHAT-030, TC-CHAT-031, TC-CHAT-032, TC-CHAT-033, TC-E2E-01-M, TC-E2E-01-T, TC-E2E-01-D, TC-E2E-05-M, TC-E2E-05-T, TC-E2E-05-D |
| FR-CHAT-009 | TC-CHAT-034, TC-CHAT-035 |
| FR-CHAT-010 | TC-CHAT-036, TC-CHAT-037 |
| FR-CHAT-011 | TC-CHAT-021, TC-CHAT-022, TC-E2E-05-M, TC-E2E-05-T, TC-E2E-05-D |
| FR-CHAT-012 | TC-CHAT-038, TC-CHAT-039, TC-CHAT-040, TC-CHAT-041, TC-E2E-03-M, TC-E2E-03-T, TC-E2E-03-D |
| FR-CHAT-013 | TC-CHAT-042, TC-CHAT-043, TC-E2E-01-M, TC-E2E-01-T, TC-E2E-01-D |
| FR-CHAT-014 | TC-CHAT-008, TC-CHAT-009, TC-CHAT-010 |
| FR-CHAT-015 | TC-CHAT-011 |
| FR-RETRIEVAL-001 | TC-RETRIEVAL-001, TC-RETRIEVAL-002 |
| FR-RETRIEVAL-002 | TC-RETRIEVAL-003 |
| FR-RETRIEVAL-003 | TC-RETRIEVAL-004 |
| FR-RETRIEVAL-004 | TC-RETRIEVAL-005 |
| FR-RETRIEVAL-005 | TC-RETRIEVAL-006 |
| FR-RETRIEVAL-006 | TC-RETRIEVAL-007 |
| FR-SOURCE-001 | TC-SOURCE-001, TC-SOURCE-002 |
| FR-SOURCE-002 | TC-SOURCE-003 |
| FR-SOURCE-003 | TC-SOURCE-004 |
| FR-SOURCE-004 | TC-SOURCE-005 |
| FR-SOURCE-005 | TC-SOURCE-006 |
| FR-SOURCE-006 | TC-SOURCE-007 |
| FR-EXTRACT-001 | TC-EXTRACT-001, TC-E2E-08-M, TC-E2E-08-T, TC-E2E-08-D |
| FR-EXTRACT-002 | TC-EXTRACT-002, TC-EXTRACT-003, TC-E2E-08-M, TC-E2E-08-T, TC-E2E-08-D |
| FR-EXTRACT-003 | TC-EXTRACT-004, TC-E2E-08-M, TC-E2E-08-T, TC-E2E-08-D |
| FR-EXTRACT-004 | TC-EXTRACT-005, TC-EXTRACT-006, TC-EXTRACT-007, TC-EXTRACT-008, TC-E2E-08-M, TC-E2E-08-T, TC-E2E-08-D |
| FR-EXTRACT-005 | TC-EXTRACT-009, TC-EXTRACT-010 |
| FR-EXTRACT-006 | TC-EXTRACT-011 |
| FR-KG-001 | TC-KG-001, TC-KG-002 |
| FR-KG-002 | TC-KG-003 |
| FR-KG-003 | TC-KG-004, TC-KG-005 |
| FR-KG-004 | TC-KG-006, TC-KG-007, TC-E2E-07-M, TC-E2E-07-T, TC-E2E-07-D |
| FR-KG-005 | TC-KG-008, TC-KG-009, TC-E2E-07-M, TC-E2E-07-T, TC-E2E-07-D |
| FR-KG-006 | TC-KG-010, TC-KG-011, TC-E2E-07-M, TC-E2E-07-T, TC-E2E-07-D |
| FR-KG-007 | TC-KG-012 |
| FR-KG-008 | TC-KG-013 |
| FR-RESP-001 | TC-RESP-001, TC-E2E-09-D |
| FR-RESP-002 | TC-RESP-002, TC-E2E-09-D |
| FR-RESP-003 | TC-RESP-003, TC-E2E-09-T |
| FR-RESP-004 | TC-RESP-004, TC-E2E-09-M, TC-E2E-SMOKE-02 |
| FR-RESP-005 | TC-RESP-005, TC-E2E-09-M |
| FR-RESP-006 | TC-RESP-006, TC-E2E-09-M |
| FR-RESP-007 | TC-RESP-007, TC-E2E-09-M |
| FR-RESP-008 | TC-RESP-008, TC-E2E-09-M |
| FR-RESP-009 | TC-RESP-009, TC-E2E-09-M |
| FR-RESP-010 | TC-RESP-010, TC-E2E-09-M |
| FR-RESP-011 | TC-RESP-011, TC-E2E-09-M |
| FR-RESP-012 | TC-RESP-012, TC-E2E-09-M |
| FR-RESP-013 | TC-RESP-013(N/A 可选) |
| FR-HEALTH-001 | TC-HEALTH-001, TC-HEALTH-002, TC-HEALTH-003 |
| FR-HEALTH-002 | TC-HEALTH-004, TC-HEALTH-005, TC-E2E-03-M, TC-E2E-03-T, TC-E2E-03-D |
| FR-HEALTH-003 | TC-HEALTH-006, TC-KG-012 |
| FR-HEALTH-004 | TC-HEALTH-007, TC-EXTRACT-011 |
| FR-HEALTH-005 | TC-HEALTH-008, TC-CHAT-030 |
非功能要求(PRD §14,非 FR 编号)由 TC-NFR-001~005 覆盖:首屏性能(TC-NFR-001)、长任务反馈(TC-NFR-002)、凭据安全(TC-NFR-003)、零外网(TC-NFR-004)、浏览器兼容(TC-NFR-005)。
2.3 TC→FR 矩阵(每条 TC 列出其全部 FR,证明无孤儿)
| TC | FR |
|---|---|
| TC-SHELL-001 | FR-SHELL-001 |
| TC-SHELL-002 | FR-SHELL-001 |
| TC-SHELL-003 | FR-SHELL-002 |
| TC-SHELL-004 | FR-SHELL-002 |
| TC-SHELL-005 | FR-SHELL-002 |
| TC-SHELL-006 | FR-SHELL-003 |
| TC-SHELL-007 | FR-SHELL-003 |
| TC-SHELL-008 | FR-SHELL-004 |
| TC-SHELL-009 | FR-SHELL-004 |
| TC-SHELL-010 | FR-SHELL-005 |
| TC-SHELL-011 | FR-SHELL-006 |
| TC-SHELL-012 | FR-SHELL-006 |
| TC-SHELL-013 | FR-SHELL-007 |
| TC-SHELL-014 | FR-SHELL-007 |
| TC-SHELL-015 | FR-SHELL-007 |
| TC-THEME-001 | FR-THEME-001 |
| TC-THEME-002 | FR-THEME-001 |
| TC-THEME-003 | FR-THEME-002 |
| TC-THEME-004 | FR-THEME-002 |
| TC-THEME-005 | FR-THEME-003 |
| TC-THEME-006 | FR-THEME-004 |
| TC-THEME-007 | FR-THEME-005 |
| TC-THEME-008 | FR-THEME-005 |
| TC-THEME-009 | FR-THEME-006 |
| TC-THEME-010 | FR-THEME-006 |
| TC-SESSION-001 | FR-SESSION-001 |
| TC-SESSION-002 | FR-SESSION-001 |
| TC-SESSION-003 | FR-SESSION-001 |
| TC-SESSION-004 | FR-SESSION-002 |
| TC-SESSION-005 | FR-SESSION-003 |
| TC-SESSION-006 | FR-SESSION-003 |
| TC-SESSION-007 | FR-SESSION-004 |
| TC-SESSION-008 | FR-SESSION-004 |
| TC-SESSION-009 | FR-SESSION-005 |
| TC-SESSION-010 | FR-SESSION-006 |
| TC-SESSION-011 | FR-SESSION-006 |
| TC-SESSION-012 | FR-SESSION-007 |
| TC-SESSION-013 | FR-SESSION-007 |
| TC-SESSION-014 | FR-SESSION-007 |
| TC-SESSION-015 | FR-SESSION-007 |
| TC-SESSION-016 | FR-SESSION-008 |
| TC-SESSION-017 | FR-SESSION-008 |
| TC-SESSION-018 | FR-SESSION-009 |
| TC-CHAT-001 | FR-CHAT-001 |
| TC-CHAT-002 | FR-CHAT-001 |
| TC-CHAT-003 | FR-CHAT-002 |
| TC-CHAT-004 | FR-CHAT-003 |
| TC-CHAT-005 | FR-CHAT-003 |
| TC-CHAT-006 | FR-CHAT-004 |
| TC-CHAT-007 | FR-CHAT-004 |
| TC-CHAT-008 | FR-CHAT-014 |
| TC-CHAT-009 | FR-CHAT-014 |
| TC-CHAT-010 | FR-CHAT-014 |
| TC-CHAT-011 | FR-CHAT-015 |
| TC-CHAT-012 | FR-CHAT-005 |
| TC-CHAT-013 | FR-CHAT-006 |
| TC-CHAT-014 | FR-CHAT-006 |
| TC-CHAT-015 | FR-CHAT-006 |
| TC-CHAT-016 | FR-CHAT-006 |
| TC-CHAT-017 | FR-CHAT-007 |
| TC-CHAT-018 | FR-CHAT-007 |
| TC-CHAT-019 | FR-CHAT-007 |
| TC-CHAT-020 | FR-CHAT-007 |
| TC-CHAT-021 | FR-CHAT-011 |
| TC-CHAT-022 | FR-CHAT-011 |
| TC-CHAT-023 | FR-CHAT-008 |
| TC-CHAT-024 | FR-CHAT-008 |
| TC-CHAT-025 | FR-CHAT-008 |
| TC-CHAT-026 | FR-CHAT-008 |
| TC-CHAT-027 | FR-CHAT-008 |
| TC-CHAT-028 | FR-CHAT-008 |
| TC-CHAT-029 | FR-CHAT-008 |
| TC-CHAT-030 | FR-CHAT-008, FR-HEALTH-005 |
| TC-CHAT-031 | FR-CHAT-008 |
| TC-CHAT-032 | FR-CHAT-008 |
| TC-CHAT-033 | FR-CHAT-008 |
| TC-CHAT-034 | FR-CHAT-009 |
| TC-CHAT-035 | FR-CHAT-009 |
| TC-CHAT-036 | FR-CHAT-010 |
| TC-CHAT-037 | FR-CHAT-010 |
| TC-CHAT-038 | FR-CHAT-012 |
| TC-CHAT-039 | FR-CHAT-012 |
| TC-CHAT-040 | FR-CHAT-012 |
| TC-CHAT-041 | FR-CHAT-012 |
| TC-CHAT-042 | FR-CHAT-013 |
| TC-CHAT-043 | FR-CHAT-013 |
| TC-RETRIEVAL-001 | FR-RETRIEVAL-001 |
| TC-RETRIEVAL-002 | FR-RETRIEVAL-001 |
| TC-RETRIEVAL-003 | FR-RETRIEVAL-002 |
| TC-RETRIEVAL-004 | FR-RETRIEVAL-003 |
| TC-RETRIEVAL-005 | FR-RETRIEVAL-004 |
| TC-RETRIEVAL-006 | FR-RETRIEVAL-005 |
| TC-RETRIEVAL-007 | FR-RETRIEVAL-006 |
| TC-SOURCE-001 | FR-SOURCE-001 |
| TC-SOURCE-002 | FR-SOURCE-001 |
| TC-SOURCE-003 | FR-SOURCE-002 |
| TC-SOURCE-004 | FR-SOURCE-003 |
| TC-SOURCE-005 | FR-SOURCE-004 |
| TC-SOURCE-006 | FR-SOURCE-005 |
| TC-SOURCE-007 | FR-SOURCE-006 |
| TC-EXTRACT-001 | FR-EXTRACT-001 |
| TC-EXTRACT-002 | FR-EXTRACT-002 |
| TC-EXTRACT-003 | FR-EXTRACT-002 |
| TC-EXTRACT-004 | FR-EXTRACT-003 |
| TC-EXTRACT-005 | FR-EXTRACT-004 |
| TC-EXTRACT-006 | FR-EXTRACT-004 |
| TC-EXTRACT-007 | FR-EXTRACT-004 |
| TC-EXTRACT-008 | FR-EXTRACT-004 |
| TC-EXTRACT-009 | FR-EXTRACT-005 |
| TC-EXTRACT-010 | FR-EXTRACT-005 |
| TC-EXTRACT-011 | FR-EXTRACT-006, FR-HEALTH-004 |
| TC-KG-001 | FR-KG-001 |
| TC-KG-002 | FR-KG-001 |
| TC-KG-003 | FR-KG-002 |
| TC-KG-004 | FR-KG-003 |
| TC-KG-005 | FR-KG-003 |
| TC-KG-006 | FR-KG-004 |
| TC-KG-007 | FR-KG-004 |
| TC-KG-008 | FR-KG-005 |
| TC-KG-009 | FR-KG-005 |
| TC-KG-010 | FR-KG-006 |
| TC-KG-011 | FR-KG-006 |
| TC-KG-012 | FR-KG-007, FR-HEALTH-003 |
| TC-KG-013 | FR-KG-008 |
| TC-RESP-001 | FR-RESP-001 |
| TC-RESP-002 | FR-RESP-002 |
| TC-RESP-003 | FR-RESP-003 |
| TC-RESP-004 | FR-RESP-004 |
| TC-RESP-005 | FR-RESP-005 |
| TC-RESP-006 | FR-RESP-006 |
| TC-RESP-007 | FR-RESP-007 |
| TC-RESP-008 | FR-RESP-008 |
| TC-RESP-009 | FR-RESP-009 |
| TC-RESP-010 | FR-RESP-010 |
| TC-RESP-011 | FR-RESP-011 |
| TC-RESP-012 | FR-RESP-012 |
| TC-RESP-013 | FR-RESP-013(N/A 可选) |
| TC-HEALTH-001 | FR-HEALTH-001 |
| TC-HEALTH-002 | FR-HEALTH-001 |
| TC-HEALTH-003 | FR-HEALTH-001 |
| TC-HEALTH-004 | FR-HEALTH-002 |
| TC-HEALTH-005 | FR-HEALTH-002 |
| TC-HEALTH-006 | FR-HEALTH-003 |
| TC-HEALTH-007 | FR-HEALTH-004 |
| TC-HEALTH-008 | FR-HEALTH-005 |
| TC-NFR-001 | PRD §14 首屏可交互(非 FR) |
| TC-NFR-002 | PRD §14 长任务反馈(非 FR) |
| TC-NFR-003 | PRD §14 数据安全(非 FR) |
| TC-NFR-004 | PRD §14 离线容忍(非 FR) |
| TC-NFR-005 | PRD §14 浏览器支持(非 FR) |
| TC-E2E-01-M | FR-CHAT-008, FR-CHAT-013 |
| TC-E2E-01-T | FR-CHAT-008, FR-CHAT-013 |
| TC-E2E-01-D | FR-CHAT-008, FR-CHAT-013 |
| TC-E2E-02-M | FR-CHAT-006 |
| TC-E2E-02-T | FR-CHAT-006 |
| TC-E2E-02-D | FR-CHAT-006 |
| TC-E2E-03-M | FR-CHAT-012, FR-HEALTH-002 |
| TC-E2E-03-T | FR-CHAT-012, FR-HEALTH-002 |
| TC-E2E-03-D | FR-CHAT-012, FR-HEALTH-002 |
| TC-E2E-04-M | FR-SESSION-001, FR-SESSION-003, FR-SESSION-004, FR-SESSION-005, FR-SESSION-006, FR-SESSION-007 |
| TC-E2E-04-T | FR-SESSION-001, FR-SESSION-003, FR-SESSION-004, FR-SESSION-005, FR-SESSION-006, FR-SESSION-007 |
| TC-E2E-04-D | FR-SESSION-001, FR-SESSION-003, FR-SESSION-004, FR-SESSION-005, FR-SESSION-006, FR-SESSION-007 |
| TC-E2E-05-M | FR-CHAT-008, FR-CHAT-011 |
| TC-E2E-05-T | FR-CHAT-008, FR-CHAT-011 |
| TC-E2E-05-D | FR-CHAT-008, FR-CHAT-011 |
| TC-E2E-06-M | FR-THEME-002 |
| TC-E2E-06-T | FR-THEME-002 |
| TC-E2E-06-D | FR-THEME-002 |
| TC-E2E-07-M | FR-KG-004, FR-KG-005, FR-KG-006 |
| TC-E2E-07-T | FR-KG-004, FR-KG-005, FR-KG-006 |
| TC-E2E-07-D | FR-KG-004, FR-KG-005, FR-KG-006 |
| TC-E2E-08-M | FR-EXTRACT-001, FR-EXTRACT-002, FR-EXTRACT-003, FR-EXTRACT-004 |
| TC-E2E-08-T | FR-EXTRACT-001, FR-EXTRACT-002, FR-EXTRACT-003, FR-EXTRACT-004 |
| TC-E2E-08-D | FR-EXTRACT-001, FR-EXTRACT-002, FR-EXTRACT-003, FR-EXTRACT-004 |
| TC-E2E-09-M | FR-RESP-004, FR-RESP-005, FR-RESP-006, FR-RESP-007, FR-RESP-008, FR-RESP-009, FR-RESP-010, FR-RESP-011, FR-RESP-012 |
| TC-E2E-09-T | FR-RESP-003 |
| TC-E2E-09-D | FR-RESP-001, FR-RESP-002, FR-SHELL-001 |
| TC-E2E-SMOKE-01 | FR-SHELL-007 |
| TC-E2E-SMOKE-02 | FR-SHELL-001, FR-SHELL-003, FR-RESP-004 |
2.4 模块分布
| 模块 | TC 条数(含 E2E 拆分) |
|---|---|
| SHELL | 15 |
| THEME | 10 |
| SESSION | 18 |
| CHAT | 43 |
| RETRIEVAL | 7 |
| SOURCE | 7 |
| EXTRACT | 11 |
| KG | 13 |
| RESP | 13 |
| HEALTH | 8 |
| NFR(PRD §14) | 5 |
| E2E 主线/冒烟 | 29 |
| 合计 | 179 |
3. 执行说明
3.1 分层运行命令(TEST_PLAN §7)
# L1 后端单元(远端 plantai-bff conda 环境;A 组自测,T 复跑确认)
cd sdk && pytest tests -m unit -q
# L2 后端接口(respx mock 上游;用例见 L2 标注项)
cd sdk && pytest tests -m api -q
# L2s 上游 smoke(仅集成点 M2 与验收前手动各跑一次;远端本机执行)
bash tests/smoke_upstreams.sh
# L3+L4 前端(vitest;远端 ui/)
cd ui && npx vitest run
# L5 E2E(三档视口;dev server 由 playwright webServer 自动拉起或复用已启动的 5180)
cd ui && npx playwright test # playwright 未安装时:npx playwright@1.49.1 test
3.2 Mock 场景使用(ARCHITECTURE.md §6)
mock server:python sdk/mock/mock_server.py(127.0.0.1:8099),场景经查询参数或请求头 X-Mock-Scenario 切换。E2E 中由测试代码在请求头注入或 mock 路由拦截实现:
| scenario | 用途 | 使用用例示例 |
|---|---|---|
normal |
主流程(SSE 回放 12 帧真实时序) | TC-CHAT-023~033、TC-E2E-01/02 |
empty |
空结果(source_count=0、answer 为「未找到…」fallback) | TC-SOURCE-007、TC-KG-013、TC-CHAT-027(空态分支) |
slow |
慢响应/长流(持续视觉反馈断言) | TC-CHAT-021、TC-CHAT-029、TC-NFR-002 |
error |
上游故障(502 UPSTREAM_ERROR) | TC-CHAT-039、TC-E2E-03 |
degraded |
QA 不可用(503 UPSTREAM_DOWN) | TC-HEALTH-004/005、TC-EXTRACT-011、TC-KG-012、TC-E2E-03 |
注意:mock 的场景切换仅覆盖 QA 端点;extraction/kg 的 503 降级在 E2E 中用 Playwright route 拦截模拟。
3.3 真实上游注意事项(DISCOVERY.md)
- QA LLM 402 降级(最重要):上游 DeepSeek 余额不足,
degraded_execution=true,answer 为 fallback 证据列表而非自然语言答案。涉及自然语言答案内容的断言(TC-CHAT-027 等,标注「依赖充值」)在充值前执行时只验结构不断内容:answer 字段存在、text 块渲染、fallback 文案可见即可。检索/证据/来源/图谱断言(sources、chains、graph 块)不受 402 影响,照常执行。 - 耗时:单次 search ≈92s、stream ≈37–38s(DISCOVERY.md §4/§6.8)。真实上游的 L5/L6 用例需把 expect 超时调到 ≥180s(search 模式)/≥120s(chat 模式);BFF 读超时 300s。
- 上游只绑 127.0.0.1:L2s 必须在远端本机跑;Mac 本地经 SSH 隧道跑(见脚本注释)。
- Neo4j 当选实例 direct-v1-full(bolt 18788 / HTTP 18575),schema canonical;
/api/kg/*依赖 BFF 的 Bolt 直连(上游 kg 路由已死,不探测)。 - 抽取上游:8000 正常;7900 为容器(jobs 空属正常,BFF 只在 8000 不可达时回落);新上传任务可能因 MinerU 路径缺失 failed——TC-EXTRACT-008 就是断言该错误文案路径。
3.4 L5 运行环境一次性准备(已验证)
- Node 18(远端):
npx playwright@1.49.1 test首次运行自动下载 chromium 1148(≈150MB)。实测 CDN 可达(Mac 直连 2.9MB/s)。 @playwright/test模块解析:spec 文件位于tests/e2e/,在ui/node_modules之外,Node 默认解析路径找不到@playwright/test,会报ERR_MODULE_NOT_FOUND。已在 Mac 本地验证(playwright 1.61.1 实测复现;1.49.1 CJS 变换同样回退到默认 ESM 解析,机制相同)。运行处须一次性执行(不污染任何 agent 归属文件——node_modules不入库不同步):Mac 本地本次验证的等价做法(未改动# 远端,一次性 cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new ln -sfn ui/node_modules node_modules # 若目录已存在先确认其内容再覆盖 cd ui && npm install --save-dev @playwright/test@1.49.1 # 写入 package.json 需 PM 裁决,见 §5 未决问题 4ui/package.json):安装到/tmp/pw-test-runner后ln -sfn /tmp/pw-test-runner/node_modules/@playwright/test ui/node_modules/@playwright/test,再ln -sfn ui/node_modules <repo>/node_modules,以 playwright 1.61.1(匹配本机已缓存 chromium-1228)跑通 6/6。- 本地 Node 26 已知问题:playwright 1.49.1 的
module.register()loader 在 Node 26 + 系统高负载下启动极慢/挂起;远端 Node 18 无此问题。本地验证用 playwright 1.61.1 替代。 - 本机验证结果(M0 冒烟):
6 passed(mobile/tablet/desktop × 2 条冒烟)。冒烟断言对 B1 已交付的 AppShell 走真实分支(.app-shell/.top-bar/.status-bar,移动端.status-bar按 FR-RESP-004 断言隐藏),M0 占位期走占位回退分支。
4. E2E 骨架交付说明(本波)
| 文件 | 内容 |
|---|---|
ui/playwright.config.js |
三项目 mobile(iPhone 14 390×844,触摸+UA)/tablet(iPad 768×1024)/desktop(1440×900);chromium;baseURL http://127.0.0.1:5180;webServer npm run dev -- --strictPort(已有 dev 复用);失败保留 trace/screenshot |
tests/e2e/smoke.spec.js |
2 条冒烟(TC-E2E-SMOKE-01/02),三档视口各跑一次共 6 条结果。AppShell 断言带 M0 占位回退(B1 交付后自动走真实分支,无需改测试) |
tests/smoke_upstreams.sh |
L2s 冒烟:QA 7920 health/info、抽取 8000 health/jobs、7900 health、Neo4j(HTTP 18575 Cypher 只读查询)、mock 8099 health;PASS/FAIL 表 + 非零退出码;支持 QA_URL/EXTRACT_URL/ALT_URL/MOCK_URL/NEO4J_HTTP/TIMEOUT 环境变量覆盖 |
全量 L1–L5 执行从 M2 集成后开始(TEST_PLAN §5 轮次纪律);完整 E2E 用例(TC-E2E-01~09)按本文件在后续轮次落为 spec 文件。
5. 未决问题(提请 PM)
- 若干 UI 类断言依赖 UI_SPEC 类名(如
agent-dot-item、p-step);若 B1 实现时类名有偏差,需按 UI_SPEC §5.3 对齐(T 不自行改用例契约)。 - TC-EXTRACT-005/007(上传进度/超限)依赖 B2/B3 上传 UI 与 50MB 校验落点(前端或 BFF)——已按「前端拦截或 422」双路径写预期,无需改用例。
- TC-KG 节点详情在 Neo4j 不可用时的属性来源为「消息内 graph 块已有属性」;graph 块节点若不含属性字段,该断言在实现时需以实际块结构微调步骤(预期语义不变)。
- L5 依赖安装需 PM 裁决:
tests/e2e/的 spec 需要解析@playwright/test,而它位于ui/node_modules之外。两个方案二选一:(a) 远端一次性npm install --save-dev @playwright/test@1.49.1(写入 B1 归属的ui/package.json,需 PM 批准);(b) 保持npx playwright@1.49.1并在远端建node_modules根符号链接 +@playwright/test符号链接(不改 package.json,M2 前由 T/PM 在远端一次性执行)。本波按 (b) 在 Mac 本地验证通过(6/6 pass),建议远端集成点采用 (b)。 - task 验证命令差异:任务书给的验证命令
npx playwright test(无版本)在本机无已安装 playwright 时会从 npm 拉最新版;远端 Node 18 +npx playwright@1.49.1仍会拉 1.49.1 的 chromium 1148(≈150MB,CDN 实测可达)。已在本文件 §3.4 记录完整步骤。