Skip to content

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

free, no account, live for 30 days.

Shipping…

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-py

Or 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 .url

engine: 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

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.