Banana Slides Release Gauntlet:面向生成式 AI 产品的 8 级发布前自动化验收方案
本文完整解读 Banana Slides 仓库中的《Release Gauntlet 发布前自动化测试验收方案》(docs/superpowers/specs/2026-07-02-release-gauntlet-test-plan.md),核心目标是用一条命令
npm run release:gauntlet自动发现绝大多数功能与体验问题,并对无法自动验证的剩余风险生成明确的《残余风险报告》交给开发者做最小化人工抽查。读完本文,你将掌握"按判定器强弱分层配 Oracle""体验问题不变量化""VLM-as-Judge 质量评审""多人格 Agent 探索"等一套可直接落地的 AI 产品发布验收方法论,并看到它在 Banana Slides 现有 unit/e2e 资产上的具体挂载方式。
0. 第一性原理:为什么"99% 自动检出"不能靠堆测试用例
一个 bug 被自动发现,必须同时满足两个条件:
- 触达(Reachability):测试执行路径覆盖到了出问题的状态;
- 判定(Oracle):存在一个自动化的"对/错"判断标准。
传统测试方案(本项目现有的 unit + e2e)在两个维度上都有结构性盲区:
- 触达盲区:测试只走"编写者想到的路径"。真实用户会乱点、中途刷新、断网、开两个标签页、上传奇怪的文件。
- 判定盲区:Banana Slides 是生成式 AI 产品,核心产出(幻灯片图片)没有唯一正确答案。
expect(x).toBe(y)无法判断"这页 PPT 文字被截断了""对比度太低看不清"——而这些恰恰是用户最在意的"体验问题"。
所以本方案的核心不是"写更多用例",而是按判定器(Oracle)的强弱把产品分层,给每一层配上它能用的最强判定器:
| 产品层 | 行为性质 | 最强可用 Oracle |
|---|---|---|
| API 契约 / DB / 任务状态机 / 导出文件结构 | 确定性 | 精确断言、Schema 校验、属性测试 |
| 前端 UI 逻辑 / 路由 / i18n / 状态管理 | 确定性 | e2e 断言 + 控制台零报错 + 视觉回归 |
| 用户旅程的健壮性(乱序操作、异常输入、网络故障) | 半确定性 | 不变量断言(永不白屏、永不无提示等待、永不静默失败)+ Agent 探索 |
| AI 生成产物质量(排版、可读性、贴合描述) | 概率性 | VLM-as-Judge(视觉模型按评分卡打分) |
| 主观审美 / 品味 | 主观 | 无自动 Oracle → 进入残余风险报告 |
关键洞察:"体验问题"大多可以转写成不变量(invariant),从而变成可自动判定的:
- 「困惑的说明文字」→ 不变量:所有可见文案必须在 i18n 字典中成对存在(zh/en),且 VLM 审阅每个界面截图回答"这个界面上有没有让新用户困惑的文案?"
- 「无提示的等待」→ 不变量:任何 >800ms 的 pending 网络请求期间,页面上必须存在 loading 指示元素(spinner/skeleton/progress)。这条可以写成 Playwright 全局钩子,挂在全部 70 个现有 e2e 上白捡覆盖。
- 「点了没反应」→ 不变量:任何 click 后 2s 内必须发生 DOM 变化、路由变化或网络请求之一。
- 「白屏/崩溃」→ 不变量:任何时刻 console 无 uncaught error,root 元素非空。
这些不变量的提出并非空想,它们与仓库现状一一对应:例如 frontend/src/hooks/useT.ts 实现了"组件级内联字典 + 全局字典 fallback"的 i18n 机制,useT() 接收 { zh, en } 双层字典——这种组件级内联最容易漏翻,也正是 L0 的"i18n 完整性扫描"要覆盖的对象;而 e2e 中已大量出现对 loading 状态的显式等待(如 frontend/e2e/ui-full-flow.spec.ts 中对"生成中..."按钮与 skeleton 的轮询),说明"无提示等待"确实是真实痛点。
1. 总体架构:8 层流水线(Release Gauntlet)
按"便宜的先跑、贵的后跑、fail-fast"排列。L0–L3 是现有资产的加固,L4–L7 是新增的、真正拉开与普通项目差距的部分。
L0 静态防线(~2min) → lint / tsc / py_compile / 依赖审计 / 迁移可逆性 / i18n 完整性
L1 单元+集成(~5min) → 现有 pytest + vitest(补齐属性测试)
L2 契约防漂移(~3min) → 后端 OpenAPI schema ⇄ 前端 mock ⇄ 真实响应 三方一致性
L3 确定性 E2E(~15min) → 现有 70 个 spec + 全局不变量钩子 + 视觉回归矩阵
L4 产物验证(~10min) → PPTX/PDF/视频/编辑版 PPTX 的结构级深度校验
L5 真实 AI 全流程(~25min)→ 现有 ui-full-flow + provider 矩阵 + VLM 质量评分
L6 Agent 探索测试(~40min)→ 多人格 AI agent 驱动真实浏览器做破坏性/探索性测试
L7 混沌与升级(~15min) → 故障注入、并发、旧版本数据升级、环境矩阵
→ 产出《发布报告》+《残余风险报告》
总时长 ≈ 2 小时(L5–L7 可并行压到 ~1h)。按项目"不在意测试成本"的原则,真实 API 调用费用预计每次 $3–8。
这条流水线与仓库里现成的 CI 资产是叠加关系而非替换关系:.github/workflows/ 下已存在 ci-test.yml(PR 快检)、nightly.yml(夜间任务)、release-desktop.yml(桌面端发布)等工作流,方案在第 4 节明确了它们的挂法(见下文)。
2. 各层详细设计
L0 静态防线(新增 3 项)
现有:flake8(根目录 package.json 的 lint:backend 已配置 uv run flake8 backend/ --count --select=E9,F63,F7,F82)、eslint(lint:frontend 走 cd frontend && npm run lint)、npm run build。新增:
tsc --noEmit严格类型检查(build 能过不代表类型干净)。仓库前端已含build:check(tsc && vite build)先例,说明项目认可"类型检查先于构建"的思路,L0 把它变成 release 前的硬门槛。- 迁移可逆性测试:空库
alembic upgrade head→downgrade base→ 再upgrade head;再用上一个 release tag 的 DB fixture 直接 upgrade(见 L7)。仓库 migrations 目录下有 35 个版本文件(backend/migrations/versions),含多个*_merge_*与make_settings_columns_nullable_for_env_*这类涉及数据变动的迁移,可逆性验证的必要性显而易见。 - i18n 完整性扫描:脚本遍历所有
useT()内联字典,断言 zh/en 键成对、无空串、无残留的TODO/中英混杂。仓库全局字典位于 frontend/src/locales/en.json 与 frontend/src/locales/zh.json,而组件级内联字典散布在各组件中——这正是最容易漏翻的部分。
L1 单元 + 属性测试
现有测试保留。新增 property-based testing(后端 hypothesis)打确定性核心:
file_parser_service:随机构造损坏/超大/空/非 UTF-8 的 PDF/DOCX/MD,不变量 = 永不抛未捕获异常,永远返回结构化错误。仓库中 backend/services/file_parser_service.py 是解析入口,对应已有测试 backend/tests/unit/test_file_parser_service.py。- 任务状态机(
task_manager):随机操作序列(创建/取消/并发完成),不变量 = 状态只能沿合法边迁移,Task 永不停留在 pending 超时。仓库中 backend/services/task_manager.py 用ThreadPoolExecutor+ 内存任务跟踪实现后台任务(无需 Celery/Redis),状态机迁移是它的核心正确性所在。 - 导出:随机页数/比例/缺图组合 → 导出函数要么成功要么给出用户可读错误。导出逻辑集中在 backend/services/export_service.py 与 backend/utils/pptx_builder.py。
L2 契约防漂移(本项目最大的隐藏风险)
现有 70 个 e2e 里大量使用 page.route() mock。mock 与真实后端漂移是这类测试体系最经典的假阴性来源:后端改了字段名,mock e2e 全绿,真实环境全挂。
对策(三方一致性):
- 后端用 flask 路由自动导出 OpenAPI schema(
apispec或手写 schema 文件均可)。 - 写一个 pytest:对每个端点用 seed 数据发真实请求,响应体用 schema 校验。
- 写一个 lint 脚本:扫描
frontend/e2e/**/*.spec.ts中所有page.route()的 mock JSON,用同一份 schema 校验。mock 不符合 schema = 测试自身失败。
从此 schema 是唯一事实源,前后端和测试三方谁漂移谁报警。这一层直接回应了仓库里大量 e2e 依赖 mock 的现状——例如 frontend/e2e/ui-full-flow-mocked.spec.ts 与 frontend/e2e/ui-full-flow.spec.ts 并存,前者不消耗真实 API 成本但依赖 mock 数据的真实性。
L3 确定性 E2E:全局不变量钩子(性价比最高的一项)
不新写用例,而是给 playwright.config.ts 加全局 fixture,让现有全部 spec 在跑原有断言的同时,免费检查 §0 列出的体验不变量:
- console error/warn 收集器:任何 uncaught error / React warning → 测试失败
- 网络监听器:任何非预期 4xx/5xx → 失败;>800ms pending 且无 loading 元素 → 失败
- 死按钮探测:每次 click 后 2s 无任何反馈 → 失败
- a11y 快扫(axe-core):严重级别违规 → 失败(对比度、无 label 的输入框
本身就是"体验问题")
现有配置 frontend/playwright.config.ts 已经具备很好的挂载基础:testDir: './e2e'、testMatch: '**/*.spec.ts'、fullyParallel: true、失败时保留截图/视频/trace(screenshot: 'only-on-failure' 等),并且 webServer 会通过 docker compose up -d 拉起后端。方案要做的,是新增一个全局 fixture 注入不变量检查器,让约 70 个 spec(frontend/e2e/ 下现有 spec 文件数量与此吻合)零改动地获得新 Oracle。
视觉回归升级为矩阵:现有 frontend/e2e/visual-regression.spec.ts 已用 toHaveScreenshot + maxDiffPixels 做基线比对,方案将其扩展到 {zh, en} × {desktop 1280, mobile 375} × 关键页面(Home / Outline / Detail / Preview / Settings / History),基线截图入库,diff 超阈值即失败。移动端视口是现有测试完全没覆盖的盲区(当前 playwright.config.ts 的 projects 只配置了 chromium 桌面视口;memory 里也记录了要避免 hover-only 交互)。
L4 导出产物深度验证
导出是用户拿到手的最终交付物,一票否决。对 seed 项目(frontend/e2e/helpers/seed-project.ts 已支持免 AI 造数据:直接把 fixtures 图片落盘并直改 SQLite,绕过 AI 图片生成)执行全部导出路径,然后打开文件验内脏,而不只是断言 HTTP 200:
- PPTX:
python-pptx打开 → 页数一致、每页含图、页面比例与项目设置一致、文件能被 LibreOffice headless 转 PDF 不报错(近似"能被 Office 打开"的自动化代理)。 - 编辑版 PPTX:提取的文本框内容与页面描述文本做包含性比对;字体/位置字段非空。这依赖 backend/utils/pptx_builder.py 的编辑版导出实现。
- PDF:
pypdf校验页数、页面尺寸、每页有内容流。 - 视频:
ffprobe校验时长 >0、有视频流、(若配了旁白)有音频流、无损坏帧。视频导出逻辑在 backend/services/tts_video_service.py。
顺带一提,现有 ui-full-flow.spec.ts 已经在下载 PPTX 后用 validate_pptx.py 脚本做了初步内容校验(校验页数、关键词包含),L4 是把这种"下载成功即通过"升级为系统性结构校验。
L5 真实 AI 全流程 + VLM 质量评审(解决"生成质量"盲区)
在现有 nightly ui-full-flow.spec.ts(真实 Gemini)基础上:
-
Provider 矩阵:gemini 真跑;openai/vertex 至少跑到"请求构造正确 + 能解析真实格式的录制响应"(用录制回放,避免多份 API key 成本)。仓库的 provider 抽象层(backend/services/ai_providers/ 下的
text/、image/、ocr/子目录)为这种矩阵提供了清晰的边界。 -
VLM-as-Judge:全流程生成的每页幻灯片图片 + 对应页面描述,送给视觉模型(Gemini/Claude),按固定评分卡打分:
逐项 0–2 分:文字完整无截断 / 无元素重叠 / 对比度可读 / 内容与描述相符 / 版式基本对齐 / 无明显 AI 伪影(乱码字、畸变图标) 任一项 0 分 → 该页 FAIL,附截图和理由进报告同一模型跑 3 次取多数投票压制评审随机性。这是把"人眼验收"自动化的核心组件。仓库中 backend/services/ai_providers/image/ 已有基于视觉模型能力的基础设施(如
subject_extraction_provider、rmbg_segmentation_provider),VLM 评审可复用同一套视觉模型接入通道。 -
界面文案审查:对 L3 矩阵产生的每张关键页截图,让 VLM 回答"假设你是第一次使用的用户,这个界面上是否有困惑、歧义或中英混杂的文案?"——直接对应项目既有 UX 约束中"避免困惑文案"的要求。
L6 Agent 探索测试(覆盖"没人想到的路径")
用 Claude Agent SDK(或 Claude Code headless + Playwright MCP)启动 5 个人格 agent,各自在独立 seed 环境里自由使用产品 30 分钟,唯一的判定标准就是 L3 那套全局不变量(白屏/报错/死按钮/无提示等待)+ agent 自身的主观报告:
| 人格 | 行为脚本倾向 |
|---|---|
| 小白用户 | 只看界面提示操作,遇到看不懂的文案就记录 |
| 急性子 | 疯狂连点、不等 loading 就切页、生成中途刷新/后退 |
| 破坏者 | 超长输入、emoji/RTL 文本、0 页大纲、上传 200MB 文件、非法文件改后缀 |
| 双开用户 | 两个标签页同时编辑同一项目(SQLite 并发写的经典雷区) |
| 英文移动端用户 | en locale + 375px 视口走完整流程 |
Agent 探索的本质是用 LLM 的常识替代人类测试员的直觉去扩大触达面。每个 agent 结束后输出结构化 findings(截图 + 复现步骤),由一个汇总 agent 去重、按严重度排序、并自动尝试用 Playwright 写出最小复现脚本——能稳定复现的进 bug 列表,不能复现的进残余风险报告标记为 flaky 疑点。
值得说明的是,"双开用户"人格针对的是一个真实风险点:仓库后端使用 SQLite(frontend/e2e/helpers/seed-project.ts 中直接对 backend/instance/database.db 执行 SQL),两个标签页并发写是 SQLite 的经典雷区,L7 的并发压测也会专门盯住 database is locked。
L7 混沌、并发与升级
- 故障注入(Playwright route + 后端故障开关):
- AI API 返回 429/500/超时/截断 JSON → 断言前端有用户可读的错误提示且可重试;
- 生成图片进行到 50% 时 kill 后端再拉起 → 任务状态可恢复或明确失败,不僵死 pending;
- 磁盘写满(小容量 tmpfs 挂载 uploads/)→ 导出给出明确错误。
- 并发压测:10 个项目同时生成 →
ThreadPoolExecutor(见 backend/services/task_manager.py)不死锁、SQLite 无database is locked泄漏到用户、任务全部到达终态。 - 升级测试:仓库里存一份上一 release 的真实 DB + uploads fixture,新版本直接启动其上 → 迁移成功、历史项目可打开、可继续编辑导出。每次 release 后由脚本自动刷新这份 fixture。这正是 L0"迁移可逆性测试"里"用上一个 release tag 的 DB fixture 直接 upgrade"的配套动作,仓库 35 个迁移版本的存在让这类测试有了实际价值。
- 环境矩阵:docker-compose(根目录 docker-compose.yml)、Dockerfile.allinone、源码直跑三种方式各做一次冒烟(启动 → 健康检查 → 创建项目 → seed 导出)。
3. 残余风险报告(那 1% 的诚实清单)
流水线最后一步自动生成 release-report/RISK.md,内容不是套话,而是由本次运行的实际覆盖数据推导:
- 本次未触达清单(自动统计):未真跑的 provider×model 组合、未覆盖的浏览器(只测了 Chromium;Safari/Firefox 未测)、未测的操作系统字体渲染差异。
- 无自动 Oracle 的项(固定 + VLM 标记的低置信项):
- PPTX 在真实 PowerPoint / WPS / Keynote 中的渲染保真度(LibreOffice 转换只是代理指标);
- 幻灯片的审美品味(VLM 只能判"错",不能判"美");
- 视频旁白的语音自然度;
- 上游 AI API 未来行为变化(今天全绿不代表下周模型更新后还绿)。
- 人工抽查清单(≤10 分钟):由以上两项生成的具体动作,例如"用 PowerPoint 打开 release-artifacts/sample.pptx 翻一遍""肉眼看 VLM 打了 1 分(及格线边缘)的这 3 页截图"。
- Flaky 隔离区:本次重试后才通过的用例列表——它们是未来 bug 的高发预测区。
这份报告把"自动化没覆盖到的地方"显式暴露出来,避免"测试全绿 = 可以放心发布"的错觉。
4. 与现有资产的关系 & 落地路线
| 阶段 | 内容 | 工作量 | 检出增益 |
|---|---|---|---|
| P1(先做,性价比最高) | L3 全局不变量钩子 + console/网络/loading 监听挂到现有 70 个 spec;L0 三项新增;L4 产物深度校验 | ~3 天 | 高:现有用例覆盖面 × 新 Oracle |
| P2 | L2 契约三方一致性;L1 属性测试;L7 升级/并发测试 | ~4 天 | 高:消灭 mock 漂移假阴性 |
| P3 | L5 VLM-as-Judge + provider 矩阵;视觉回归矩阵 | ~3 天 | 中高:生成质量首次可自动判定 |
| P4 | L6 多人格 agent 探索 + 自动复现脚本生成;残余风险报告生成器 | ~5 天 | 中:覆盖长尾路径 |
入口统一为:
npm run release:gauntlet # 全量,release 前必跑
npm run release:gauntlet -- --fast # L0–L4,日常合并前
CI 挂法:现有 ci-test.yml 不动(PR 快检);nightly 跑 --fast;打 release tag 时跑全量并把两份报告附到 GitHub Release。这与仓库现有工作流划分一致:.github/workflows/ 下 ci-test.yml / pr-quick-check.yml 承担 PR 快检、nightly.yml 承担夜间验证、release-desktop.yml 承担桌面端发布构建,Release Gauntlet 作为发布前的最终关卡叠加其上。
5. 诚实的边界声明
- "找出 99% 问题"作为渐近目标成立,作为承诺不成立——测试只能证明 bug 存在,不能证明不存在(Dijkstra)。本方案的可证伪指标是:发布后逃逸缺陷数。每个逃逸缺陷做归因(属于哪层的触达/判定失败),反哺对应层。这个闭环(而非任何一次性的用例集合)才是验收体系真正"无敌"的部分。
- VLM 评审和 agent 探索是概率性组件,会有假阳性(误报)——用多数投票和自动复现脚本过滤;也会有假阴性——靠残余风险报告兜底。
- 成本随层数上升:L5–L6 每次运行有真实 API 开销,已按项目"不在意测试成本"原则设计,但
--fast模式保证日常开发零额外成本。
6. 方案精髓速览
- Oracle 分层是总纲:确定性层用断言与 Schema,半确定性层用不变量,概率性层用 VLM 评分,主观层诚实降级为人工抽查清单——每一层都只使用它能承载的最强判定器。
- 不变量是体验问题的可计算形式:白屏、无提示等待、死按钮、困惑文案,全部可写成 Playwright 全局钩子,且能挂在既有 spec 上白捡覆盖。
- 契约三方一致性堵住 mock 假阴性,这是"测试全绿、上线全挂"类事故的根治方案。
- 残余风险报告把不可自动验证的部分显式化,配合"逃逸缺陷归因反哺"的闭环,让发布验收体系随项目演进持续变强。
对想要在自己项目中复制这套方案的团队,建议从 P1 起步:先给现有 e2e 挂上全局不变量钩子(L3),再补静态防线与导出产物校验(L0/L4),用最小的成本获得最大的检出增益。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



