mcpField
U
umbraco-e2e-testing

umbraco-e2e-testing

github.com/umbraco测试与质量27372 次安装6 周前更新
代码仓库

关于 umbraco-e2e-testing

使用 Playwright 和 Umbraco acceptance test helpers 对 Umbraco 后台扩展进行端到端测试的技能。它区分核心 Umbraco 操作所用的辅助工具与自定义扩展界面所用的原始 Playwright 操作。

主要功能

  1. 01针对运行中的 Umbraco 实例测试
  2. 02使用辅助工具操作核心界面和 API
  3. 03用原始 Playwright 测试自定义扩展元素
  4. 04配置身份验证和可复用测试装置
  5. 05重置持久化数据以保证测试起点一致

适用场景

  1. 01验证完整的后台用户工作流
  2. 02测试数据持久化和 API 响应
  3. 03检查身份验证与授权
  4. 04在发布前运行验收测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-e2e-testing

安装后在 Claude Code 中输入/umbraco-e2e-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

examples/document-type-crud/
.gitignore
package-lock.json
package.json
playwright.config.ts
README.md
tsconfig.json
umbraco.config.ts
examples/document-type-crud/tests/
auth.setup.ts
document-type.spec.ts

详细介绍

Umbraco 端到端测试

使用 Playwright 和 @umbraco-cms/acceptance-test-helpers 对 Umbraco 后台扩展进行端到端测试。这种方式会针对实际运行的 Umbraco 实例进行测试,验证完整的用户工作流。

重要:使用 Testhelpers 操作 Umbraco 核心功能

对Umbraco 核心操作使用 @umbraco-cms/acceptance-test-helpers:

包用途必需原因
@umbraco-cms/acceptance-test-helpersUI 和 API 辅助工具处理身份验证、导航和核心实体的增删改查
@umbraco/json-models-builders测试数据构建器创建结构正确的有效 Umbraco 实体

为什么要对 Umbraco 核心功能使用 testhelpers?

  • Umbraco 使用 data-mark 而非 data-testid,testhelpers 会处理这一差异
  • 身份验证令牌管理较为复杂,testhelpers 会管理 STORAGE_STAGE_PATH
  • API 设置和清理需要特定的载荷格式,构建器可确保格式正确
  • 选择器会随版本变化,testhelpers 会屏蔽这些差异
// WRONG - Raw Playwright for core Umbraco (brittle)
await page.goto('/umbraco');
await page.fill('[name="email"]', '[email protected]');

// CORRECT - Testhelpers for core Umbraco
import { test } from '@umbraco-cms/acceptance-test-helpers';

test('my test', async ({ umbracoApi, umbracoUi }) => {
  await umbracoUi.goToBackOffice();
  await umbracoUi.login.enterEmail('[email protected]');
});
何时使用原生 Playwright

对于自定义扩展,请使用 umbracoUi.page(原生 Playwright),因为 testhelpers 不认识你的自定义元素:

test('my custom extension', async ({ umbracoUi }) => {
  // Testhelpers for core navigation
  await umbracoUi.goToBackOffice();
  await umbracoUi.content.goToSection(ConstantHelper.sections.settings);

  // Raw Playwright for YOUR custom elements
  await umbracoUi.page.getByRole('link', { name: 'My Custom Item' }).click();
  await expect(umbracoUi.page.locator('my-custom-workspace')).toBeVisible();
});
使用 Testhelpers 的场景使用 umbracoUi.page 的场景
登录/注销自定义树项
导航到任意分区(包括自定义分区)自定义工作区元素
通过 API 创建/编辑文档自定义实体操作
内置 UI 交互自定义 UI 组件

使用场景

  • 测试完整的用户工作流
  • 测试数据持久性
  • 测试身份验证/授权
  • 发布前进行验收测试
  • 使用真实 API 响应进行集成测试

相关技能

  • umbraco-testing - 测试概览的主技能
  • umbraco-playwright-testhelpers - testhelpers 包的完整参考
  • umbraco-test-builders - 用于测试数据的 JsonModels.Builders
  • umbraco-mocked-backoffice - 无需真实后端即可测试(速度更快)

文档


设置

依赖项

添加到 package.json:

{
  "devDependencies": {
    "@playwright/test": "^1.56",
    "@umbraco-cms/acceptance-test-helpers": "^18.0.0",
    "@umbraco/json-models-builders": "^18.0.0",
    "dotenv": "^16.3.1"
  },
  "scripts": {
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:e2e:debug": "playwright test --debug"
  }
}

然后运行:

npm install
npx playwright install chromium

版本兼容性:testhelpers 版本应与 Umbraco 版本匹配:

UmbracoTesthelpers
18.x^18.0.0
17.x^17.0.15
14.x^14.x
配置

创建 playwright.config.ts:

import { defineConfig, devices } from '@playwright/test';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export const STORAGE_STATE = join(__dirname, 'tests/e2e/.auth/user.json');

// CRITICAL: Testhelpers read auth tokens from this file
process.env.STORAGE_STAGE_PATH = STORAGE_STATE;

export default defineConfig({
  testDir: './tests/e2e',
  timeout: 30 * 1000,
  expect: { timeout: 5000 },
  fullyParallel: false,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: 1,
  reporter: process.env.CI ? 'line' : 'html',
  use: {
    baseURL: process.env.UMBRACO_URL || 'https://localhost:44325',
    trace: 'retain-on-failure',
    ignoreHTTPSErrors: true,
    // CRITICAL: Umbraco uses 'data-mark' not 'data-testid'
    testIdAttribute: 'data-mark',
  },
  projects: [
    {
      name: 'setup',
      testMatch: '**/*.setup.ts',
    },
    {
      name: 'e2e',
      testMatch: '**/*.spec.ts',
      dependencies: ['setup'],
      use: {
        ...devices['Desktop Chrome'],
        ignoreHTTPSErrors: true,
        storageState: STORAGE_STATE,
      },
    },
  ],
});
重要设置
设置值必需原因
testIdAttribute'data-mark'Umbraco 使用 data-mark,而非 data-testid
STORAGE_STAGE_PATH指向 user.json 的路径Testhelpers 会从该文件读取身份验证令牌
ignoreHTTPSErrorstrue用于使用自签名证书的本地开发环境

如果不设置 testIdAttribute: 'data-mark',所有 getByTestId() 调用都会失败。

身份验证设置

创建 tests/e2e/auth.setup.ts:

import { test as setup } from '@playwright/test';
import { STORAGE_STATE } from '../../playwright.config';
import { ConstantHelper, UiHelpers } from '@umbraco-cms/acceptance-test-helpers';

setup('authenticate', async ({ page }) => {
  const umbracoUi = new UiHelpers(page);

  await umbracoUi.goToBackOffice();
  await umbracoUi.login.enterEmail(process.env.UMBRACO_USER_LOGIN!);
  await umbracoUi.login.enterPassword(process.env.UMBRACO_USER_PASSWORD!);
  await umbracoUi.login.clickLoginButton();
  await umbracoUi.login.goToSection(ConstantHelper.sections.settings);
  await page.context().storageState({ path: STORAGE_STATE });
});
环境变量

创建 .env(并将其添加到 .gitignore):

UMBRACO_URL=https://localhost:44325
[email protected]
UMBRACO_USER_PASSWORD=yourpassword
UMBRACO_DATA_PATH=/path/to/Umbraco.Web.UI/App_Data  # Optional: for data reset
变量必需用途
UMBRACO_URL是后台 URL
UMBRACO_USER_LOGIN是管理员邮箱
UMBRACO_USER_PASSWORD是管理员密码
UMBRACO_DATA_PATH否用于重置测试数据的 App_Data 路径(参见“使用持久化数据进行测试”)
目录结构
my-extension/
├── src/
│   └── ...
├── tests/
│   └── e2e/
│       ├── .auth/
│       │   └── user.json       # Auth state (gitignored)
│       ├── auth.setup.ts       # Authentication
│       └── my-extension.spec.ts
├── playwright.config.ts
├── .env                        # Gitignored
├── .env.example
└── package.json

模式

测试夹具
import { test } from '@umbraco-cms/acceptance-test-helpers';

test('my test', async ({ umbracoApi, umbracoUi }) => {
  // umbracoApi - API helpers for setup/teardown
  // umbracoUi - UI helpers for backoffice interaction
});
AAA 模式(准备-执行-断言)
test('can create content', async ({ umbracoApi, umbracoUi }) => {
  // Arrange - Setup via API
  await umbracoApi.documentType.createDefaultDocumentType('TestDocType');

  // Act - Perform user actions via UI
  await umbracoUi.goToBackOffice();
  await umbracoUi.content.goToSection(ConstantHelper.sections.content);
  await umbracoUi.content.clickActionsMenuAtRoot();

  // Assert - Verify results
  expect(await umbracoApi.document.doesNameExist('TestContent')).toBeTruthy();
});
幂等清理
test.afterEach(async ({ umbracoApi }) => {
  await umbracoApi.document.ensureNameNotExists(contentName);
  await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});
API 辅助工具(umbracoApi)

文档类型:

await umbracoApi.documentType.createDefaultDocumentType('TestDocType');
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(
  'TestDocType', 'Textstring', dataTypeData.id
);
await umbracoApi.documentType.ensureNameNotExists('TestDocType');

文档:

await umbracoApi.document.createDefaultDocument('TestContent', docTypeId);
await umbracoApi.document.createDocumentWithTextContent(
  'TestContent', docTypeId, 'value', 'Textstring'
);
await umbracoApi.document.publish(contentId);
await umbracoApi.document.ensureNameNotExists('TestContent');

数据类型:

const dataType = await umbracoApi.dataType.getByName('Textstring');
await umbracoApi.dataType.create('MyType', 'Umbraco.TextBox', 'Umb.PropertyEditorUi.TextBox', []);
使用构建器处理复杂数据

对于复杂的测试数据,请使用 @umbraco/json-models-builders:

import { DocumentTypeBuilder, DocumentBuilder } from '@umbraco/json-models-builders';

test('create complex document type', async ({ umbracoApi }) => {
  // Build a document type with multiple properties
  const docType = new DocumentTypeBuilder()
    .withName('Article')
    .withAlias('article')
    .addGroup()
      .withName('Content')
      .addTextBoxProperty()
        .withAlias('title')
        .withLabel('Title')
        .done()
      .addRichTextProperty()
        .withAlias('body')
        .withLabel('Body')
        .done()
      .done()
    .build();

  await umbracoApi.documentType.create(docType);
});

为什么要使用构建器?

  • 流畅的 API 让复杂结构一目了然
  • 确保载荷结构符合 Umbraco API 的要求
  • 处理必填字段和默认值
  • 在 TypeScript 中提供类型安全
UI 辅助工具(umbracoUi)

导航:

await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName('My Page');
在侧边栏中测试自定义树

测试自定义树扩展(例如设置中的扩展)时,请使用此模式处理异步加载和滚动:

test('should click custom tree item', async ({ umbracoUi }) => {
  await umbracoUi.goToBackOffice();
  await umbracoUi.content.goToSection(ConstantHelper.sections.settings);

  // 1. Wait for your tree heading (custom trees often at bottom of sidebar)
  await umbracoUi.page.getByRole('heading', { name: 'My Tree' }).waitFor({ timeout: 15000 });

  // 2. Scroll into view (important - sidebar may be long)
  await umbracoUi.page.getByRole('heading', { name: 'My Tree' }).scrollIntoViewIfNeeded();

  // 3. Wait for tree items to load (async from API)
  const item1Link = umbracoUi.page.getByRole('link', { name: 'Item 1' });
  await item1Link.waitFor({ timeout: 15000 });

  // 4. Click the item
  await item1Link.click();

  // Assert workspace loads
  await expect(umbracoUi.page.locator('my-tree-workspace-editor')).toBeVisible({ timeout: 15000 });
});

为什么使用此模式?

  • 自定义树通常位于设置侧边栏底部
  • 树项会从 API 异步加载
  • 使用 getByRole('link', { name: '...' }) 比通用的 umb-tree-item 选择器更可靠
  • 内置树(文档类型等)也使用 umb-tree-item,会导致选择器冲突

内容操作:

await umbracoUi.content.clickActionsMenuAtRoot();
await umbracoUi.content.clickCreateActionMenuOption();
await umbracoUi.content.chooseDocumentType('TestDocType');
await umbracoUi.content.enterContentName('My Page');
await umbracoUi.content.enterTextstring('My text value');
await umbracoUi.content.clickSaveButton();

常量:

import { ConstantHelper } from '@umbraco-cms/acceptance-test-helpers';

ConstantHelper.sections.content
ConstantHelper.sections.settings
ConstantHelper.buttons.save
ConstantHelper.buttons.saveAndPublish

示例

完整测试
import { expect } from '@playwright/test';
import { ConstantHelper, NotificationConstantHelper, test } from '@umbraco-cms/acceptance-test-helpers';

const contentName = 'TestContent';
const documentTypeName = 'TestDocType';
const dataTypeName = 'Textstring';
const contentText = 'Test content text';

test.afterEach(async ({ umbracoApi }) => {
  await umbracoApi.document.ensureNameNotExists(contentName);
  await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});

test('can create content', { tag: '@smoke' }, async ({ umbracoApi, umbracoUi }) => {
  // Arrange
  const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
  await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(
    documentTypeName, dataTypeName, dataTypeData.id
  );

  // Act
  await umbracoUi.goToBackOffice();
  await umbracoUi.content.goToSection(ConstantHelper.sections.content);
  await umbracoUi.content.clickActionsMenuAtRoot();
  await umbracoUi.content.clickCreateActionMenuOption();
  await umbracoUi.content.chooseDocumentType(documentTypeName);
  await umbracoUi.content.enterContentName(contentName);
  await umbracoUi.content.enterTextstring(contentText);
  await umbracoUi.content.clickSaveButton();

  // Assert
  await umbracoUi.content.waitForContentToBeCreated();
  expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
  const contentData = await umbracoApi.document.getByName(contentName);
  expect(contentData.values[0].value).toBe(contentText);
});

test('can publish content', async ({ umbracoApi, umbracoUi }) => {
  // Arrange
  const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
  const docTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(
    documentTypeName, dataTypeName, dataTypeData.id
  );
  await umbracoApi.document.createDocumentWithTextContent(
    contentName, docTypeId, contentText, dataTypeName
  );

  // Act
  await umbracoUi.goToBackOffice();
  await umbracoUi.content.goToSection(ConstantHelper.sections.content);
  await umbracoUi.content.clickActionsMenuForContent(contentName);
  await umbracoUi.content.clickPublishActionMenuOption();
  await umbracoUi.content.clickConfirmToPublishButton();

  // Assert
  await umbracoUi.content.doesSuccessNotificationHaveText(
    NotificationConstantHelper.success.published
  );
  const contentData = await umbracoApi.document.getByName(contentName);
  expect(contentData.variants[0].state).toBe('Published');
});
可运行示例:tree-example

tree-example 演示了如何对自定义树扩展进行端到端测试:

位置:umbraco-backoffice/examples/tree-example/Client/

# Run E2E tests (requires running Umbraco)
URL=https://localhost:44325 \
[email protected] \
UMBRACO_USER_PASSWORD=yourpassword \
npm run test:e2e                # 7 tests

主要文件:

  • tests/playwright.e2e.config.ts - 包含身份验证设置的端到端配置
  • tests/auth.setup.ts - 使用 testhelpers 进行身份验证
  • tests/tree-e2e.spec.ts - 对设置侧边栏中的自定义树进行测试
可运行示例:notes-wiki(带数据重置的全栈示例)

notes-wiki 演示了如何对持久化数据和 CRUD 操作进行端到端测试:

位置:umbraco-backoffice/examples/notes-wiki/Client/

# Run E2E tests (with data reset)
URL=https://localhost:44325 \
[email protected] \
UMBRACO_USER_PASSWORD=yourpassword \
UMBRACO_DATA_PATH=/path/to/App_Data \
npm run test:e2e                # 16 tests

主要文件:

  • tests/playwright.e2e.config.ts - 配置中包含用于重置数据的 globalSetup
  • tests/global-setup.ts - 在测试前将数据重置为初始状态
  • tests/test-seed-data.json - 已知的测试数据(笔记、文件夹)
  • tests/notes-wiki-e2e.spec.ts - CRUD 和导航测试

演示内容:

  • 使用 goToSection('notes') 测试自定义分区
  • 在每次测试运行前重置基于文件的数据
  • 测试树导航、文件夹和工作区
  • 通过“查看操作”按钮执行实体操作(比右键单击更可靠)
  • 测试仪表板和工作区视图

测试具有持久化数据的扩展

如果扩展会持久化数据(JSON 文件、数据库等),测试需要可预测的初始状态。

全局设置模式

添加 globalSetup,以便在测试前重置数据:

playwright.e2e.config.ts:

export default defineConfig({
  // ... other config
  globalSetup: './global-setup.ts',
});

global-setup.ts:

import { FullConfig } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';

async function globalSetup(config: FullConfig) {
  const dataPath = process.env.UMBRACO_DATA_PATH;
  if (!dataPath) {
    console.warn('⚠️  UMBRACO_DATA_PATH not set. Skipping data reset.');
    return;
  }

  const targetFile = path.join(dataPath, 'MyExtension/data.json');
  const seedFile = path.join(__dirname, 'test-seed-data.json');

  // Ensure directory exists
  fs.mkdirSync(path.dirname(targetFile), { recursive: true });

  // Copy seed data to target
  fs.copyFileSync(seedFile, targetFile);
  console.log('🌱 Reset data to seed state');
}

export default globalSetup;

test-seed-data.json:

{
  "items": [
    { "id": "test-1", "name": "Test Item 1" },
    { "id": "test-2", "name": "Test Item 2" }
  ]
}
环境变量

添加 UMBRACO_DATA_PATH,以定位 Umbraco 的 App_Data 文件夹:

UMBRACO_DATA_PATH=/path/to/Umbraco.Web.UI/App_Data npm run test:e2e

测试自定义分区

自定义分区可使用 testhelpers 的 goToSection() 方法,只需传入分区路径名:

// Section pathname - matches what you defined in section/constants.ts
const MY_SECTION = 'my-section';

// Helper to navigate to custom section using testhelpers
async function goToMySection(umbracoUi: any) {
  await umbracoUi.goToBackOffice();
  await umbracoUi.content.goToSection(MY_SECTION);
  await umbracoUi.page.waitForTimeout(500);
}

test('should navigate to custom section', async ({ umbracoUi }) => {
  await goToMySection(umbracoUi);

  // Assert - your dashboard or tree should be visible
  await expect(umbracoUi.page.getByText('Welcome')).toBeVisible({ timeout: 15000 });
});

// To verify the section exists in the section bar:
test('should display my section', async ({ umbracoUi }) => {
  await umbracoUi.goToBackOffice();
  await expect(umbracoUi.page.getByRole('tab', { name: 'My Section' })).toBeVisible({ timeout: 15000 });
});

测试上下文菜单(实体操作)

测试树项上的实体操作。由于 testhelpers 不涵盖自定义实体操作,因此使用 umbracoUi.page。

重要:在 Umbraco 中,实体操作会作为按钮呈现在下拉菜单中,而不是直接使用 menuitem 角色。最可靠的做法是使用“查看操作”按钮,而不是右键单击:

test('should show delete action via actions button', async ({ umbracoUi }) => {
  await goToMySection(umbracoUi);

  // Wait for tree item
  const itemLink = umbracoUi.page.getByRole('link', { name: 'My Item' });
  await itemLink.waitFor({ timeout: 15000 });

  // Hover to reveal action buttons
  await itemLink.hover();

  // Click the "View actions" button to open dropdown
  const actionsButton = umbracoUi.page.getByRole('button', { name: "View actions for 'My Item'" });
  await actionsButton.click();

  // Wait for dropdown and check for actions (actions are BUTTONS, not menuitems!)
  await umbracoUi.page.waitForTimeout(500);
  const deleteButton = umbracoUi.page.getByRole('button', { name: 'Delete' });
  const renameButton = umbracoUi.page.getByRole('button', { name: 'Rename' });

  // Assert - at least one action should be visible
  await expect(deleteButton.or(renameButton)).toBeVisible({ timeout: 5000 });
});

test('should delete item via actions menu', async ({ umbracoUi }) => {
  await goToMySection(umbracoUi);

  const itemLink = umbracoUi.page.getByRole('link', { name: 'Item to Delete' });
  await itemLink.waitFor({ timeout: 15000 });

  // Hover and open actions menu
  await itemLink.hover();
  await umbracoUi.page.getByRole('button', { name: "View actions for 'Item to Delete'" }).click();

  // Click delete button
  await umbracoUi.page.getByRole('button', { name: 'Delete' }).click();

  // Confirm deletion (if modal appears)
  const confirmButton = umbracoUi.page.getByRole('button', { name: /Confirm|Delete/i });
  if (await confirmButton.isVisible({ timeout: 2000 }).catch(() => false)) {
    await confirmButton.click();
  }

  // Assert - item should be gone
  await expect(itemLink).not.toBeVisible({ timeout: 15000 });
});
替代方案:右键上下文菜单

右键单击也可行,但使用操作按钮的方法更可靠:

// Right-click approach (less reliable than actions button)
await itemLink.click({ button: 'right' });
await umbracoUi.page.waitForTimeout(500);
await umbracoUi.page.getByRole('button', { name: 'Delete' }).click();

CRUD 测试模式

对于自定义扩展,请使用 umbracoUi.page 进行 UI 交互。对于 Umbraco 核心内容,建议使用 umbracoApi 助手进行设置和清理。

通过操作菜单创建(自定义扩展)
test('should create new item', async ({ umbracoUi }) => {
  await goToMySection(umbracoUi);

  // Hover over parent folder and use Create button
  const folderLink = umbracoUi.page.getByRole('link', { name: 'Parent Folder' });
  await folderLink.hover();

  // Scope to specific menu to avoid ambiguity with multiple items
  const folderMenu = umbracoUi.page.getByRole('menu').filter({ hasText: 'Parent Folder' });
  const createButton = folderMenu.getByRole('button', { name: 'Create Note' });
  await createButton.click();

  // Assert - workspace should open with "New" indicator
  await expect(umbracoUi.page.locator('my-workspace-editor')).toBeVisible({ timeout: 15000 });
});
限定到特定树项

如果存在多个包含相似元素的树项,请将选择器限定到特定范围,以避免产生歧义:

// WRONG - ambiguous when multiple folders have "Create" buttons
const createButton = page.getByRole('button', { name: 'Create' });

// CORRECT - scoped to specific folder's menu
const folderMenu = page.getByRole('menu').filter({ hasText: 'My Folder' });
const createButton = folderMenu.getByRole('button', { name: 'Create' });
更新并保存
test('should update item', async ({ umbracoUi }) => {
  await goToMySection(umbracoUi);

  // Navigate to item
  await umbracoUi.page.getByRole('link', { name: 'Test Item' }).click();
  await umbracoUi.page.locator('my-workspace-editor').waitFor({ timeout: 15000 });

  // Update field
  const titleInput = umbracoUi.page.locator('uui-input#title');
  await titleInput.clear();
  await titleInput.fill('Updated Title');

  // Save
  await umbracoUi.page.getByRole('button', { name: /Save/i }).click();

  // Wait for save to complete
  await umbracoUi.page.waitForTimeout(2000);

  // Assert - header should reflect change
  await expect(umbracoUi.page.getByText('Updated Title')).toBeVisible();
});

运行测试

# Run all E2E tests
npm run test:e2e

# Run with UI mode (visual debugging)
npm run test:e2e:ui

# Run specific test file
npx playwright test tests/e2e/my-extension.spec.ts

# Run with specific tag
npx playwright test --grep "@smoke"

# Run in debug mode
npx playwright test --debug

故障排除

getByTestId() 找不到元素

确保在 playwright.config.ts 中设置了 testIdAttribute: 'data-mark'。

身份验证失败
  • 检查 .env 凭据是否正确
  • 确保 Umbraco 实例正在运行
  • 验证是否已设置 STORAGE_STAGE_PATH
测试超时
  • 在配置中增加超时时间
  • 确保 Umbraco 响应正常
  • 检查浏览器控制台中的 JS 错误
测试在 CI 中失败
  • 确保可以访问 Umbraco 实例
  • 在 CI 中设置环境变量
  • 使用 npx playwright install chromium

替代方案:MSW 模式(无需后端)

如需在没有真实 Umbraco 后端的情况下更快地进行测试,请使用模拟的 backoffice方法。

调用:skill: umbraco-mocked-backoffice

方面真实后端(此技能)MSW 模式
设置运行中的 Umbraco 实例克隆 Umbraco-CMS,npm install
身份验证必需不需要
速度较慢较快
使用场景集成/验收UI/组件测试

评论

支持 Markdown