Share a PHPUnit coverage report
--coverage-html produces one of the better artifacts in PHP tooling: the
actual source, line by line, with what the suite touched and what it
didn't. It answers the question a coverage percentage never does, which is
which branch is untested.
Then it sits in build/coverage/ on a CI runner and expires.
Generate it
vendor/bin/phpunit --coverage-html build/coverage
You need a coverage driver. PCOV in CI — it is much faster than Xdebug and produces the same report. Xdebug is the right tool locally, where step-through debugging is why you have it installed.
The output is a directory:
build/coverage/
├── index.html
├── Foo/Bar.php.html # one page per source file
├── _css/ _js/ _icons/
└── dashboard.html
That's the detail that breaks the naive share: publish index.html alone
and you get an unstyled index whose every drill-down link is dead.
Drop the folder in
Drag build/coverage/ (or a zip of it) into
Comma:
index.htmlbecomes the report body — the page carrying the comment layer.- The per-file pages, CSS, JS and icons upload alongside it, and relative references are rewritten to the uploaded copies, so the drill-down from summary to namespace to annotated source resolves.
- Scripts run inside a sandboxed iframe (
allow-scripts, noallow-same-origin), so the dashboard charts and the sortable tables keep working.
From CI
vendor/bin/phpunit --coverage-html build/coverage || true
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile html build/coverage/index.html \
--arg title "Coverage — $(git rev-parse --short HEAD)" \
'{title: $title, html: $html}')"
The API is JSON-only, so the per-file pages go up through
POST /api/v1/reports/$REPORT_ID/assets as base64 — one call per file,
worth scripting once. See the API reference.
The || true and an if: always() (or after_script in GitLab) keep the
publish alive when tests fail, which is when the report matters most. Use
a scoped token (reports:write) from CI secrets and
PATCH a saved report id so one URL accumulates a revision per run. See
publishing from CI and
GitLab CI HTML reports.
What the URL changes
- The PR links the coverage. Reviewers click instead of rebuilding.
- Feedback lands on the line. Select the uncovered branch, leave a thread — "this is the failure path in the payment handler" — and it stays anchored across runs. See commenting on HTML.
- Revisions diff. Which lines changed coverage, rather than a percentage drifting.
Who can see it
The coverage report contains your source code. Access is per report — private, team, anyone signed in at your domain, or anyone with the link — and team or private is the right default here (domain-gating is Enterprise). See sharing & access control.
Limits
- Entry HTML: 5 MB. Assets: 25 MB per file, 250 MB and 500 files total. A large application can exceed 500 source pages — publish the namespaces under review rather than the whole tree, or point the report at the changed paths.
- Scripts run, sandboxed — no same-origin access.
- 60 requests/minute per token.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.
Related
- Share a coverage report · Share an lcov report
- Share a JUnit report —
--log-junitfor the CI-native view - Share a Go test report · Share a pytest report
- Publish from CI