首页
/ AutoGPT Platform 前端测试体系实战:Playwright + Vitest + MSW 四层测试策略与落地细节

AutoGPT Platform 前端测试体系实战:Playwright + Vitest + MSW 四层测试策略与落地细节

2026-09-06 13:39:38作者:凌朦慧Richard

本文为 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 命名:

playwright.config.ts 中通过 testMatch: /.*-happy-path\.spec\.ts/ 精确锁定了这一命名约定——只有 happy-path 命名的规格文件会进入 E2E 套件,与文档"E2E 昂贵、只覆盖关键路径"的原则在配置层面相互印证。

2.2 必须从 coverage-fixture 导入 test

文档明确规定:导入 testexpect 时必须使用 ./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 startreuseExistingServer: 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*.ts hook 只是为了支撑某个页面/组件,就测那个页面/组件,不要额外加 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.tsxsrc/**/*.test.tsscripts/**/*.test.ts,并指定全局 setup 文件为 ./src/tests/integrations/vitest.setup.tsx;覆盖率使用 V8 provider,报告输出 cobertura 到 ./coverage,排除测试与 stories 文件、src/playwright/**src/tests/**

全局 setup 文件 vitest.setup.tsx 的源码解释了几个仓库特有的坑:

  1. reactcache 垫片:React 18.3 只在 react-server 导出条件下提供 cache,Vitest 未设置该条件,任何从 "react" 导入 cache 的模块会在求值时抛 "cache is not a function"。setup 用 vi.mock 将其垫片为恒等函数——在单测语境下缓存契约退化为"不去重",语义上是正确的。
  2. @number-flow/react 的 mock:NumberFlow 渲染进 shadow-DOM 自定义元素并在 React 之外调度动画,与 React 18 并发渲染器在清理阶段冲突("Should not already be working")。setup 用纯 <span> 输出格式化数值替代,因为测试查询的是包裹层的 aria-label 而非其内部实现。
  3. MSW 生命周期beforeAll 中调用 mockNextjsModules()mockAuthRequest()(后者仅为避免 cookies() 调用而发送 null 用户;需要用户数据时在具体测试中 Mock 认证动作),随后 server.listen({ onUnhandledRequest: "error" })——任何未被 Mock 的请求会直接报错,强制 Mock 落在 API 边界上;afterEach 执行 cleanup()server.resetHandlers(),保证用例间 handler 不串扰。

渲染辅助函数在 test-utils.tsx 中:导出的 render 是包了 TestProviderscustomRender,自动注入 QueryClientProviderretry: false 避免测试中无限重试)、NuqsTestingAdapter(URL 状态)、BackendAPIProviderOnboardingProviderTooltipProvider 五层 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.tsxComponent.tsx 同目录共存。仓库提供对应脚本:pnpm storybook(6006 端口)、pnpm build-storybookpnpm 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.tsmock-auth-request.tsxsetup-nextjs-mocks.tsxtest-utils.tsxvitest.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 条):

  1. 测行为,不测实现 —— 按 role/text 查询,不按 class name;
  2. 一个概念一个断言 —— 测试保持聚焦;
  3. 在边界处 Mock —— Mock API 调用,而非内部函数;
  4. 集成测试就近存放 —— __tests__/ 与组件同目录;
  5. E2E 昂贵 —— 只用于关键 happy path,优先集成测试;
  6. AI agent 擅长写集成测试 —— 补充测试覆盖时从这里入手;
  7. 优先组件/页面测试而非 hook 测试 —— 不为组件实现细节添加 renderHook() 覆盖;
  8. 使用生成的 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 前端补充测试覆盖时的标准操作依据。

登录后查看全文
热门项目推荐
相关项目推荐