Vendure Dashboard E2E 测试架构实战:Playwright 双服务器基础设施与 Dashboard 扩展驱动的测试页面
2026/9/16 14:24:40 网站建设 项目流程

Vendure Dashboard E2E 测试架构实战:Playwright 双服务器基础设施与 Dashboard 扩展驱动的测试页面

【免费下载链接】vendureOpen-source headless commerce platform built with TypeScript, NestJS, React, and GraphQL项目地址: https://gitcode.com/GitHub_Trending/ve/vendure

Vendure 是一个基于 TypeScript、NestJS、React 与 GraphQL 构建的开源 headless 电商平台,其管理后台(Dashboard)的端到端测试(E2E)是一套独立的 Playwright 测试套件。本文以 packages/dashboard/e2e/README.md 为核心,结合仓库内的真实配置与源码,系统讲解该套件的运行方式、双服务器架构、配置边界、测试目录组织,以及如何通过 Dashboard 扩展机制为 E2E 测试添加自定义测试页面。读完本文,你将掌握如何在本仓库中运行、理解并扩展这套 E2E 测试基础设施。

套件概览:Playwright + 双服务器

Dashboard E2E 套件位于 packages/dashboard/e2e/,是一套基于Playwright的浏览器级端到端测试,覆盖登录认证、商品目录、客户、营销、订单、系统设置、作业队列等管理后台核心功能。

整套测试基础设施由两个相互独立的服务器协同工作:

  1. Vendure 后端—— 在global-setup.ts中通过@vendure/testingcreateTestEnvironment启动,监听 constants.ts 中定义的端口(当前为3050)。后端会用种子数据(商品、客户等)初始化,并配置自定义字段与测试专用插件。
  2. Vite 开发服务器—— 由 Playwright 配置中的webServer选项启动(见 playwright.config.ts),负责提供 Dashboard 前端页面,这也是 Playwright 测试实际导航访问的服务器。

测试的完整生命周期为:globalSetup启动 Vendure 后端 → PlaywrightwebServer构建并预览 Vite 前端 → 测试执行 →globalTeardown销毁后端。销毁逻辑在 global-teardown.ts 中实现,它读取globalSetup挂到globalThis.__VENDURE_SERVER__上的服务实例并调用server.destroy()

运行测试

packages/dashboard/目录下执行以下命令即可运行整个套件:

CI=true VITE_TEST_PORT=5176 npx playwright test --config e2e/playwright.config.ts --reporter=list

运行单个测试文件:

CI=true VITE_TEST_PORT=5176 npx playwright test --config e2e/playwright.config.ts e2e/tests/components/form-inputs.spec.ts --reporter=list

几个关键环境变量与配置项(见 playwright.config.ts):

  • VITE_TEST_PORT:Vite 预览服务器的端口,未设置时默认回退到5174。测试的baseURLhttp://localhost:${VITE_PORT}
  • CI:CI 环境下启用forbidOnly、将retries设为 1、workers限制为 4,并使用github+list两种 reporter;本地则默认使用 HTML reporter,retries为 0、不限制并行 worker(fullyParallel: true)。
  • VENDURE_CONFIG_PATH:指向fixtures/e2e-vendure-config.ts,供 Vite 插件发现 Dashboard 扩展。
  • VITE_ADMIN_API_PORT/VITE_ADMIN_API_HOST:由webServer注入,告知前端后端的地址与端口。

webServer的实际命令是npx vite build && npx vite preview --port ${VITE_PORT}(而非开发模式的vite dev),超时时间为 120 秒;本地运行时若端口上已有服务器(reuseExistingServer: !process.env.CI)会复用。

双服务器如何共享一套配置:两张配置表的分工

两套服务器使用相互独立的配置,这是理解本套件最关键的一点:

服务器配置用途
Vendure 后端global-setup.ts(导入 fixtures/e2e-shared-config.ts)以测试数据库、CORS、自定义字段及仅服务器端插件启动 Vendure 服务
Vite 开发服务器fixtures/e2e-vendure-config.ts(通过VENDURE_CONFIG_PATH注入)告诉 Vite 插件需要加载哪些 Dashboard 扩展

从源码看,后端的完整配置在 global-setup.ts 中通过mergeConfig(defaultTestConfig, {...})构建,关键点包括:

  • apiOptions.port设为VENDURE_PORT(3050);
  • paymentOptions.paymentMethodHandlers使用e2e-shared-config.ts导出的e2ePaymentMethodHandlers(即dummyPaymentHandler);
  • assetOptions.assetStorageStrategy使用自定义的E2eAssetStorageStrategy——该策略将资源 URL 生成为可解析的绝对地址(http://test-asset.local/${identifier}),避免默认测试策略产生的非法 URL 导致VendureImage组件抛出异常;
  • importExportOptions.importAssetsDir指向packages/core/e2e/fixtures/assets,让种子商品(如 "Laptop")直接拥有真实的主图,资源相关的测试无需运行时再上传;
  • customFields使用e2eCustomFields
  • catalogOptions.collectionFilters显式合并defaultCollectionFilterse2eCollectionFiltersmergeConfig会替换数组,必须手动保留默认值);
  • CORS 需手动设置{ origin: true, credentials: true }——因为 Dashboard 的 fetch 使用credentials: 'include',要求服务器回显请求来源而非通配符*并开启凭证;mergeConfig无法用布尔值覆盖对象,所以此处显式赋值(global-setup.ts)。

而 fixtures/e2e-vendure-config.ts 中的配置并不会真正启动 Vendure 服务器——它的唯一职责是让 Vite 插件的配置内省机制编译出@VendurePlugin装饰器中带dashboard入口点的插件(如FormInputsTestPluginAlertTestPlugin),从而在开发服务器启动时动态导入对应扩展。其中dbConnectionOptionsauthOptions只是满足VendureConfig类型约束的占位符。

重要边界:自定义字段只属于后端配置

自定义字段只能放在global-setup.ts(经由e2e-shared-config.ts)中,绝不能放进e2e-vendure-config.ts

原因在于:Vite 插件会根据其配置生成 Dashboard 的 GraphQL schema,若在其中加入 struct 类型自定义字段,会导致商品创建 mutation 失败(表单会发送空 struct 数据,被后端拒绝)。而 Dashboard 本身会在运行时从后端 API 动态发现自定义字段,因此无需(也不应该)在 Vite 侧重复声明。这一点在 e2e-shared-config.ts 的文件头注释中同样有明确说明。

e2e-shared-config.ts之所以独立成文件,还因为它只包含纯数据(自定义字段、支付处理器、集合过滤器),不含 NestJS 插件或装饰器,因此不需要经过 SWC 编译,可以直接被global-setup.ts静态导入。其中定义的e2eCustomFields覆盖了 string、float、int、boolean、datetime、text、localeString、localeText、list、struct 等几乎所有字段类型,并按 General / SEO / Details / Lists / Struct 多个 tab 组织,用于全面验证 Dashboard 的自定义字段渲染能力;e2eCollectionFilters则复现了 issue #4987(字符串列表参数在配置化操作输入框中丢失数字形式条目)的场景。

测试目录组织

packages/dashboard/e2e/ ├── fixtures/ # 测试数据与插件 │ ├── e2e-shared-config.ts # 自定义字段与支付处理器(仅后端) │ ├── e2e-vendure-config.ts # 供 Vite 插件发现扩展的 Vendure 配置 │ ├── form-inputs-test-plugin.ts # 示例:仅用于 E2E 的 Dashboard 插件 │ ├── form-inputs-test-dashboard/ # 上述插件对应的 Dashboard 扩展 │ │ ├── index.tsx # 入口(defineDashboardExtension) │ │ └── form-inputs-test-page.tsx │ ├── alert-test-plugin.ts # 另一个仅用于 E2E 的插件(多插件告警回归 #4729) │ ├── alert-test-dashboard/ │ │ └── index.tsx │ ├── custom-history-entry-plugin.ts │ └── initial-data.ts ├── page-objects/ # Page Object 模型(login-page / list-page / detail-page) ├── utils/ # 测试工具(crud-test-factory、vendure-admin-client) ├── tests/ │ ├── auth/ # 登录与认证(含 auth.setup.ts 登录态预置) │ ├── catalog/ # 商品、集合、facet、资源 │ ├── components/ # 共享 UI 组件行为 │ ├── customers/ # 客户与客户分组 │ ├── marketing/ # 促销 │ ├── sales/ # 订单与订单修改 │ ├── settings/ # 渠道、角色、支付方式等 │ ├── system/ # 作业队列、健康检查、定时任务 │ ├── dashboard/ # Dashboard 首页与洞察 │ └── regression/ # 历史 issue 回归测试(如 #4729、#4730 等) ├── global-setup.ts ├── global-teardown.ts ├── playwright.config.ts └── constants.ts

目录布局与 README 描述基本一致,仓库中还额外存在page-objects/login-page.tslist-page.base.tsdetail-page.base.ts)与utils/crud-test-factory.tsvendure-admin-client.ts),以及tests/dashboard/tests/regression/两个分类。

Playwright 的认证流程值得单独说明:配置中定义了setupchromium两个 project(playwright.config.ts),setup运行auth.setup.ts完成登录并把登录态写入.auth/admin.jsonchromiumproject 通过storageState复用该登录态,并以dependencies: ['setup']保证顺序,避免每个测试重复执行登录。

通过 Dashboard 扩展添加测试页面

当 E2E 测试需要一个自定义页面(例如在隔离环境中测试表单组件)时,应当使用Dashboard 扩展机制,而不是直接把文件放进src/app/routes/目录。这样做既能把测试代码隔离在生产源码树之外,又能顺带验证真实插件所使用的扩展基础设施。下面按 README 的步骤逐步展开,并给出仓库中的真实实现作为对照。

第 1 步:创建 VendurePlugin

e2e/fixtures/中创建一个极简插件,声明dashboard入口点:

// e2e/fixtures/my-test-plugin.ts import { VendurePlugin } from '@vendure/core'; @VendurePlugin({ dashboard: './my-test-dashboard/index.tsx', }) export class MyTestPlugin {}

仓库中的真实示例是 fixtures/form-inputs-test-plugin.ts:FormInputsTestPlugin通过@VendurePlugin({ dashboard: './form-inputs-test-dashboard/index.tsx' })声明扩展入口。文件头注释说明,该扩展由 Vite 插件的配置内省发现,并在开发服务器启动时被动态导入。

第 2 步:创建 Dashboard 扩展入口

入口文件调用defineDashboardExtension注册路由(可选地注册导航项、widget、表单组件等):

// e2e/fixtures/my-test-dashboard/index.tsx import { defineDashboardExtension } from '@vendure/dashboard'; import { MyTestPage } from './my-test-page'; defineDashboardExtension({ routes: [ { path: '/my-test-page', component: () => <MyTestPage />, }, ], });

真实实现见 fixtures/form-inputs-test-dashboard/index.tsx,它注册了/form-inputs-test路由;fixtures/alert-test-dashboard/index.tsx 则通过另一个独立的defineDashboardExtension调用注册告警——两个插件各自贡献扩展的组合正是 issue #4729(多插件场景下告警不轮询)的触发条件,对应回归测试见 tests/regression/issue-4729-alerts-do-not-poll-multi-plugin.spec.ts。

第 3 步:编写页面组件

页面是普通的 React 组件,无需使用 TanStack Router 的基于文件的动态路由。UI 组件一律从@vendure/dashboard导入(不要用内部的@/vdb/路径):

// e2e/fixtures/my-test-dashboard/my-test-page.tsx import { Page, PageLayout, PageTitle, FullWidthPageBlock } from '@vendure/dashboard'; export function MyTestPage() { return ( <Page pageId="my-test-page"> <PageTitle>My Test Page</PageTitle> <PageLayout> <FullWidthPageBlock blockId="my-test-page"> {/* test content */} </FullWidthPageBlock> </PageLayout> </Page> ); }

真实示例 fixtures/form-inputs-test-dashboard/form-inputs-test-page.tsx 展示了更完整的形态:它用useForm(react-hook-form)为每种输入类型(string、int、boolean、datetime、带 options 的 string)构造ConfigurableFieldDef,通过FormControlAdapter渲染输入控件,并用一个按钮切换Controllerdisabled状态,专门用于验证 issue #4424 中内置表单控件对 disabled 状态的处理(Base UI 的 Switch、Select、Popover 使用 portal 与自定义事件处理器,绕过了 HTML 原生<fieldset disabled>机制)。

第 4 步:在 E2E 的 Vendure 配置中注册插件

把插件加入e2e/fixtures/e2e-vendure-config.ts

import { MyTestPlugin } from './my-test-plugin'; export const config: VendureConfig = { // ... plugins: [FormInputsTestPlugin, MyTestPlugin], };

Playwright 配置已经通过VENDURE_CONFIG_PATH指向该文件,因此 Vite 插件会自动发现新增的 Dashboard 扩展,无需额外接线。真实文件 fixtures/e2e-vendure-config.ts 中注册的是[FormInputsTestPlugin, AlertTestPlugin]

第 5 步:针对页面编写测试

test('should do something on my test page', async ({ page }) => { await page.goto('/my-test-page'); // ... });

仓库中的完整测试示例见 tests/components/form-inputs.spec.ts,它覆盖了多种断言模式,可直接作为模板:

  • page.goto('/form-inputs-test')进入测试页,并断言页面标题可见;
  • [data-slot="field"]定位器配合[data-slot="field-label"]精确筛选指定字段(filter({ has: ... }));
  • 按控件语义角色断言:文本框getByRole('textbox')、数字输入getByRole('spinbutton')、开关getByRole('switch')、日期触发按钮getByRole('button')、下拉getByRole('combobox')
  • 验证 disabled 行为:点击toggle-disabled后断言控件toBeDisabled(),并验证强制点击不会打开 popover / listbox、开关状态不变;
  • 最后验证关闭 disabled 后所有控件恢复可交互,确保状态切换是双向的。

这套用例既覆盖了原生<input>/<textarea>(原生 disabled 机制生效),也覆盖了基于 Base UI 的复合控件(必须由组件自身透传 disabled),体现了该测试页存在的意义。

为什么不用文件拷贝方式添加测试页面?

README 明确列出了把测试文件直接复制进src/app/routes/的三点弊端:

  1. 测试代码会进入生产源码树,存在被随包发布的风险;
  2. TanStack Router 的 Vite 插件会为测试页面生成路由条目,污染routeTree.gen.ts
  3. 文件拷贝方案需要在globalTeardown中清理,一旦测试运行被中断就会留下脏文件,非常脆弱。

而通过扩展机制添加测试页面可以完全规避这些问题,同时还能顺带验证扩展路由基础设施本身的正确性——即测试基础设施与产品基础设施互相印证。

常见陷阱与建议

综合 README 与源码实现,运行或扩展本套件时有几点值得注意:

  • 自定义字段的放置边界:struct 类型自定义字段若出现在 Vite 侧配置中,会导致商品创建 mutation 失败;自定义字段一律放在e2e-shared-config.ts(经global-setup.ts生效),因为 Dashboard 在运行时从后端 API 发现字段。
  • 含装饰器的插件需要 SWC 编译:global-setup.ts 中的importWithSwc函数专门用@swc/core将 fixture 编译为 ES module(开启decoratorsdecoratorMetadata),因为 Playwright 内置的 esbuild/Babel 转译器不支持 NestJS 的emitDecoratorMetadataCustomHistoryEntryPlugin正是因此采用动态加载而非静态导入。
  • mergeConfig的数组与对象语义:它会替换数组(所以collectionFilters要显式保留默认值),也无法用布尔值覆盖对象(所以 CORS 要显式赋对象)。
  • 端口约定:后端固定为VENDURE_PORT = 3050(constants.ts),前端端口由VITE_TEST_PORT决定,默认5174
  • 测试数据来源:种子数据来自 fixtures/initial-data.ts,商品 CSV 复用packages/core/e2e/fixtures/e2e-products-full.csv,资源文件复用packages/core/e2e/fixtures/assets,避免在套件内重复维护一套数据。

结语

Vendure Dashboard 的 E2E 套件展示了一套值得借鉴的双服务器测试架构:用@vendure/testing启动真实后端、用 PlaywrightwebServer承载 Vite 构建的前端,二者通过VENDURE_CONFIG_PATH与环境变量衔接;自定义字段等"数据型"配置与插件扩展等"代码型"配置严格分层,避免 schema 生成与运行时行为互相干扰。而"用扩展机制写测试页面"这一实践,更是把 Dashboard 的插件能力本身纳入了测试覆盖面——测试页面既是测试工具,也是扩展基础设施的验证用例。对于任何基于 Vendure 做二次开发的团队,这套套件既是质量保障,也是学习 Dashboard 扩展机制的绝佳范本。

【免费下载链接】vendureOpen-source headless commerce platform built with TypeScript, NestJS, React, and GraphQL项目地址: https://gitcode.com/GitHub_Trending/ve/vendure

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询