first commit
This commit is contained in:
117
contributors/code/e2e/README.md
Normal file
117
contributors/code/e2e/README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 端到端测试
|
||||
|
||||
本文档旨在为 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 } ) => {
|
||||
// ...
|
||||
} );
|
||||
} );
|
||||
```
|
||||
37
contributors/code/e2e/migration.md
Normal file
37
contributors/code/e2e/migration.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 迁移指南
|
||||
|
||||
本文档概述了将 Jest + Puppeteer 测试迁移至 Playwright 的典型流程。请注意,迁移过程也是重构或重写部分测试的良好契机。开始迁移前,请先阅读[最佳实践](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/README.md#best-practices)指南。
|
||||
|
||||
## 测试迁移步骤
|
||||
|
||||
1. 在 `packages/e2e-tests/specs` 中选择要迁移的测试套件,将 `.test.js` 重命名为 `.spec.js`,并置于 `test/e2e/specs` 内的相同文件夹结构中。
|
||||
2. 从 `@wordpress/e2e-test-utils-playwright` 引入测试辅助工具:
|
||||
```js
|
||||
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
|
||||
```
|
||||
3. 将所有出现的 `describe`、`beforeAll`、`beforeEach`、`afterEach` 和 `afterAll` 加上 `test.` 前缀。例如,`describe` 变为 `test.describe`。
|
||||
4. 使用[夹具 API](https://playwright.dev/docs/test-fixtures) 引入之前全局的变量,如 `page` 和 `browser`。
|
||||
5. 删除所有对 `e2e-test-utils` 的导入。改为使用夹具 API 直接获取 `admin`、`editor`、`pageUtils` 和 `requestUtils`。(注意:`admin`、`editor` 和 `pageUtils` 不能在 `beforeAll` 和 `afterAll` 中使用,需改用 `requestUtils` 重写。)
|
||||
6. 如果缺少某个工具函数,且步骤较少,可尝试直接在测试中内联实现操作。如果认为有必要作为测试工具函数实现,请参考以下[指南](#测试工具函数迁移步骤)。
|
||||
7. 根据推荐的[最佳实践](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/README.md#best-practices)手动迁移测试中的其他细节。请注意,尽管 Playwright 和 Puppeteer 的 API 差异较小,但仍需进行一些手动调整。
|
||||
|
||||
## 测试工具函数迁移步骤
|
||||
|
||||
在迁移测试工具函数前,请仔细考虑是否必要。Playwright 提供了许多易读且强大的 API,使得许多工具函数不再需要。可先尝试在测试中直接内联实现相同功能。仅当此方法不适用时,再参考以下指南。适合在 `e2e-test-utils-playwright` 包中实现的工具函数示例包括复杂的浏览器 API(如 `pageUtils.dragFiles` 和 `pageUtils.pressKeys`)以及状态设置 API(如 `requestUtils.*`)。
|
||||
|
||||
<div class="callout callout-info">
|
||||
<code>e2e-test-utils-playwright</code> 包并非旨在完全替代 Jest + Puppeteer 的 <code>e2e-test-utils</code> 包。部分工具函数仅为简化迁移过程而创建,并非必需。
|
||||
</div>
|
||||
|
||||
Playwright 工具函数的组织方式与 `e2e-test-utils` 包略有不同。`e2e-test-utils-playwright` 包将工具函数分为以下文件夹:
|
||||
- `admin` - 与 WordPress 后台或 WordPress 后台用户界面相关的工具函数(例如 `visitAdminPage`)。
|
||||
- `editor` - 用于块编辑器的工具函数(例如 `clickBlockToolbarButton`)。
|
||||
- `pageUtils` - 用于与浏览器交互的通用工具函数(例如 `pressKeys`)。
|
||||
- `requestUtils` - 用于发起 REST API 请求的工具函数(例如 `activatePlugin`)。这些工具函数用于测试的初始化和清理。
|
||||
|
||||
1. 复制 `e2e-test-utils` 中的现有文件,根据工具函数类型粘贴到 `e2e-test-utils-playwright` 的 `admin`、`editor`、`page` 或 `request` 文件夹中。
|
||||
2. 更新工具函数中需要重写的部分:
|
||||
- `page` 和 `browser` 变量在 `admin`、`editor` 和 `pageUtils` 中可通过 `this.page` 和 `this.browser` 访问。
|
||||
- 同一类中的所有其他工具函数可通过 `this` 访问,并绑定到同一实例。可移除所有 `import` 语句,改用 `this` 访问。
|
||||
- 如果熟悉 TypeScript,可考虑将工具函数更新为 TypeScript 版本。
|
||||
3. 在 `index.ts` 中导入新迁移的工具函数,并将其作为实例字段放入 `Admin`/`Editor`/`PageUtils`/`RequestUtils` 类中。
|
||||
125
contributors/code/e2e/overusing-snapshots.md
Normal file
125
contributors/code/e2e/overusing-snapshots.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# 过度使用快照测试
|
||||
|
||||
看看下面的代码。你第一眼能理解这个测试想要做什么吗?
|
||||
|
||||
```js
|
||||
await editor.insertBlock( { name: 'core/quote' } );
|
||||
await page.keyboard.type( '1' );
|
||||
await page.keyboard.press( 'Enter' );
|
||||
await page.keyboard.press( 'Enter' );
|
||||
|
||||
expect( await editor.getEditedPostContent() ).toMatchSnapshot();
|
||||
|
||||
await page.keyboard.press( 'Backspace' );
|
||||
await page.keyboard.type( '2' );
|
||||
|
||||
expect( await editor.getEditedPostContent() ).toMatchSnapshot();
|
||||
```
|
||||
|
||||
这段代码改编自Gutenberg项目中的真实代码,移除了测试标题和注释,并重构为Playwright版本。理想情况下,端到端测试应该具备自文档化和最终用户可读性;毕竟它们试图模拟最终用户与应用程序的交互方式。然而,这段代码中存在几个值得警惕的问题。
|
||||
|
||||
## 快照测试存在的问题
|
||||
|
||||
由Jest推广的[快照测试](https://jestjs.io/docs/snapshot-testing)在恰当使用时是个很好的工具。但可能因为它功能强大,开发者经常过度使用。已有许多[文章](https://kentcdodds.com/blog/effective-snapshot-testing)讨论过这个问题。在这个具体案例中,快照测试未能清晰体现开发者的意图。如果不查看其他信息,我们无法明确断言的具体内容。这使得代码难以理解,给除原作者外的所有读者增加了认知负担。作为读者,我们不得不反复查看代码才能完全理解它们。增加的代码复杂性阻碍了贡献者根据需求修改测试的意愿。有时甚至会让原作者感到困惑,导致意外[提交错误的快照](https://github.com/WordPress/gutenberg/pull/42780#discussion_r949865612)。
|
||||
|
||||
以下是包含测试标题和注释的同一测试。现在你明白这些断言的实际含义了:
|
||||
|
||||
```js
|
||||
it( '可以在末尾拆分', async () => {
|
||||
// ...
|
||||
|
||||
// 期望在引用块外有空段落
|
||||
expect( await getEditedPostContent() ).toMatchSnapshot();
|
||||
|
||||
// ...
|
||||
|
||||
// 期望段落被合并到引用块中
|
||||
expect( await getEditedPostContent() ).toMatchSnapshot();
|
||||
} );
|
||||
```
|
||||
|
||||
开发者的意图现在稍微清晰了些,但仍感觉与测试本身脱节。你可能会想尝试[内联快照](https://jestjs.io/docs/snapshot-testing#inline-snapshots),这确实解决了需要在文件间跳转的问题,但它们仍然不具备自文档化特性也不够明确。我们可以做得更好。
|
||||
|
||||
## 解决方案
|
||||
|
||||
与其在注释中写断言说明,不如尝试直接明确地写出来。借助`editor.getBlocks`方法,我们可以将其重写为更简单和原子化的断言:
|
||||
|
||||
```js
|
||||
// ...
|
||||
|
||||
// 期望在引用块外有空段落
|
||||
await expect.poll( editor.getBlocks ).toMatchObject( [
|
||||
{
|
||||
name: 'core/quote',
|
||||
innerBlocks: [
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '1' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '' },
|
||||
}
|
||||
] );
|
||||
|
||||
// ...
|
||||
|
||||
// 期望段落被合并到引用块中
|
||||
await expect.poll( editor.getBlocks ).toMatchObject( [ {
|
||||
name: 'core/quote',
|
||||
innerBlocks: [
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '1' },
|
||||
},
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '2' },
|
||||
},
|
||||
],
|
||||
} ] );
|
||||
```
|
||||
|
||||
这些断言更加可读和明确。你可以添加额外的断言或将现有断言拆分成多个,以突出它们的重要性。是否保留注释取决于你,但当代码本身已经足够可读时,通常可以省略注释。
|
||||
|
||||
## 快照测试的变体
|
||||
|
||||
由于Playwright缺乏内联快照功能,一些迁移的测试使用字符串断言(`toBe`)来模拟类似效果,避免创建大量快照文件:
|
||||
|
||||
```js
|
||||
expect( await editor.getEditedPostContent() ).toBe( `<!-- wp:paragraph -->
|
||||
<p>段落</p>
|
||||
<!-- /wp:paragraph -->` );
|
||||
```
|
||||
|
||||
我们可以将这种模式视为快照测试的变体,在编写时应遵循相同的规则。通常最好使用`editor.getBlocks`或其他方法重写它们,以进行明确断言:
|
||||
|
||||
```js
|
||||
await expect.poll( editor.getBlocks ).toMatchObject( [ {
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '段落' },
|
||||
} ] );
|
||||
```
|
||||
|
||||
## 测试覆盖率怎么办?
|
||||
|
||||
将明确断言与快照测试比较,我们确实在这个测试中损失了一些测试覆盖率。当我们需要断言块的完整序列化内容时,快照测试仍然有用。但幸运的是,集成测试中的一些测试已经对每个核心块的[完整内容](https://github.com/WordPress/gutenberg/blob/trunk/test/integration/fixtures/blocks/README.md)进行了断言。这些测试在Node.js中运行,比在Playwright中重复相同测试要快得多。在我的机器上运行273个测试用例仅需约5.7秒。这类测试在单元或集成级别效果很好,我们可以在不损失测试覆盖率的情况下更快地运行它们。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
在端到端测试中很少需要快照测试,通常有更好的替代方案可以利用明确断言。在确实没有其他合适替代方案时,我们应遵循使用快照测试的最佳实践。
|
||||
|
||||
### 避免巨大的快照
|
||||
|
||||
巨大的快照难以阅读和审查。而且,当所有内容都重要时,实际上没有任何内容是重要的。巨大的快照会阻碍我们关注快照的重要部分。
|
||||
|
||||
### 避免重复的快照
|
||||
|
||||
如果你发现在同一个测试中创建了多个相似内容的快照,这可能表明你应该进行更原子化的断言。重新思考你想要测试什么,如果第一个快照只是第二个的参考,那么你真正需要的是快照之间的**差异**。将第一个结果存储在变量中,然后断言结果之间的差异。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [有效的快照测试 - Kent C. Dodds](https://kentcdodds.com/blog/effective-snapshot-testing)
|
||||
- [常见测试错误 - Kent C. Dodds](https://kentcdodds.com/blog/common-testing-mistakes)
|
||||
Reference in New Issue
Block a user