AutoGPT Platform 前端测试体系实战:Playwright + Vitest + MSW 四层测试策略与落地细节
本文为 AutoGPT 平台前端(autogpt_platform/frontend)的测试规范指南,对应仓库内的官方测试规则文档 CLAUDE.md(其正文通过 @AGENTS.md 约定包含同目录的 AGENTS.md,两份内容等价)。读完你可以掌握该仓库的四层测试选型方法(E2E / 集成 / 单元 / 视觉)、Playwright 覆盖率采集的实现原理、MSW + Orval 自动生成 API Mock 的用法,以及可直接复制运行的测试命令与目录组织规范。
一、四层测试体系总览
文档将前端测试划分为四种类型,并给出速度量级与适用目的:
| 类型 | 工具 | 速度 | 目的 |
|---|---|---|---|
| E2E | Playwright | 慢(约 5s/用例) | 真实浏览器、完整用户旅程 |
| 集成 | Vitest + RTL | 快(约 100ms) | 组件 + Mock 的 API |
| 单元 | Vitest + RTL | 最快(约 10ms) | 单个函数 / 组件 |
| 视觉 | Storybook + Chromatic | 不计入常规运行 | UI 外观、设计系统 |
选型时遵循如下决策流程(摘自原文档,可直接作为 Code Review 检查表):
Does it need a REAL browser/backend?
├─ YES → E2E (Playwright)
└─ NO
└─ Does it involve API calls or complex state?
├─ YES → Integration (Vitest + RTL)
└─ NO
└─ Is it about visual appearance?
├─ YES → Storybook
└─ NO → Unit (Vitest + RTL)
各层的工具版本可从 package.json 确认:@playwright/test 1.56.1、vitest 4.1.0、msw 2.11.6、orval 7.13.0、happy-dom 20.8.9、storybook 9.1.5 与 chromatic 13.3.3。
二、E2E 测试(Playwright):集中式管理与自动覆盖率
2.1 使用范围与文件位置
E2E 只用于"必须在真实浏览器中工作"的关键旅程:认证流程(登录/注册/登出)、支付或敏感操作、依赖真实浏览器 API(剪贴板、下载)的流程、必须端到端跑通的跨页导航。
文件集中放在 src/playwright/*.spec.ts,官方解释原因是 E2E 数量会刻意保持较少。当前仓库中 autogpt_platform/frontend/src/playwright/ 下的用例与之一一对应,均为 *-happy-path.spec.ts 命名:
- auth-happy-path.spec.ts
- marketplace-happy-path.spec.ts
- builder-happy-path.spec.ts
- library-happy-path.spec.ts
- api-keys-happy-path.spec.ts、copilot-happy-path.spec.ts、publish-happy-path.spec.ts、settings-happy-path.spec.ts
playwright.config.ts 中通过 testMatch: /.*-happy-path\.spec\.ts/ 精确锁定了这一命名约定——只有 happy-path 命名的规格文件会进入 E2E 套件,与文档"E2E 昂贵、只覆盖关键路径"的原则在配置层面相互印证。
2.2 必须从 coverage-fixture 导入 test
文档明确规定:导入 test 和 expect 时必须使用 ./coverage-fixture 而不是 @playwright/test,以自动采集每个用例的 V8 覆盖率用于 Codecov 上报:
// correct
import { test, expect } from "./coverage-fixture";
// wrong - bypasses coverage collection
import { test, expect } from "@playwright/test";
从 coverage-fixture.ts 的源码可以看出其实现:它基于 base.extend 注册了一个 scope: "test"、auto: true 的自动 fixture,在每个用例执行前调用 page.coverage.startJSCoverage({ resetOnNavigation: false }),执行结束后调用 stopJSCoverage() 并把结果交给 monocart-reporter 的 addCoverageReport(jsCoverageList, test.info())。两处 try/catch 分别保证:覆盖率 API 不可用时不阻断用例本身,以及"不让覆盖率收尾失败掩盖真实的测试失败"。
配套的 playwright.config.ts 还配置了 monocart-reporter 报告器(输出 ./coverage/e2e/report.html 与 cobertura 文件),并对 Next.js 产物做了专门处理:entryFilter 只纳入 /_next/static/ 下的入口、sourceFilter 只统计 src/ 源码,sourcePath 剥掉 webpack://_N_E/ 前缀,sourceMapResolver 从 CI 拷贝的 .next-static-coverage 目录解析 sourcemap。其他关键配置值得注意:
fullyParallel: true,worker 数由PLAYWRIGHT_WORKERS控制,CI 下默认 8;retries: CI 下默认 2(可用PLAYWRIGHT_RETRIES覆盖),forbidOnly: !!CI防止误提交test.only;webServer本地开发时自动执行pnpm start,reuseExistingServer: true;storageState自动接受 Cookie 横幅,避免遮挡元素;screenshot: "only-on-failure",CI 下 trace 策略为on-first-retry。
2.3 运行命令
package.json 中的脚本约定:
# 先以 NEXT_PUBLIC_PW_TEST=true 构建,再运行 E2E(不重复构建)
pnpm test:e2e
# 等价于:
NEXT_PUBLIC_PW_TEST=true next build --turbo && playwright test
# 已有构建产物时跳过构建
pnpm test:e2e:no-build
# UI 交互模式调试 / 录制脚本
pnpm test:e2e:ui
pnpm gentests # playwright codegen http://localhost:3000
三、集成测试(Vitest + RTL):页面级起步 + MSW Mock
3.1 适用场景与文件位置
集成测试用于"组件连同其依赖(API 调用、状态)"的行为验证:带 Mock API 响应的页面级行为、会拉取数据的组件、触发 API 的交互、单页内的功能流程。
文档要求集成测试从页面级起步,不必为每个小组件写,并就近放在组件旁的 __tests__ 目录中:
/library/
__tests__/
main.test.tsx
searching-agents.test.tsx
agents-pagination.test.tsx
page.tsx
useLibraryPage.ts
先写一个 main.test.tsx,随体量增长再拆分。集成测试应做到三件事:渲染页面或复杂模态框(如 AgentPublishModal)→ 通过 MSW Mock API 请求 → 用 Testing Library 断言 UI 场景。
3.2 两条重要偏好
- 优先测 UI 面,而非直接测 hook:如果
use*.tshook 只是为了支撑某个页面/组件,就测那个页面/组件,不要额外加renderHook()测试;只有无法通过 UI 干净验证的共享业务逻辑 hook 才直接测。 - 优先使用 Orval 生成的 Mock:使用
src/app/api/__generated__/endpoints/*/*.msw.ts生成的 MSW handler 与响应构造器,而不是手写 API 响应对象或 Mock 页面/组件的 hook。
// 示例:测试页面渲染 API 数据
import { server } from "@/mocks/mock-server";
import { getDeleteV2DeleteStoreSubmissionMockHandler422 } from "@/app/api/__generated__/endpoints/store/store.msw";
test("shows error when submission fails", async () => {
// 覆盖默认 handler,返回错误状态
server.use(getDeleteV2DeleteStoreSubmissionMockHandler422());
render(<MarketplacePage />);
await screen.findByText("Featured Agents");
// ... 断言错误 UI
});
提示(原文档 Tip):绝大多数情况使用
findBy...系列方法——它们会等待元素出现,异步代码不会造成抖动(flaky);而getBy...不等待、立即报错。
3.3 Vitest 运行环境:setup 文件做了什么
vitest.config.mts 中声明了 environment: "happy-dom",收集 src/**/*.test.tsx、src/**/*.test.ts、scripts/**/*.test.ts,并指定全局 setup 文件为 ./src/tests/integrations/vitest.setup.tsx;覆盖率使用 V8 provider,报告输出 cobertura 到 ./coverage,排除测试与 stories 文件、src/playwright/**、src/tests/**。
全局 setup 文件 vitest.setup.tsx 的源码解释了几个仓库特有的坑:
react的cache垫片:React 18.3 只在react-server导出条件下提供cache,Vitest 未设置该条件,任何从"react"导入cache的模块会在求值时抛 "cache is not a function"。setup 用vi.mock将其垫片为恒等函数——在单测语境下缓存契约退化为"不去重",语义上是正确的。@number-flow/react的 mock:NumberFlow 渲染进 shadow-DOM 自定义元素并在 React 之外调度动画,与 React 18 并发渲染器在清理阶段冲突("Should not already be working")。setup 用纯<span>输出格式化数值替代,因为测试查询的是包裹层的 aria-label 而非其内部实现。- MSW 生命周期:
beforeAll中调用mockNextjsModules()与mockAuthRequest()(后者仅为避免cookies()调用而发送 null 用户;需要用户数据时在具体测试中 Mock 认证动作),随后server.listen({ onUnhandledRequest: "error" })——任何未被 Mock 的请求会直接报错,强制 Mock 落在 API 边界上;afterEach执行cleanup()与server.resetHandlers(),保证用例间 handler 不串扰。
渲染辅助函数在 test-utils.tsx 中:导出的 render 是包了 TestProviders 的 customRender,自动注入 QueryClientProvider(retry: false 避免测试中无限重试)、NuqsTestingAdapter(URL 状态)、BackendAPIProvider、OnboardingProvider、TooltipProvider 五层 Provider;另导出 normalizeWhitespace,因为 MorphingTextAnimation 逐字符渲染并用不换行空格表示空格,断言文本前需归一化空白。
四、单元测试(Vitest + RTL)
单元测试用于隔离的组件与工具函数:纯工具函数(如 lib/utils.ts)、不同 props 下的组件渲染、组件状态变化、带独立业务逻辑的共享 hook。文件与源文件同目录同名:Component.test.tsx 紧邻 Component.tsx。
// 示例:测试组件正确渲染
render(<AgentCard title="My Agent" />);
expect(screen.getByText("My Agent")).toBeInTheDocument();
五、视觉测试(Storybook)
Storybook + Chromatic 用于设计系统与视觉外观:原子组件(Button、Input、Badge)、分子组件(Dialog、Card)、视觉状态(hover、disabled、loading)、响应式布局。Component.stories.tsx 与 Component.tsx 同目录共存。仓库提供对应脚本:pnpm storybook(6006 端口)、pnpm build-storybook、pnpm test-storybook:ci(并发构建静态 Storybook 并运行 test-storybook)。
六、MSW Mocking:默认全绿 + 按用例覆盖错误码
API Mock 由 MSW 承担,handler 由 Orval 从 OpenAPI schema 自动生成。其默认行为是:所有客户端请求都被拦截,返回 200 状态与 faker 生成的数据。
mock-server.ts 仅两行即体现该架构:
import { setupServer } from "msw/node";
import { mockHandlers } from "./mock-handlers";
export const server = setupServer(...mockHandlers);
测试特定错误场景时,用生成好的错误码 handler 覆盖默认行为(如 422)。生成的 handler 位于 src/app/api/__generated__/endpoints/*/,每个端点按不同状态码各有一个 handler。Orval 生成流程由 orval.config.ts 驱动,可通过 pnpm generate:api(或 generate:api:force)刷新。
另有一条边界约定:浏览器专用的 Playwright 辅助代码放在 src/playwright/,不要放进 src/tests/。src/tests/integrations/ 目前实际包含 copilot-sse.ts、mock-auth-request.tsx、setup-nextjs-mocks.tsx、test-utils.tsx 与 vitest.setup.tsx,与文档中的文件组织图一致。
七、目录组织与测试优先级矩阵
文档给出的整体目录结构(相对 src/):
src/
├── components/
│ └── atoms/
│ └── Button/
│ ├── Button.tsx
│ ├── Button.test.tsx # 单元测试
│ └── Button.stories.tsx # 视觉测试
├── app/
│ └── (platform)/
│ └── marketplace/
│ └── components/
│ └── MainMarketplacePage/
│ ├── __tests__/
│ │ ├── main.test.tsx # 集成测试
│ │ └── search-agents.test.tsx # 集成测试
│ ├── MainMarketplacePage.tsx
│ └── useMainMarketplacePage.ts
├── lib/
│ ├── utils.ts
│ └── utils.test.ts # 单元测试
├── mocks/
│ ├── mock-handlers.ts # MSW handlers(Orval 自动生成)
│ └── mock-server.ts # MSW server 配置
├── playwright/
│ ├── *.spec.ts # E2E 测试(Playwright)——集中存放
│ ├── pages/ # Playwright 页面对象
│ └── utils/ # Playwright 辅助 / fixtures
└── tests/
├── integrations/
│ ├── test-utils.tsx # 测试工具
│ └── vitest.setup.tsx # 集成测试 setup
└── AGENTS.md # 面向 agent 的测试指引
优先级矩阵:
| 组件类型 | 测试优先级 | 推荐测试 |
|---|---|---|
| 页面 / 功能 | 最高 | 集成测试 |
| 自定义 Hook | 中 | 所属页面的集成测试,或共享 hook 单元测试 |
| 工具函数 | 高 | 单元测试 |
| Organisms(复杂组件) | 高 | 集成测试 |
| Molecules | 中 | 单元 + Storybook |
| Atoms | 中 | 仅 Storybook* |
*原子组件通常足够简单,Storybook 视觉测试即可覆盖。
八、不要测什么 + 八条黄金法则
不要测:第三方库内部实现(Radix UI、React Query);CSS 样式细节(交给 Storybook);无逻辑的简单 prop 透传组件;TypeScript 类型本身。
黄金法则(原文档 Golden Rules,共 8 条):
- 测行为,不测实现 —— 按 role/text 查询,不按 class name;
- 一个概念一个断言 —— 测试保持聚焦;
- 在边界处 Mock —— Mock API 调用,而非内部函数;
- 集成测试就近存放 ——
__tests__/与组件同目录; - E2E 昂贵 —— 只用于关键 happy path,优先集成测试;
- AI agent 擅长写集成测试 —— 补充测试覆盖时从这里入手;
- 优先组件/页面测试而非 hook 测试 —— 不为组件实现细节添加
renderHook()覆盖; - 使用生成的 API Mock —— 优先 Orval MSW helper,而非手写 API 对象桩。
九、快速上手:完整运行路径小结
在 autogpt_platform/frontend 目录下(Node 24.x、pnpm 10):
pnpm test:unit # vitest run --coverage,单测 + 集成测试
pnpm test:unit:watch # 监听模式
pnpm test:e2e # 先构建(NEXT_PUBLIC_PW_TEST=true)再 playwright test
pnpm test-storybook:ci # 构建 Storybook 静态站点并跑视觉测试
pnpm storybook # 本地 6006 端口查看组件文档
其中 test:unit 对应 vitest.config.mts 的 happy-dom + V8 覆盖率配置;test:e2e 依赖 playwright.config.ts 的 happy-path 匹配规则与 monocart 覆盖率报告。新增 E2E 用例时,文件命名为 xxx-happy-path.spec.ts 并从 ./coverage-fixture 导入 test,即可自动纳入套件与覆盖率统计。
以上规则与配置均出自仓库内 测试规则文档 及其配套源码,可作为为 AutoGPT Platform 前端补充测试覆盖时的标准操作依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00