Playwright test reporting
A practical guide to reporting Playwright results: what each built-in reporter actually does, which ones to run in CI, how to merge sharded runs, and how to add hosted, historical analysis — AI failure clustering, flaky-test scoring, and per-launch risk — on top with Qualflare.
Playwright’s built-in reporters, explained
Playwright test reporting is the process of turning raw run results — passes, failures, retries, traces — into something a team can read and act on. Playwright ships several built-in reporters, and they compose: you can run a console reporter for humans and a machine-readable one for tools in the same run.
list/line/dot. Console output at three verbosity levels.listprints a line per test (the local default);dotprints one character per test, which keeps CI logs short and is the default on CI.html. A self-contained interactive report inplaywright-report/— filterable, with attached traces, screenshots, and videos. The best way to debug a single run locally.json. The full structured result — every test, attempt, retry, error, and attachment — written to a file you point tools at. This is the richest machine-readable output Playwright produces.junit. JUnit-style XML. Less detailed than JSON, but the lingua franca of CI platforms: GitLab, Jenkins, and Azure DevOps all render it natively as a test report tab.blob. A raw intermediate report whose only job is to be merged later — the building block for sharded runs (more below).github. GitHub Actions annotations: failures show up inline on the PR diff. Zero value outside GitHub, cheap to add inside it.
A pragmatic config runs lightweight reporters locally and machine-readable ones in CI:
// playwright.config.ts — typical local vs CI reporter split
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: process.env.CI
? [['dot'], ['json', { outputFile: 'playwright-results.json' }], ['github']]
: [['list'], ['html']],
}); Why the built-in HTML report isn’t enough
The HTML report is excellent at what it’s designed for — inspecting one run. Its limits
show up the moment you have a pipeline: the report is written to a local
playwright-report folder and overwritten on the
next run. There’s no history across CI runs, no aggregation of results from parallel jobs, and no
way to see whether a test has been getting flakier over the last two weeks. You can archive each
run’s report as a CI artifact, but a folder of 200 zip files is storage, not reporting — nothing
connects run #1481 to run #1482, and nobody opens them.
For anything beyond a single run, you need a place that collects results over time and analyzes them: trend lines, flakiness scores, failure grouping. That’s the gap the rest of this guide fills — first the CI plumbing, then the analysis layer.
CI patterns: GitHub Actions, GitLab, Jenkins
GitHub Actions. Add the
github reporter for PR annotations, write JSON
for downstream tools, and always upload results even when tests fail — a reporting step that only
runs on green builds is useless on the day you need it:
# .github/workflows/e2e.yml
- name: Run Playwright tests
run: npx playwright test
- name: Upload results to Qualflare
if: always() # upload even when tests fail — that's the point
run: qf my-project collect playwright-results.json GitLab CI. GitLab renders JUnit XML natively in the pipeline’s Tests tab, so emit JUnit alongside whatever else you run:
// playwright.config.ts — JUnit XML for CI test-report integrations
export default defineConfig({
reporter: [['junit', { outputFile: 'results.xml' }]],
}); # .gitlab-ci.yml — JUnit output doubles as GitLab's native test report
e2e:
script:
- npx playwright test
artifacts:
when: always
reports:
junit: results.xml
paths:
- playwright-report/ Jenkins. Same idea: produce
results.xml with the JUnit reporter and hand it
to the classic junit 'results.xml' pipeline
step. Jenkins tracks pass/fail counts per build — useful, though it knows nothing about retries or
flakiness semantics.
Whatever the platform, the principle is the same: a console
reporter for the log, a machine-readable file for tools, uploaded with
if: always() semantics.
Sharded runs: one report from parallel jobs
Once a suite takes more than ~10 minutes, you’ll split it across machines with
--shard — and instantly create a reporting
problem: four jobs now produce four partial reports. Playwright’s answer is the
blob reporter plus
merge-reports:
# Run a 4-way shard — each job writes a blob report
npx playwright test --shard=1/4 --reporter=blob
# (repeat with 2/4, 3/4, 4/4 in parallel CI jobs;
# upload each job's blob-report/ directory as a CI artifact) # In a final CI job: download all blob reports, merge to one JSON, upload once
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-results.json \
npx playwright merge-reports --reporter json ./all-blob-reports
qf my-project collect playwright-results.json The merged output is indistinguishable from a single-machine run, so any consumer of Playwright results — the HTML viewer, your CI’s JUnit tab, or Qualflare’s CLI — works on it unchanged. One upload per pipeline, not one per shard, keeps your launch history clean.
Common Playwright reporting problems (and fixes)
- The results file is missing after a crashed run. Reporters
write their output when the run finishes — if the process is killed (CI timeout, OOM), the JSON
file may never be written. Set a job-level timeout above Playwright’s own
globalTimeoutso Playwright ends the run and reports, instead of the CI runner killing it mid-flight. - CLI reporter config silently replaces yours. Passing
--reporteron the command line overrides the config file’s reporter array rather than adding to it — a classic cause of “the JSON stopped appearing in CI.” Keep the full reporter list inplaywright.config.tsand avoid the flag in CI scripts, or repeat every reporter in the flag. - Mismatched shard reports won’t merge.
merge-reportsrequires all blob reports to come from the same Playwright version. If shards install dependencies independently, a lockfile update landing mid-pipeline can split versions across shards — pin the version and install from the lockfile (npm ci). - The HTML report self-opens and hangs CI. The HTML
reporter’s default
open: 'on-failure'tries to serve the report after a failed local run; on CI that can stall a job. Setopen: 'never'whenprocess.env.CIis set. - Artifacts balloon to gigabytes. Traces and videos for
every test add up fast. Record them only when they’re useful:
trace: 'on-first-retry'andvideo: 'retain-on-failure'keep failure debugging intact while shrinking artifact uploads by an order of magnitude. - Pass rates look fine but the suite is rotting. Retries hide instability: a test that passes on attempt two counts as a pass. Track the flaky status (Playwright marks retried-then-passed tests as flaky in JSON output) — if you only watch the red/green summary, flakiness compounds until the suite stops being trusted. This is the metric to alert on, not the pass rate.
Send Playwright results to Qualflare
It’s a 2-minute, two-step setup. First, add Playwright’s JSON reporter so each run writes a machine-readable results file (keep your HTML reporter for local debugging):
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Keep your existing reporters and add JSON for upload:
reporter: [
['html'],
['json', { outputFile: 'playwright-results.json' }],
],
}); Then upload that file with the Qualflare CLI. There’s no per-framework configuration — the CLI auto-detects the Playwright format and attaches your Git branch and commit:
# Upload the results — the CLI auto-detects the Playwright format
qf my-project collect playwright-results.json
In CI it’s the one extra step shown in the GitHub Actions snippet above — and identical in GitLab
CI, Bitbucket Pipelines, and Jenkins. Authenticate the CLI once with your Qualflare access token,
stored as a CI secret — see the
CLI docs.
For sharded suites, run qf collect on the
merged JSON from merge-reports.
What you get on top of Playwright
- AI failure clustering. When a deploy breaks 40 Playwright specs, Qualflare groups them by root cause so you triage a handful of clusters instead of 40 stack traces.
- Flaky-test scoring from your retry data. The CLI reads Playwright’s native retry counts and flaky status, and Qualflare scores each test’s flakiness across runs — so you can quarantine or fix the genuinely unstable ones.
- Per-launch risk. Every CI run becomes a “launch” with a risk rating, the failing areas, and recommended next steps — a ship / don’t-ship signal that arrives with the results.
- History & trends. Pass rate, slowest specs, and flakiness over time across branches and shards — the aggregation the HTML report can’t do.
- Context & defects. Failures keep their Playwright traces and screenshots, and you can spin up a defect straight from a failing run.
Built-in HTML reporter vs Qualflare
| Playwright HTML report | Qualflare | |
|---|---|---|
| History across CI runs | — | Yes |
| Aggregates sharded / parallel runs | Via merge-reports | Yes |
| AI failure clustering (root cause) | — | Yes |
| Flaky-test scoring over time | — | Yes |
| Local, zero-setup, offline | Yes | — |
| Traces & screenshots per failure | Yes | Yes |
They’re complementary: keep the HTML reporter for local debugging, add Qualflare for hosted, historical CI observability.
Get AI analysis on your Playwright runs
Start free — add the JSON reporter, run qf collect, and get your first AI analysis in minutes.
Qualflare works the same with pytest, Cypress, Jest, JUnit and 20+ more frameworks. Weighing tools? See how it compares to other test management platforms, or browse all framework reporting guides.
Frequently asked questions
Which Playwright reporter should I use in CI?
Use a quiet console reporter (dot) plus a machine-readable one: JSON if a tool ingests your results, JUnit XML if your CI platform renders test reports natively (GitLab, Jenkins, Azure DevOps), and the github reporter for inline annotations in GitHub Actions. Reporters compose, so you can run several at once. Keep the HTML reporter for local debugging rather than CI.
How do I merge reports from sharded Playwright runs?
Run each shard with --reporter=blob, upload each job’s blob-report directory as a CI artifact, then in a final job download them all and run npx playwright merge-reports --reporter json (or html/junit) over the directory. The merged output looks exactly like a single-run report, so anything that consumes Playwright results — including Qualflare’s CLI — works on it unchanged.
Does Qualflare replace the Playwright HTML reporter?
No — it complements it. Playwright’s built-in HTML reporter is great for inspecting a single local run, but it’s overwritten on each run and keeps no history across CI. Qualflare ingests your Playwright results (JSON or JUnit) and adds hosted, historical reporting with AI failure clustering, flaky-test scoring, and per-launch risk across every run. Many teams keep the HTML reporter for local debugging and use Qualflare for CI-wide observability.
How do I send Playwright results to Qualflare?
Enable Playwright’s JSON reporter (reporter: [['json', { outputFile: 'playwright-results.json' }]]), run your tests, then upload with the Qualflare CLI: qf <project> collect playwright-results.json. The CLI auto-detects the Playwright format and attaches Git metadata, creating a tracked launch. JUnit XML works too.
Does Qualflare detect flaky Playwright tests?
Yes. The CLI reads Playwright’s native retry counts and flaky status directly from the results — no extra configuration — and Qualflare scores each test’s flakiness from history across runs. You can see which Playwright tests are intermittently failing and whether that’s trending up or down.
Does it work in GitHub Actions, GitLab CI, and Jenkins?
Yes. Add a step after your Playwright run that calls qf <project> collect on the results file. The CLI auto-attaches the Git branch and commit (or pass --branch/--commit), so each CI run becomes a tracked launch with AI analysis. The same flow works in GitLab CI, Bitbucket Pipelines, and Jenkins.
Setup reflects the Qualflare CLI (docs.qualflare.com) and Playwright’s reporters as of June 2026. Published 11 June 2026. Written by İbrahim Süren, Qualflare.