Share a Karma or Angular test report

ng test --code-coverage produces a genuinely useful artifact: your source, line by line, with what the specs touched and what they didn't. It answers the question a coverage percentage never does — which branch is untested, and does that branch matter?

Then it lands in coverage/ on a CI runner, gets zipped as an artifact, and is never opened by anyone.

Generate it

ng test --watch=false --code-coverage
# → coverage/<project-name>/index.html

Under a bare Karma setup, karma-coverage does the same thing with the same Istanbul output. For pass/fail results rather than coverage, karma-htmlfile-reporter writes a single self-contained file, which is the easier shape to publish.

The coverage output is a directory:

coverage/my-app/
├── index.html
├── src/app/…/foo.component.ts.html   # one page per source file
├── base.css  prettify.css
└── prettify.js  sorter.js  block-navigation.js

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 coverage/my-app/ (or a zip of it) into Comma:

  • index.html becomes the report body — the page carrying the comment layer.
  • The per-file pages, CSS and JS upload alongside it, and relative references are rewritten to the uploaded copies, so the drill-down from summary to directory to annotated source resolves.
  • Scripts run inside a sandboxed iframe (allow-scripts, no allow-same-origin), so the sortable columns and the next/previous-uncovered-block navigation keep working.

From CI

ng test --watch=false --browsers=ChromeHeadless --code-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 coverage/my-app/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 matters: a failing spec fails the step, and the publish has to survive it. Add if: always() in GitHub Actions, after_script in GitLab, post { always { … } } in Jenkins. Use a scoped token (reports:write) from CI secrets and PATCH a saved report id, so one URL accumulates a revision per run instead of a new orphan link every build. See publishing from CI.

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 error path in the interceptor" — and it stays anchored there across runs. See commenting on HTML.
  • Revision diffs beat the percentage. Two runs, one diff, which lines actually changed coverage rather than a number drifting by a third of a point.

Who can see it

The coverage report contains your source code. Access is per report — private, your team, anyone signed in at your domain, or anyone with the link — and team or private is the right default for a private repo (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 Angular app can exceed 500 source pages — publish the feature module under review rather than the whole tree.
  • Scripts run, sandboxed — no same-origin access.
  • 60 requests/minute per token.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Publish a coverage report →

Related