Files
2026-07-13 21:36:47 +08:00

17 KiB
Raw Permalink Blame History

报告与构建产物

使用时机:配置测试输出,用于本地调试、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' 每个测试 仅失败的 中等

模式 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 构建产物上传:

# 上传 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 报告器已能满足你的需求(通常确实如此)。

# 安装 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 中始终将 dothtml 配对使用
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/,截图将从报告中丢失。

相关文档