断言

简介

Playwright 提供了测试断言,使用 expect 函数进行断言。要进行断言,调用 expect(value) 并选择一个反映期望的匹配器。可以使用许多【通用的匹配器】,如 toEqualtoContaintoBeTruthy 等来断言任何条件。

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.pollexpect.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 实例,以拥有自己的默认设置,如 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');
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 回调。

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

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

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

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

与 expect 库兼容

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

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