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.html becomes 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 src and href references 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.

Create your first report →