Assertions
Playwright Test 使用 expect 库进行测试断言。该库提供了许多匹配器,如 toEqual、toContain、toMatch、toMatchSnapshot 等等:
expect(success).toBeTruthy();
Playwright 还对其进行了扩展,提供了便捷的异步匹配器,这些匹配器会等待,直到满足预期条件。主要参考以下示例:
await expect(page.locator('.status')).toHaveText('Submitted');
Playwright Test 会不断重新测试选择器为 .status 的节点,直到获取的节点拥有 "Submitted" 文本。它会一遍又一遍地重新获取节点并进行检查,直到满足条件或达到超时时间。你可以传递这个超时时间,也可以通过测试配置中的 testConfig.expect 值进行全局配置。
默认情况下,断言的超时时间设置为 5 秒。了解更多关于 各种超时 的信息。
否定匹配器(Negating Matchers)
通常,我们可以通过在匹配器前面添加 .not 来断言相反的情况:
expect(value).not.toEqual(0);
await expect(locator).not.toContainText("some text");
软断言(Soft Assertions)
默认情况下,断言失败会终止测试执行。Playwright 也支持 软断言:软断言失败 不会 终止测试执行,但会将测试标记为失败。
// 进行一些检查,即使失败也不会停止测试...
await expect.soft(page.locator('#status')).toHaveText('Success');
await expect.soft(page.locator('#eta')).toHaveText('1 day');
// ... 继续测试以检查更多内容。
await page.locator('#next-page').click();
await expect.soft(page.locator('#title')).toHaveText('Make another order');
在测试执行期间的任何时候,你都可以检查是否有软断言失败:
// 进行一些检查,即使失败也不会停止测试...
await expect.soft(page.locator('#status')).toHaveText('Success');
await expect.soft(page.locator('#eta')).toHaveText('1 day');
// 如果有软断言失败,则避免继续运行。
expect(test.info().errors).toHaveLength(0);
自定义 Expect 消息(Custom Expect Message)
你可以将自定义错误消息作为第二个参数传递给 expect 函数,例如:
await expect(page.getByText('Name'), 'should be logged in').toBeVisible();
错误信息如下所示:
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);
轮询(Polling)
你可以使用 expect.poll 将任何同步 expect 转换为异步轮询。
以下方法将轮询给定函数,直到其返回 HTTP 状态 200:
await expect.poll(async () => {
const response = await page.request.get('https://api.example.com');
return response.status();
}, {
// 自定义错误消息,可选。
message: 'make sure API eventually succeeds', // custom error message
// 轮询 10 秒;默认为 5 秒。传递 0 以禁用超时。
timeout: 10000,
}).toBe(200);
你还可以指定自定义的轮询间隔:
await expect.poll(async () => {
const response = await page.request.get('https://api.example.com');
return response.status();
}, {
// 探测,等待 1s,探测,等待 2s,探测,等待 10s,探测,等待 10s,探测...。默认为 [100, 250, 500, 1000]。
intervals: [1_000, 2_000, 10_000],
timeout: 60_000
}).toBe(200);
- expect(locator).toBeChecked([options])
- expect(locator).toBeDisabled([options])
- expect(locator).toBeEditable([options])
- expect(locator).toBeEmpty([options])
- expect(locator).toBeEnabled([options])
- expect(locator).toBeFocused([options])
- expect(locator).toBeHidden([options])
- expect(locator).toBeVisible([options])
- expect(locator).toContainText(expected[, options])
- expect(locator).toHaveAttribute(name, value[, options])
- expect(locator).toHaveClass(expected[, options])
- expect(locator).toHaveCount(count[, options])
- expect(locator).toHaveCSS(name, value[, options])
- expect(locator).toHaveId(id[, options])
- expect(locator).toHaveJSProperty(name, value[, options])
- expect(locator).toHaveScreenshot(name[, options])
- expect(locator).toHaveScreenshot([options])
- expect(locator).toHaveText(expected[, options])
- expect(locator).toHaveValue(value[, options])
- expect(locator).toHaveValues(values[, options])
- expect(locator).not
- expect(page).toHaveScreenshot(name[, options])
- expect(page).toHaveScreenshot([options])
- expect(page).toHaveTitle(titleOrRegExp[, options])
- expect(page).toHaveURL(urlOrRegExp[, options])
- expect(page).not
- expect(apiResponse).toBeOK()
- expect(apiResponse).not
- expect(screenshot).toMatchSnapshot(name[, options])
- expect(screenshot).toMatchSnapshot([options])
expect(locator).not
Added in: v1.20- type: <LocatorAssertions>
使断言检查相反的条件。例如,此代码测试定位器不包含文本 "error":
await expect(locator).not.toContainText('error');
expect(locator).toBeChecked([options])
Added in: v1.20确保 Locator 指向已选中的输入框。
const locator = page.getByLabel('Subscribe to newsletter');
await expect(locator).toBeChecked();
expect(locator).toBeDisabled([options])
Added in: v1.20确保 Locator 指向一个禁用的元素。元素如果具有 "disabled" 属性或通过 'aria-disabled' 禁用,则被视为禁用。请注意,只有原生控制元素(如 HTML button、input、select、textarea、option、optgroup)可以通过设置 "disabled" 属性来禁用。浏览器会忽略其他元素上的 "disabled" 属性。
const locator = page.locator('button.submit');
await expect(locator).toBeDisabled();
expect(locator).toBeEditable([options])
Added in: v1.20确保 Locator 指向可编辑元素。
const locator = page.getByRole('textbox');
await expect(locator).toBeEditable();
expect(locator).toBeEmpty([options])
Added in: v1.20确保 Locator 指向一个空的可编辑元素或没有文本的 DOM 节点。
const locator = page.locator('div.warning');
await expect(locator).toBeEmpty();
expect(locator).toBeEnabled([options])
Added in: v1.20确保 Locator 指向已启用的元素。
const locator = page.locator('button.submit');
await expect(locator).toBeEnabled();
expect(locator).toBeFocused([options])
Added in: v1.20确保 Locator 指向一个聚焦的 DOM 节点。
const locator = page.getByRole('textbox');
await expect(locator).toBeFocused();
expect(locator).toBeHidden([options])
Added in: v1.20确保 Locator 要么不解析为任何 DOM 节点,要么解析为一个 不可见 的节点。
const locator = page.locator('.my-element');
await expect(locator).toBeHidden();
expect(locator).toBeVisible([options])
Added in: v1.20确保 Locator 指向一个 已连接 且 可见 的 DOM 节点。
const locator = page.locator('.my-element');
await expect(locator).toBeVisible();
expect(locator).toContainText(expected[, options])
Added in: v1.20expected<string|RegExp|Array<string|RegExp>> 期望的子字符串或正则表达式,或者是它们的列表。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保 Locator 指向包含给定文本的元素。你也可以使用正则表达式作为值。
const locator = page.locator('.title');
await expect(locator).toContainText('substring');
await expect(locator).toContainText(/\d messages/);
如果你传递一个数组作为期望值,期望如下:
- Locator 解析为一个元素列表。
- 这个列表的 子集 中的元素分别包含期望数组中的文本。
- 匹配的元素子集与期望数组的顺序相同。
- 期望数组中的每个文本值都由列表中的某个元素匹配。
例如,考虑以下列表:
<ul>
<li>Item Text 1</li>
<li>Item Text 2</li>
<li>Item Text 3</li>
</ul>
咱们来看看如何使用断言:
// ✓ 包含正确顺序的正确项目
await expect(page.locator('ul > li')).toContainText(['Text 1', 'Text 3']);
// ✖ 顺序错误
await expect(page.locator('ul > li')).toContainText(['Text 3', 'Text 2']);
// ✖ 没有包含此文本的项目
await expect(page.locator('ul > li')).toContainText(['Some 33']);
// ✖ Locator 指向外部列表元素,而不是列表项
await expect(page.locator('ul')).toContainText(['Text 3']);
expect(locator).toHaveAttribute(name, value[, options])
Added in: v1.20name<string> 属性名称。 Added in: v1.18#value<string|RegExp> 期望的属性值。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保 Locator 指向具有给定属性的元素。
const locator = page.locator('input');
await expect(locator).toHaveAttribute('type', 'text');
expect(locator).toHaveClass(expected[, options])
Added in: v1.20expected<string|RegExp|Array<string|RegExp>> 期望的类名或正则表达式,或者是它们的列表。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保 Locator 指向具有给定 CSS 类的元素。这需要完全匹配或使用宽松的正则表达式。
<div class='selected row' id='component'></div>
const locator = page.locator('#component');
await expect(locator).toHaveClass(/selected/);
await expect(locator).toHaveClass('selected row');
请注意,如果传递数组作为期望值,则可以断言整个元素列表:
const locator = page.locator('list > .component');
await expect(locator).toHaveClass(['component', 'component selected', 'component']);
expect(locator).toHaveCount(count[, options])
Added in: v1.20确保 Locator 解析为确切数量的 DOM 节点。
const list = page.locator('list > .component');
await expect(list).toHaveCount(3);
expect(locator).toHaveCSS(name, value[, options])
Added in: v1.20name<string> CSS 属性名称。 Added in: v1.18#value<string|RegExp> CSS 属性值。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保 Locator 解析为具有给定计算 CSS 样式的元素。
const locator = page.getByRole('button');
await expect(locator).toHaveCSS('display', 'flex');
expect(locator).toHaveId(id[, options])
Added in: v1.20确保 Locator 指向具有给定 DOM 节点 ID 的元素。
const locator = page.getByRole('textbox');
await expect(locator).toHaveId('lastname');
expect(locator).toHaveJSProperty(name, value[, options])
Added in: v1.20name<string> 属性名称。 Added in: v1.18#value<Object> 属性值。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保 Locator 指向具有给定 JavaScript 属性的元素。请注意,此属性可以是原始类型,也可以是普通的可序列化 JavaScript 对象。
const locator = page.locator('.component');
await expect(locator).toHaveJSProperty('loaded', true);
expect(locator).toHaveScreenshot(name[, options])
Added in: v1.23options?<Object>animations?<"disabled"|"allow"> 当设置为"disabled"时,停止 CSS 动画、CSS 过渡和 Web 动画。动画会根据其持续时间得到不同的处理:#- 有限动画快进至完成,因此它们将触发
transitionend事件。 - 无限动画取消至初始状态,然后在截图后重新播放。
默认为
"disabled",即禁用动画。- 有限动画快进至完成,因此它们将触发
caret?<"hide"|"initial"> 当设置为"hide"时,截图将隐藏文本插入符号。当设置为"initial"时,文本插入符号的行为不会改变。默认为"hide"。#mask?<Array<Locator>> 指定在截取截图时应屏蔽的定位器。屏蔽的元素将被粉红色的框#FF00FF覆盖,该框完全覆盖其边界框。#maxDiffPixelRatio?<number> 像素差异与像素总数的可接受比例,介于0和1之间。默认值可通过TestConfig.expect配置。默认未设置。#maxDiffPixels?<number> 可接受的像素差异数量。默认值可通过TestConfig.expect配置。默认未设置。#omitBackground?<boolean> 隐藏默认的白色背景,允许捕获具有透明度的截图。不适用于jpeg图像。默认为false。#scale?<"css"|"device"> 当设置为"css"时,截图将在页面上的每个 css 像素拥有一个像素。对于高 dpi 设备,这将保持截图较小。使用"device"选项将为每个设备像素生成一个像素,因此高 dpi 设备的截图将大两倍甚至更大。#默认为
"css"。threshold?<number> YIQ 颜色空间 中比较图像中相同像素之间可接受的感知颜色差异,介于零(严格)和一(宽松)之间,默认值可通过TestConfig.expect配置。默认为0.2。#timeout?<number> 重试断言的时间。默认为TestConfig.expect中的timeout。#
此函数将等待,直到两个连续的定位器截图产生相同的结果,然后将最后一张截图与期望值进行比较。
const locator = page.getByRole('button');
await expect(locator).toHaveScreenshot('image.png');
expect(locator).toHaveScreenshot([options])
Added in: v1.23options?<Object>animations?<"disabled"|"allow"> 当设置为"disabled"时,停止 CSS 动画、CSS 过渡和 Web 动画。动画会根据其持续时间得到不同的处理:#- 有限动画快进至完成,因此它们将触发
transitionend事件。 - 无限动画取消至初始状态,然后在截图后重新播放。
默认为
"disabled",即禁用动画。- 有限动画快进至完成,因此它们将触发
caret?<"hide"|"initial"> 当设置为"hide"时,截图将隐藏文本插入符号。当设置为"initial"时,文本插入符号的行为不会改变。默认为"hide"。#mask?<Array<Locator>> 指定在截取截图时应屏蔽的定位器。屏蔽的元素将被粉红色的框#FF00FF覆盖,该框完全覆盖其边界框。#maxDiffPixelRatio?<number> 像素差异与像素总数的可接受比例,介于0和1之间。默认值可通过TestConfig.expect配置。默认未设置。#maxDiffPixels?<number> 可接受的像素差异数量。默认值可通过TestConfig.expect配置。默认未设置。#omitBackground?<boolean> 隐藏默认的白色背景,允许捕获具有透明度的截图。不适用于jpeg图像。默认为false。#scale?<"css"|"device"> 当设置为"css"时,截图将在页面上的每个 css 像素拥有一个像素。对于高 dpi 设备,这将保持截图较小。使用"device"选项将为每个设备像素生成一个像素,因此高 dpi 设备的截图将大两倍甚至更大。#默认为
"css"。threshold?<number> YIQ 颜色空间 中比较图像中相同像素之间可接受的感知颜色差异,介于零(严格)和一(宽松)之间,默认值可通过TestConfig.expect配置。默认为0.2。#timeout?<number> 重试断言的时间。默认为TestConfig.expect中的timeout。#
此函数将等待,直到两个连续的定位器截图产生相同的结果,然后将最后一张截图与期望值进行比较。
const locator = page.getByRole('button');
await expect(locator).toHaveScreenshot();
expect(locator).toHaveText(expected[, options])
Added in: v1.20expected<string|RegExp|Array<string|RegExp>> 期望的子字符串或正则表达式,或者是它们的列表。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保 Locator 指向具有给定文本的元素。你也可以使用正则表达式作为值。
const locator = page.locator('.title');
await expect(locator).toHaveText(/Welcome, Test User/);
await expect(locator).toHaveText(/Welcome, .*/);
如果你传递一个数组作为期望值,期望如下:
- Locator 解析为一个元素列表。
- 元素的数量等于数组中期望值的数量。
- 列表中的元素具有与期望数组值一一对应、顺序相同的文本。
例如,考虑以下列表:
<ul>
<li>Text 1</li>
<li>Text 2</li>
<li>Text 3</li>
</ul>
咱们来看看如何使用断言:
// ✓ 拥有正确顺序的正确项目
await expect(page.locator('ul > li')).toHaveText(['Text 1', 'Text 2', 'Text 3']);
// ✖ 顺序错误
await expect(page.locator('ul > li')).toHaveText(['Text 3', 'Text 2', 'Text 1']);
// ✖ 最后一项不匹配
await expect(page.locator('ul > li')).toHaveText(['Text 1', 'Text 2', 'Text']);
// ✖ Locator 指向外部列表元素,而不是列表项
await expect(page.locator('ul')).toHaveText(['Text 1', 'Text 2', 'Text 3']);
expect(locator).toHaveValue(value[, options])
Added in: v1.20确保 Locator 指向具有给定输入值的元素。你也可以使用正则表达式作为值。
const locator = page.locator('input[type=number]');
await expect(locator).toHaveValue(/[0-9]/);
expect(locator).toHaveValues(values[, options])
Added in: v1.23确保 Locator 指向多选/组合框(即具有 multiple 属性的 select),并且选定了指定的值。
例如,给定以下元素:
<select id="favorite-colors" multiple>
<option value="R">Red</option>
<option value="G">Green</option>
<option value="B">Blue</option>
</select>
const locator = page.locator("id=favorite-colors");
await locator.selectOption(["R", "G"]);
await expect(locator).toHaveValues([/R/, /G/]);
expect(page).not
Added in: v1.20- type: <PageAssertions>
使断言检查相反的条件。例如,此代码测试页面 URL 不包含 "error":
await expect(page).not.toHaveURL('error');
expect(page).toHaveScreenshot(name[, options])
Added in: v1.23options?<Object>animations?<"disabled"|"allow"> 当设置为"disabled"时,停止 CSS 动画、CSS 过渡和 Web 动画。动画会根据其持续时间得到不同的处理:#- 有限动画快进至完成,因此它们将触发
transitionend事件。 - 无限动画取消至初始状态,然后在截图后重新播放。
默认为
"disabled",即禁用动画。- 有限动画快进至完成,因此它们将触发
caret?<"hide"|"initial"> 当设置为"hide"时,截图将隐藏文本插入符号。当设置为"initial"时,文本插入符号的行为不会改变。默认为"hide"。#fullPage?<boolean> 当设置为true时,截取完整可滚动页面的截图,即不仅仅是当前可见视窗。默认为false。#mask?<Array<Locator>> 指定在截取截图时应屏蔽的定位器。屏蔽的元素将被粉红色的框#FF00FF覆盖,该框完全覆盖其边界框。#maxDiffPixelRatio?<number> 像素差异与像素总数的可接受比例,介于0和1之间。默认值可通过TestConfig.expect配置。默认未设置。#maxDiffPixels?<number> 可接受的像素差异数量。默认值可通过TestConfig.expect配置。默认未设置。#omitBackground?<boolean> 隐藏默认的白色背景,允许捕获具有透明度的截图。不适用于jpeg图像。默认为false。#scale?<"css"|"device"> 当设置为"css"时,截图将在页面上的每个 css 像素拥有一个像素。对于高 dpi 设备,这将保持截图较小。使用"device"选项将为每个设备像素生成一个像素,因此高 dpi 设备的截图将大两倍甚至更大。#默认为
"css"。style?<string> 截图时在页面上应用的样式表字符串。这正是注入<style>标签在拍摄截图之前并且之后立即将其移除的地方。#threshold?<number> YIQ 颜色空间 中比较图像中相同像素之间可接受的感知颜色差异,介于零(严格)和一(宽松)之间,默认值可通过TestConfig.expect配置。默认为0.2。#timeout?<number> 重试断言的时间。默认为TestConfig.expect中的timeout。#
此函数将等待,直到两个连续的面截图产生相同的结果,然后将最后一张截图与期望值进行比较。
await expect(page).toHaveScreenshot('image.png');
expect(page).toHaveScreenshot([options])
Added in: v1.23options?<Object>animations?<"disabled"|"allow"> 当设置为"disabled"时,停止 CSS 动画、CSS 过渡和 Web 动画。动画会根据其持续时间得到不同的处理:#- 有限动画快进至完成,因此它们将触发
transitionend事件。 - 无限动画取消至初始状态,然后在截图后重新播放。
默认为
"disabled",即禁用动画。- 有限动画快进至完成,因此它们将触发
caret?<"hide"|"initial"> 当设置为"hide"时,截图将隐藏文本插入符号。当设置为"initial"时,文本插入符号的行为不会改变。默认为"hide"。#fullPage?<boolean> 当设置为true时,截取完整可滚动页面的截图,即不仅仅是当前可见视窗。默认为false。#mask?<Array<Locator>> 指定在截取截图时应屏蔽的定位器。屏蔽的元素将被粉红色的框#FF00FF覆盖,该框完全覆盖其边界框。#maxDiffPixelRatio?<number> 像素差异与像素总数的可接受比例,介于0和1之间。默认值可通过TestConfig.expect配置。默认未设置。#maxDiffPixels?<number> 可接受的像素差异数量。默认值可通过TestConfig.expect配置。默认未设置。#omitBackground?<boolean> 隐藏默认的白色背景,允许捕获具有透明度的截图。不适用于jpeg图像。默认为false。#scale?<"css"|"device"> 当设置为"css"时,截图将在页面上的每个 css 像素拥有一个像素。对于高 dpi 设备,这将保持截图较小。使用"device"选项将为每个设备像素生成一个像素,因此高 dpi 设备的截图将大两倍甚至更大。#默认为
"css"。style?<string> 截图时在页面上应用的样式表字符串。这正是注入<style>标签在拍摄截图之前并且之后立即将其移除的地方。#threshold?<number> YIQ 颜色空间 中比较图像中相同像素之间可接受的感知颜色差异,介于零(严格)和一(宽松)之间,默认值可通过TestConfig.expect配置。默认为0.2。#timeout?<number> 重试断言的时间。默认为TestConfig.expect中的timeout。#
此函数将等待,直到两个连续的面截图产生相同的结果,然后将最后一张截图与期望值进行比较。
await expect(page).toHaveScreenshot();
expect(page).toHaveTitle(titleOrRegExp[, options])
Added in: v1.20titleOrRegExp<string|RegExp> 期望的标题或正则表达式。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保页面具有给定的标题。
await expect(page).toHaveTitle(/Playwright/);
expect(page).toHaveURL(urlOrRegExp[, options])
Added in: v1.20urlOrRegExp<string|RegExp> 期望的 URL 或正则表达式。 Added in: v1.18#options?<Object>- returns: <Promise<void>>#
确保页面导航到给定的 URL。
await expect(page).toHaveURL(/.*checkout/);
expect(apiResponse).not
Added in: v1.20- type: <APIResponseAssertions>
使断言检查相反的条件。例如,此代码测试响应状态不是 OK:
await expect(response).not.toBeOK();
expect(apiResponse).toBeOK()
Added in: v1.20确保响应状态码在 200..299 范围内。
await expect(response).toBeOK();
expect(screenshot).toMatchSnapshot(name[, options])
Added in: v1.20确保截图与预期的快照匹配。
const screenshot = await page.screenshot();
expect(screenshot).toMatchSnapshot('landing-page.png');
// 传递选项以自定义快照比较并生成名称。
expect(await page.screenshot()).toMatchSnapshot('landing-page.png', {
maxDiffPixels: 27, // allow no more than 27 different pixels.
});
// 配置图像匹配阈值。
expect(await page.screenshot()).toMatchSnapshot('landing-page.png', { threshold: 0.3 });
// 通过传递文件路径段为快照文件提供一些结构。
expect(await page.screenshot()).toMatchSnapshot(['landing', 'step2.png']);
expect(await page.screenshot()).toMatchSnapshot(['landing', 'step3.png']);
了解更多关于 视觉比较 的信息。
expect(screenshot).toMatchSnapshot([options])
Added in: v1.22options?<Object>maxDiffPixelRatio?<number> 像素差异与像素总数的可接受比例,介于0和1之间。默认值可通过TestConfig.expect配置。默认未设置。#maxDiffPixels?<number> 可接受的像素差异数量。默认值可通过TestConfig.expect配置。默认未设置。#name?<string|Array<string>> 快照名称。如果未传递,则在多次调用时使用测试名称和序号。#threshold?<number> YIQ 颜色空间 中比较图像中相同像素之间可接受的感知颜色差异,介于零(严格)和一(宽松)之间,默认值可通过TestConfig.expect配置。默认为0.2。#
- returns: <void>#
确保传递的值(string 或 Buffer)与存储在测试快照目录中的预期快照匹配。
// 基本用法,文件名派生自测试名称。
expect(await page.screenshot()).toMatchSnapshot();
// 传递选项以自定义快照比较并生成名称。
expect(await page.screenshot()).toMatchSnapshot({
maxDiffPixels: 27, // allow no more than 27 different pixels.
});
// 配置图像匹配阈值和快照名称。
expect(await page.screenshot()).toMatchSnapshot({
name: 'landing-page.png',
threshold: 0.3,
});
了解更多关于 视觉比较 的信息。