Playwright 断言(Assertions)
本章全面介绍 Playwright 的断言体系,包括自动重试断言、通用断言以及软断言的用法。
什么是断言
断言(Assertion)用来验证测试的预期结果是否与实际结果一致。
Playwright 提供了两套断言:通用断言(来自 Jest)和自动重试断言(Playwright 独有)。
自动重试断言 vs 通用断言
自动重试断言(Async Matchers)
Playwright 的 Web 特定断言会自动重试,直到条件满足或超时,必须使用 await。
实例
// 自动重试断言 —— 会等待条件满足
await expect(page.getByText('RUNOOB')).toBeVisible();
await expect(page.getByText('RUNOOB')).toBeVisible();
通用断言
用于非 Web 特定的值比较,不自动重试。
实例
// 通用断言 —— 立即执行,不等待
expect(1 + 1).toBe(2);
expect('hello').toContain('ell');
expect(true).toBeTruthy();
expect(1 + 1).toBe(2);
expect('hello').toContain('ell');
expect(true).toBeTruthy();
| 类型 | 是否自动重试 | 是否需要 await | 适用场景 |
|---|---|---|---|
| 自动重试断言 | 是 | 必须 await | 页面元素、URL、标题等 Web 特定内容 |
| 通用断言 | 否 | 不需要 | 普通变量、数组、对象等 JavaScript 值 |
页面级断言
实例
// 页面标题断言
await expect(page).toHaveTitle('RUNOOB - 学的不仅是技术,更是梦想!');
await expect(page).toHaveTitle(/RUNOOB/);
// 页面 URL 断言
await expect(page).toHaveURL('https://www.runoob.com/');
await expect(page).toHaveURL(/runoob\.com/);
await expect(page).toHaveTitle('RUNOOB - 学的不仅是技术,更是梦想!');
await expect(page).toHaveTitle(/RUNOOB/);
// 页面 URL 断言
await expect(page).toHaveURL('https://www.runoob.com/');
await expect(page).toHaveURL(/runoob\.com/);
可见性断言
实例
const btn = page.getByRole('button', { name: '提交' });
// 元素可见
await expect(btn).toBeVisible();
// 元素不可见
await expect(btn).toBeHidden();
// 元素在 DOM 中存在(但不一定可见)
await expect(btn).toBeAttached();
// 元素在视口内可见
await expect(btn).toBeInViewport();
// 元素可见
await expect(btn).toBeVisible();
// 元素不可见
await expect(btn).toBeHidden();
// 元素在 DOM 中存在(但不一定可见)
await expect(btn).toBeAttached();
// 元素在视口内可见
await expect(btn).toBeInViewport();
文本与值断言
实例
const heading = page.getByRole('heading', { name: 'RUNOOB' });
// 元素的文本内容精确匹配
await expect(heading).toHaveText('学的不仅是技术,更是梦想!');
// 元素的文本内容包含子串
await expect(heading).toContainText('技术');
// input 的当前值
await expect(page.getByLabel('用户名')).toHaveValue('runoob_user');
// 元素内容为空
await expect(page.getByTestId('error-msg')).toBeEmpty();
// 元素的文本内容精确匹配
await expect(heading).toHaveText('学的不仅是技术,更是梦想!');
// 元素的文本内容包含子串
await expect(heading).toContainText('技术');
// input 的当前值
await expect(page.getByLabel('用户名')).toHaveValue('runoob_user');
// 元素内容为空
await expect(page.getByTestId('error-msg')).toBeEmpty();
状态断言
实例
const checkbox = page.getByLabel('同意协议');
const input = page.getByLabel('邮箱');
// 元素已启用
await expect(input).toBeEnabled();
// 元素已禁用
await expect(input).toBeDisabled();
// 元素可编辑
await expect(input).toBeEditable();
// 复选框已勾选
await expect(checkbox).toBeChecked();
// 元素已获得焦点
await expect(input).toBeFocused();
const input = page.getByLabel('邮箱');
// 元素已启用
await expect(input).toBeEnabled();
// 元素已禁用
await expect(input).toBeDisabled();
// 元素可编辑
await expect(input).toBeEditable();
// 复选框已勾选
await expect(checkbox).toBeChecked();
// 元素已获得焦点
await expect(input).toBeFocused();
属性断言
实例
const element = page.locator('.runoob-logo');
// DOM 属性断言
await expect(element).toHaveAttribute('src', '/static/images/logo.png');
// 只检查属性存在
await expect(element).toHaveAttribute('alt');
// CSS 类名断言
await expect(element).toHaveClass('logo-primary');
// CSS 属性值断言
await expect(element).toHaveCSS('display', 'block');
// 元素 ID 断言
await expect(element).toHaveId('main-logo');
// 无障碍名称(可访问名称)
await expect(page.getByRole('button')).toHaveAccessibleName('提交');
// DOM 属性断言
await expect(element).toHaveAttribute('src', '/static/images/logo.png');
// 只检查属性存在
await expect(element).toHaveAttribute('alt');
// CSS 类名断言
await expect(element).toHaveClass('logo-primary');
// CSS 属性值断言
await expect(element).toHaveCSS('display', 'block');
// 元素 ID 断言
await expect(element).toHaveId('main-logo');
// 无障碍名称(可访问名称)
await expect(page.getByRole('button')).toHaveAccessibleName('提交');
数量断言
实例
// 匹配元素数量
const items = page.getByRole('listitem');
await expect(items).toHaveCount(5);
// 配合 filter 使用
const activeItems = page.getByRole('listitem').filter({ hasText: '已激活' });
await expect(activeItems).toHaveCount(3);
const items = page.getByRole('listitem');
await expect(items).toHaveCount(5);
// 配合 filter 使用
const activeItems = page.getByRole('listitem').filter({ hasText: '已激活' });
await expect(activeItems).toHaveCount(3);
通用断言速查
| 方法 | 说明 | 示例 |
|---|---|---|
toBe(value) | 严格相等(===) | expect(x).toBe(42) |
toEqual(value) | 深度相等 | expect(obj).toEqual({ a: 1 }) |
toContain(item) | 数组/字符串包含 | expect(arr).toContain('a') |
toBeTruthy() | 值是真值 | expect(x).toBeTruthy() |
toBeFalsy() | 值是假值 | expect(x).toBeFalsy() |
toBeNull() | 值为 null | expect(x).toBeNull() |
toBeGreaterThan(n) | 大于 n | expect(x).toBeGreaterThan(10) |
toBeLessThan(n) | 小于 n | expect(x).toBeLessThan(100) |
toHaveLength(n) | 数组长度 | expect(arr).toHaveLength(3) |
expect.soft() 软断言
普通断言失败后会立即终止测试。
软断言失败后不会终止,会继续执行后续代码,在测试结束时统一报告所有失败。
实例
test('表单验证多项检查', async ({ page }) => {
await page.goto('https://example.com/form');
// 软断言 —— 失败不会停止测试
await expect.soft(page.getByLabel('用户名')).toBeVisible();
await expect.soft(page.getByLabel('密码')).toBeVisible();
await expect.soft(page.getByLabel('邮箱')).toBeVisible();
await expect.soft(page.getByRole('button', { name: '注册' })).toBeEnabled();
// 如果以上任一断言失败,会继续检查,最后统一报告
});
await page.goto('https://example.com/form');
// 软断言 —— 失败不会停止测试
await expect.soft(page.getByLabel('用户名')).toBeVisible();
await expect.soft(page.getByLabel('密码')).toBeVisible();
await expect.soft(page.getByLabel('邮箱')).toBeVisible();
await expect.soft(page.getByRole('button', { name: '注册' })).toBeEnabled();
// 如果以上任一断言失败,会继续检查,最后统一报告
});
在测试表单页面时使用
expect.soft()可以一次性发现所有缺失的字段,而不需要逐个修复后才看到下一个问题。这能大幅提高调试效率。
断言超时
自动重试断言默认超时 5 秒,可在配置文件或单次断言中自定义:
实例
// 单个断言设置超时(毫秒)
await expect(page.getByText('加载中...')).toBeHidden({ timeout: 10000 });
// 在配置文件中全局设置
// playwright.config.ts:
// export default defineConfig({
// expect: { timeout: 10000 },
// });
await expect(page.getByText('加载中...')).toBeHidden({ timeout: 10000 });
// 在配置文件中全局设置
// playwright.config.ts:
// export default defineConfig({
// expect: { timeout: 10000 },
// });
