斷言
簡介
Playwright 在 expect
函數中包含了測試斷言。若要進行斷言,請呼叫 expect(value)
並選擇一個符合期望的匹配器。有許多通用匹配器,例如 toEqual
、toContain
、toBeTruthy
,可用於斷言任何條件。
expect(success).toBeTruthy();
Playwright 也包含了網頁特定的非同步匹配器,它們會等待直到滿足預期條件。請看以下範例
await expect(page.getByTestId('status')).toHaveText('Submitted');
Playwright 將會重新測試具有 status
測試 ID 的元素,直到取得的元素具有 "Submitted"
文字。它會重複重新獲取元素並檢查,直到條件滿足或達到逾時時間。您可以傳遞此逾時時間,或透過測試設定中的 testConfig.expect 值設定一次。
預設情況下,斷言的逾時時間設定為 5 秒。深入瞭解各種逾時。
自動重試斷言
以下斷言將會重試,直到斷言通過或達到斷言逾時時間。請注意,重試斷言是非同步的,因此您必須 await
它們。
非重試斷言
這些斷言允許測試任何條件,但不自動重試。大多數時候,網頁會非同步顯示資訊,而使用非重試斷言可能會導致測試不穩定。
盡可能優先使用自動重試斷言。對於需要重試的更複雜斷言,請使用 expect.poll
或 expect.toPass
。
否定匹配器
一般來說,我們可以透過在匹配器前面加上 .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
實例,使其具有自己的預設值,例如 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');
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
回呼。
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
。
import { test, expect } from './fixtures';
test('amount', async () => {
await expect(page.locator('.cart')).toHaveAmount(4);
});
與 expect 函式庫的相容性
請勿將 Playwright 的 expect
與 expect
函式庫混淆。後者未完全與 Playwright 測試執行器整合,因此請務必使用 Playwright 自己的 expect
。
從多個模組組合自訂匹配器
您可以從多個檔案或模組組合自訂匹配器。
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);
import { test, expect } from './fixtures';
test('passes', async ({ database }) => {
await expect(database).toHaveDatabaseUser('admin');
});