断言
简介
Playwright 提供了测试断言,使用 expect
函数进行断言。要进行断言,调用 expect(value)
并选择一个反映期望的匹配器。可以使用许多【通用的匹配器】,如 toEqual
、toContain
、toBeTruthy
等来断言任何条件。
expect(success).toBeTruthy();
javascript
Playwright 还包括基于 Web 的【异步匹配器】,这些匹配器会等待直到预期条件满足。例如:
await expect(page.getByTestId('status')).toHaveText('Submitted');
javascript
Playwright 会重新测试具有 status
测试 ID 的元素,直到该元素的文本是 "Submitted"
。它会反复重新获取元素并进行检查,直到条件满足或者超时。你可以通过测试配置中的 testConfig.expect
来设置超时值。
默认情况下,断言的超时时间为 5 秒。你可以了解更多关于【超时的信息】。
自动重试断言
以下断言会自动重试,直到断言通过,或者达到断言超时。需要注意的是,重试断言是异步的,因此必须使用 await
进行等待。
断言 | 描述 |
---|---|
await expect(locator).toBeAttached() |
元素已附加 |
await expect(locator).toBeChecked() |
复选框已勾选 |
await expect(locator).toBeDisabled() |
元素已禁用 |
await expect(locator).toBeEditable() |
元素可编辑 |
await expect(locator).toBeEmpty() |
容器为空 |
await expect(locator).toBeEnabled() |
元素已启用 |
await expect(locator).toBeFocused() |
元素已聚焦 |
await expect(locator).toBeHidden() |
元素不可见 |
await expect(locator).toBeInViewport() |
元素在视口内 |
await expect(locator).toBeVisible() |
元素可见 |
await expect(locator).toContainText() |
元素包含文本 |
await expect(locator).toHaveAccessibleDescription() |
元素具有匹配的可访问描述 |
await expect(locator).toHaveAccessibleName() |
元素具有匹配的可访问名称 |
await expect(locator).toHaveAttribute() |
元素具有指定的 DOM 属性 |
await expect(locator).toHaveClass() |
元素具有指定的类 |
await expect(locator).toHaveCount() |
列表有指定数量的子元素 |
await expect(locator).toHaveCSS() |
元素具有指定的 CSS 属性 |
await expect(locator).toHaveId() |
元素具有指定的 ID |
await expect(locator).toHaveJSProperty() |
元素具有指定的 JavaScript 属性 |
await expect(locator).toHaveRole() |
元素具有指定的 ARIA 角色 |
await expect(locator).toHaveScreenshot() |
元素具有截图 |
await expect(locator).toHaveText() |
元素具有指定的文本 |
await expect(locator).toHaveValue() |
输入框有指定的值 |
await expect(locator).toHaveValues() |
选择框选择了选项 |
await expect(page).toHaveScreenshot() |
页面有截图 |
await expect(page).toHaveTitle() |
页面有标题 |
await expect(page).toHaveURL() |
页面有 URL |
await expect(response).toBeOK() |
响应是 OK 状态 |
非自动重试断言
这些断言可以测试任何条件,但不会自动重试。大多数时候,网页是异步显示信息的,使用非重试断言可能会导致不稳定的测试。
尽可能使用自动重试断言。对于需要重试的复杂断言,使用 expect.poll
或 expect.toPass
。
断言 | 描述 |
---|---|
expect(value).toBe() |
值相同 |
expect(value).toBeCloseTo() |
数字近似相等 |
expect(value).toBeDefined() |
值不是 undefined |
expect(value).toBeFalsy() |
值为假,例如 false、0、null 等 |
expect(value).toBeGreaterThan() |
数字大于指定值 |
expect(value).toBeGreaterThanOrEqual() |
数字大于或等于指定值 |
expect(value).toBeInstanceOf() |
对象是某个类的实例 |
expect(value).toBeLessThan() |
数字小于指定值 |
expect(value).toBeLessThanOrEqual() |
数字小于或等于指定值 |
expect(value).toBeNaN() |
值为 NaN |
expect(value).toBeNull() |
值为 null |
expect(value).toBeTruthy() |
值为真,即不是 false、0、null 等 |
expect(value).toBeUndefined() |
值为 undefined |
expect(value).toContain() |
字符串包含子字符串 |
expect(value).toContain() |
数组或集合包含某个元素 |
expect(value).toContainEqual() |
数组或集合包含类似的元素 |
expect(value).toEqual() |
值相似 - 深度相等和模式匹配 |
expect(value).toHaveLength() |
数组或字符串的长度 |
expect(value).toHaveProperty() |
对象具有指定的属性 |
expect(value).toMatch() |
字符串匹配正则表达式 |
expect(value).toMatchObject() |
对象包含指定的属性 |
expect(value).toStrictEqual() |
值相似,包括属性类型 |
expect(value).toThrow() |
函数抛出错误 |
expect(value).any() |
匹配任意实例 |
expect(value).anything() |
匹配任意值 |
expect(value).arrayContaining() |
数组包含特定元素 |
expect(value).closeTo() |
数字近似相等 |
expect(value).objectContaining() |
对象包含特定属性 |
expect(value).stringContaining() |
字符串包含子字符串 |
expect(value).stringMatching() |
字符串匹配正则表达式 |
否定匹配器
通常,我们可以通过在匹配器前加 .not
来断言相反的情况:
expect(value).not.toEqual(0);
await expect(locator).not.toContainText('some text');
javascript
软断言
默认情况下,断言失败会终止测试执行。Playwright 还支持软断言:软断言失败不会终止测试执行,但会将测试标记为失败。
// 执行一些检查,如果失败不会停止测试...
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');
// ...继续测试,检查更多内容
await page.getByRole('link', { name: 'next page' }).click();
await expect.soft(page.getByRole('heading', { name: 'Make another order' })).toBeVisible();
javascript
在测试执行过程中,你可以随时检查是否有任何软断言失败:
// 执行一些检查,如果失败不会停止测试...
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');
// 如果有软断言失败,避免继续执行
expect(test.info().errors).toHaveLength(0);
javascript
注意:软断言仅适用于 Playwright 测试运行器。
自定义期望消息
你可以在 expect 函数的第二个参数中指定自定义期望消息。例如:
await expect(page.getByText('Name'), 'should be logged in').toBeVisible();
javascript
这个消息将显示在报告中,无论断言是通过还是失败,提供关于断言的更多上下文。
当 expect
通过时,你可能会看到像这样的成功步骤:
✅ should be logged in @example.spec.ts:18
bash
当 expect 失败时,错误信息将如下所示:
Error: should be logged in
Call log:
- expect.toBeVisible with timeout 5000ms
- waiting for "getByText('Name')"
2 |
3 | test('example test', async({ page }) => {
> 4 | await expect(page.getByText('Name'), 'should be logged in').toBeVisible();
| ^
5 | });
6 |
bash
软断言也支持自定义消息:
expect.soft(value, 'my soft assertion').toBe(56);
javascript
expect.configure
您可以创建自己预配置的 expect
实例,以拥有自己的默认设置,如 timeout
和 soft
。
const slowExpect = expect.configure({ timeout: 10000 });
await slowExpect(locator).toHaveText('Submit');
// Always do soft assertions.
const softExpect = expect.configure({ soft: true });
await softExpect(locator).toHaveText('Submit');
javascript
expect.poll
您可以使用 expect.poll
将任何同步的 expect
转换为异步轮询。
以下方法将轮询给定的函数,直到它返回 HTTP 状态 200:
await expect.poll(async () => {
const response = await page.request.get('https://api.example.com');
return response.status();
}, {
// Custom expect message for reporting, optional.
message: 'make sure API eventually succeeds',
// Poll for 10 seconds; defaults to 5 seconds. Pass 0 to disable timeout.
timeout: 10000,
}).toBe(200);
javascript
您还可以指定自定义轮询间隔:
await expect.poll(async () => {
const response = await page.request.get('https://api.example.com');
return response.status();
}, {
// Probe, wait 1s, probe, wait 2s, probe, wait 10s, probe, wait 10s, probe
// ... Defaults to [100, 250, 500, 1000].
intervals: [1_000, 2_000, 10_000],
timeout: 60_000
}).toBe(200);
javascript
expect.toPass
您可以重试代码块,直到它们成功通过为止。
await expect(async () => {
const response = await page.request.get('https://api.example.com');
expect(response.status()).toBe(200);
}).toPass();
javascript
您还可以指定自定义超时和重试间隔:
await expect(async () => {
const response = await page.request.get('https://api.example.com');
expect(response.status()).toBe(200);
}).toPass({
// Probe, wait 1s, probe, wait 2s, probe, wait 10s, probe, wait 10s, probe
// ... Defaults to [100, 250, 500, 1000].
intervals: [1_000, 2_000, 10_000],
timeout: 60_000
});
javascript
请注意,默认情况下,toPass
的超时为 0,并且不尊重自定义的 【expect 超时】。
使用 expect.extend 添加自定义匹配器
您可以通过提供自定义匹配器来扩展 Playwright 断言。这些匹配器将在 expect
对象上可用。
在这个例子中,我们添加了一个自定义的 toHaveAmount
函数。自定义匹配器应该返回一个表示断言是否通过的 pass
标志,以及一个在断言失败时使用的 message
回调。
import { expect as baseExpect } from '@playwright/test'; import type { Page, Locator } from '@playwright/test'; export { test } from '@playwright/test'; export const expect = baseExpect.extend({ async toHaveAmount(locator: Locator, expected: number, options?: { timeout?: number }) { const assertionName = 'toHaveAmount'; let pass: boolean; let matcherResult: any; try { await baseExpect(locator).toHaveAttribute('data-amount', String(expected), options); pass = true; } catch (e: any) { matcherResult = e.matcherResult; pass = false; } const message = pass ? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + '\n\n' + `Locator: ${locator}\n` + `Expected: not ${this.utils.printExpected(expected)}\n` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '') : () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + '\n\n' + `Locator: ${locator}\n` + `Expected: ${this.utils.printExpected(expected)}\n` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : ''); return { message, pass, name: assertionName, expected, actual: matcherResult?.actual, }; }, });
typescript
现在我们可以在测试中使用 toHaveAmount
。
import { test, expect } from './fixtures'; test('amount', async () => { await expect(page.locator('.cart')).toHaveAmount(4); });
typescript
与 expect 库兼容
不要混淆 Playwright 的 |
组合来自多个模块的自定义匹配器
您可以将来自多个文件或模块的自定义匹配器结合起来。
import { mergeTests, mergeExpects } from '@playwright/test'; import { test as dbTest, expect as dbExpect } from 'database-test-utils'; import { test as a11yTest, expect as a11yExpect } from 'a11y-test-utils'; export const expect = mergeExpects(dbExpect, a11yExpect); export const test = mergeTests(dbTest, a11yTest);
typescript
import { test, expect } from './fixtures'; test('passes', async ({ database }) => { await expect(database).toHaveDatabaseUser('admin'); });
typescript