# 端到端测试

本文档旨在为 Gutenberg 项目中如何使用 Playwright 编写端到端测试提供操作指南和最佳实践。

<div class="callout callout-info">

若您正在使用旧版 Jest + Puppeteer 框架，请参阅专用指南。若需从 Jest + Puppeteer 迁移测试，请查阅<a href="https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/migration.md">迁移指南</a>。

</div>

## 运行测试

```bash
# 运行所有可用测试
npm run test:e2e

# 以图形界面模式运行
npm run test:e2e -- --headed

# 在特定浏览器中运行测试（支持 `chromium`、`firefox` 或 `webkit`）
npm run test:e2e -- --project=webkit --project=firefox

# 运行单个测试文件
npm run test:e2e -- <测试文件路径> # 例如：npm run test:e2e -- site-editor/title.spec.js

# 调试模式
npm run test:e2e -- --debug
```

若您在 Linux 环境下开发，目前需以图形界面模式测试 Webkit 浏览器。若不愿或无法使用图形界面运行（例如无图形界面环境），可在命令前添加 [`xvfb-run`](https://manpages.ubuntu.com/manpages/xenial/man1/xvfb-run.1.html) 在虚拟环境中运行：

```bash
# 运行所有可用测试
xvfb-run npm run test:e2e

# 仅运行 webkit 测试
xvfb-run -- npm run test:e2e -- --project=webkit
```

若您正在使用 VS Code 进行编辑，可安装 [Playwright 扩展插件](https://playwright.dev/docs/getting-started-vscode)来辅助运行、编写和调试测试。

## 最佳实践

请阅读 Playwright 的[最佳实践指南](https://playwright.dev/docs/best-practices)。

### 禁止使用 `$`，改用 `locator`

实际上，任何返回 `ElementHandle` 的 API 均[不推荐使用](https://playwright.dev/docs/api/class-page#page-query-selector)，包括 `$`、`$$`、`$eval`、`$$eval` 等。[`Locator`](https://playwright.dev/docs/api/class-locator) 是更优秀的 API，可与 Playwright 的[断言功能](https://playwright.dev/docs/api/class-locatorassertions)配合使用。由于定位器采用惰性求值且不返回 Promise，该特性在页面对象模型中表现尤为出色。

### 使用可访问性选择器

尽可能使用 [`getByRole`](https://playwright.dev/docs/locators#locate-by-role) 构建查询。这样既能编写无障碍查询，又无需依赖内部实现细节：

```js
// 选择包含无障碍名称"Hello World"的按钮（不区分大小写）
page.getByRole( 'button', { name: 'Hello World' } );
```

还支持链式调用以执行复杂查询：

```js
// 在"区块库"区域下选择名称为"按钮"的选项
page.getByRole( 'region', { name: 'Block Library' } )
	.getByRole( 'option', { name: 'Buttons' } )
```

更多使用技巧请参阅[官方文档](https://playwright.dev/docs/locators)。

### 选择器默认采用严格模式

为提升元素查询的最佳实践，选择器默认启用[严格模式](https://playwright.dev/docs/api/class-browser#browser-new-page-option-strict-selectors)，当查询结果包含多个元素时将抛出错误。

### 避免过度封装测试工具，简单工具应内联实现

`e2e-test-utils` 因包含过多工具函数而变得臃肿。其中大部分函数足够简单，可直接在测试中内联实现。借助可访问性选择器，现在编写简单工具函数更加容易。对于特定页面的工具函数，建议采用页面对象模型（但使用 `requestUtils` 清理状态的情况除外，这类工具更适合放在 `e2e-test-utils` 中）。仅当操作足够复杂且需要重复使用时，才考虑创建工具函数。

### 优先采用页面对象模型而非工具函数

如前所述，[页面对象模型](https://playwright.dev/docs/pom)是在特定页面创建可复用工具函数的首选方案。

使用页面对象模型的核心优势在于能将工具函数按命名空间分组，更便于发现和使用。实际上，`e2e-test-utils-playwright` 包中的 `PageUtils` 本身就是页面对象模型，既避免了全局变量的使用，又支持通过 `this` 实现工具函数间的相互调用。

### 通过 REST 接口清理或设置状态

在测试前后手动设置状态效率低下，特别是在测试间需要重复设置时。推荐通过 API 调用来设置状态。请使用 `requestUtils.rest` 和 `requestUtils.batchRest` 调用 [REST API](https://developer.wordpress.org/rest-api/reference/)（如有需要可将其添加到 `requestUtils` 中）。我们仍需为手动设置状态编写测试，但此类测试只需编写一次。

### 避免使用全局变量

在之前的 Jest + Puppeteer 端到端测试中，`page` 和 `browser` 被暴露为全局变量。当同一测试中涉及多个页面/标签页，或需要并行运行多个测试时，这种方式会使工作变得困难。`@playwright/test` 引入了[测试固件](https://playwright.dev/docs/test-fixtures)概念，允许我们将 `page`、`browser` 及其他参数注入到测试中。

### 使用显式断言

单个测试中可根据需要插入多个断言。尽可能使用显式断言是更佳实践。例如，若需在点击按钮前确认其存在，可在执行 `locator.click()` 前使用 `expect( locator ).toBeVisible()`。这样能使测试流程更清晰易读。

## 常见误区

### [过度使用快照](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/overusing-snapshots.md)

## 跨浏览器测试

默认情况下测试仅在 Chromium 中运行。您可以通过给测试添加标签来指定运行浏览器。在测试标题任意位置使用 `@browser` 即可在对应浏览器中运行测试。默认情况下测试始终会在 Chromium 中运行，可通过添加 `-chromium` 后缀禁用 Chromium 测试。支持的浏览器包括 `chromium`、`firefox` 和 `webkit`。

```js
test( '我将在 @firefox 和 @webkit 中运行（默认包含 chromium）', async ( { page } ) => {
	// ...
} );

test( '我仅在 @firefox 中运行（排除 -chromium）', async ( { page } ) => {
	// ...
} );

test.describe( '测试分组 (@webkit, -chromium)', () => {
	test( '我仅在 webkit 中运行', async ( { page } ) => {
		// ...
	} );
} );
```