17 KiB
报告与构建产物
使用时机:配置测试输出,用于本地调试、CI 仪表盘与团队可视性。每个项目从第一天起就需要一个报告策略。
快速参考
# 查看最新 HTML 报告
npx playwright show-report
# 使用特定报告器运行
npx playwright test --reporter=html
npx playwright test --reporter=dot # 最简 CI 输出
npx playwright test --reporter=line # 每个测试一行
npx playwright test --reporter=json # 机器可读
npx playwright test --reporter=junit # CI 集成
# 通过 CLI 使用多个报告器
npx playwright test --reporter=dot,html
# 合并分片报告
npx playwright merge-reports --reporter=html ./blob-report
模式
模式 1:多报告器配置
使用时机:每个项目都应如此。你始终需要至少两个报告器:一个给人看,一个给 CI 用。 避免时机:永远不需要——始终配置报告器。
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: process.env.CI
? [
// CI:机器可读 + 人类可读 + CI 注释
["dot"], // 最简控制台输出
["html", { open: "never" }], // 可浏览的报告(作为构建产物上传)
["junit", { outputFile: "test-results/junit.xml" }], // CI 测试标签页集成
["github"], // PR 注释(仅限 GitHub Actions)
]
: [
// 本地:详细控制台 + 自动打开报告
["list"], // 详细控制台输出
["html", { open: "on-failure" }], // 失败时自动打开
],
})
模式 2:内置报告器详解
使用时机:为你的场景选择正确的报告器。
| 报告器 | 输出 | 最佳用途 |
|---|---|---|
list |
每个测试一行,显示通过/失败 | 本地开发 |
line |
测试完成时更新单行 | 本地,较简洁 |
dot |
每个测试一个点:. 通过,F 失败 |
CI 日志(最简) |
html |
带有追踪记录的交互式 HTML 页面 | 运行后分析 |
json |
机器可读的 JSON 输出到 stdout 或文件 | 自定义工具、仪表盘 |
junit |
JUnit XML | CI 平台(Azure DevOps、Jenkins、CircleCI) |
github |
GitHub Actions 注释 | GitHub PR |
blob |
用于分片合并的二进制归档 | 分片式 CI 运行 |
JSON 报告器——写入文件:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [["json", { outputFile: "test-results/results.json" }]],
})
JUnit 报告器——自定义输出:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [
[
"junit",
{
outputFile: "test-results/junit.xml",
stripANSIControlSequences: true,
includeProjectInTestName: true,
},
],
],
})
模式 3:自定义报告器
使用时机:内置报告器不满足你的需求——你需要 Slack 通知、数据库日志或自定义仪表盘。 避免时机:内置报告器或现有的第三方报告器已能满足你的情况。
// reporters/slack-reporter.ts
import type {
FullConfig,
FullResult,
Reporter,
Suite,
TestCase,
TestResult,
} from "@playwright/test/reporter"
class SlackReporter implements Reporter {
private passed = 0
private failed = 0
private skipped = 0
private failures: string[] = []
onTestEnd(test: TestCase, result: TestResult) {
switch (result.status) {
case "passed":
this.passed++
break
case "failed":
case "timedOut":
this.failed++
this.failures.push(`${test.title}: ${result.error?.message?.split("\n")[0]}`)
break
case "skipped":
this.skipped++
break
}
}
async onEnd(result: FullResult) {
const total = this.passed + this.failed + this.skipped
const emoji = this.failed > 0 ? ":red_circle:" : ":large_green_circle:"
const text = [
`${emoji} *Playwright Tests*: ${result.status}`,
`Passed: ${this.passed} | Failed: ${this.failed} | Skipped: ${this.skipped} | Total: ${total}`,
`Duration: ${(result.duration / 1000).toFixed(1)}s`,
]
if (this.failures.length > 0) {
text.push("", "*Failures:*")
this.failures.slice(0, 5).forEach((f) => text.push(` - ${f}`))
if (this.failures.length > 5) {
text.push(` ...and ${this.failures.length - 5} more`)
}
}
const webhookUrl = process.env.SLACK_WEBHOOK_URL
if (webhookUrl) {
await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: text.join("\n") }),
})
}
}
}
export default SlackReporter
注册自定义报告器:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [["dot"], ["html", { open: "never" }], ["./reporters/slack-reporter.ts"]],
})
模式 4:追踪记录文件管理
使用时机:调试测试失败时。追踪记录会捕获操作、网络请求、DOM 快照和控制台日志的完整时间线。
避免时机:永远不要在 CI 中完全禁用追踪记录——on-first-retry 设置的额外开销极小。
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
retries: process.env.CI ? 2 : 0,
use: {
// 'on-first-retry':仅在测试失败并重试时记录追踪。
// 通过的测试几乎没有额外开销,失败时则可完整调试。
trace: "on-first-retry",
},
})
追踪选项:
| 值 | 记录追踪 | 时机 | 额外开销 |
|---|---|---|---|
'off' |
从不 | -- | 无 |
'on' |
每个测试 | 始终 | 高(文件体积大) |
'on-first-retry' |
失败后首次重试时 | 仅重试时 | 极小 |
'retain-on-failure' |
每个测试,只保留失败的 | 失败时 | 中等 |
'retain-on-first-failure' |
每个测试,只保留首次失败 | 首次失败时 | 中等 |
查看追踪记录:
# 本地打开追踪查看器
npx playwright show-trace test-results/my-test/trace.zip
# 从 HTML 报告打开追踪(在报告中点击"Traces"标签页)
npx playwright show-report
# 在线追踪查看器(上传 trace.zip)
# https://trace.playwright.dev
模式 5:截图与视频配置
使用时机:测试失败的视觉证据对调试或错误报告很有价值。
避免时机:永远不要在 CI 中禁用截图——on-failure 设置成本很低。
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
use: {
// 截图
screenshot: "only-on-failure", // 失败时捕获最终状态
// 视频
video: "retain-on-failure", // 全部录制,只保留失败的
// 视频尺寸(可选——越小越省磁盘)
video: {
mode: "retain-on-failure",
size: { width: 1280, height: 720 },
},
},
})
截图选项:
| 值 | 捕获时机 | 磁盘开销 |
|---|---|---|
'off' |
从不 | 无 |
'on' |
每个测试(结束时) | 高 |
'only-on-failure' |
仅失败的测试 | 低 |
视频选项:
| 值 | 录制 | 保留 | 磁盘开销 |
|---|---|---|---|
'off' |
从不 | -- | 无 |
'on' |
每个测试 | 全部 | 非常高 |
'on-first-retry' |
重试时 | 重试过的测试 | 低 |
'retain-on-failure' |
每个测试 | 仅失败的 | 中等 |
模式 6:CI 构建产物组织
使用时机:在 CI 中保持测试构建产物有序且可访问。
推荐的目录结构:
test-results/ # Playwright 默认输出目录
├── my-test-chromium/
│ ├── trace.zip # 追踪文件
│ ├── test-failed-1.png # 截图
│ └── video.webm # 视频录制
├── another-test-firefox/
│ ├── trace.zip
│ └── test-failed-1.png
└── junit.xml # JUnit 报告(如已配置)
playwright-report/ # HTML 报告目录
├── index.html
└── data/
└── ...
blob-report/ # 用于分片合并的 blob 报告
└── report-1.zip
GitHub Actions 构建产物上传:
# 上传 HTML 报告(始终上传——即使测试通过也有用)
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 14
# 上传追踪记录和截图(仅失败时——节省存储空间)
- uses: actions/upload-artifact@v4
if: failure()
with:
name: test-traces
path: |
test-results/**/trace.zip
test-results/**/*.png
test-results/**/*.webm
retention-days: 7
模式 7:Allure 集成
使用时机:你的团队在多个测试框架中使用 Allure 进行测试报告。 避免时机:内置的 HTML 报告器已能满足你的需求(通常确实如此)。
# 安装 Allure 报告器
npm install -D allure-playwright
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [
["line"],
[
"allure-playwright",
{
detail: true,
outputFolder: "allure-results",
suiteTitle: true,
},
],
],
})
# 生成并查看 Allure 报告
npx allure generate allure-results -o allure-report --clean
npx allure open allure-report
# 或使用 Allure CLI
allure serve allure-results
为测试添加 Allure 元数据:
import { test, expect } from "@playwright/test"
import { allure } from "allure-playwright"
test("checkout flow", async ({ page }) => {
await allure.epic("E-Commerce")
await allure.feature("Checkout")
await allure.story("Credit Card Payment")
await allure.severity("critical")
await page.goto("/checkout")
// ... 测试实现
})
决策指南
| 场景 | 报告器配置 | 原因 |
|---|---|---|
| 本地开发 | [['list'], ['html', { open: 'on-failure' }]] |
详细控制台 + 失败时自动打开报告 |
| GitHub Actions | [['dot'], ['html'], ['github']] |
最简日志 + 报告构建产物 + PR 注释 |
| GitLab CI | [['dot'], ['html'], ['junit']] |
最简日志 + 报告构建产物 + 测试标签页 |
| Azure DevOps/Jenkins | [['dot'], ['html'], ['junit']] |
JUnit 用于原生测试结果集成 |
| 分片式 CI | [['blob'], ['github']] |
Blob 用于合并;github 用于 PR 注释 |
| 团队使用 Allure | [['line'], ['allure-playwright']] |
跨框架报告一致性 |
| 自定义仪表盘 | [['json', { outputFile: '...' }]] + 自定义报告器 |
JSON 提供数据,自定义用于通知 |
| 构建产物 | 收集时机 | 保留天数 | 上传条件 |
|---|---|---|---|
| HTML 报告 | 始终 | 14 天 | if: ${{ !cancelled() }} |
追踪记录 (.zip) |
失败时 | 7 天 | if: failure() |
截图 (.png) |
失败时 | 7 天 | if: failure() |
视频 (.webm) |
失败时 | 7 天 | if: failure() |
| JUnit XML | 始终 | 14 天 | if: ${{ !cancelled() }} |
| Blob 报告 | 始终(分片时) | 1 天 | if: ${{ !cancelled() }} |
反模式
| 反模式 | 问题 | 应改为 |
|---|---|---|
| 未配置报告器 | 仅默认 list;无可持久化的报告 |
始终配置 html + 一个 CI 报告器 |
在 CI 中使用 trace: 'on' |
巨大的构建产物(每个测试 50-100 MB),上传缓慢 | 使用 trace: 'on-first-retry' |
在 CI 中使用 video: 'on' |
存储成本极高;拖慢测试执行速度 | 使用 video: 'retain-on-failure' |
| 仅在失败时上传构建产物 | 测试通过时没有报告;无法验证结果 | 使用 if: ${{ !cancelled() }}(始终)上传 |
| 构建产物无保留期限 | CI 存储空间在数周内被填满 | 设置 retention-days: 7-14 |
仅使用 dot 报告器,无 HTML |
运行后无法深入查看失败的测试 | 在 CI 中始终将 dot 与 html 配对使用 |
| JUnit 输出到 stdout | 干扰控制台输出;难以解析 | 写入文件:['junit', { outputFile: 'results/junit.xml' }] |
自定义报告器阻塞 onEnd |
缓慢的 Slack/HTTP 调用延迟流水线完成 | 在自定义报告器中使用 Promise.race 加超时 |
故障排除
HTML 报告为空或缺少测试
原因:另一个报告器存在冲突,或者 outputFolder 被覆盖为非默认路径。
修复:检查你的报告器配置。HTML 报告默认输出到 playwright-report/:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [["html", { outputFolder: "playwright-report", open: "never" }]],
})
追踪记录过大,无法作为 CI 构建产物上传
原因:trace: 'on' 录制了每个测试,包括通过的。
修复:切换为 'on-first-retry',并确保在 CI 中 retries > 0:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
retries: process.env.CI ? 2 : 0,
use: {
trace: "on-first-retry",
},
})
JUnit XML 未被 CI 平台识别
原因:输出路径与 CI 任务期望的路径不匹配,或者文件为空。
修复:确保路径与你的 CI 配置一致:
// playwright.config.ts -- outputFile 路径
reporter: [['junit', { outputFile: 'test-results/junit.xml' }]],
# GitHub Actions
- uses: dorny/test-reporter@v1
with:
path: test-results/junit.xml
reporter: java-junit
# Azure DevOps
- task: PublishTestResults@2
inputs:
testResultsFiles: 'test-results/junit.xml'
# Jenkins
junit 'test-results/junit.xml'
merge-reports 生成空报告
原因:分片使用了 html 报告器而非 blob。只有 blob 输出可以被合并。
修复:对分片运行使用 blob 报告器:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: process.env.CI
? [["blob"], ["dot"]] // blob 用于合并,dot 用于控制台
: [["html", { open: "on-failure" }]],
})
截图中未出现在 HTML 报告中
原因:screenshot: 'off',或者截图在 test-results/ 中但未链接到报告。
修复:启用截图并确保两个目录都可用:
use: {
screenshot: 'only-on-failure',
},
HTML 报告会自动嵌入来自 test-results/ 的截图。如果你移动或删除了 test-results/,截图将从报告中丢失。
相关文档
- ci/ci-github-actions.md —— GitHub Actions 中的构建产物上传
- ci/ci-gitlab.md —— GitLab 中的构建产物配置
- ci/parallel-and-sharding.md —— 用于分片运行的 blob 报告器
- core/configuration.md —— trace、screenshot、video 设置
- core/debugging.md —— 使用追踪记录和截图进行调试