# Reporting and Artifacts > **When to use**: Configuring test output for local debugging, CI dashboards, and team visibility. Every project needs a reporting strategy from day one. ## Quick Reference ```bash # View the last HTML report npx playwright show-report # Run with specific reporter npx playwright test --reporter=html npx playwright test --reporter=dot # minimal CI output npx playwright test --reporter=line # one line per test npx playwright test --reporter=json # machine-readable npx playwright test --reporter=junit # CI integration # Multiple reporters via CLI npx playwright test --reporter=dot,html # Merge shard reports npx playwright merge-reports --reporter=html ./blob-report ``` ## Patterns ### Pattern 1: Multi-Reporter Configuration **Use when**: Every project. You always want at least two reporters: one for humans, one for CI. **Avoid when**: Never -- always configure reporters. ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ reporter: process.env.CI ? [ // CI: machine-readable + human-readable + CI annotations ["dot"], // minimal console output ["html", { open: "never" }], // browsable report (uploaded as artifact) ["junit", { outputFile: "test-results/junit.xml" }], // CI test tab integration ["github"], // PR annotations (GitHub Actions only) ] : [ // Local: detailed console + auto-opening report ["list"], // verbose console output ["html", { open: "on-failure" }], // auto-open on failure ], }) ``` ### Pattern 2: Built-in Reporters in Detail **Use when**: Choosing the right reporter for your context. | Reporter | Output | Best For | | -------- | --------------------------------------- | ---------------------------------------------- | | `list` | One line per test with pass/fail | Local development | | `line` | Updates a single line as tests complete | Local, less verbose | | `dot` | Single dot per test: `.` pass, `F` fail | CI logs (minimal) | | `html` | Interactive HTML page with traces | Post-run analysis | | `json` | Machine-readable JSON to stdout or file | Custom tooling, dashboards | | `junit` | JUnit XML | CI platforms (Azure DevOps, Jenkins, CircleCI) | | `github` | GitHub Actions annotations | GitHub PRs | | `blob` | Binary archive for shard merging | Sharded CI runs | **JSON reporter -- write to file:** ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ reporter: [["json", { outputFile: "test-results/results.json" }]], }) ``` **JUnit reporter -- customize output:** ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ reporter: [ [ "junit", { outputFile: "test-results/junit.xml", stripANSIControlSequences: true, includeProjectInTestName: true, }, ], ], }) ``` ### Pattern 3: Custom Reporter **Use when**: Built-in reporters don't meet your needs -- you want Slack notifications, database logging, or custom dashboards. **Avoid when**: A built-in reporter or existing third-party reporter covers your case. ```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 ``` **Register the custom reporter:** ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ reporter: [["dot"], ["html", { open: "never" }], ["./reporters/slack-reporter.ts"]], }) ``` ### Pattern 4: Trace File Management **Use when**: Debugging test failures. Traces capture a complete timeline of actions, network requests, DOM snapshots, and console logs. **Avoid when**: Never disable traces entirely in CI -- the on-first-retry setting has minimal overhead. ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ retries: process.env.CI ? 2 : 0, use: { // 'on-first-retry': records trace only when a test fails and retries. // Minimal overhead on passing tests, full debugging on failures. trace: "on-first-retry", }, }) ``` **Trace options:** | Value | Records trace | When | Overhead | | --------------------------- | ------------------------------------ | ------------- | ------------------ | | `'off'` | Never | -- | None | | `'on'` | Every test | Always | High (large files) | | `'on-first-retry'` | On first retry after failure | Retries only | Minimal | | `'retain-on-failure'` | Every test, keeps only failures | Failures | Medium | | `'retain-on-first-failure'` | Every test, keeps only first failure | First failure | Medium | **Viewing traces:** ```bash # Open trace viewer locally npx playwright show-trace test-results/my-test/trace.zip # Open trace from HTML report (click "Traces" tab in the report) npx playwright show-report # Online trace viewer (upload trace.zip) # https://trace.playwright.dev ``` ### Pattern 5: Screenshot and Video Configuration **Use when**: Visual evidence of test failures is valuable for debugging or bug reports. **Avoid when**: Never disable screenshots in CI -- the on-failure setting is cheap. ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ use: { // Screenshots screenshot: "only-on-failure", // capture final state on failure // Video video: "retain-on-failure", // record all, keep only failures // Video size (optional -- smaller = less disk) video: { mode: "retain-on-failure", size: { width: 1280, height: 720 }, }, }, }) ``` **Screenshot options:** | Value | Captures | Disk cost | | ------------------- | ------------------- | --------- | | `'off'` | Never | None | | `'on'` | Every test (at end) | High | | `'only-on-failure'` | Failed tests only | Low | **Video options:** | Value | Records | Keeps | Disk cost | | --------------------- | ---------- | ------------- | --------- | | `'off'` | Never | -- | None | | `'on'` | Every test | All | Very high | | `'on-first-retry'` | On retry | Retried tests | Low | | `'retain-on-failure'` | Every test | Failed only | Medium | ### Pattern 6: Artifact Organization for CI **Use when**: Keeping test artifacts organized and accessible in CI. **Recommended directory structure:** ``` test-results/ # Playwright's default output directory ├── my-test-chromium/ │ ├── trace.zip # Trace file │ ├── test-failed-1.png # Screenshot │ └── video.webm # Video recording ├── another-test-firefox/ │ ├── trace.zip │ └── test-failed-1.png └── junit.xml # JUnit report (if configured) playwright-report/ # HTML report directory ├── index.html └── data/ └── ... blob-report/ # Blob report for shard merging └── report-1.zip ``` **GitHub Actions artifact upload:** ```yaml # Upload HTML report (always -- useful even when tests pass) - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report path: playwright-report/ retention-days: 14 # Upload traces and screenshots (only on failure -- saves storage) - 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 ``` ### Pattern 7: Allure Integration **Use when**: Your team uses Allure for test reporting across multiple test frameworks. **Avoid when**: The built-in HTML reporter meets your needs (it usually does). ```bash # Install Allure reporter 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 # Generate and view Allure report npx allure generate allure-results -o allure-report --clean npx allure open allure-report # Or use Allure CLI allure serve allure-results ``` **Add Allure metadata to tests:** ```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") // ... test implementation }) ``` ## Decision Guide | Scenario | Reporter Configuration | Why | | ---------------------- | ----------------------------------------------------- | ------------------------------------------------ | | Local development | `[['list'], ['html', { open: 'on-failure' }]]` | Verbose console + auto-opening report on failure | | GitHub Actions | `[['dot'], ['html'], ['github']]` | Minimal logs + report artifact + PR annotations | | GitLab CI | `[['dot'], ['html'], ['junit']]` | Minimal logs + report artifact + test tab | | Azure DevOps / Jenkins | `[['dot'], ['html'], ['junit']]` | JUnit for native test results integration | | Sharded CI | `[['blob'], ['github']]` | Blob for merging; github for PR annotations | | Team uses Allure | `[['line'], ['allure-playwright']]` | Cross-framework reporting consistency | | Custom dashboard | `[['json', { outputFile: '...' }]]` + custom reporter | JSON for data, custom for notifications | | Artifact | When to Collect | Retention | Upload Condition | | -------------------- | ---------------- | --------- | ------------------------- | | HTML report | Always | 14 days | `if: ${{ !cancelled() }}` | | Traces (`.zip`) | On failure | 7 days | `if: failure()` | | Screenshots (`.png`) | On failure | 7 days | `if: failure()` | | Videos (`.webm`) | On failure | 7 days | `if: failure()` | | JUnit XML | Always | 14 days | `if: ${{ !cancelled() }}` | | Blob report | Always (sharded) | 1 day | `if: ${{ !cancelled() }}` | ## Anti-Patterns | Anti-Pattern | Problem | Do This Instead | | -------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------- | | No reporter configured | Default `list` only; no persistent report | Always configure `html` + one CI reporter | | `trace: 'on'` in CI | Massive artifacts (50-100 MB per test), slow uploads | Use `trace: 'on-first-retry'` | | `video: 'on'` in CI | Enormous storage cost; slows test execution | Use `video: 'retain-on-failure'` | | Only uploading artifacts on failure | No report when tests pass; can't verify results | Upload with `if: ${{ !cancelled() }}` (always) | | No retention limits on artifacts | CI storage fills up within weeks | Set `retention-days: 7-14` | | Using only `dot` reporter with no HTML | Can't drill into failures after the run | Always pair `dot` with `html` in CI | | JUnit output to stdout | Interferes with console output; hard to parse | Write to file: `['junit', { outputFile: 'results/junit.xml' }]` | | Custom reporter that blocks `onEnd` | Slow Slack/HTTP calls delay pipeline completion | Use `Promise.race` with a timeout in custom reporters | ## Troubleshooting ### HTML report is empty or missing tests **Cause**: Another reporter is conflicting, or `outputFolder` was overridden to a non-default path. **Fix**: Check your reporter config. The HTML report defaults to `playwright-report/`: ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ reporter: [["html", { outputFolder: "playwright-report", open: "never" }]], }) ``` ### Traces are too large for CI artifact upload **Cause**: `trace: 'on'` records every test, even passing ones. **Fix**: Switch to `'on-first-retry'` and ensure `retries > 0` in CI: ```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 not recognized by CI platform **Cause**: Output path doesn't match what the CI task expects, or the file is empty. **Fix**: Ensure the path matches your CI configuration: ```ts // playwright.config.ts -- the outputFile path 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` produces empty report **Cause**: Shards are using `html` reporter instead of `blob`. Only `blob` output can be merged. **Fix**: Use blob reporter for sharded runs: ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ reporter: process.env.CI ? [["blob"], ["dot"]] // blob for merge, dot for console : [["html", { open: "on-failure" }]], }) ``` ### Screenshots not appearing in HTML report **Cause**: `screenshot: 'off'` or screenshots are in `test-results/` but not linked to the report. **Fix**: Enable screenshots and ensure both directories are available: ```ts use: { screenshot: 'only-on-failure', }, ``` The HTML report automatically embeds screenshots from `test-results/`. If you move or delete `test-results/`, screenshots will be missing from the report. ## Related - [ci/ci-github-actions.md](ci-github-actions.md) -- artifact upload in GitHub Actions - [ci/ci-gitlab.md](ci-gitlab.md) -- artifact configuration in GitLab - [ci/parallel-and-sharding.md](parallel-and-sharding.md) -- blob reporter for sharded runs - [core/configuration.md](../core/configuration.md) -- trace, screenshot, video settings - [core/debugging.md](../core/debugging.md) -- using traces and screenshots for debugging