跳到主要內容

斷言

簡介

Playwright 在 expect 函數中包含了測試斷言。若要進行斷言,請呼叫 expect(value) 並選擇一個符合期望的匹配器。有許多通用匹配器,例如 toEqualtoContaintoBeTruthy,可用於斷言任何條件。

expect(success).toBeTruthy();

Playwright 也包含了網頁特定的非同步匹配器,它們會等待直到滿足預期條件。請看以下範例

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

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()元素具有 class 屬性
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()值為 falsy,例如 false0null 等。
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()值為 truthy,即不是 false0null 等。
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');

軟斷言

預設情況下,斷言失敗會終止測試執行。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 訊息

您可以將自訂 expect 訊息指定為 expect 函數的第二個引數,例如

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

此訊息將會顯示在報告器中,無論是通過還是失敗的 expect,以提供更多關於斷言的上下文。

當 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 逾時時間

使用 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 函式庫的相容性

注意

請勿將 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');
});