Playwright 测试结构与钩子
本章介绍 Playwright 测试的组织方式,包括测试分组、执行控制以及生命周期钩子的用法。
test.describe() 测试分组
test.describe() 用于将相关的测试组织在一起,形成逻辑分组。
实例
import { test, expect } from '@playwright/test';
// 第一层分组
test.describe('RUNOOB 首页测试', () => {
test('页面标题正确', async ({ page }) => {
await page.goto('https://www.runoob.com/');
await expect(page).toHaveTitle(/RUNOOB/);
});
test('导航栏存在', async ({ page }) => {
await page.goto('https://www.runoob.com/');
// 断言导航元素存在
await expect(page.locator('nav')).toBeVisible();
});
});
// 可以定义多个 describe 块
test.describe('搜索功能测试', () => {
test('搜索框可见', async ({ page }) => {
await page.goto('https://www.runoob.com/');
await expect(
page.getByPlaceholder('搜索')
).toBeVisible();
});
});
测试报告会按照 describe 分组显示,方便查看结果。
嵌套 describe
test.describe() 可以嵌套,形成树状结构:
实例
test.describe('登录功能', () => {
test('正确账号密码登录', async ({ page }) => { /* ... */ });
test('错误密码登录失败', async ({ page }) => { /* ... */ });
});
test.describe('注册功能', () => {
test('填写正确信息注册', async ({ page }) => { /* ... */ });
test('已存在的邮箱注册失败', async ({ page }) => { /* ... */ });
});
});
test.skip() 跳过测试
test.skip() 用于跳过某个测试,被跳过的测试不会执行。
实例
test.skip('尚未完成的功能测试', async ({ page }) => {
// 这个测试不会执行
});
// 条件跳过(运行时判断)
test('在特定条件下跳过', async ({ page, browserName }) => {
// 在 Firefox 中跳过
test.skip(browserName === 'firefox', 'Firefox 暂不支持此功能');
// 测试逻辑仅在非 Firefox 浏览器中执行
await page.goto('https://www.runoob.com/');
});
test.fail() 标记预期失败
test.fail() 用于标记一个已知会失败的测试。
实例
test.fail('已知 Bug:搜索功能暂未实现', async ({ page }) => {
await page.goto('https://www.runoob.com/');
// 这个断言预期会失败
await expect(page.locator('.search-results')).toBeVisible();
});
// 如果测试意外通过了,会报告为失败
// 这样能提醒你"Bug 已修复,可以移除 test.fail 标记了"
test.fail()的作用是反向标记:测试失败=符合预期,测试通过=异常(可能需要移除标记)。
test.only() 只运行当前测试
test.only() 用于在开发调试时只运行特定测试,忽略文件中的其他测试。
实例
test.only('我正在调试这个测试', async ({ page }) => {
// 只有这个测试会运行
await page.goto('https://www.runoob.com/');
});
test('这个测试会被忽略', async ({ page }) => {
// 由于上面有 test.only(),这个不会运行
});
test.only()是调试工具,不应提交到代码仓库。如果你在playwright.config.ts中设置了forbidOnly: true,CI 上会检测到only并让构建失败。
test.fixme() 标记待修复
test.fixme() 与 test.skip() 类似,但语义上表示"这个测试需要修复"。
实例
test.fixme('登录流程因 API 变更需要更新', async ({ page }) => {
// 这个测试暂时不会执行
});
test.slow() 标记慢测试
test.slow() 将测试的超时时间增加 3 倍。
实例
test.slow('大数据量测试', async ({ page }) => {
// 这个测试的默认超时将是 90 秒(30秒 × 3)
// 适合需要大量数据加载或复杂计算的场景
});
测试钩子(Hooks)
钩子函数让你在测试的不同阶段执行准备和清理工作。
test.beforeEach() 与 test.afterEach()
beforeEach 在每个测试前执行,afterEach 在每个测试后执行。
实例
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
// 每个测试前都导航到同一页面
await page.goto('https://www.runoob.com/');
});
test.afterEach(async ({ page }) => {
// 每个测试后都执行清理(如清除创建的数据)
// Playwright 会自动清理浏览器 context,
// 这里的清理主要指服务端状态
console.log('测试完成,开始清理...');
});
test('第一个测试', async ({ page }) => {
// page 已经在 RUNOOB 首页了
await expect(page).toHaveTitle(/RUNOOB/);
});
test('第二个测试', async ({ page }) => {
// page 同样已经在 RUNOOB 首页
// 这是一个全新的 context,前一个测试的 Cookie/Storage 不会残留
await expect(page.locator('nav')).toBeVisible();
});
test.beforeAll() 与 test.afterAll()
beforeAll 在当前作用域所有测试之前执行一次,afterAll 在所有测试之后执行一次。
实例
test.beforeAll(async ({ browser }) => {
// 在所有测试之前创建测试用户(只执行一次)
// 注意:beforeAll 不能使用 page fixture
});
test.afterAll(async ({ browser }) => {
// 在所有测试之后删除测试用户(只执行一次)
});
test('查看用户列表', async ({ page }) => { /* ... */ });
test('编辑用户信息', async ({ page }) => { /* ... */ });
});
beforeAll和afterAll不能访问pagefixture,因为 page 是每个测试独立的。如果需要操作页面,请使用beforeEach。
钩子的作用域与执行顺序
钩子的作用域取决于它们所在的位置:
实例
test.beforeEach(async () => {
console.log('文件级 beforeEach');
});
test.describe('分组 A', () => {
// 分组级钩子 —— 仅作用于分组 A
test.beforeEach(async () => {
console.log('分组 A 的 beforeEach');
});
test('测试 A1', async () => { /* ... */ });
test('测试 A2', async () => { /* ... */ });
});
test.describe('分组 B', () => {
// 分组 B 没有自己的钩子,只有文件级钩子生效
test('测试 B1', async () => { /* ... */ });
});
执行顺序:外层钩子先执行,内层钩子后执行。
对于 A1 和 A2:先输出 "文件级 beforeEach",再输出 "分组 A 的 beforeEach"。
对于 B1:只输出 "文件级 beforeEach"。
