Skip to main content
Back to Blog
Guide
2026-05-07

Playwright Reporters Complete Guide: HTML, Allure, JUnit, Blob

Configure Playwright reporters in 2026: built-in HTML, list, dot, line, junit, json, blob, plus Allure, Monocart, and custom reporter authoring.

Playwright Reporters Complete Guide: HTML, Allure, JUnit, Blob

Test reporters are how Playwright communicates results to humans, machines, and CI services. The default HTML report is rich enough for local debugging; JUnit XML feeds GitHub Actions and Jenkins; Allure adds product manager dashboards; blob enables sharded report merging. In 2026 every team should run at least two reporters at once: one for humans, one for CI. This guide is the complete reference.

For the broader CI story, read the Playwright CI GitHub Actions Complete Guide. The playwright-e2e skill helps AI assistants pick the right reporter mix.

Built-in reporters

ReporterOutputBest for
listStreaming list with timingLocal dev
dotOne char per testCI logs
lineSingle line summaryCI logs (default in CI)
htmlInteractive HTML reportLocal debugging
junitJUnit XMLGitHub Actions, Jenkins
jsonJSON dumpCustom downstream tooling
blobMergeable archiveSharded CI
githubAnnotations in PRsGitHub Actions
nullNo outputManual reporters

Configuring reporters

Reporters live in playwright.config.ts. Pass a string for defaults, or a tuple for options.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['list'],
    ['html', { open: 'never', outputFolder: 'playwright-report' }],
    ['junit', { outputFile: 'test-results/junit.xml' }],
    process.env.CI ? ['github'] : ['null'],
  ],
});

Multiple reporters run in parallel. Each receives the same events.

The HTML reporter in depth

The HTML reporter writes a static site with timeline, trace links, screenshots, and stdout per test.

OptionTypePurpose
outputFolderstringDirectory to write into
open'never' | 'always' | 'on-failure'When to auto-open
hoststringBind address when serving
portnumberPort when serving
attachmentsBaseURLstringCDN URL for attachments

After a run, view with:

npx playwright show-report

The site is fully static; you can upload it to S3, GitHub Pages, or any HTTP host without a backend.

Trace integration

When configured with trace: 'on' or trace: 'on-first-retry', the HTML report links to trace files. Click "View trace" on any test to open the trace viewer in a new tab.

use: {
  trace: 'on-first-retry',
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
},

The blob reporter for sharded CI

The blob reporter writes a serialized archive that the merge-reports CLI combines into a single HTML report.

- run: pnpm exec playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@v4
  with:
    name: blob-${{ matrix.shardIndex }}
    path: blob-report

Then in a merge job:

- uses: actions/download-artifact@v4
  with:
    pattern: blob-*
    merge-multiple: true
    path: all-blob-reports
- run: pnpm exec playwright merge-reports --reporter=html ./all-blob-reports

The blob reporter is the only built-in option that produces mergeable output. Without it, shard reports overwrite each other.

JUnit XML for CI integration

JUnit is the lingua franca of CI tools. Most providers ingest it automatically.

['junit', {
  outputFile: 'test-results/junit.xml',
  stripANSIControlSequences: true,
  embedAttachmentsAsProperty: 'testrun_evidence',
}],

The embedAttachmentsAsProperty option inlines failure screenshots into the XML as base64.

GitHub Actions annotations

The github reporter posts annotations directly to PR diffs.

['github'],

When a test fails, the failure appears as an annotation on the relevant line of the test file in the PR's Files tab. No further configuration is required.

Allure: the heavyweight option

Allure adds rich grouping, history, severity, owners, and attachments. Install:

pnpm add -D allure-playwright
reporter: [
  ['list'],
  ['allure-playwright', { outputFolder: 'allure-results' }],
],

After running tests, generate and serve the report:

npx allure generate allure-results --clean
npx allure open allure-report

Add metadata in test code:

import { test } from '@playwright/test';
import { allure } from 'allure-playwright';

test('user signs in', async ({ page }) => {
  await allure.epic('Authentication');
  await allure.feature('Sign in');
  await allure.story('Email + password');
  await allure.severity('critical');
  await allure.owner('asha@example.com');
  await allure.tag('@smoke');

  await page.goto('/login');
  // ... rest of test
});

The generated report shows tests grouped by epic, feature, and story, with history and trends across CI runs.

Monocart: a lightweight Allure alternative

Monocart produces a single self-contained HTML file with similar features.

pnpm add -D monocart-reporter
reporter: [
  ['monocart-reporter', {
    name: 'QASkills',
    outputFile: './monocart-report/index.html',
    coverage: { entryFilter: (entry) => true },
  }],
],

The output file works offline and is small enough to email.

Writing a custom reporter

For org-specific needs (Slack messages, Datadog metrics, internal dashboards), implement a custom reporter.

// reporters/slack-reporter.ts
import type { Reporter, TestCase, TestResult, FullResult } from '@playwright/test/reporter';

class SlackReporter implements Reporter {
  private failures: { test: string; error: string }[] = [];

  onTestEnd(test: TestCase, result: TestResult) {
    if (result.status !== 'passed' && result.status !== 'skipped') {
      this.failures.push({
        test: test.titlePath().join(' > '),
        error: result.errors[0]?.message ?? 'unknown',
      });
    }
  }

  async onEnd(result: FullResult) {
    if (this.failures.length === 0) return;
    const text = this.failures
      .map((f) => `*${f.test}*\n\`\`\`${f.error}\`\`\``)
      .join('\n');
    await fetch(process.env.SLACK_WEBHOOK!, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text }),
    });
  }
}

export default SlackReporter;

Register in config:

reporter: [
  ['list'],
  ['./reporters/slack-reporter.ts'],
],

Reporter lifecycle methods

The full Reporter interface:

MethodWhen it fires
onBeginBefore any tests run
onTestBeginA test is about to start
onStepBeginA step within a test starts
onStdOutA test wrote to stdout
onStdErrA test wrote to stderr
onStepEndA step within a test ends
onTestEndA test finishes
onEndAll tests have finished
onErrorA non-test error fires
onExitProcess is exiting

Most reporters only need onTestEnd and onEnd.

Attaching files to reports

Tests can attach arbitrary files via testInfo.attach.

test('captures API response', async ({ page, request }) => {
  const response = await request.get('/api/users');
  await test.info().attach('api-response.json', {
    body: await response.body(),
    contentType: 'application/json',
  });
});

Attachments appear in the HTML, Allure, and Monocart reports as downloadable files.

CI configuration matrix

CI serviceReporter setup
GitHub Actions['github'], ['blob']
GitLab CI['junit']
CircleCI['junit', { outputFile: '/tmp/junit/junit.xml' }]
Jenkins['junit'] + JUnit plugin
Azure DevOps['junit'] + PublishTestResults task
Buildkite['junit'] + buildkite-test-collector

Always include ['list'] for streaming logs; bare CI reporters are silent until completion.

Common pitfalls

Pitfall 1: Forgetting blob for sharded CI. Without blob, every shard tries to write to playwright-report and the last one wins. Merge reports require blob.

Pitfall 2: Opening HTML report in CI. open: 'always' in CI tries to spawn a browser and fails. Use 'never' and upload as artifact.

Pitfall 3: JUnit path mismatch. GitHub Actions expects test-results/junit.xml; CircleCI expects /tmp/junit/junit.xml. Use env vars to differentiate.

Pitfall 4: Allure history not persisting. Allure's trend chart requires the history folder from previous runs. Cache it between runs or upload to S3.

Pitfall 5: Custom reporters that throw. A throwing reporter can mask test failures. Wrap all I/O in try/catch and log to stderr.

Anti-patterns

  • Running only json and writing your own consumer. The built-in HTML reporter renders the same data better.
  • Sending every test result to Slack. Slack fatigue defeats the alert. Limit to failures on main branch.
  • Storing HTML reports in the repo. Generate in CI and upload as artifact.
  • Configuring reporters in test files. Always keep them in playwright.config.ts so they apply uniformly.

Performance considerations

Reporters run in-process. Heavy I/O in a custom reporter can slow CI. Buffer events and flush in onEnd to minimize overhead.

class BufferedReporter implements Reporter {
  private buffer: any[] = [];
  onTestEnd(test: TestCase, result: TestResult) {
    this.buffer.push({ test: test.title, status: result.status });
  }
  async onEnd() {
    await uploadBatch(this.buffer);
  }
}

Conclusion and next steps

Pick one human reporter (HTML or Allure), one CI reporter (JUnit or GitHub), and blob if you shard. That mix covers debugging, automation, and PR feedback.

Install the playwright-e2e skill so AI assistants set up the right reporter mix. For broader CI patterns, see Playwright CI GitHub Actions Complete Guide. For trace-based debugging, Playwright Screenshots Videos Traces Complete Guide covers the artifact side.

Playwright Reporters Complete Guide: HTML, Allure, JUnit, Blob | QASkills.sh