跳至主要内容

断言

简介

Playwright 包含以 expect 函数形式的测试断言。要进行断言,调用 expect(value) 并选择一个反映期望的匹配器。有许多通用匹配器,如 toEqualtoContaintoBeTruthy,可用于断言任何条件。

expect(success).toBeTruthy();

Playwright 还包含针对网页的async matchers,这些匹配器会等待直到满足预期条件。请看以下示例:

await expect(page.getByTestId('status')).toHaveText('Submitted');

Playwright 将重新测试带有测试ID status 的元素,直到获取的元素包含文本 "Submitted"。它会不断重新获取元素并进行检查,直到满足条件或达到超时时间。您可以通过传递此超时时间或在测试配置中通过 testConfig.expect 值一次性配置它。

默认情况下,断言超时时间设置为5秒。了解更多关于各种超时设置

自动重试断言

以下断言将重试直到断言通过,或达到断言超时。请注意重试断言是异步的,因此您必须await它们。

AssertionDescription
await expect(locator).toBeAttached()Element is attached
await expect(locator).toBeChecked()Checkbox is checked
await expect(locator).toBeDisabled()Element is disabled
await expect(locator).toBeEditable()Element is editable
await expect(locator).toBeEmpty()Container is empty
await expect(locator).toBeEnabled()Element is enabled
await expect(locator).toBeFocused()Element is focused
await expect(locator).toBeHidden()Element is not visible
await expect(locator).toBeInViewport()Element intersects viewport
await expect(locator).toBeVisible()Element is visible
await expect(locator).toContainText()Element contains text
await expect(locator).toHaveAccessibleDescription()Element has a matching accessible description
await expect(locator).toHaveAccessibleName()Element has a matching accessible name
await expect(locator).toHaveAttribute()Element has a DOM attribute
await expect(locator).toHaveClass()Element has a class property
await expect(locator).toHaveCount()List has exact number of children
await expect(locator).toHaveCSS()Element has CSS property
await expect(locator).toHaveId()Element has an ID
await expect(locator).toHaveJSProperty()Element has a JavaScript property
await expect(locator).toHaveRole()Element has a specific ARIA role
await expect(locator).toHaveScreenshot()Element has a screenshot
await expect(locator).toHaveText()Element matches text
await expect(locator).toHaveValue()Input has a value
await expect(locator).toHaveValues()Select has options selected
await expect(page).toHaveScreenshot()Page has a screenshot
await expect(page).toHaveTitle()Page has a title
await expect(page).toHaveURL()Page has a URL
await expect(response).toBeOK()Response has an OK status

非重试断言

这些断言允许测试任何条件,但不会自动重试。大多数情况下,网页会异步显示信息,使用非重试断言可能导致测试不稳定。

尽可能优先使用自动重试断言。对于需要重试的更复杂断言,请使用expect.pollexpect.toPass

AssertionDescription
expect(value).toBe()Value is the same
expect(value).toBeCloseTo()Number is approximately equal
expect(value).toBeDefined()Value is not undefined
expect(value).toBeFalsy()Value is falsy, e.g. false, 0, null, etc.
expect(value).toBeGreaterThan()Number is more than
expect(value).toBeGreaterThanOrEqual()Number is more than or equal
expect(value).toBeInstanceOf()Object is an instance of a class
expect(value).toBeLessThan()Number is less than
expect(value).toBeLessThanOrEqual()Number is less than or equal
expect(value).toBeNaN()Value is NaN
expect(value).toBeNull()Value is null
expect(value).toBeTruthy()Value is truthy, i.e. not false, 0, null, etc.
expect(value).toBeUndefined()Value is 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');

软断言

默认情况下,失败的断言会终止测试执行。Playwright还支持软断言:失败的软断言不会终止测试执行,但会将测试标记为失败。

// Make a few checks that will not stop the test when failed...
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');

// ... and continue the test to check more things.
await page.getByRole('link', { name: 'next page' }).click();
await expect.soft(page.getByRole('heading', { name: 'Make another order' })).toBeVisible();

在测试执行的任何阶段,您都可以检查是否存在任何软断言失败:

// Make a few checks that will not stop the test when failed...
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');

// Avoid running further if there were soft assertion failures.
expect(test.info().errors).toHaveLength(0);

请注意,软断言仅适用于Playwright测试运行器。

自定义期望消息

你可以指定一个自定义的期望消息作为expect函数的第二个参数,例如:

await expect(page.getByText('Name'), 'should be logged in').toBeVisible();

该消息将在报告器中显示,无论是对于通过的还是失败的期望断言,都能提供更多关于断言的上下文信息。

当expect断言通过时,你可能会看到如下成功步骤:

✅ should be logged in    @example.spec.ts:18

当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 |

软断言也支持自定义消息:

expect.soft(value, 'my soft assertion').toBe(56);

expect.configure

您可以创建自己的预配置expect实例,使其具有自己的默认值,例如timeoutsoft

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');

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);

您还可以指定自定义轮询间隔:

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);

expect.toPass

您可以重试代码块,直到它们成功通过。

await expect(async () => {
const response = await page.request.get('https://api.example.com');
expect(response.status()).toBe(200);
}).toPass();

您还可以指定自定义超时和重试间隔:

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
});

请注意,默认情况下toPass的超时时间为0,并且不遵循自定义的expect timeout

使用expect.extend添加自定义匹配器

您可以通过提供自定义匹配器来扩展Playwright断言。这些匹配器将在expect对象上可用。

在这个示例中,我们添加了一个自定义的toHaveAmount函数。自定义匹配器应返回一个pass标志表示断言是否通过,以及一个在断言失败时使用的message回调函数。

fixtures.ts
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,
};
},
});

现在我们可以在测试中使用toHaveAmount

example.spec.ts
import { test, expect } from './fixtures';

test('amount', async () => {
await expect(page.locator('.cart')).toHaveAmount(4);
});

与expect库的兼容性

note

请不要将Playwright的expectexpect混淆。后者并未完全集成到Playwright测试运行器中,因此请确保使用Playwright自带的expect

组合来自多个模块的自定义匹配器

您可以将来自多个文件或模块的自定义匹配器组合在一起。

fixtures.ts
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);
test.spec.ts
import { test, expect } from './fixtures';

test('passes', async ({ database }) => {
await expect(database).toHaveDatabaseUser('admin');
});