Your Python coverage HTML report, as a link on every PR
coverage html turns a Python test run into a browsable site with per-line hit highlighting. Instead of letting the report rot in an artifacts zip, publish it — reviewers open a link and see exactly which lines your diff missed.
Drop your Python coverage report here — get a live link
.zip, folder, or single .html — free, no account, live for 30 days.
The artifact dead-end
The console summary tells you coverage dropped 2%. The HTML report tells you which lines. Guess which one ends up zipped in the artifacts tab where nobody opens it.
Reviewers deserve the annotated source — yellow partial branches, red misses — one click from the PR, not a download-and-extract ritual.
Two lines to a live link
Build your output like you already do, then hand the folder to the action:
Generate the report
coverage run -m pytest && coverage html.github/workflows/ci.yml
- uses: bitgate/ship-page-action@v1
if: always()
with:
engine: coverage-pyOr with plain curl — no account needed
cd htmlcov && zip -qr - . | curl -fsS -X POST https://ship.page/deploy -H "Content-Type: application/zip" --data-binary @- | jq -r .urlengine: coverage-py makes the action find htmlcov/ on its own — prefer explicit? path: htmlcov/ does the same thing. Omit both and every known report gets probed; two or more matches land behind one URL with a built-in switcher. The link goes in the job summary — add comment: true and it lands on the pull request too.
What you get
- Per-file annotated source renders exactly as coverage.py wrote it — served from the root of its own subdomain
- Works with pytest-cov too: --cov-report=html produces the same htmlcov/ layout
- Omit path entirely — the action auto-detects htmlcov/
- The URL is the access control — viewers need no GitHub account or repo access, slugs are unguessable, and there's no public index
- Anonymous links live for 30 days; deploy with an API key to control the TTL, or drop expiry entirely on a paid plan
Questions
Does it work with pytest-cov?
Yes — pytest --cov=. --cov-report=html writes htmlcov/, the exact layout the action looks for.
Can I keep one stable coverage URL per branch?
Yes: name: cov-${{ github.ref_name }} plus an sp_ API key redeploys the same URL on every push to that branch.
Is my source code exposed?
The report contains annotated source, so treat the URL like a secret: slugs are unguessable and there's no listing. For sensitive repos, use expiry — anonymous drops self-destruct after 30 days.
Can I view a coverage.py HTML report without hosting it myself?
Yes — that's the whole point. The action (or one curl) puts htmlcov/ on a live URL; no server, no bucket, no Pages setup. Anyone with the link browses the annotated source.
Does the report expire?
Anonymous drops expire after 30 days. An sp_ API key lets you set the TTL per deploy, and paid plans can keep reports live indefinitely.
Related recipes
- JaCoCo: JaCoCo coverage your team can actually click
- pytest-html: Your pytest report, one click from the PR
- Gradle: Put your Gradle test reports on a URL
Try it on your next run
Anonymous deploys need no account and no key — paste the step, get a link. When you want stable URLs that redeploy in place, an API key and a name are all it takes.
Need to share the report with someone outside GitHub? How to share a GitHub Actions artifact without a login.