Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
infrastructure-ci-cd/reporting.md
1# Test Reports & Artifacts23## Table of Contents451. [CLI Commands](#cli-commands)62. [Reporter Configuration](#reporter-configuration)73. [Custom Reporter](#custom-reporter)84. [Trace Configuration](#trace-configuration)95. [Screenshot & Video Settings](#screenshot--video-settings)106. [Artifact Directory Structure](#artifact-directory-structure)117. [CI Artifact Upload](#ci-artifact-upload)128. [Decision Guide](#decision-guide)139. [Anti-Patterns](#anti-patterns)1410. [Troubleshooting](#troubleshooting)1516> **When to use**: Configuring test output for debugging, CI dashboards, and team visibility.1718## CLI Commands1920```bash21# Display last HTML report22npx playwright show-report2324# Specify reporter25npx playwright test --reporter=html26npx playwright test --reporter=dot # minimal CI output27npx playwright test --reporter=line # one line per test28npx playwright test --reporter=json # machine-readable29npx playwright test --reporter=junit # CI integration3031# Combine reporters32npx playwright test --reporter=dot,html3334# Merge sharded reports35npx playwright merge-reports --reporter=html ./blob-report36```3738## Reporter Configuration3940### Environment-Based Setup4142```typescript43// playwright.config.ts44import { defineConfig } from '@playwright/test';4546export default defineConfig({47reporter: process.env.CI48? [49['dot'],50['html', { open: 'never' }],51['junit', { outputFile: 'results/junit.xml' }],52['github'],53]54: [55['list'],56['html', { open: 'on-failure' }],57],58});59```6061### Reporter Types6263| Reporter | Output | Use Case |64|---|---|---|65| `list` | One line per test | Local development |66| `line` | Single updating line | Local, less verbose |67| `dot` | `.` pass, `F` fail | CI logs |68| `html` | Interactive HTML page | Post-run analysis |69| `json` | Machine-readable JSON | Custom tooling |70| `junit` | JUnit XML | CI platforms |71| `github` | PR annotations | GitHub Actions |72| `blob` | Binary archive | Shard merging |7374### JSON Output to File7576```typescript77import { defineConfig } from '@playwright/test';7879export default defineConfig({80reporter: [81['json', { outputFile: 'results/output.json' }],82],83});84```8586### JUnit Customization8788```typescript89import { defineConfig } from '@playwright/test';9091export default defineConfig({92reporter: [93['junit', {94outputFile: 'results/junit.xml',95stripANSIControlSequences: true,96includeProjectInTestName: true,97}],98],99});100```101102## Custom Reporter103104Build custom reporters for Slack notifications, database logging, or dashboards.105106```typescript107// reporters/notification-reporter.ts108import type {109FullResult,110Reporter,111TestCase,112TestResult,113} from '@playwright/test/reporter';114115class NotificationReporter implements Reporter {116private passed = 0;117private failed = 0;118private skipped = 0;119private failures: string[] = [];120121onTestEnd(test: TestCase, result: TestResult) {122switch (result.status) {123case 'passed':124this.passed++;125break;126case 'failed':127case 'timedOut':128this.failed++;129this.failures.push(`${test.title}: ${result.error?.message?.split('\n')[0]}`);130break;131case 'skipped':132this.skipped++;133break;134}135}136137async onEnd(result: FullResult) {138const total = this.passed + this.failed + this.skipped;139const status = this.failed > 0 ? 'FAILED' : 'PASSED';140const message = [141`Tests ${status}`,142`Passed: ${this.passed} | Failed: ${this.failed} | Skipped: ${this.skipped}`,143`Duration: ${(result.duration / 1000).toFixed(1)}s`,144];145146if (this.failures.length > 0) {147message.push('', 'Failures:');148this.failures.slice(0, 5).forEach((f) => message.push(` - ${f}`));149if (this.failures.length > 5) {150message.push(` ...and ${this.failures.length - 5} more`);151}152}153154const webhookUrl = process.env.NOTIFICATION_WEBHOOK;155if (webhookUrl) {156const controller = new AbortController();157const timeout = setTimeout(() => controller.abort(), 5000);158try {159await fetch(webhookUrl, {160method: 'POST',161headers: { 'Content-Type': 'application/json' },162body: JSON.stringify({ text: message.join('\n') }),163signal: controller.signal,164});165} catch (error) {166// Intentionally swallow notifier failures to avoid blocking test completion167console.warn('Webhook notification failed:', error.message);168} finally {169clearTimeout(timeout);170}171}172}173}174175export default NotificationReporter;176```177178**Register custom reporter:**179180```typescript181import { defineConfig } from '@playwright/test';182183export default defineConfig({184reporter: [185['dot'],186['html', { open: 'never' }],187['./reporters/notification-reporter.ts'],188],189});190```191192## Trace Configuration193194Traces capture actions, network requests, DOM snapshots, and console logs.195196```typescript197import { defineConfig } from '@playwright/test';198199export default defineConfig({200retries: process.env.CI ? 2 : 0,201use: {202trace: 'on-first-retry',203},204});205```206207### Trace Options208209| Value | Behavior | Overhead |210|---|---|---|211| `'off'` | Never records | None |212| `'on'` | Every test | High |213| `'on-first-retry'` | On first retry after failure | Minimal |214| `'retain-on-failure'` | Records all, keeps failures | Medium |215| `'retain-on-first-failure'` | Records all, keeps first failure | Medium |216217### Viewing Traces218219```bash220# Local trace viewer221npx playwright show-trace results/my-test/trace.zip222223# From HTML report (click Traces tab)224npx playwright show-report225226# Online viewer: https://trace.playwright.dev227```228229## Screenshot & Video Settings230231```typescript232import { defineConfig } from '@playwright/test';233234export default defineConfig({235use: {236screenshot: 'only-on-failure',237video: 'retain-on-failure',238},239});240```241242### Video with Custom Size243244```typescript245use: {246video: {247mode: 'retain-on-failure',248size: { width: 1280, height: 720 },249},250},251```252253### Screenshot Options254255| Value | Captures | Disk Cost |256|---|---|---|257| `'off'` | Never | None |258| `'on'` | Every test | High |259| `'only-on-failure'` | Failed tests | Low |260261### Video Options262263| Value | Records | Keeps | Disk Cost |264|---|---|---|---|265| `'off'` | Never | — | None |266| `'on'` | Every test | All | Very high |267| `'on-first-retry'` | On retry | Retried | Low |268| `'retain-on-failure'` | Every test | Failed | Medium |269270## Artifact Directory Structure271272```text273test-results/274├── checkout-test-chromium/275│ ├── trace.zip276│ ├── test-failed-1.png277│ └── video.webm278├── login-test-firefox/279│ ├── trace.zip280│ └── test-failed-1.png281└── junit.xml282283playwright-report/284├── index.html285└── data/286287blob-report/288└── report-1.zip289```290291## CI Artifact Upload292293### GitHub Actions294295```yaml296- uses: actions/upload-artifact@v4297if: ${{ !cancelled() }}298with:299name: playwright-report300path: playwright-report/301retention-days: 14302303- uses: actions/upload-artifact@v4304if: failure()305with:306name: test-traces307path: |308test-results/**/trace.zip309test-results/**/*.png310test-results/**/*.webm311retention-days: 7312```313314## Decision Guide315316| Scenario | Reporter Configuration |317|---|---|318| Local development | `[['list'], ['html', { open: 'on-failure' }]]` |319| GitHub Actions | `[['dot'], ['html'], ['github']]` |320| GitLab CI | `[['dot'], ['html'], ['junit']]` |321| Azure DevOps / Jenkins | `[['dot'], ['html'], ['junit']]` |322| Sharded CI | `[['blob'], ['github']]` |323| Custom dashboard | `[['json', { outputFile: '...' }]]` + custom reporter |324325| Artifact | When to Collect | Retention | Upload Condition |326|---|---|---|---|327| HTML report | Always | 14 days | `if: ${{ !cancelled() }}` |328| Traces | On failure | 7 days | `if: failure()` |329| Screenshots | On failure | 7 days | `if: failure()` |330| Videos | On failure | 7 days | `if: failure()` |331| JUnit XML | Always | 14 days | `if: ${{ !cancelled() }}` |332| Blob report | Always (sharded) | 1 day | `if: ${{ !cancelled() }}` |333334## Anti-Patterns335336| Anti-Pattern | Problem | Solution |337|---|---|---|338| No reporter configured | Default `list` only; no persistent report | Configure `html` + CI reporter |339| `trace: 'on'` in CI | Massive artifacts, slow uploads | Use `trace: 'on-first-retry'` |340| `video: 'on'` in CI | Enormous storage, slower tests | Use `video: 'retain-on-failure'` |341| Upload artifacts only on failure | No report when tests pass | Upload with `if: ${{ !cancelled() }}` |342| No retention limits | CI storage fills quickly | Set `retention-days: 7-14` |343| Only `dot` reporter | Cannot drill into failures | Pair `dot` with `html` |344| JUnit to stdout | Interferes with console output | Write to file |345| Blocking `onEnd` in custom reporter | Slow HTTP calls delay pipeline | Use `Promise.race` with timeout |346347## Troubleshooting348349### Empty HTML Report350351Check reporter config. HTML report defaults to `playwright-report/`:352353```typescript354import { defineConfig } from '@playwright/test';355356export default defineConfig({357reporter: [['html', { outputFolder: 'playwright-report', open: 'never' }]],358});359```360361### Traces Too Large362363Switch from `trace: 'on'` to `'on-first-retry'` with retries enabled:364365```typescript366import { defineConfig } from '@playwright/test';367368export default defineConfig({369retries: process.env.CI ? 2 : 0,370use: {371trace: 'on-first-retry',372},373});374```375376### JUnit XML Not Recognized377378Ensure path matches CI configuration:379380```typescript381reporter: [['junit', { outputFile: 'results/junit.xml' }]],382```383384```yaml385# GitHub Actions386- uses: dorny/test-reporter@latest387with:388path: results/junit.xml389reporter: java-junit390391# Azure DevOps392- task: PublishTestResults@latest393inputs:394testResultsFiles: 'results/junit.xml'395396# Jenkins397junit 'results/junit.xml'398```399400### Empty Merged Report401402Use `blob` reporter for sharded runs (not `html`):403404```typescript405import { defineConfig } from '@playwright/test';406407export default defineConfig({408reporter: process.env.CI409? [['blob'], ['dot']]410: [['html', { open: 'on-failure' }]],411});412```413414### Missing Screenshots in Report415416Enable screenshots and keep both directories:417418```typescript419use: {420screenshot: 'only-on-failure',421},422```423424The HTML report embeds screenshots from `test-results/`. Deleting that directory removes screenshots from the report.425