斷言
簡介
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 也支援軟斷言:失敗的軟斷言不會終止測試執行,但會將測試標記為失敗。
// 進行一些檢查,失敗時不會停止測試...
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();
在測試執行期間的任何時候,您可以檢查是否有任何軟斷言失敗:
// 進行一些檢查,失敗時不會停止測試...
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');
// 如果有軟斷言失敗,避免繼續執行。
expect(test.info().errors).toHaveLength(0);
請注意,軟斷言只在 Playwright 測試執行器中有效。
自訂預期訊息
您可以指定自訂預期訊息作為 expect
函式的第二個參數,例如:
await expect(page.getByText('Name'), 'should be logged in').toBeVisible();
此訊息會在報告器中顯示,無論預期通過或失敗,都會提供關於斷言的更多背景資訊。
當預期通過時,您可能會看到這樣的成功步驟:
✅ should be logged in @example.spec.ts:18
當預期失敗時,錯誤會看起來像這樣:
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 slowExpected(locator).toHaveText('Submit');
// 始終使用軟斷言。
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',
// 輪詢 10 秒;預設為 5 秒。傳遞 0 以停用逾時。
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.configure({ soft: true })
與 expect.poll 結合,在輪詢邏輯中執行軟斷言。
const softExpect = expect.configure({ soft: true });
await softExpect.poll(async () => {
const response = await page.request.get('https://api.example.com');
return response.status();
}, {}).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 逾時。
Add custom matchers using expect.extend
您可以透過提供自訂比對器來擴充 Playwright 斷言。這些比對器將在 expect
物件上可用。
在此範例中,我們新增自訂的 toHaveAmount
函式。自訂比對器應傳回 pass
旗標,指示斷言是否通過,以及在斷言失敗時使用的 message
回呼。
import { expect as baseExpect } from '@playwright/test';
import type { 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 {
const expectation = this.isNot ? baseExpect(locator).not : baseExpect(locator);
await expectation.toHaveAttribute('data-amount', String(expected), options);
pass = true;
} catch (e: any) {
matcherResult = e.matcherResult;
pass = false;
}
if (this.isNot) {
pass =!pass;
}
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');
});