Playwright Test 注解与标签实战指南:Annotations、Tags 与条件跳过机制
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
本文基于 Playwright 官方文档 test-annotations-js.md 展开,系统讲解 Playwright Test 的标签(tags)与注解(annotations)体系:从内置的skip/fail/fixme/slow注解,到自定义 tag 的声明与--grep过滤,再到组级条件跳过、beforeEach中的fixme以及运行时注解。读完本文,你将能够用注解精确控制"哪些测试运行、哪些不运行、失败是否符合预期",并结合 testType.ts 等源码理解其底层实现。
内置注解:skip、fail、fixme、slow
Playwright 支持在测试报告(test report)中展示标签和注解。你可以随时添加自定义的 tag 和 annotation,而 Playwright 本身就内置了几个最常用的注解:
test.skip将测试标记为"无关",Playwright 不会执行该测试。适用于测试在某些配置(如某个浏览器、某个平台)下不适用的场景。test.fail将测试标记为"预期失败"。Playwright 会照常执行该测试,并验证它确实失败;如果测试反而通过了,Playwright 会报错提醒你。test.fixme同样将测试标记为失败状态,但与fail不同,Playwright 根本不会执行它。当测试执行很慢或直接崩溃时,用fixme把它挂起来。test.slow将测试标记为慢测试,并将其超时时间乘以三倍。
注解可以加在单个测试上,也可以加在测试组(test.describe)上。内置注解支持条件形式——传入条件参数(truthy 时生效),条件还可以依赖测试 fixtures(如browserName、isMobile)。同一个测试上可以叠加多个注解,甚至可以来自不同的配置组合。
从源码结构看,这些注解本质上是类型受限的注解对象。test.ts 中定义了Modifier类型,其type字段只允许'slow' | 'fixme' | 'skip' | 'fail'四种取值,与文档列出的四个内置注解一一对应。而测试的预期结果(expected status)也在 test.ts 中根据注解推导:
if (annotation.type === 'skip' || annotation.type === 'fixme') this.expectedStatus = 'skipped'; else if (annotation.type === 'fail' && this.expectedStatus !== 'skipped') this.expectedStatus = 'failed';这解释了文档中"skip 与 fixme 都不运行、fail 必须运行且必须失败"的行为差异:skip/fixme 使预期状态变为skipped,而 fail 使预期状态变为failed(若已被 skip/fixme 覆盖则保持 skipped)。
所有内置注解方法在 testType.ts 中统一注册到test对象上:
test.only = wrapFunctionWithLocation(this._createTest.bind(this, 'only')); test.skip = wrapFunctionWithLocation(this._modifier.bind(this, 'skip')); test.fixme = wrapFunctionWithLocation(this._modifier.bind(this, 'fixme')); test.fail = wrapFunctionWithLocation(this._modifier.bind(this, 'fail')); test.slow = wrapFunctionWithLocation(this._modifier.bind(this, 'slow'));skip、fixme、fail、slow共用同一个_modifier实现,这也意味着它们在"条件跳过"等场景下具有完全一致的调用形态。
聚焦测试:test.only
当你只想运行某个(或某些)测试时,可以用test.only聚焦。项目中只要存在被聚焦的测试,就只有这些测试会运行,整个项目范围内的其他测试都会被跳过:
test.only('focus this test', async ({ page }) => { // Run only focused tests in the entire project. });从源码看,test.only在 testType.ts 中通过设置test._only = true标记实现,调度器据此过滤其余测试。
跳过单个测试:test.skip
最简单的跳过方式:
test.skip('skip this test', async ({ page }) => { // This test is not run });条件跳过单个测试
在测试体内根据条件动态跳过。注意这里test.skip的第一个参数是条件(而非测试标题):
test('skip this test', async ({ page, browserName }) => { test.skip(browserName === 'firefox', 'Still working on it'); });_modifier的源码(testType.ts)揭示了它的三种调用位置:
- 在
describe块中传函数 —— 注册为_modifiers,加载阶段按条件求值; - 在
describe块中传条件(非 function)—— 条件为假时直接return,不产生注解,为真则推送静态注解; - 在测试运行期间 —— 调用
testInfo._modifier(...)对当前测试生效。
这也解释了为什么条件形式"可以依赖测试 fixtures":在测试体内调用时,_modifier直接作用于当前TestInfo实例。
用 test.describe 分组测试
用test.describe给测试一个逻辑名称,也可以把 before/after 钩子的作用域限定在组内:
import { test, expect } from '@playwright/test'; test.describe('two tests', () => { test('one', async ({ page }) => { // ... }); test('two', async ({ page }) => { // ... }); });给测试打标签(Tags)
当你想给测试打上@fast/@slow之类的标签,然后在测试报告中按标签过滤,或者只运行带某个标签的测试时,tags 就有用武之地。
给测试打标签有两种方式:声明测试时通过 details 对象提供tag,或者直接在测试标题中加入@前缀的 token。注意tag 必须以@符号开头:
import { test, expect } from '@playwright/test'; test('test login page', { tag: '@fast', }, async ({ page }) => { // ... }); test('test full report @slow', async ({ page }) => { // ... });也可以给整个组打标签,或一次提供多个标签:
import { test, expect } from '@playwright/test'; test.describe('group', { tag: '@report', }, () => { test('test report header', async ({ page }) => { // ... }); test('test full report', { tag: ['@slow', '@vrt'], }, async ({ page }) => { // ... }); });声明后,可以用--grep命令行选项(见 test-cli.md 的 all-options 一节)只运行带特定标签的测试:
npx playwright test --grep @fastPowerShell 下需要加引号:
npx playwright test --grep "@fast"如果想反过来,跳过带某标签的测试,用--grep-invert:
npx playwright test --grep-invert @fast运行包含任一标签的测试(逻辑OR):
npx playwright test --grep "@fast|@slow"运行同时包含两个标签的测试(逻辑AND,借助正则前瞻):
npx playwright test --grep "(?=.*@fast)(?=.*@slow)"除了命令行,还可以在配置文件中通过grep配置项(TestConfig.grep和TestProject.grep)对测试做过滤,适合把"只跑 @fast 子集"之类的策略固化到 playwright.config.ts 一类的配置文件里。
从源码结构看,标签与注解走的是同一套 details 校验管道:testType.ts 中validateTestDetails校验后,test.annotations.push(...validatedDetails.annotations)与test._tags.push(...validatedDetails.tags)分别落位;组级 tags 则存在Suite._tags(test.ts 中注释明确区分了"显式声明的 tags"与"标题中解析出的 tags")。--grep的匹配对象正是由两部分标题与 tags 组合而成的检索路径(Suite._collectGrepTitlePath,test.ts),因此标题里的@slow与 details 里的tag: '@slow'对--grep而言等价。
自定义注解(Annotations)
当你需要比 tag 更"有料"的信息时,可以使用注解。注解由type和description组成,可以在 reporter API 中读取。Playwright 内置的 HTML 报告会展示所有注解,唯独不展示type以_开头的注解——这是一个留给内部的命名空间,例如框架自身使用的元数据注解。
例如,用一个 issue URL 注解测试:
import { test, expect } from '@playwright/test'; test('test login page', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23180', }, }, async ({ page }) => { // ... });同样可以注解整个组,或一次提供多个注解:
import { test, expect } from '@playwright/test'; test.describe('report tests', { annotation: { type: 'category', description: 'report' }, }, () => { test('test report header', async ({ page }) => { // ... }); test('test full report', { annotation: [ { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23180' }, { type: 'performance', description: 'very slow test!' }, ], }, async ({ page }) => { // ... }); });条件跳过一组测试
给test.skip/test.fixme传回调函数即可实现组级条件跳过,例如让一组测试只在 Chromium 上运行:
test.describe('chromium only', () => { test.skip(({ browserName }) => browserName !== 'chromium', 'Chromium only!'); test.beforeAll(async () => { // This hook is only run in Chromium. }); test('test 1', async ({ page }) => { // This test is only run in Chromium. }); test('test 2', async ({ page }) => { // This test is only run in Chromium. }); });对照_modifier的源码(testType.ts):在 describe 块中传入 function 时,它被压入suite._modifiers,等运行阶段按 fixture 参数求值后再决定是否给组内测试打上 skip/fixme 注解。
在 beforeEach 钩子中使用 fixme
如果连beforeEach钩子本身都不想让它执行(比如页面在移动端还没适配),可以把注解放进钩子内部:
test.beforeEach(async ({ page, isMobile }) => { test.fixme(isMobile, 'Settings page does not work in mobile yet'); await page.goto('http://localhost:3000/settings'); }); test('user profile', async ({ page }) => { await page.getByText('My Profile').click(); // ... });条件成立时,当前测试被标记为 fixme(预期 skipped),钩子中test.fixme之后的语句不再有意义地影响结果。这对应_modifier的第三种调用形态:运行期间经testInfo._modifier(type, location, ...)作用于当前测试(testType.ts)。
运行时注解:test.info().annotations
测试已经在运行时,也可以动态追加注解,写入test.info().annotations:
test('example test', async ({ page, browser }) => { test.info().annotations.push({ type: 'browser version', description: browser.version(), }); // ... });这些注解会随TestInfo一起进入 reporter 数据(在 reporterTestRun.ts、teleEmitter.ts 等报告中随测试结果一并序列化),因此 HTML 报告、JSON 报告、JUnit 报告等都能拿到它们。
小结:注解体系如何落到源码
把文档行为与源码对应起来,可以得到一张清晰的映射表:
| 文档中的能力 | 源码位置与机制 |
|---|---|
test.skip/fixme/fail/slow四内置注解 | testType.ts 统一经_modifier注册 |
test.only聚焦 | testType.ts 置test._only = true |
skip/fixme/fail的标题式调用test.skip(title, body) | testType.ts 转发到_createTest,并在 L120-L123 追加带location的注解 |
| 预期状态推导(skip/fixme → skipped,fail → failed) | test.ts |
| 组级条件注解(回调形式) | suite._modifiers(test.ts 定义,testType.ts 写入) |
| 自定义 tags/annotations 的声明校验 | validateTestDetails(testType.ts),分别落入TestCase.annotations、Suite._staticAnnotations与各级_tags |
这套机制让 Playwright Test 的"选择运行哪些测试"完全数据化:静态注解在文件加载期就写入Suite._staticAnnotations/TestCase.annotations,动态注解在运行期经TestInfo落位,最终统一呈现在测试报告中——这也是注解(annotation)与标签(tag)虽然入口相似、语义却分层的根本原因:tag 服务于过滤(--grep、grep配置),annotation 服务于表达(reporter API、HTML 报告展示)。
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考