diff --git a/AGENTS.md b/AGENTS.md index ce1cea71..21c2a047 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,6 @@ - `doc/test-plan/ai-test.md`:涉及 AI 回归测试范围、live case 设计或 suite 覆盖维护时阅读。 - `doc/test-plan/settings-test.md`:涉及 Settings 测试范围或测试边界时阅读。 - `doc/test-plan/workbench-test.md`:涉及 Workbench 回归测试范围、真实 e2e case 设计、发送前置条件验证或 suite 覆盖维护时阅读。 -- `doc/test-plan/showcase-test.md`:涉及官网样例采集任务、采集 case 或 Showcase 命令时阅读。 - `doc/test.md`:涉及测试体系、UI E2E 回归规则、runner 或回归测试流程时阅读。 - `doc/tidy.md`:需要按周期整理文档、PRD、测试计划、测试用例或清理代码整洁度(死代码、兼容残留、无用代码、简洁性)时,先按其中流程审计并等待决策。 @@ -59,7 +58,6 @@ │ ├── test-plan/ # 专项测试计划 │ │ ├── ai-test.md # AI 回归测试计划 │ │ ├── settings-test.md # Settings 测试计划 -│ │ ├── showcase-test.md # 官网样例采集计划 │ │ └── workbench-test.md # Workbench 测试计划 │ └── tidy.md # 周期性文档、测试与代码整理流程 ├── src/ # 应用源码 @@ -75,10 +73,10 @@ ├── tests/ # 自动化测试 │ ├── case/ # 测试用例 │ │ ├── ai/ # AI 测试用例 +│ │ ├── showcase/ # 独立官网样例采集 │ │ ├── settings/ # Settings 测试用例 │ │ ├── unit/ # backend 单元测试 │ │ └── workbench/ # Workbench 测试用例 -│ ├── showcase/ # 官网样例采集任务 │ ├── fixtures/ # 长期复用测试输入 │ │ ├── workspace/ # 工作区语义测试共享输入目录 │ │ └── sqlite/ # SQLite 升级与版本门禁 fixture diff --git a/doc/test-plan/showcase-test.md b/doc/test-plan/showcase-test.md deleted file mode 100644 index 32a65563..00000000 --- a/doc/test-plan/showcase-test.md +++ /dev/null @@ -1,102 +0,0 @@ -# Showcase 样例采集计划 - -## 1. 定位 - -Showcase 是官网样例采集任务,不是产品回归、E2E 回归或发布门禁。它只通过 `pnpm test:showcase` 显式执行,不被 `pnpm test:e2e:*`、Workbench 回归命令或发布命令发现。 - -采集使用真实 Electron UI、真实 AI 和真实文件处理链路,目的是产出可审核的截图、样例文件和用量记录;采集失败不改变产品回归范围。 - -## 2. 目录与命令 - -采集文件位于: - -```text -tests/showcase/ - .showcase.test.ts - createSeeds.ts - shared.ts -``` - -运行全部采集任务: - -```bash -pnpm test:showcase -``` - -只运行指定任务: - -```bash -pnpm test:showcase -- --case -``` - -运行日志写入 `tests/logs/showcase/collection//`,官网导出文件写入 `tests/logs/showcase/export//`。 - -## 3. 共同约束 - -- 每个任务必须创建隔离临时工作区,结束后清理。 -- 必须通过真实 Settings / Workbench UI 配置模型、创建伙伴和发送请求;不使用 fake AI、fake 工具结果或直接写入产品状态。 -- 新建样例可以把结果复制为 fixture;编辑样例必须复用已有过关 fixture,不能临时新建文件凑截图。 -- 每个任务记录截图、样例文件、模型、耗时和实际用量。 -- Showcase 不参与日常 E2E、正式包 E2E 或发布回归的默认发现;需要正式回归的产品能力必须另有 `tests/case/**` 回归 case。 - -## 4. 采集任务 - -### `docx-weekly-report` - -- What:根据站会纪要、客户风险和财务草稿生成管理层 Word 周报。 -- How:真实 AI 写入 `云枢智服-9月第2周经营周报.docx`,截审核态和预览态。 -- Oracle:Word 可解析并包含进展、风险、下周计划;至少有一张审核图和一张预览图,并记录用量。 - -### `docx-weekly-report-edit` - -- What:修改已有周报,采集审核态绿增红删。 -- How:必须使用过关 Word fixture,发送风险排序和负责人补充请求,截 `.docx-content`。 -- Oracle:产生真实 Word 修改记录和非零修改;至少有一张审核图,并记录用量。 - -### `excel-budget` - -- What:从空工作区生成部门明细和汇总表组成的 9 月费用预算。 -- How:真实 AI 写入 `云枢智服-9月费用预算.xlsx`,按工作表截审核态和预览态。 -- Oracle:xlsx 可解析且至少两张表;至少有一张审核图和一张预览图,并记录用量。 - -### `excel-budget-edit` - -- What:修改已有费用预算,采集审核态红绿框。 -- How:必须使用过关 Excel fixture,压低品牌推广金额并补充已发生列,截数据区域。 -- Oracle:文件包含已发生列和目标金额,存在非零修改,并记录用量。 - -### `ppt-management-briefing-review` - -- What:从空工作区生成管理层经营简报,采集新增页面审核态。 -- How:真实 AI 创建 16:9 PPT,按缩略图截 `pptDiffAfter`。 -- Oracle:PPTX 写回且至少有一张审核图,并记录用量。 - -### `ppt-management-briefing-edit` - -- What:修改已有经营复盘,采集修改前后对照。 -- How:必须使用过关 PPT fixture,修改封面和未达标 KPI,截审核态整块 diff。 -- Oracle:出现修改前后画布和至少一张审核图,并记录用量。 - -### `xinghe-service-contract` - -- What:引用星河服务合同 PDF,回答尾款支付时间,采集原文页和回答。 -- How:只读引用 PDF,不改原件;渲染合同页并截最终回答。 -- Oracle:回答包含终验/验收后十个工作日;原件 bytes 不变;至少有两张原文图和一张回答图。 - -### `xinghe-kb-reply` - -- What:根据本地星河资料回复交付承诺和验收口径。 -- How:准备星河资料和清和干扰资料,完成真实索引后不使用 `@` 文件名直接提问;截资料页和最终回答。 -- Oracle:使用本地知识库工具,不调用浏览器或网页搜索;回答包含 95% 承诺及验收 / P1 / 7 天依据。 - -### `court-typical-cases` - -- What:用内置浏览器检索最高人民法院建设工程典型案例。 -- How:真实 AI 驱动内置浏览器,采集空白检索、已填词、结果三帧及最终回答。 -- Oracle:调用 `browser_use`;三类浏览器帧完整;回答涉及工程款、结算、违约或典型案例。 - -### `chailv-tax-deduction` - -- What:搜索企业员工国内差旅住宿和交通税前扣除口径。 -- How:不指定网站,真实 AI 使用网页搜索,截带来源的最终回答。 -- Oracle:调用 `web_search`;回答包含住宿或交通、税前扣除和来源依据。 diff --git a/doc/test-plan/workbench-test.md b/doc/test-plan/workbench-test.md index d26ad512..01022a38 100644 --- a/doc/test-plan/workbench-test.md +++ b/doc/test-plan/workbench-test.md @@ -2,29 +2,27 @@ ## 1. 总体目标 -- 本计划定义 Workbench 用户可见交互的回归范围,并对齐 [workbench.md](../prd/workbench.md) 的 `WB-*` 需求和验收编号。 +- 本计划定义 Workbench 用户可见交互的测试范围,并对齐 [workbench.md](../prd/workbench.md) 的 `WB-*` 需求和验收编号。 - 通用测试、runner、raw logs、UI E2E 和 live 断言规则遵循 `doc/test.md`。 -- Workbench UI E2E 回归测试必须通过真实 Electron UI 配模型、创建伙伴、发送消息、选择 `@` 资料、预览、回退和操作右侧面板。设置页可另加文件地址,但发送不再要求绑定工作空间目录。 -- 涉及 AI 的 Workbench UI E2E 回归测试必须使用真实模型调用;live 配置只读内置服务商 Key 池与选中目标:`AI_LIVE_KEY_`(`DEEPSEEK` / `DASHSCOPE` / `MIMO` / `ZHIPU`)、可选 `AI_LIVE_PROVIDER`(默认 `dashscope`)、可选 `AI_LIVE_MODEL`(默认该服务商测试默认模型,通义为 `qwen3.8-flash`)。`baseUrl` / `providerKind` 由产品 registry 推导。日常 case 通过 Settings「推荐」类型写入当前目标服务商;全服务商冒烟见 `live-preset-provider-smoke`。 -- `provider-matrix` 与 `wps-office` 都是正式 E2E 回归范围内的 Workbench case;发布时按版本影响范围选择,影响面广时随正式 E2E 全量执行。Showcase 已独立为单独采集任务。 -- 发版回归按版本改动影响范围选择正式包 E2E;影响范围较广或无法可靠收敛时执行全部正式 E2E,包含 `provider-matrix` 和 `wps-office`。Showcase 不属于正式 E2E。 +- Workbench UI E2E 测试必须通过真实 Electron UI 配模型、创建伙伴、发送消息、选择 `@` 资料、预览、回退和操作右侧面板。设置页可另加文件地址,但发送不再要求绑定工作空间目录。 +- 涉及 AI 的 Workbench UI E2E 测试必须使用真实模型调用;live 配置只读内置服务商 Key 池与选中目标:`AI_LIVE_KEY_`(`DEEPSEEK` / `DASHSCOPE` / `MIMO` / `ZHIPU`)、可选 `AI_LIVE_PROVIDER`(默认 `dashscope`)、可选 `AI_LIVE_MODEL`(默认该服务商测试默认模型,通义为 `qwen3.8-flash`)。`baseUrl` / `providerKind` 由产品 registry 推导。日常 case 通过 Settings「推荐」类型写入当前目标服务商;全服务商冒烟见 `live-preset-provider-smoke`。 - case 因生产行为缺口失败时,不在测试中硬改业务行为,统一记录为产品或生产实现待决项。 ## 2. 测试边界 -- Workbench UI E2E 回归测试通过脚本在 case 运行时把 `tests/fixtures/workspace` 复制到临时目录;需要文件资料时通过 Settings UI 添加文件地址、检查更新和更新索引。前置始终是创建伙伴;不断言工作区选择器、`.ncurator` 或 `__draft__`。 +- Workbench UI E2E 测试通过脚本在 case 运行时把 `tests/fixtures/workspace` 复制到临时目录;需要文件资料时通过 Settings UI 添加文件地址、检查更新和更新索引。前置始终是创建伙伴;不断言工作区选择器、`.ncurator` 或 `__draft__`。 - Workbench 正式 e2e 不允许直接调用 `workspacePrepare`、直接写模型配置、fake model、fake search、fake docx、fake AI event、fake attachment pick 或固定 document id。 - Workbench e2e 的 workspace 文件索引允许在测试环境通过 `NCURATOR_WORKSPACE_KNOWLEDGE_CACHE=1` 启用真实 split / vector 缓存;缓存只按文件内容 hash、扩展名、splitter 参数和 embedding model 复用计算结果,不替代 workspace 文件记录、document id、搜索结果或 UI 状态。Source E2E 直接共享内容缓存并按 case 隔离统计;Source 与正式包 E2E 在多 case 时都先执行 `warmup` 预热共享 fixture,正式包 E2E 再把缓存种子复制给隔离 case。 - Workbench 模型选择 helper 优先按预设 option 的稳定调用名 `data-testid`(`AI_LIVE_MODEL`)点选;未命中再按展示名;仍未命中则进入「自定义模型」按名称搜索。 -- Workbench UI E2E 回归测试需要真实索引完成信号和 UI 可见状态作为等待条件;IPC observer 和 job 查询只能作为辅助证据,不能用来造业务状态。 -- `file-preview-actions` 当前按生产真实索引能力覆盖 `pdf/docx/md/txt/xlsx/xlsm/xltx/xltm`:连续打开的文件要分别保留为资源 Tab,并能切回旧 Tab;Excel 普通预览还要覆盖只读、切 Sheet 和拖列宽后预览仍可用。`workspace-file-explorer-open-refresh` 覆盖工作区文件列表在打开时重新读取磁盘最新目录(含外部新建文件)。`file-preview-external-change-auto-refresh` 另行覆盖所有实时预览的统一磁盘监听:以普通文本、DOCX、PPTX、XLSX 为代表验证,文件地址内的文件和 `userData/partners/{id}/runtime/assets` 中的真实附件复用同一预览刷新链路;审核 Diff 快照不监听;仅文件内容变化才提示“已自动刷新”,打开外部应用或仅元数据变化不提示。WPS 刷新分两条:正式回归由 `wps-sync-dispatch-on-write-and-revert` 覆盖馆长对 docx / xlsx / pptx 的 `/status` dispatch,不打开 WPS;真窗口刷新 `wps-office-reload-on-write-and-revert` 在路径 `result/wps-office/`,属于正式 E2E,支持 `pnpm test:workbench:wps-office` 单独执行,因本机授权弹窗可能卡住自动化。正式 E2E 启动时跳过覆盖拷加载项文件,但会启动回环 `/addon`;`publish.xml` 与授权 path 固定 `http://127.0.0.1:18723/addon/`,端口文件记录实际监听端口。若 `18723` 上已是本馆长 `/status` 通道,后启动实例不得把 `publish.xml` / 授权 path 改到临时端口。安装器与 `publish.xml` / `authaddin` 合并由 unit 覆盖。 +- Workbench UI E2E 测试需要真实索引完成信号和 UI 可见状态作为等待条件;IPC observer 和 job 查询只能作为辅助证据,不能用来造业务状态。 +- `file-preview-actions` 当前按生产真实索引能力覆盖 `pdf/docx/md/txt/xlsx/xlsm/xltx/xltm`:连续打开的文件要分别保留为资源 Tab,并能切回旧 Tab;Excel 普通预览还要覆盖只读、切 Sheet 和拖列宽后预览仍可用。`workspace-file-explorer-open-refresh` 覆盖工作区文件列表在打开时重新读取磁盘最新目录(含外部新建文件)。`file-preview-external-change-auto-refresh` 另行覆盖所有实时预览的统一磁盘监听:以普通文本、DOCX、PPTX、XLSX 为代表验证,文件地址内的文件和 `userData/partners/{id}/runtime/assets` 中的真实附件复用同一预览刷新链路;审核 Diff 快照不监听;仅文件内容变化才提示“已自动刷新”,打开外部应用或仅元数据变化不提示。WPS 刷新分两条:正式测试由 `wps-sync-dispatch-on-write-and-revert` 覆盖馆长对 docx / xlsx / pptx 的 `/status` dispatch,不打开 WPS;真窗口刷新 `wps-office-reload-on-write-and-revert` 在路径 `result/wps-office/`,属于正式 E2E,支持 `pnpm test:workbench:wps-office` 单独执行,因本机授权弹窗可能卡住自动化。正式 E2E 启动时跳过覆盖拷加载项文件,但会启动回环 `/addon`;`publish.xml` 与授权 path 固定 `http://127.0.0.1:18723/addon/`,端口文件记录实际监听端口。若 `18723` 上已是本馆长 `/status` 通道,后启动实例不得把 `publish.xml` / 授权 path 改到临时端口。安装器与 `publish.xml` / `authaddin` 合并由 unit 覆盖。 - docx apply case 需要真实 docx 输入、真实 AI 编辑、真实写回记录、真实预览和真实回退。 - text-file apply case 需要真实 Markdown 或代码文件、真实 `text_file_edit`、真实结构化 diff、独立 `TextFileDiffViewer` 和按原始 bytes 回退。 - image generation apply case 需要真实模型先学习外置 `image-generation` 技能、真实调用 `image_generate`、生成可解码图片、展示修改入口和图片预览,并完成新建图片回退。 - `browser-use-task-panel` 需要真实 AI run 触发 `browser_use` 并驱动内置浏览器完成本地内容输入与 hover-退出网页任务(observe→actionId act,支持 text 注入);必须在无进程级远程调试口下完成。同时验证多个浏览器 Tab 独立历史、共享持久站点数据、切换文件后网页状态不销毁,以及同 URL 标签不会让 Stagehand 串页,不能用 fake browser 替代。 - `web_search` 单独覆盖:搜索 case 验证 type=search 后 type=fetch 读取网页正文;直接 URL case 验证 type=fetch 读取网页正文后再回答。两类 case 均不得因 `web_search` 自动展开右侧搜索列表(该列表面板已不存在),对齐 `WB-CORE-007-AC4`。Composer 搜索模式与右侧搜索列表面板已删除,废弃 `WB-COMPOSER-004`、`WB-PANEL-002`。 - Workbench 附件链路不限文件扩展名,任何可被文件选择器选中的普通文件都可以添加,不因扩展名拒绝;`+` 和拖放保留源文件绝对路径,粘贴文件保存到 `userData/partners/{id}/runtime/assets//`。`assetBucketId` 为输入草稿 UUID,发送给 AI 时只传附件名称与路径,不再做发送前识图或文件解析;文件是否可被 AI 工具处理由 AI 工具层自行判断。无伙伴时空态 Composer 不接受附件。 -- Excel / PPT / DOCX / PDF 工具的输入输出、operation、原子失败与 diff artifact 语义由 AI tool 回归承担;Workbench apply case 只验证真实用户流程、预览入口、审核交互与回退,不重复枚举底层操作矩阵。其中 Excel 工具侧覆盖 `WB-EXCEL-001-AC3`、`WB-EXCEL-001-AC4`、`WB-EXCEL-001-AC6`、`WB-EXCEL-001-AC7`、`WB-EXCEL-002-AC2`、`WB-EXCEL-002-AC3`、`WB-EXCEL-002-AC5`、`WB-EXCEL-002-AC6`、`WB-EXCEL-002-AC7`、`WB-EXCEL-002-AC8`(`WB-EXCEL-002-AC3` 记录/baseline **只在 ai-test 硬断言**);Workbench e2e 覆盖 `WB-EXCEL-001-AC5`、`WB-EXCEL-002-AC1`、`WB-EXCEL-002-AC4`、`WB-EXCEL-002-AC9` 用户闭环(UI 可见单入口,不重复硬断言 AC3)。 +- Excel / PPT / DOCX / PDF 工具的输入输出、operation、原子失败与 diff artifact 语义由 AI tool 测试承担;Workbench apply case 只验证真实用户流程、预览入口、审核交互与回退,不重复枚举底层操作矩阵。其中 Excel 工具侧覆盖 `WB-EXCEL-001-AC3`、`WB-EXCEL-001-AC4`、`WB-EXCEL-001-AC6`、`WB-EXCEL-001-AC7`、`WB-EXCEL-002-AC2`、`WB-EXCEL-002-AC3`、`WB-EXCEL-002-AC5`、`WB-EXCEL-002-AC6`、`WB-EXCEL-002-AC7`、`WB-EXCEL-002-AC8`(`WB-EXCEL-002-AC3` 记录/baseline **只在 ai-test 硬断言**);Workbench e2e 覆盖 `WB-EXCEL-001-AC5`、`WB-EXCEL-002-AC1`、`WB-EXCEL-002-AC4`、`WB-EXCEL-002-AC9` 用户闭环(UI 可见单入口,不重复硬断言 AC3)。 - DOCX 读取结果和历史结果按递归 children 树展示,Workbench 必须能显示统一的 `nodeId`、visual kind、`imagePath` 和 story;DOCX 写结果复用统一状态展示,普通写入不展示冗余正文,创建操作只展示 `createdIds`。格式操作按 text、paragraph、table、row、cell、section 展示。图片理解统一由 `agent-control` 内置的 `read_image` 把图片加入主模型请求。 - Workbench 历史只展示白名单内的必要 DOCX 字段,不展示 worker、identity 或 replay 内部状态。DOCX 事件以 `nodeId` 定位,PPT 继续使用自己的 `location`,两套协议互不复用。 @@ -46,16 +44,16 @@ Workbench 顶层 suite 按用户任务闭环划分,不逐一复制 PRD 小节 - Workbench 测试全部是 `e2e`,覆盖 Electron、renderer、IPC、真实 AI、右侧面板和文件系统联动。 - 涉及 AI 的 case 必须记录模型、耗时、完成度评分和失败归因;Oracle 引用 [workbench.md](../prd/workbench.md) 的需求或验收编号。 - AI 内置浏览器 case 必须验证右侧浏览器面板、网页操作结果和最终 assistant 输出形成闭环,不能只验证面板出现。 -- docx apply 相关 case 必须使用真实 docx 文件验证预览与回退;仅写入 `.docx` 后缀文本文件不满足回归 case 口径。 +- docx apply 相关 case 必须使用真实 docx 文件验证预览与回退;仅写入 `.docx` 后缀文本文件不满足测试 case 口径。 - 依赖本地文件语义的测试统一复用 `tests/fixtures/workspace` 作为共享输入目录,但该目录只作为真实输入文件来源;运行时通过设置页添加为文件地址,并先创建伙伴。 - `How` 和 `Oracle` 必须映射到 [workbench.md](../prd/workbench.md) 的需求或验收编号。 -- `WB-BLOCKER-003`(上下文压缩提示)依赖真实模型使用量与正式压缩阈值触发,运行成本不确定,不作为每次源码回归的固定 case,发布时按影响评估单独执行真实检查,检查运行中与完成态过程组,且不得为 E2E 降低生产阈值或伪造 compaction 事件。压缩阈值随当前模型 effectiveWindow(`min(256000, floor(modelContext × 0.8))`)。 -- `WB-WPS-001` 正式 E2E 由 `wps-sync-dispatch-on-write-and-revert` 覆盖馆长对 docx / xlsx / pptx 写盘后的 open dispatch;真窗口刷新由 `wps-office-reload-on-write-and-revert`(`pnpm test:workbench:wps-office`)覆盖已授权 WPS 文字 / 表格 / 演示,发布时按影响范围选择,正式 E2E 全量回归包含该组。已打开文档在写盘前由加载项 close 放锁,加载项复查文档确实已关闭才上报成功,关不掉则上报失败且馆长不写盘;写盘前等到文件可写,写成功后再刷新窗口。通道按 close-result 的 `remaining` 与占位文档是否建起来选择,不得用 SSE 是否断开判断;`remaining` 为 0 且占位已建起时仍走加载项 open,占位建不起来才用对应组件 exe 拉起并轮询 list 要摘录,不得 `cmd start`;被指派实例 open 失败或超时降级为一次系统打开。未安装 WPS 或对应组件加载项未授权时后者跳过该格式;已授权却连不上则算该格式失败,不得只测 docx 就当作三种格式都过。Workbench e2e 启动时跳过覆盖拷加载项文件,仍启动回环 `/addon`;`publish.xml` 与授权 path 固定优先端口 `18723`,不得改写到临时端口。xlsx / pptx 窗口刷新还要求 `/status` 的 `clients` 出现 `et` / `wpp`,不能只靠文字组件。加载项不得探测其他组件 `Active*` 对象;同组件多实例时 close / open 只投给持有该文档的 `clientId`,旧实例的文档列表和失败回报都不得影响当前实例。 +- `WB-BLOCKER-003`(上下文压缩提示)依赖真实模型使用量与正式压缩阈值触发,运行成本不确定,不作为每次源码测试的固定 case,发布时按影响评估单独执行真实检查,检查运行中与完成态过程组,且不得为 E2E 降低生产阈值或伪造 compaction 事件。压缩阈值随当前模型 effectiveWindow(`min(256000, floor(modelContext × 0.8))`)。 +- `WB-WPS-001` 正式 E2E 由 `wps-sync-dispatch-on-write-and-revert` 覆盖馆长对 docx / xlsx / pptx 写盘后的 open dispatch;真窗口刷新由 `wps-office-reload-on-write-and-revert`(`pnpm test:workbench:wps-office`)覆盖已授权 WPS 文字 / 表格 / 演示,发布时按影响范围选择,正式 E2E 全量测试包含该组。已打开文档在写盘前由加载项 close 放锁,加载项复查文档确实已关闭才上报成功,关不掉则上报失败且馆长不写盘;写盘前等到文件可写,写成功后再刷新窗口。通道按 close-result 的 `remaining` 与占位文档是否建起来选择,不得用 SSE 是否断开判断;`remaining` 为 0 且占位已建起时仍走加载项 open,占位建不起来才用对应组件 exe 拉起并轮询 list 要摘录,不得 `cmd start`;被指派实例 open 失败或超时降级为一次系统打开。未安装 WPS 或对应组件加载项未授权时后者跳过该格式;已授权却连不上则算该格式失败,不得只测 docx 就当作三种格式都过。Workbench e2e 启动时跳过覆盖拷加载项文件,仍启动回环 `/addon`;`publish.xml` 与授权 path 固定优先端口 `18723`,不得改写到临时端口。xlsx / pptx 窗口刷新还要求 `/status` 的 `clients` 出现 `et` / `wpp`,不能只靠文字组件。加载项不得探测其他组件 `Active*` 对象;同组件多实例时 close / open 只投给持有该文档的 `clientId`,旧实例的文档列表和失败回报都不得影响当前实例。 - WPS 相关 e2e 必须自带环境归零:进用例前和用例结束后各做一次「先不带 `/F` 请求关闭 wps / et / wpp,超时再强杀」,并清掉 `ncurator-workbench-wps-*` 临时工作区;三种格式之间也要归零。不归零时上一轮残留实例会连上本轮回环冒充本轮客户端,让 registry、dispatch 与断言随机失败,且临时工作区因文件锁删不掉。 ## 4. 目录与命令 -Workbench 回归测试目录: +Workbench 测试目录: ```text tests/case/workbench/ @@ -65,7 +63,7 @@ tests/case/workbench/ timeline/ message/ scope/ - provider-matrix/ # 正式 E2E:发版按影响范围或全量回归 + provider-matrix/ # 正式 E2E:发版按影响范围或全量测试 run/ process/ blocker/ @@ -74,16 +72,16 @@ tests/case/workbench/ result/ preview/ apply/ - wps-office/ # 正式 E2E:真开 WPS;发版按影响范围或全量回归 + wps-office/ # 正式 E2E:真开 WPS;发版按影响范围或全量测试 scheduled-task/ ``` -回归测试命令: +测试命令: - `pnpm test:workbench` - `pnpm test:workbench:e2e` -- `pnpm test:workbench:provider-matrix`(全服务商冒烟,可单独执行;发版按影响范围或全量回归) -- `pnpm test:workbench:wps-office`(本机已授权 WPS,可单独执行;发版按影响范围或全量回归) +- `pnpm test:workbench:provider-matrix`(全服务商冒烟,可单独执行;发版按影响范围或全量测试) +- `pnpm test:workbench:wps-office`(本机已授权 WPS,可单独执行;发版按影响范围或全量测试) 自动更新模拟(聚合入口,不证明真实安装成功): @@ -101,12 +99,12 @@ pnpm test:update:e2e 补充规则: -- `pnpm test:workbench:e2e` 是 Workbench UI 源码 E2E 回归测试入口。 +- `pnpm test:workbench:e2e` 是 Workbench UI 源码 E2E 测试入口。 - 需要验证正式安装包时,调试使用 `pnpm test:e2e:package -- --bundle `;它会校验并安装已构建候选包,再对安装后的真实应用执行所选正式 E2E,不重新构建。发布时通过 `release:win` / `release:mac` 的 `--test-only` 按影响评估选择范围,不带过滤参数时执行全部正式 E2E。 - `conversation / run / result / scheduled-task` 是稳定 suite,不因 PRD 小节增删而轻易重排。 - suite 下的 `partner / composer / timeline / message / scope / process / blocker / browser / persistence / preview / apply` 是 group,只在同一 suite 内用于收敛相近 case。不存在 `search/` 目录。 -## 5. 回归测试清单 +## 5. 测试清单 ### 5.1 `conversation` @@ -243,7 +241,7 @@ pnpm test:update:e2e - Why:日常全量 live 只跑 `AI_LIVE_PROVIDER`/`AI_LIVE_MODEL` 选定目标;需要单独 smoke 覆盖「有 Key 的全部内置服务商」连通性。 - How:读取 `listConfiguredLiveLlmTargets()`;为每个目标走 Settings UI 创建推荐服务商;创建伙伴后依次点预设 option(不得走自定义搜索兜底)并发送「你好」,等待 run 完成。 - Oracle:每个已配置 Key 的服务商都有非空最终回答;未配置 Key 的服务商不参与、不失败。 -- Not:不评价回答质量;正式 E2E 全量回归包含本 case,也可用 `pnpm test:workbench:provider-matrix` 或 `--case live-preset-provider-smoke` 单独执行。 +- Not:不评价回答质量;正式 E2E 全量测试包含本 case,也可用 `pnpm test:workbench:provider-matrix` 或 `--case live-preset-provider-smoke` 单独执行。 #### `user-message-actions` @@ -258,10 +256,10 @@ pnpm test:update:e2e - 测试类型:`e2e / live` - What:`+`、粘贴与拖入中间聊天区添加附件时,图片只以路径和元数据进入伙伴消息记录,常规文档和非白名单扩展名也能加入,并保持图片预览、文件打开、删除、普通文本粘贴和大文件提示行为。粘贴保存到伙伴 runtime assets。 -- Why:`+` 与拖放必须把源文件路径交给 AI,粘贴仍需保存副本,回归必须守住这两条不同链路。 +- Why:`+` 与拖放必须把源文件路径交给 AI,粘贴仍需保存副本,测试必须守住这两条不同链路。 - How:创建伙伴;需要文件资料时通过设置页添加文件地址;通过系统文件窗口选择图片和多种文件,覆盖拖入、删除后重新添加图片、大文件 warning,并通过真实模型发送一次带附件消息;发送后读取 `ai_run` 与 log 投影消息。 - Oracle:文件拖入中间聊天区时显示覆盖层,放下后消失;无伙伴时不添加附件;`+` 选择的文件保留原始源路径;粘贴文件落到 `userData/partners/{id}/runtime/assets/`;图片预览和删除正常;发送后图片附件仍是路径,持久消息不含 Base64,对齐 `WB-COMPOSER-010`、`WB-COMPOSER-013-AC2`、`WB-COMPOSER-013-AC3`;运行时缓存、HTTP(S) 原地址和失效占位由 AI tool 契约覆盖,对齐 `WB-COMPOSER-013-AC4`。 -- Not:不在跨平台业务回归中验证系统原生文件选择器的视觉细节;不通过 `attachmentPickResponse` 或直接 `attachmentSaveFile` 伪造附件结果。 +- Not:不在跨平台业务测试中验证系统原生文件选择器的视觉细节;不通过 `attachmentPickResponse` 或直接 `attachmentSaveFile` 伪造附件结果。 #### `reference-mention-keyboard` @@ -285,7 +283,7 @@ pnpm test:update:e2e - 测试类型:`e2e` - What:空伙伴输入区布局、右侧默认无占位折叠、标签栏密度与折叠入口、Tab 溢出滚动、伙伴级标签和展开状态恢复、中间|右分隔条拖拽与阈值吸合、窗口缩放时主区比例同步、浏览器缩放菜单、地址栏“前往”与“将打开外部浏览器”按钮。 -- Why:右侧资源工作台和聊天输入区共享窗口高度与标题栏命中区域,普通功能测试无法发现输入框被推出窗口、折叠按钮放错区域、Tab 无法滚动、分隔条拖拽/吸合失效或原生浏览器遮挡浮层等视觉交互回归。 +- Why:右侧资源工作台和聊天输入区共享窗口高度与标题栏命中区域,普通功能测试无法发现输入框被推出窗口、折叠按钮放错区域、Tab 无法滚动、分隔条拖拽/吸合失效或原生浏览器遮挡浮层等视觉交互测试。 - How:直接进入真实 Workbench,先确认无伙伴空态右侧 shell 不存在并测量输入区位置;创建伙伴后展开右侧,拖拽中间|右分隔条确认宽度实时变化,拖至阈值以下确认立即吸合折叠;拉宽窗口确认中间与右侧按同一比例同步变宽;在 A/B 伙伴分别创建不同标签并设置不同活动标签和展开状态,来回切换;再连续创建八个浏览器 Tab 验证滚动和菜单。 - Oracle:各伙伴恢复自己的标签、活动状态和展开状态;右侧宽度比例、分隔条与吸合对齐 `WB-PANEL-001-AC10`(含 macOS 窗口边缘 live-resize 限帧与缩放期关闭面板 transition);无消息时输入区保持居中,有消息后输入区固定在主区底部,对齐 `WB-COMPOSER-009-AC11`;其余布局、动画、滚动和菜单要求不变,对齐 `WB-PANEL-001`、`WB-SESSION-008`。 - Not:不验证公网网页、模型回答质量或系统窗口按钮本身;Stagehand targetId、共享持久分区和历史恢复继续由 `browser-use-task-panel` 覆盖。 @@ -611,9 +609,9 @@ pnpm test:update:e2e #### `wps-office-reload-on-write-and-revert` -- 测试类型:`e2e / exclusive`(路径 `result/wps-office/`;可单独执行,发版按影响范围选择或随全量回归) +- 测试类型:`e2e / exclusive`(路径 `result/wps-office/`;可单独执行,发版按影响范围选择或随全量测试) - What:本机已授权的 WPS 打开目标 docx、xlsx、pptx 后,真实 AI 写回/回退让对应组件加载项先 close 放锁再写盘再 open,窗口摘录分别含标记和恢复原文。 -- Why:只证明馆长 dispatch 不能证明用户看到的 WPS 窗口已换新稿。Windows 上 WPS 独占锁必须先 close 才能写盘。文字、表格、演示是独立客户端,只测 docx 不能证明 Excel / PPT 窗口会刷新。授权弹窗可能卡住无人值守自动化,因此提供单独命令;发版时按影响范围选择,发版全量回归包含该 case。 +- Why:只证明馆长 dispatch 不能证明用户看到的 WPS 窗口已换新稿。Windows 上 WPS 独占锁必须先 close 才能写盘。文字、表格、演示是独立客户端,只测 docx 不能证明 Excel / PPT 窗口会刷新。授权弹窗可能卡住无人值守自动化,因此提供单独命令;发版时按影响范围选择,发版全量测试包含该 case。 - How:执行 `pnpm test:workbench:wps-office` 或 `--case wps-office-reload-on-write-and-revert`。不覆盖拷加载项、不点授权、不用辅助功能点 UI。进用例前先做 WPS 环境归零(不带 `/F` 请求关闭 wps / et / wpp,超时再强杀,然后清 `ncurator-workbench-wps-*` 临时工作区),否则上一轮残留实例会冒充本轮客户端。馆长回环先起来;`publish.xml` 与授权 path 保持 `http://127.0.0.1:18723/addon/`。若回环不在优先端口 `18723`(说明本机另有馆长占用),跳过整案,因为已打开的 WPS 仍从优先端口拉加载项。独占 `18723` 时,对 `sample-edit.docx`、`经营周报.xlsx`、`品牌策略提案.pptx` 分别用本机 WPS 打开临时工作区文件:macOS 走 `open -a`;Windows 按注册表 `InstallRoot` 与默认安装目录解析启动器 / 对应组件 exe(`wps.exe` / `et.exe` / `wpp.exe`),进程拉起后立即返回、不等待 WPS 退出。等待 `/status` 出现对应组件客户端(`wps` / `et` / `wpp`)且该文档已登记。再通过真实 Workbench UI 发送编辑;写盘前加载项 close 并上报 `remaining`,馆长等到文件锁释放再写盘;写回后读该路径 `reload-result` 摘录含标记,回退后摘录不含标记。标记落点必须在加载项摘录采样范围内(xlsx 指定写 `H1`,不能让模型自选任意空白单元格,否则摘录读不到),摘录采样为表格 200 行 × 50 列、演示前 10 页、单次上限 20000 字符。`remaining` 为 0 时由占位文档撑住组件进程后继续走加载项 open;占位建不起来才用对应组件 exe 拉起(不得 `cmd start` 把表格/演示塞进已打开的文字窗口),再经 list 轮询取摘录、不再二次 Open;被指派实例报 open 失败或超时则降级一次系统打开。每种格式跑完立即归零 WPS,下一种格式从空环境开始;单种格式失败只记该格式失败并继续跑后面的格式,不得让 docx 或 xlsx 的问题把 pptx 挡在门外。本机未装 WPS、或该组件 `authaddin` 未授权则跳过该格式;该组件已授权但约 90 秒内加载项未连接则该格式失败。三种格式都未授权才跳过整案。 - Oracle:磁盘写回含标记、回退恢复原文;已授权的每种格式都必须给出结论,加载项 `reload-result` 写回后摘录含标记、回退后不含标记,对齐 `WB-WPS-001-AC1`、`WB-WPS-001-AC2`、`WB-WPS-001-AC3`。断言数据按格式列出 covered / failed 及失败原因,任一已授权格式未 covered 即失败。未连接时不发 close;刷新失败不回滚已成功写盘,对齐 `WB-WPS-001-AC4`。超时报错必须带上 `clientId`、已登记文档和被拒的 close / open 回报原因,便于区分「投错实例」和「组件真的打不开」。用例结束后在删除临时工作区前先归零 WPS,清理目录遇到占用不作为失败。 - Not:不断言授权弹窗文案、功能区页签肉眼可见或滚动恢复。`WB-WPS-001-AC5` 只作为未授权跳过说明。验收以 `/status` 为准。 @@ -656,7 +654,7 @@ pnpm test:update:e2e ### 5.4 `scheduled-task` -定时任务回归单独归类,Oracle 引用 [`workbench.md`](../prd/workbench.md) 的 `ST-*` 编号。调度触发时点由真实程序运行与系统时间决定,case 通过「下一分钟」级别的短周期调度验证端到端触发,不伪造系统时钟。下列 AC 由 unit `tests/case/unit/scheduled-task-service.test.ts` 承担,不进入 Workbench E2E 发现范围:`ST-PARTNER-001-AC2`、`ST-RUN-001-AC3`、`ST-RUN-001-AC4`、`ST-RUN-003-AC1/AC2`、`ST-RUN-004-AC2/AC3`、`ST-RUN-005-AC1/AC2/AC3`。`ST-PARTNER-001-AC1`(删除专属伙伴后任务仍在)标为不测。 +定时任务测试单独归类,Oracle 引用 [`workbench.md`](../prd/workbench.md) 的 `ST-*` 编号。调度触发时点由真实程序运行与系统时间决定,case 通过「下一分钟」级别的短周期调度验证端到端触发,不伪造系统时钟。下列 AC 由 unit `tests/case/unit/scheduled-task-service.test.ts` 承担,不进入 Workbench E2E 发现范围:`ST-PARTNER-001-AC2`、`ST-RUN-001-AC3`、`ST-RUN-001-AC4`、`ST-RUN-003-AC1/AC2`、`ST-RUN-004-AC2/AC3`、`ST-RUN-005-AC1/AC2/AC3`。`ST-PARTNER-001-AC1`(删除专属伙伴后任务仍在)标为不测。 #### `scheduled-task-view-entry-and-isolation` diff --git a/doc/test.md b/doc/test.md index 0d983036..370449aa 100644 --- a/doc/test.md +++ b/doc/test.md @@ -5,7 +5,6 @@ - 本文是 NCurator-Desktop 的唯一测试总规范。 - 各业务域的 suite、case 和领域规则只在对应 `doc/test-plan/**` 中维护,本文不重复登记 case。 - `ai-test` 专门覆盖 AI 架构及其工具,全部是 backend;`settings-test` 和 `workbench-test` 专门覆盖对应 PRD 功能点,全部是 E2E;Unit 只补足前述范围未覆盖的 backend 函数、类和底层逻辑。 -- Showcase 是独立的显式采集任务,不属于产品回归或 E2E 集合;使用独立目录和 `pnpm test:showcase` 命令,范围只在 `doc/test-plan/showcase-test.md` 维护。 测试体系只沉淀一层长期执行产物: @@ -17,17 +16,14 @@ ### 2.1 测试分类 -- 回归测试 +- 测试用例 - 列入 `doc/test-plan/**`,放在 `tests/case///**`。 - 用于长期维护、批量执行和生成可追溯运行产物。 -- 显式采集任务 - - 登记在 `doc/test-plan/showcase-test.md`,放在 `tests/showcase/**`。 - - 使用独立采集命令,不能被回归 runner 或发布命令发现。 - `unit` - 放在 `tests/case/unit/**`。 - 全部是 backend 单元测试,不允许导入 Renderer、React、jsdom 或 Electron UI。 - 不进入 `doc/test-plan/**`,由 unit runner 独立发现和执行。 -- 不落测试文件的排查不属于测试文档治理对象;新增测试文件前必须先确认归入 AI、Settings、Workbench、Unit 或 Showcase。 +- 不落测试文件的排查不属于测试文档治理对象;新增测试文件前必须先确认归入 AI、Settings、Workbench 或 Unit。 ### 2.2 目录结构 @@ -40,20 +36,18 @@ tests/ unit/ settings/ workbench/ - showcase/ logs/ ``` 规则: - 目录使用 kebab-case。 -- 回归测试发现目录使用通用树形结构:`tests/case////.test.ts`。 -- 回归 E2E 测试文件使用 `tests/case////.e2e.test.ts`。 -- Showcase 采集文件使用 `tests/showcase/.showcase.test.ts`,不进入回归 runner。 +- 正式测试发现目录使用通用树形结构:`tests/case////.test.ts`。 +- 正式 E2E 测试文件使用 `tests/case////.e2e.test.ts`。 - `suite` 是第一层稳定分类;`groups...` 是 suite 下可选的子目录。 -- 各回归 domain 的 `suite` 列表与归属,只在对应 `doc/test-plan/**` 中定义;Showcase 不使用回归 domain/suite。 -- 当某个 `suite` 不需要继续细分时,回归 case 可以直接放在该 `suite` 目录下。 -- `tests/case/**` 中除 `unit/**` 外,只允许放已在 `doc/test-plan/**` 登记的回归 case。 +- 各测试 domain 的 `suite` 列表与归属,只在对应 `doc/test-plan/**` 中定义。 +- 当某个 `suite` 不需要继续细分时,测试 case 可以直接放在该 `suite` 目录下。 +- `tests/case/{ai,settings,unit,workbench}/**` 只允许放已在 `doc/test-plan/**` 登记的正式测试 case。 - `tests/fixtures/workspace/` 是共享工作区 fixture 根目录;凡是依赖“工作区里有什么文件”的测试,以及 backend/unit 直接引用的固定文件输入,统一复用该目录。 - `tests/fixtures/sqlite/` 放数据库迁移 fixture。 - `tests/fixtures/**` 全部是只读测试输入:测试禁止对其中任何文件原地写入、覆盖或修改;需要写入时,必须通过 `fixtures: []` 声明由 runner/harness 复制到隔离临时 workspace(或按 E2E 规则复制到临时目录),测完删除。fixture 需要更新时直接提交新二进制,禁止引入任何 fixture 生成器脚本(生成器不得以 `.test.ts` 形态进入 `tests/case/**` 自动发现)。 @@ -61,20 +55,20 @@ tests/ ### 2.3 稳定映射模型 -回归测试按 `domain / suite / groups... / caseId` 映射到目录和日志;普通 `.test.ts` 为 backend,`.e2e.test.ts` 为 E2E。固定边界为:`ai`、`unit` 只能是 backend,`settings`、`workbench` 只能是 E2E。Showcase 使用独立的 `showcase / collection / caseId` 采集日志映射。 +测试按 `domain / suite / groups... / caseId` 映射到目录和日志;普通 `.test.ts` 为 backend,`.e2e.test.ts` 为 E2E。固定边界为:`ai`、`unit` 只能是 backend,`settings`、`workbench` 只能是 E2E。 -## 3. 回归与采集 case 规则 +## 3. 测试用例规则 ### 3.1 准入规则 - 新增任何测试文件前,必须先与用户确认目标分类。 -- `doc/test-plan/**` 是回归 case 的唯一规格来源;新增、修改或补齐回归 case 时,必须先更新对应测试计划,再修改测试代码。Showcase 采集任务遵循 `doc/test-plan/showcase-test.md`。 -- `tests/case//**` 只能放已登记的回归 case;`tests/case/unit/**` 不进入领域回归计划,但仍必须满足 Unit backend 边界。 -- 每个回归 `caseId` 都必须稳定映射到唯一测试定义与唯一日志目录。一个测试文件可以承载多个紧密相关的 case,但 `--case ` 必须能只执行目标 case。 +- `doc/test-plan/**` 是测试 case 的唯一规格来源;新增、修改或补齐测试 case 时,必须先更新对应测试计划,再修改测试代码。 +- `tests/case//**` 只能放已登记的测试 case;`tests/case/unit/**` 不进入领域测试计划,但仍必须满足 Unit backend 边界。 +- 每个测试 `caseId` 都必须稳定映射到唯一测试定义与唯一日志目录。一个测试文件可以承载多个紧密相关的 case,但 `--case ` 必须能只执行目标 case。 -### 3.2 回归 case 共享字段 +### 3.2 测试 case 共享字段 -回归 case 和 Showcase 采集任务在 `doc/test-plan/**` 中统一使用以下字段: +测试 case 在 `doc/test-plan/**` 中统一使用以下字段: - `测试类型` - `backend` 或 `e2e`。 @@ -89,7 +83,7 @@ tests/ - `Not` - 明确不验证什么,防止实现时擅自扩张范围。 -### 3.3 UI E2E 回归规则 +### 3.3 UI E2E 测试规则 - UI E2E 必须通过真实 UI 和产品链路完成被测行为;禁止 fake、mock、stub、直接写状态或内部写 IPC 绕过。 - fixture 和 helper 只能准备真实输入、执行用户可见操作、等待结果和收集证据,不能替代产品输出。 @@ -101,7 +95,7 @@ tests/ ### 3.4 真实 AI / live 断言规则 - live 模型配置(backend `ai/human/live` 与 Workbench AI E2E 共用)与 opt-in 命令只在对应 `doc/test-plan/**` 维护,本文不重复。 -- 涉及真实 AI 调用的回归测试,硬断言只验证完成态、关键事实、关键工具或事件、关键 artifact,以及用户诉求是否落地;最终自然语言回答不得作为主要正确性证据。 +- 涉及真实 AI 调用的测试,硬断言只验证完成态、关键事实、关键工具或事件、关键 artifact,以及用户诉求是否落地;最终自然语言回答不得作为主要正确性证据。 - 不对最终文案做逐字断言,不断言固定工具顺序,不断言固定 prompt 原文,不因表达差异判失败。 - 如果用户诉求已落地但断言过严,应先调整 `Oracle` 和测试代码,再根据 raw logs 记录完成度评分。 - 完成度评分由执行者或 agent 写入运行结论;测试代码不得在执行过程中修改文档。 @@ -111,12 +105,11 @@ tests/ ### 4.1 固定顺序 1. 先确认是否新增或调整测试,以及目标分类。 -2. 如果是回归测试,先更新对应 `doc/test-plan/**`,再修改 `tests/case//**`。 -3. 如果是 Showcase 采集,先更新 `doc/test-plan/showcase-test.md`,再修改 `tests/showcase/**`。 -4. 如果是 Unit,放入 `tests/case/unit/**`,保持 backend-only,不更新 `doc/test-plan/**`。 -5. 回归测试通过 `scripts/test/workflow.mjs` 执行;Showcase 通过 `scripts/test/showcase.mjs` 执行。 -6. 先看 `timeline.jsonl`;不足时再看 `runtime-events.jsonl`、`meta.json` 和 `artifacts.json`。 -7. 最后基于 raw logs 回流结论,决定是否需要更新测试计划或实现代码。 +2. 如果是测试用例,先更新对应 `doc/test-plan/**`,再修改 `tests/case//**`。 +3. 如果是 Unit,放入 `tests/case/unit/**`,保持 backend-only,不更新测试计划。 +4. 通过 `scripts/test/workflow.mjs` 执行并产出日志。 +5. 先看 `timeline.jsonl`;不足时再看 `runtime-events.jsonl`、`meta.json` 和 `artifacts.json`。 +6. 最后基于 raw logs 回流结论,决定是否需要更新测试计划或实现代码。 ### 4.2 runner 与常用命令 @@ -151,19 +144,12 @@ runner 不负责: - 源码 E2E - `pnpm test:e2e:source`:构建当前源码并运行 `tests/case/**` 中的全部 E2E。 - `pnpm test:e2e:package -- --bundle `:校验并安装已经构建的正式包,再对安装后的可执行程序运行所选正式 E2E。安装落在本机 `NCurator-E2E` 目录;Windows 可能覆盖本机已安装的正式包。测完删除 E2E 安装目录及指向它的入口。 -- 发版回归 E2E - - `pnpm release:win -- --tag --test-only [回归过滤参数]` - - `pnpm release:mac -- --tag --test-only [回归过滤参数]` - - 不带过滤参数执行全部发版回归;发版回归允许包含日常默认集合之外的正式回归用例,但不包含显式采集任务。具体决策见 `doc/deploy/release.md`。 - Settings - `pnpm test:settings` - `pnpm test:settings:e2e` - Workbench - `pnpm test:workbench` - `pnpm test:workbench:e2e` -- Showcase - - `pnpm test:showcase` - - `pnpm test:showcase -- --case ` runner 支持的过滤参数: @@ -184,10 +170,9 @@ runner 支持的过滤参数: - runner 强制执行固定 domain/type 边界。backend 普通测试并发为 6;Source E2E 与正式包 E2E 在多 case 时都先串行执行全仓唯一的 `warmup` 用例(先冷启动索引共享 fixture,后续 case 复用缓存)。Source 的所有 case 直接共享按构建指纹保存的 split/vector 内容缓存,各 case 只隔离统计文件;正式包 E2E 则把缓存种子复制到后续 case 的隔离缓存目录。随后开放总并发 3、live 并发 2,`exclusive` 用例串行独占;某个 case 失败后不自动重跑,继续收集剩余 case;warm-up 失败则不启动后续并发。 - E2E(source 与正式包)即使通过 `--domain`/`--suite`/`--case` 过滤范围,只要本次实际执行的 case 数超过 1,runner 也会自动把全仓唯一的 `warmup` 用例纳入执行并先串行预热,避免过滤后漏掉 warmup 导致全部 case 冷启动重复索引;`--case` 过滤时 `warmup` 用例不受 `--testNamePattern` 影响。 -- E2E 默认执行类型为 `normal`;真实 AI/服务调用声明 `execution: 'live'`,原生对话框、鼠标和桌面焦点用例声明 `execution: 'exclusive'`,唯一的向量冷启动用例声明 `execution: 'warmup'`。`provider-matrix` 与 `wps-office` 都属于正式 E2E,按影响评估选择或在全量回归时执行。 -- `source` 是源码目标,直接复用内容缓存;`package` 只接受既有 release bundle,安装后运行所选 E2E,不负责构建。两者都不发现 `tests/showcase/**`。 -- native binding、知识库缓存和临时 Electron app 由 runner 自动准备;普通回归不手工修改 `node_modules` 或复用正式包缓存。 -- 正式包测试、发布门禁、Tag 前 Unit/非 live AI(及发布准备必跑的 `human-prefix-cache-retain-live`)以及发布后日志清理只在 `doc/deploy/release.md` 定义,本文不重复发布流程。 +- E2E 默认执行类型为 `normal`;真实 AI/服务调用声明 `execution: 'live'`,原生对话框、鼠标和桌面焦点用例声明 `execution: 'exclusive'`,唯一的向量冷启动用例声明 `execution: 'warmup'`。 +- `source` 是源码目标,直接复用内容缓存;`package` 只接受既有 release bundle,安装后运行所选 E2E,不负责构建。 +- native binding、知识库缓存和临时 Electron app 由 runner 自动准备;普通测试不手工修改 `node_modules` 或复用正式包缓存。 ### 4.3 读取与分析 raw logs diff --git a/scripts/test/workflow.mjs b/scripts/test/workflow.mjs index 3612dfb8..950b0dfc 100644 --- a/scripts/test/workflow.mjs +++ b/scripts/test/workflow.mjs @@ -10,7 +10,7 @@ const ROOT = process.cwd() const require = createRequire(import.meta.url) const TESTS_ROOT = path.join(ROOT, 'tests') const TEST_CASES_ROOT = path.join(TESTS_ROOT, 'case') -const SHOWCASE_ROOT = path.join(TESTS_ROOT, 'showcase') +const SHOWCASE_ROOT = path.join(TEST_CASES_ROOT, 'showcase') const resolveLocalBin = (name) => path.join(ROOT, 'node_modules', '.bin', process.platform === 'win32' ? `${name}.cmd` : name) const PNPM_BIN = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm' @@ -665,7 +665,12 @@ const validateTestArchitecture = () => { } const formatScope = (input) => - [[input.domain, input.suite, ...input.groups].filter(Boolean).join('/'), input.type] + [ + input.showcase + ? 'showcase' + : [input.domain, input.suite, ...input.groups].filter(Boolean).join('/'), + input.type + ] .filter(Boolean) .join(' ') diff --git a/tests/showcase/chailv-tax-deduction.showcase.test.ts b/tests/case/showcase/chailv-tax-deduction.showcase.test.ts similarity index 97% rename from tests/showcase/chailv-tax-deduction.showcase.test.ts rename to tests/case/showcase/chailv-tax-deduction.showcase.test.ts index e7bef0b8..81a596ef 100644 --- a/tests/showcase/chailv-tax-deduction.showcase.test.ts +++ b/tests/case/showcase/chailv-tax-deduction.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -13,7 +13,7 @@ import { selectWorkbenchModel, sendWorkbenchMessage, waitForWorkbenchAiRunCompletion -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { WEB_SEARCH_PROMPT } from './createSeeds' import { computeShowcaseCost, diff --git a/tests/showcase/court-typical-cases.showcase.test.ts b/tests/case/showcase/court-typical-cases.showcase.test.ts similarity index 98% rename from tests/showcase/court-typical-cases.showcase.test.ts rename to tests/case/showcase/court-typical-cases.showcase.test.ts index 3efee04c..ca18d16d 100644 --- a/tests/showcase/court-typical-cases.showcase.test.ts +++ b/tests/case/showcase/court-typical-cases.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -13,7 +13,7 @@ import { selectWorkbenchModel, sendWorkbenchMessage, waitForWorkbenchAiRunCompletion -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { COURT_SEARCH_HOST, COURT_SEARCH_KEYWORDS, COURT_SEARCH_PROMPT } from './createSeeds' import { captureEmbeddedBrowserPng, diff --git a/tests/showcase/createSeeds.ts b/tests/case/showcase/createSeeds.ts similarity index 100% rename from tests/showcase/createSeeds.ts rename to tests/case/showcase/createSeeds.ts diff --git a/tests/showcase/docx-weekly-report-edit.showcase.test.ts b/tests/case/showcase/docx-weekly-report-edit.showcase.test.ts similarity index 98% rename from tests/showcase/docx-weekly-report-edit.showcase.test.ts rename to tests/case/showcase/docx-weekly-report-edit.showcase.test.ts index e6107d03..44b2bd7e 100644 --- a/tests/showcase/docx-weekly-report-edit.showcase.test.ts +++ b/tests/case/showcase/docx-weekly-report-edit.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -16,7 +16,7 @@ import { waitForApplyEntryByFileName, waitForWorkbenchAiRunCompletion, workbenchSelectors -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { DOCX_EDIT_PROMPT, DOCX_FILE_NAME } from './createSeeds' import { computeShowcaseCost, diff --git a/tests/showcase/docx-weekly-report.showcase.test.ts b/tests/case/showcase/docx-weekly-report.showcase.test.ts similarity index 98% rename from tests/showcase/docx-weekly-report.showcase.test.ts rename to tests/case/showcase/docx-weekly-report.showcase.test.ts index 6c71345d..ba26b56a 100644 --- a/tests/showcase/docx-weekly-report.showcase.test.ts +++ b/tests/case/showcase/docx-weekly-report.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -17,7 +17,7 @@ import { waitForApplyEntryByFileName, waitForWorkbenchAiRunCompletion, workbenchSelectors -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { computeShowcaseCost, copyShowcaseFixture, diff --git a/tests/showcase/excel-budget-edit.showcase.test.ts b/tests/case/showcase/excel-budget-edit.showcase.test.ts similarity index 98% rename from tests/showcase/excel-budget-edit.showcase.test.ts rename to tests/case/showcase/excel-budget-edit.showcase.test.ts index db792f6c..2e98c8bf 100644 --- a/tests/showcase/excel-budget-edit.showcase.test.ts +++ b/tests/case/showcase/excel-budget-edit.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -16,7 +16,7 @@ import { waitForApplyEntryByFileName, waitForWorkbenchAiRunCompletion, workbenchSelectors -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { EXCEL_EDIT_PROMPT, EXCEL_FILE_NAME } from './createSeeds' import { computeShowcaseCost, diff --git a/tests/showcase/excel-budget.showcase.test.ts b/tests/case/showcase/excel-budget.showcase.test.ts similarity index 98% rename from tests/showcase/excel-budget.showcase.test.ts rename to tests/case/showcase/excel-budget.showcase.test.ts index 6d9b3caf..ba4eca61 100644 --- a/tests/showcase/excel-budget.showcase.test.ts +++ b/tests/case/showcase/excel-budget.showcase.test.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import ExcelJS from 'exceljs' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -17,7 +17,7 @@ import { waitForApplyEntryByFileName, waitForWorkbenchAiRunCompletion, workbenchSelectors -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { EXCEL_CREATE_PROMPT, EXCEL_FILE_NAME } from './createSeeds' import { computeShowcaseCost, diff --git a/tests/showcase/ppt-management-briefing-edit.showcase.test.ts b/tests/case/showcase/ppt-management-briefing-edit.showcase.test.ts similarity index 98% rename from tests/showcase/ppt-management-briefing-edit.showcase.test.ts rename to tests/case/showcase/ppt-management-briefing-edit.showcase.test.ts index e400a8c1..9bc53d64 100644 --- a/tests/showcase/ppt-management-briefing-edit.showcase.test.ts +++ b/tests/case/showcase/ppt-management-briefing-edit.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -16,7 +16,7 @@ import { waitForApplyEntryByFileName, waitForWorkbenchAiRunCompletion, workbenchSelectors -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { PPT_EDIT_PROMPT, PPT_FILE_NAME } from './createSeeds' import { computeShowcaseCost, diff --git a/tests/showcase/ppt-management-briefing-review.showcase.test.ts b/tests/case/showcase/ppt-management-briefing-review.showcase.test.ts similarity index 98% rename from tests/showcase/ppt-management-briefing-review.showcase.test.ts rename to tests/case/showcase/ppt-management-briefing-review.showcase.test.ts index b6adca9a..1d6e97b1 100644 --- a/tests/showcase/ppt-management-briefing-review.showcase.test.ts +++ b/tests/case/showcase/ppt-management-briefing-review.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -16,7 +16,7 @@ import { waitForApplyEntryByFileName, waitForWorkbenchAiRunCompletion, workbenchSelectors -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { computeShowcaseCost, copyShowcaseFixture, diff --git a/tests/showcase/shared.ts b/tests/case/showcase/shared.ts similarity index 99% rename from tests/showcase/shared.ts rename to tests/case/showcase/shared.ts index 8d5a3c6a..797945b4 100644 --- a/tests/showcase/shared.ts +++ b/tests/case/showcase/shared.ts @@ -8,7 +8,7 @@ import { getWorkbenchLocator, getWorkbenchPrefixLocator, workbenchSelectors -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' export const USD_CNY = 7.2 diff --git a/tests/showcase/xinghe-kb-reply.showcase.test.ts b/tests/case/showcase/xinghe-kb-reply.showcase.test.ts similarity index 98% rename from tests/showcase/xinghe-kb-reply.showcase.test.ts rename to tests/case/showcase/xinghe-kb-reply.showcase.test.ts index c6ae5a51..839ac6d8 100644 --- a/tests/showcase/xinghe-kb-reply.showcase.test.ts +++ b/tests/case/showcase/xinghe-kb-reply.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, assertNoWorkbenchFakeSignals, @@ -13,7 +13,7 @@ import { selectWorkbenchModel, sendWorkbenchMessage, waitForWorkbenchAiRunCompletion -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { KB_PROMPT, KB_SOURCE_PAGES, seedKnowledgeWorkspace } from './createSeeds' import { computeShowcaseCost, rasterizeNotePage, screenshotAssistantAnswer } from './shared' diff --git a/tests/showcase/xinghe-service-contract.showcase.test.ts b/tests/case/showcase/xinghe-service-contract.showcase.test.ts similarity index 98% rename from tests/showcase/xinghe-service-contract.showcase.test.ts rename to tests/case/showcase/xinghe-service-contract.showcase.test.ts index e5e93118..d34d498b 100644 --- a/tests/showcase/xinghe-service-contract.showcase.test.ts +++ b/tests/case/showcase/xinghe-service-contract.showcase.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { runShowcaseCase } from '../harness/e2e/run-e2e-case' +import { runShowcaseCase } from '../../harness/e2e/run-e2e-case' import { WORKBENCH_LIVE_AI_E2E_TIMEOUT_MS, appendWorkbenchInputText, @@ -15,7 +15,7 @@ import { selectWorkbenchModel, submitWorkbenchCurrentDraft, waitForWorkbenchAiRunCompletion -} from '../harness/e2e/workbench' +} from '../../harness/e2e/workbench' import { PDF_FILE_NAME, PDF_PROMPT, seedPdfContractWorkspace } from './createSeeds' import { computeShowcaseCost, diff --git a/vitest.showcase.config.mts b/vitest.showcase.config.mts index de844219..41c48485 100644 --- a/vitest.showcase.config.mts +++ b/vitest.showcase.config.mts @@ -2,7 +2,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - include: ['tests/showcase/**/*.showcase.test.ts'], + include: ['tests/case/showcase/**/*.showcase.test.ts'], exclude: ['tests/harness/**', 'tests/fixtures/**', 'tests/logs/**'], environment: 'node', globals: true,