关于 umbraco-mocked-backoffice
用于在 API 调用被模拟的 Umbraco 后台界面中测试扩展的技能,无需 .NET 后端。它介绍 MSW 和模拟仓库方案及 Playwright 测试配置;外部扩展支持仍待 Umbraco 更新。
主要功能
- 01无需 .NET 后端运行后台界面
- 02使用 MSW 处理器模拟 API
- 03用模拟仓库提供可预测数据
- 04配置扩展的 Playwright 测试
- 05介绍后台 API 的模拟数据
适用场景
- 01开发期间目视测试扩展
- 02通过模拟请求测试加载和错误状态
- 03无需后端配置运行扩展界面测试
- 04无需基础设施展示扩展
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ umbraco/ umbraco-cms-backoffice-skills --skill umbraco-mocked-backoffice安装后在 Claude Code 中输入/umbraco-mocked-backoffice确认已加载。
附带文件patterns/external-msw-handlers.mdpatterns/mock-repository-pattern.md
详细介绍
Umbraco 模拟后台
状态: 这项技能目前正在等待 Umbraco 更新,以便外部扩展能够使用模拟后台。本文档介绍的模式在 Umbraco-CMS 源代码仓库中运行时有效。
运行完整的 Umbraco 后台 UI,并模拟所有 API 调用——无需 .NET 后端。
适用场景
- 在开发过程中对扩展进行视觉测试
- 无需部署后端即可快速迭代
- 在逼真的 UI 环境中测试扩展
- 无需基础设施即可演示扩展
- 无需配置后端即可进行 CI/CD 测试
相关技能
- umbraco-example-generator - 为模拟后台设置扩展(从这里开始)
- umbraco-testing - 测试概览的主技能
- umbraco-unit-testing - 独立测试扩展逻辑
- umbraco-e2e-testing - 针对真实的 Umbraco 实例进行测试
两种模拟方式
具有自定义 API 的扩展可以使用两种模拟方式:
| 方式 | 使用场景 | 最适合 |
|---|---|---|
| MSW 处理程序 | 网络级 API 模拟 | 测试错误处理、加载状态、重试 |
| 模拟存储库 | 应用级模拟 | 使用可预测的数据测试 UI(推荐) |
这两种方式都需要启用 MSW(VITE_UMBRACO_USE_MSW=on),以模拟 Umbraco 核心 API。
设置
创建扩展
使用 umbraco-example-generator 技能设置扩展:
调用:skill: umbraco-example-generator
包括:
- 克隆 Umbraco-CMS 仓库
- 扩展结构和
src/index.ts要求 - 使用
VITE_EXAMPLE_PATH和npm run dev运行
添加测试依赖项
{
"devDependencies": {
"@playwright/test": "^1.56"
},
"scripts": {
"test:mock-repo": "playwright test --config=tests/mock-repo/playwright.config.ts",
"test:msw": "playwright test --config=tests/msw/playwright.config.ts"
}
}
npm install
npx playwright install chromium
目录结构
my-extension/Client/
├── src/
│ ├── index.ts # Entry point (loads manifests, registers MSW handlers)
│ ├── manifests.ts # Production manifests
│ ├── feature/
│ │ ├── my-element.ts
│ │ └── types.ts
│ └── msw/ # MSW handlers (loaded from index.ts)
│ └── handlers.ts
├── tests/
│ ├── mock-repo/ # Mock repository tests
│ │ ├── playwright.config.ts
│ │ ├── my-extension.spec.ts
│ │ └── mock/
│ │ ├── index.ts # Mock manifests (replaces repository)
│ │ ├── mock-repository.ts
│ │ └── mock-data.ts
│ └── msw/ # MSW tests
│ ├── playwright.config.ts
│ └── my-extension.spec.ts
├── package.json
└── tsconfig.json
入口点(src/index.ts)
入口点会根据环境有条件地加载 MSW 处理程序或模拟清单:
// Entry point for external extension loading
// Run from Umbraco.Web.UI.Client with:
// VITE_EXAMPLE_PATH=/path/to/extension/Client VITE_UMBRACO_USE_MSW=on npm run dev
// VITE_EXAMPLE_PATH=/path/to/extension/Client VITE_USE_MOCK_REPO=on VITE_UMBRACO_USE_MSW=on npm run dev
// Register MSW handlers when running in MSW mode (but not mock-repo mode)
if (import.meta.env.VITE_UMBRACO_USE_MSW === 'on' && import.meta.env.VITE_USE_MOCK_REPO !== 'on') {
import('./msw/handlers.js').then(({ createHandlers }) => {
const { addMockHandlers } = (window as any).MockServiceWorker;
addMockHandlers(...createHandlers());
});
}
// Export manifests - use mock repository if VITE_USE_MOCK_REPO is set
export const manifests = import.meta.env.VITE_USE_MOCK_REPO === 'on'
? (await import('../tests/mock-repo/mock/index.js')).manifests
: (await import('./manifests.js')).manifests;
运行测试
独立支持外部示例(自动)
从 Umbraco 客户端之外的路径加载扩展,需要客户端支持绝对路径形式的 VITE_EXAMPLE_PATH(通过 Vite 的 /@fs/),并提供共享包解析器,以便 @umbraco-cms/backoffice、lit 和 @umbraco-ui/uui 都解析到客户端中的同一份副本。正式发布的 v18 仅支持相对路径(./examples/...)。
为保持独立性,本仓库的模拟配置通过 Playwright globalSetup/globalTeardown
(plugins/umbraco-testing-skills/harness/mocked-backoffice/),在开发服务器启动前向 UMBRACO_CLIENT_PATH 指定的客户端注入此支持,并在之后还原
(基于文件备份;会检测客户端是否已具备该支持,如有则不作处理)。因此无需使用 CMS 分支——只需一个已构建的 v18 客户端(在 Umbraco.Web.UI.Client 中运行 npm ci)。
首次在刚打过补丁的客户端上运行时,可能会重新优化依赖并短暂重启开发服务器(首次导航可能不稳定);配置中设定了
retries,以应对这种情况。后续运行稳定。
环境变量
| 变量 | 值 | 用途 |
|---|---|---|
VITE_EXAMPLE_PATH | /path/to/extension/Client | 扩展目录的路径 |
VITE_UMBRACO_USE_MSW | on | 为 Umbraco 核心 API 启用 MSW |
VITE_USE_MOCK_REPO | on | 使用模拟存储库代替 MSW 处理程序 |
UMBRACO_CLIENT_PATH | /path/to/Umbraco.Web.UI.Client | Umbraco 客户端的路径(用于 Playwright) |
手动启动开发服务器
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
# MSW mode (uses your handlers for custom APIs)
VITE_EXAMPLE_PATH=/path/to/extension/Client VITE_UMBRACO_USE_MSW=on npm run dev
# Mock repository mode (uses mock repository for custom APIs)
VITE_EXAMPLE_PATH=/path/to/extension/Client VITE_USE_MOCK_REPO=on VITE_UMBRACO_USE_MSW=on npm run dev
运行测试
cd /path/to/extension/Client
# Set path to Umbraco client
export UMBRACO_CLIENT_PATH=/path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
# Run MSW tests
npm run test:msw
# Run mock repository tests
npm run test:mock-repo
Playwright 配置示例
创建 tests/msw/playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const EXTENSION_PATH = resolve(__dirname, '../..');
const UMBRACO_CLIENT_PATH = process.env.UMBRACO_CLIENT_PATH;
if (!UMBRACO_CLIENT_PATH) {
throw new Error('UMBRACO_CLIENT_PATH environment variable is required');
}
const DEV_SERVER_PORT = 5176;
export default defineConfig({
testDir: '.',
testMatch: ['*.spec.ts'],
timeout: 60000,
expect: { timeout: 15000 },
fullyParallel: false,
workers: 1,
// Start dev server with extension and MSW enabled
webServer: {
command: `VITE_EXAMPLE_PATH=${EXTENSION_PATH} VITE_UMBRACO_USE_MSW=on npm run dev -- --port ${DEV_SERVER_PORT}`,
cwd: UMBRACO_CLIENT_PATH,
port: DEV_SERVER_PORT,
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
use: {
baseURL: `http://localhost:${DEV_SERVER_PORT}`,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
对于模拟存储库测试,将命令改为包含 VITE_USE_MOCK_REPO=on:
command: `VITE_EXAMPLE_PATH=${EXTENSION_PATH} VITE_USE_MOCK_REPO=on VITE_UMBRACO_USE_MSW=on npm run dev -- --port ${DEV_SERVER_PORT}`,
测试模式
导航辅助函数
import { type Page } from '@playwright/test';
async function navigateToSettings(page: Page) {
await page.goto('/section/settings');
await page.waitForLoadState('domcontentloaded');
await page.waitForSelector('umb-section-sidebar', { timeout: 30000 });
}
测试树项目
test('should display root tree items', async ({ page }) => {
await navigateToSettings(page);
await page.waitForSelector('umb-tree-item', { timeout: 15000 });
const treeItems = page.locator('umb-tree-item');
await expect(treeItems.first()).toBeVisible();
});
test('should expand tree item to show children', async ({ page }) => {
await navigateToSettings(page);
const expandableItem = page.locator('umb-tree-item').filter({ hasText: 'Group A' });
const expandButton = expandableItem.locator('button[aria-label="toggle child items"]');
await expandButton.click();
const childItem = page.locator('umb-tree-item').filter({ hasText: 'Child 1' });
await expect(childItem).toBeVisible({ timeout: 15000 });
});
MSW 模拟文档 URL
| 文档名称 | URL 路径 |
|---|---|
| 最简单的文档 | /section/content/workspace/document/edit/the-simplest-document-id |
| 所有属性 | /section/content/workspace/document/edit/all-property-editors-document-id |
故障排除
扩展未显示
- 检查扩展是否从
src/index.ts导出了manifests数组 - 检查浏览器控制台中是否有错误
- 确认
VITE_EXAMPLE_PATH指向Client目录
测试因等待元素而超时
- 确保开发服务器正在运行,且已加载扩展
- 检查浏览器控制台中是否有扩展加载错误
- 初次等待元素出现时使用更长的超时时间(15000ms+)
MSW 处理程序未拦截请求
- 检查控制台中是否有显示处理程序注册情况的
[MSW]日志 - 确认处理程序 URL 模式与实际 API 调用相匹配
- 使用浏览器 DevTools 的 Network 标签页查看实际请求 URL
可运行示例
请参阅 umbraco-backoffice-skills/examples/tree-example/Client/ 中的 tree-example:
| 路径 | 说明 |
|---|---|
src/index.ts | 根据条件加载清单的入口点 |
src/msw/handlers.ts | 用于自定义 API 的 MSW 处理程序 |
tests/mock-repo/ | 模拟仓库测试 |
tests/msw/ | MSW 测试 |
cd tree-example/Client
export UMBRACO_CLIENT_PATH=/path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run test:msw # Run MSW tests
npm run test:mock-repo # Run mock repository tests
模拟了哪些内容?
MSW 为所有后台 API 提供模拟数据:
- 文档、媒体、成员
- 文档类型、媒体类型、成员类型
- 数据类型、模板、样式表
- 用户、用户组、权限
- 语言、区域设置、词典项