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.htmlbecomes 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, noallow-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.
Related
- Share a coverage report · Share an lcov report
- Share a Jest or Vitest report · Share a Cypress report
- Share a Storybook build — the other half of component review
- Publish from CI · GitHub Actions HTML reports