Custom Reporters and Third-Party Integrations

8 min read

The HTML report is built for humans triaging a single run. CI pipelines, dashboards, and chat tools want the same data in different shapes — JUnit XML for Jenkins, JSON for analytics, Allure for stakeholders, a Slack message for the on-call. Playwright's reporter system lets you produce all of those from the same test run — no test code changes, no duplicate runs. This lesson covers the built-in reporters, the multi-reporter array, the Allure integration, writing a custom reporter from scratch, and attaching arbitrary data (screenshots, JSON blobs, files) to the report so failures carry their own context.

Multiple reporters at once

The reporter field accepts an array. Every entry runs against every test:

// playwright.config.ts
import { defineConfig } from "@playwright/test";
 
export default defineConfig({
  reporter: [
    ["list"],
    ["html", { open: "never" }],
    ["junit", { outputFile: "results/junit.xml" }],
    ["json", { outputFile: "results/results.json" }]
  ]
});

This single config produces:

  • A live terminal stream (list) so the developer sees progress.
  • An HTML report (html) for triage.
  • A JUnit XML (junit) Jenkins parses into the build summary.
  • A JSON dump (json) for analytics tooling.

Every reporter runs once per test. There's no extra cost — the test only runs once.

Built-in reporters

NameOutputUse it when
listVerbose, one line per test, with timingDeveloping locally and want to see each test name
dotSingle character per testLong suites where you want minimal noise
lineOne updating lineCI logs that scroll forever
htmlRich, browseable web reportTriaging, sharing, attaching to PRs
junitStandard JUnit XMLJenkins, GitLab CI, Azure DevOps test summaries
jsonMachine-readable JSONCustom dashboards, analytics, your own tooling
blobIntermediate format for sharded mergingMulti-shard CI (covered in the sharding lesson)
githubAnnotations on PR diffsGitHub Actions workflows

The github reporter is a quick win for any team on GitHub — failed assertions appear as inline annotations in the PR's "Files changed" tab, pointing at the exact test line.

Allure for stakeholder reporting

Allure is the de facto reporter when non-engineers need to read test results — product managers, QA leads, regulated-industry auditors. It produces a polished web UI with categories, severity tags, history, and trend graphs.

Install:

npm install --save-dev allure-playwright

Configure:

// playwright.config.ts
import { defineConfig } from "@playwright/test";
 
export default defineConfig({
  reporter: [
    ["line"],
    ["allure-playwright", { outputFolder: "allure-results", detail: true, suiteTitle: false }]
  ]
});

Annotate tests with metadata Allure surfaces:

import { test, expect } from "@playwright/test";
import { allure } from "allure-playwright";
 
test("user can complete checkout", async ({ page }) => {
  allure.epic("Checkout");
  allure.feature("Cart");
  allure.severity("critical");
  allure.owner("payments-team");
 
  await page.goto("https://www.saucedemo.com");
  // ...
});

Generate and view the report:

npx allure generate allure-results --clean
npx allure serve allure-results

Allure's history view (trend, flaky-test rate, failure clustering by epic) is what makes it stick in compliance-heavy industries — auditors love the historical record.

Writing a custom reporter

Implement the Reporter interface — every method is optional, so you wire only the events you care about:

// reporters/slack-reporter.ts
import type {
  FullConfig,
  FullResult,
  Reporter,
  TestCase,
  TestResult
} from "@playwright/test/reporter";
 
export default class SlackReporter implements Reporter {
  private failed: { title: string; error: string }[] = [];
 
  onBegin(config: FullConfig, suite: { allTests(): TestCase[] }) {
    console.log(`Starting run with ${suite.allTests().length} tests`);
  }
 
  onTestEnd(test: TestCase, result: TestResult) {
    if (result.status === "failed") {
      this.failed.push({
        title: test.title,
        error: result.error?.message?.split("\n")[0] ?? "unknown"
      });
    }
  }
 
  async onEnd(result: FullResult) {
    if (this.failed.length === 0) return;
    const text = [
      `*Playwright run ${result.status}* — ${this.failed.length} failure(s)`,
      ...this.failed.map(f => `• ${f.title} — ${f.error}`)
    ].join("\n");
 
    await fetch(process.env.SLACK_WEBHOOK!, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ text })
    });
  }
}

Wire it in:

// playwright.config.ts
reporter: [
  ["html", { open: "never" }],
  ["./reporters/slack-reporter.ts"]
]

Now every CI failure pings Slack with the test title and first line of the error — no more "did the build pass?" pings to the on-call.

Attaching data to test reports

A failure that says "expected 42, got undefined" without the API response that produced it forces you to re-run the test to investigate. Attach the response during the test and the report carries the evidence:

import { test, expect } from "@playwright/test";
 
test("API returns expected balance", async ({ page, request }, testInfo) => {
  const resp = await request.get("https://api.saucedemo.com/account/123");
  const body = await resp.json();
 
  await testInfo.attach("api-response", {
    body: JSON.stringify(body, null, 2),
    contentType: "application/json"
  });
 
  expect(body.balance).toBe(42);
});

If the assertion fails, the HTML report shows the attached response right next to the error. Same for screenshots, generated CSVs, downloaded files — anything Buffer | string.

The reporting pipeline

Coming from Cypress?

Cypress's reporter ecosystem is built around Mocha:

  • cypress-mochawesome-reporter is roughly the analogue of html.
  • mocha-junit-reporter is roughly junit.
  • Allure works for both via @shelex/cypress-allure-plugin.

The differences:

  • Playwright's reporters are first-class, not Mocha plugins, and the API (onTestEnd, onEnd) is documented and stable.
  • Multiple reporters in Cypress sometimes conflict (Mocha allows one root reporter, hence wrappers like cypress-multi-reporters); Playwright handles many natively.
  • testInfo.attach is built in; in Cypress you log artefacts to the cy.task interface and stitch them in via plugin hooks.

⚠️ Common mistakes

  • Putting secrets in the JSON reporter. Anyone who downloads results.json sees test logs verbatim. Don't console.log API tokens or PII inside tests — even green runs leak it into the artefact.
  • Forgetting to install dev dependencies for community reporters. allure-playwright and similar packages need to be present at runtime; CI fails with "Cannot find module" if they're missing. Add them to package.json, not just locally.
  • Custom reporters that throw. An exception inside onTestEnd can crash the whole reporter pipeline and lose the HTML report. Wrap risky code in try/catch — failed reporting must never erase test results.

🎯 Practice task

Build a multi-reporter setup with a custom Slack notifier. 30 minutes.

  1. Configure four reporters at once:

    import { defineConfig } from "@playwright/test";
     
    export default defineConfig({
      testDir: "./tests",
      reporter: [
        ["list"],
        ["html", { open: "never" }],
        ["junit", { outputFile: "results/junit.xml" }],
        ["./reporters/slack-reporter.ts"]
      ]
    });
  2. Implement reporters/slack-reporter.ts using the example above. For local testing, post to https://webhook.site (a free disposable webhook) instead of Slack.

  3. Write three tests: one passing, two deliberately failing. Run npx playwright test.

  4. Confirm: terminal shows list output, playwright-report/ has the HTML, results/junit.xml exists and parses, the webhook URL received a POST listing the two failures.

  5. Add testInfo.attach to one of the failing tests, attaching a JSON payload. Open the HTML report and confirm the attachment appears under the failure.

  6. Stretch: modify the Slack reporter to skip the message when fewer than 1 test failed (avoid noise on flake retries). Read result.status and the count, then decide.

  7. Stretch 2: install allure-playwright, add it to the reporter array, and generate the Allure report. Tag tests with allure.epic / allure.feature so the report has structure.

You can now produce the same evidence for a developer, a CI summary tool, an analytics dashboard, and the team Slack channel — from one test run. The next lesson uses these reports plus the trace viewer to debug a real failure end-to-end.

// tip to track lessons you complete and pick up where you left off across devices.