Skip to main content

斷言

簡介

Playwright 包含以 expect 函式形式的測試斷言。要進行斷言,呼叫 expect(value) 並選擇反映期望的匹配器。有許多像 toEqualtoContaintoBeTruthy 這樣的通用匹配器可以用來斷言任何條件。

expect(success).toBeTruthy();

Playwright 也包括特定於網頁的 非同步匹配器,它會等待直到預期條件滿足。請考慮以下範例:

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

Playwright 將會重新測試具有 status 測試 id 的元素,直到獲取的元素具有 "Submitted" 文本。它會重新獲取元素並一遍又一遍地檢查,直到條件滿足或超時為止。你可以傳遞這個超時時間,或者通過測試配置中的 testConfig.expect 值來配置一次。

預設情況下,斷言的超時時間設置為 5 秒。了解更多關於各種超時

自動重試斷言

以下斷言將重試直到斷言通過,或達到斷言超時。請注意,重試斷言是非同步的,所以你必須 await 它們。

AssertionDescription
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

AssertionDescription
expect(value).toBe()值是相同的
expect(value).toBeCloseTo()數值大約相等
expect(value).toBeDefined()值不是 undefined
expect(value).toBeFalsy()值為假,例如 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()值為真,即不是 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 函式的第二個參數,例如:

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 轉換為非同步的輪詢,使用 expect.poll

以下方法將輪詢給定函式直到它返回 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 函式。自定義匹配器應該返回一個 message 回調和一個 pass 旗標,指示斷言是否通過。

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: ${this.isNot ? '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');
});