项目文件夹

文件
2026-07-13 21:36:47 +08:00

502 行
17 KiB
Markdown

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 报告与构建产物
> **使用时机**:配置测试输出,用于本地调试、CI 仪表盘与团队可视性。每个项目从第一天起就需要一个报告策略。
## 快速参考
```bash
# 查看最新 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 用。
**避免时机**:永远不需要——始终配置报告器。
```ts
// 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 报告器——写入文件:**
```ts
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [["json", { outputFile: "test-results/results.json" }]],
})
```
**JUnit 报告器——自定义输出:**
```ts
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [
[
"junit",
{
outputFile: "test-results/junit.xml",
stripANSIControlSequences: true,
includeProjectInTestName: true,
},
],
],
})
```
### 模式 3:自定义报告器
**使用时机**:内置报告器不满足你的需求——你需要 Slack 通知、数据库日志或自定义仪表盘。
**避免时机**:内置报告器或现有的第三方报告器已能满足你的情况。
```ts
// 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
```
**注册自定义报告器:**
```ts
// 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` 设置的额外开销极小。
```ts
// 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'` | 每个测试,只保留首次失败 | 首次失败时 | 中等 |
**查看追踪记录:**
```bash
# 本地打开追踪查看器
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` 设置成本很低。
```ts
// 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'` | 每个测试 | 仅失败的 | 中等 |
### 模式 6CI 构建产物组织
**使用时机**:在 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 构建产物上传:**
```yaml
# 上传 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
```
### 模式 7Allure 集成
**使用时机**:你的团队在多个测试框架中使用 Allure 进行测试报告。
**避免时机**:内置的 HTML 报告器已能满足你的需求(通常确实如此)。
```bash
# 安装 Allure 报告器
npm install -D allure-playwright
```
```ts
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: [
["line"],
[
"allure-playwright",
{
detail: true,
outputFolder: "allure-results",
suiteTitle: true,
},
],
],
})
```
```bash
# 生成并查看 Allure 报告
npx allure generate allure-results -o allure-report --clean
npx allure open allure-report
# 或使用 Allure CLI
allure serve allure-results
```
**为测试添加 Allure 元数据:**
```ts
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/`
```ts
// 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`
```ts
// 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 配置一致:
```ts
// playwright.config.ts -- outputFile 路径
reporter: [['junit', { outputFile: 'test-results/junit.xml' }]],
```
```yaml
# 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 报告器:
```ts
// 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/` 中但未链接到报告。
**修复**:启用截图并确保两个目录都可用:
```ts
use: {
screenshot: 'only-on-failure',
},
```
HTML 报告会自动嵌入来自 `test-results/` 的截图。如果你移动或删除了 `test-results/`,截图将从报告中丢失。
## 相关文档
- [ci/ci-github-actions.md](ci-github-actions.md) —— GitHub Actions 中的构建产物上传
- [ci/ci-gitlab.md](ci-gitlab.md) —— GitLab 中的构建产物配置
- [ci/parallel-and-sharding.md](parallel-and-sharding.md) —— 用于分片运行的 blob 报告器
- [core/configuration.md](../core/configuration.md) —— trace、screenshot、video 设置
- [core/debugging.md](../core/debugging.md) —— 使用追踪记录和截图进行调试