Parse coverage reports, compare branches, and generate colorized markdown for your PRs.
This GitHub Action parses Clover and Cobertura XML coverage files and generates a colorized markdown report. On pull requests, it compares the current coverage against a stored baseline from the target branch, showing per-file differences. Reports are written to the job summary and can be posted as a PR comment using an action like marocchino/sticky-pull-request-comment.
Baseline coverage is stored as GitHub workflow artifacts, so no external server or database is needed.
- Clover & Cobertura -- auto-detects and parses both XML coverage formats
- Branch comparison -- compares PR head coverage against a baseline artifact from the base branch
- Colorized reports -- per-file coverage with green/orange/red indicators based on configurable thresholds
- Job summary & PR comments -- writes to GitHub's job summary and generates a markdown file for PR comment actions
- Failure gates -- configurable thresholds to fail the workflow on coverage drops or low overall coverage
- Flexible aggregation -- per-file, by top-level directory, by parent directory, or by configurable path depth
- Path exclusion -- exclude directories from coverage calculations
- Custom templates -- bring your own Handlebars templates for full report customization
- Shields.io badge -- optional inline coverage badge in the generated report
Run this action on pushes to your default branch so that a baseline artifact is available for PR comparisons.
name: 'coverage-baseline'
on:
push:
branches: [main]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --coverage
- uses: clearlyip/code-coverage-report-action@v7
with:
filename: coverage/clover.xmlname: 'ci'
on:
pull_request:
branches: [main]
permissions:
pull-requests: write
contents: read
actions: read
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --coverage
- uses: clearlyip/code-coverage-report-action@v7
id: coverage
with:
filename: coverage/clover.xml
fail_on_negative_difference: true
overall_coverage_fail_threshold: 70
artifact_download_workflow_names: 'coverage-baseline'
- name: Post Coverage Comment
uses: marocchino/sticky-pull-request-comment@v3
if: steps.coverage.outputs.file != '' && (success() || failure())
with:
recreate: true
path: code-coverage-results.md| Input | Required | Default | Description |
|---|---|---|---|
filename |
yes | -- | Path to the coverage XML file (Clover or Cobertura) |
github_token |
no | ${{ github.token }} |
GitHub token (needed for private repos) |
markdown_filename |
no | code-coverage-results |
Output markdown filename (without .md extension) |
badge |
no | false |
Include a shields.io coverage badge in the report |
overall_coverage_fail_threshold |
no | 0 |
Fail if overall coverage falls below this percentage |
file_coverage_error_min |
no | 50 |
Coverage below this is marked red |
file_coverage_warning_max |
no | 75 |
Coverage below this is marked orange; above is green |
fail_on_negative_difference |
no | false |
Fail if any file's coverage decreases |
negative_difference_by |
no | package |
How to evaluate negative difference: overall or package |
negative_difference_threshold |
no | 0 |
Allowed coverage drop percentage (e.g. -2 means 2% drop is OK) |
artifact_download_workflow_names |
no | current job | Comma-separated workflow names to search for baseline artifacts |
artifact_name |
no | coverage-%name% |
Artifact name pattern (%name% is replaced with the branch name) |
retention_days |
no | -- | Artifact retention period in days |
show_coverage_by_top_dir |
no | false |
Aggregate coverage by first path segment |
coverage_depth |
no | -- | Aggregate by path to the specified depth |
show_coverage_by_parent_dir |
no | false |
Aggregate by each file's parent directory |
only_list_changed_files |
no | false |
Only show files whose coverage changed |
exclude_paths |
no | -- | Comma-separated path prefixes to exclude from the report |
with_base_coverage_template |
no | -- | Path to a custom Handlebars template for reports with baseline |
without_base_coverage_template |
no | -- | Path to a custom Handlebars template for reports without baseline |
| Output | Description |
|---|---|
file |
Path to the generated markdown file |
coverage |
Overall coverage percentage |
The action behaves differently depending on the GitHub event:
-
push/schedule/workflow_dispatch-- parses the coverage file, generates a report, and uploads the coverage file as an artifact keyed by branch name. This serves as the baseline for future PR comparisons. -
pull_request-- downloads the baseline artifact from the target branch, parses both the baseline and current coverage files, computes per-file differences, generates a comparison report, and writes it to the job summary.
GitHub artifacts have a limited retention period (1-90 days for public repos, 1-400 days for private repos). To ensure baselines are available, schedule a periodic run on your default branch:
name: 'coverage-refresh'
on:
workflow_dispatch:
schedule:
- cron: '0 0 1 * *' # Monthly
jobs:
refresh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --coverage
- uses: clearlyip/code-coverage-report-action@v7
with:
filename: coverage/clover.xmlImportant
Set artifact_download_workflow_names in your PR workflow to include the name of this refresh workflow so the action can find the baseline artifact.
npm installmake buildmake watchmake testLocal testing with act
make actArtifacts avoid polluting your git history with coverage data. The trade-off is that GitHub's artifact retention is limited, so you need a periodic refresh job on your default branch (see above).
Yes. The action only cares about the XML coverage file. Any tool that produces Clover or Cobertura output (Jest, Istanbul, PHPUnit, OpenCover, etc.) will work.
Use the with_base_coverage_template and without_base_coverage_template inputs to provide your own Handlebars templates. See the default templates for reference.
This error means the GitHub token doesn't have permission to list workflow runs. There are two ways to fix it:
Option 1 — per-workflow permissions (recommended): Add actions: read to your workflow's permissions block:
permissions:
pull-requests: write
contents: read
actions: read # required to find and download baseline artifactsOption 2 — repository default permissions: In your repository go to Settings → Actions → General → Workflow permissions and select Read and write permissions. This grants the GITHUB_TOKEN broader access by default across all workflows without needing explicit permissions blocks.
Option 1 is preferred because it follows the principle of least privilege — only the workflows that need it get the permission.
