Share an HTML coverage report
Coverage tooling produces the most obviously shareable artifact in the
build — a browsable, line-by-line map of what your tests actually touch —
and then hands you a folder. htmlcov/index.html plus a hundred
sibling pages and a stylesheet. You can't paste a folder into Slack. So it
gets zipped into CI artifacts, where it expires unread, and the team goes
back to arguing about a single percentage number in the build log.
The percentage is the least interesting thing in that folder.
Drop the folder in
Comma takes HTML bundles, not just single files. Drag htmlcov/ (or
coverage/lcov-report/ from Istanbul, or a zip of either) into
the app:
index.htmlbecomes the report body — the page with the comment layer on it.- Every other HTML page, the CSS, and the images are uploaded alongside it.
- Relative
srcandhrefreferences are rewritten to the uploaded assets, so the drill-down links from the summary table keep working.
The result is one URL that browses like the local report, opens in any browser, and doesn't expire when the CI retention window closes.
From CI, with one curl
For a pipeline, publish the summary page and PATCH the same report id on every run so there is one stable URL per repo:
coverage html # writes htmlcov/
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 htmlcov/index.html '{html: $html}')"
Use a scoped token with only reports:write. Each
push appends a revision at the same URL, so "did this PR move coverage,
and where?" is a diff between two revisions rather than two
numbers in two build logs. The general pipeline pattern is written up in
publish from CI.
Supporting files go up as assets — 25 MB per file, 250 MB and 500 files per report. For a large monorepo, publish per-package reports rather than one giant bundle; it's also how anyone actually reads them.
Coverage numbers don't start conversations. Lines do.
"We're at 68%" is not a discussion. "This branch in
billing/refunds.py has never been executed by a test" is — and it's a
sentence that belongs on that line, not in a Slack thread three days
later.
Comma renders the coverage report with an anchored comment layer on top. A reviewer highlights the uncovered block and pins a thread to it: "this is the partial-refund path — worth a test before the Q4 pricing change." The thread stays attached to that report as revisions accumulate, so the next person to open the coverage report sees the reasoning instead of rediscovering the gap.
For teams that review coverage on a cadence, a routine can refresh the published report on a schedule so the link is never stale.
What renders, what doesn't
- Renders faithfully — the summary table, per-file source listings, syntax highlighting, and the green/red line shading. All of it is CSS.
- Inert — coverage.py's keyboard shortcuts and the filter box, and
Istanbul's sortable headers. Comma strips
<script>on write as defense-in-depth on an endpoint that accepts arbitrary HTML. - Browsable — the per-module pages, via the same relative links the index already uses.
Comment threads live on the report body. If a specific module is the one under review, publish that page as the report and attach the rest.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited
revision history. Run coverage html, drag the folder in, and send the
link to the person who keeps asking about the number.