Self-hosted coverage and test reporting with GitHub Actions. Uses GitHub Artifacts for storage — no external service or Codecov token required.
name: Teston:
push:
branches: [main]pull_request:
jobs:
test:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- name: Run tests with coveragerun: npm test -- --coverage
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}| Format | File Patterns | Languages/Tools |
|---|---|---|
| Clover XML | clover.xml | Istanbul/NYC (JS/TS), PHPUnit, OpenClover |
| Cobertura XML | coverage.xml, cobertura.xml | coverage.py (Python), Coverlet (.NET), PHPUnit |
| JaCoCo XML | jacoco.xml | Java, Kotlin, Scala |
| LCOV | lcov.info, *.lcov | c8, lcov (C/C++), grcov (Rust), gcov |
| Istanbul JSON | coverage-final.json | Jest, Vitest, NYC (JS/TS) |
| Go Coverage | coverage.out, cover.out | go test -coverprofile |
| Codecov JSON | codecov.json | cargo-llvm-cov (Rust), custom tools |
| Input | Description | Required | Default |
|---|---|---|---|
token | GitHub token for API access and artifacts | Yes | — |
base-branch | Base branch to compare results against | No | Auto-detected |
enable-tests | Enable test results reporting | No | true |
enable-coverage | Enable coverage reporting | No | true |
post-pr-comment | Post results as a PR comment | No | false |
comment-key | Namespace the PR comment per step so steps sharing this action post separate comments instead of overwriting each other | No | — |
| Input | Description | Required | Default |
|---|---|---|---|
files | Comma-separated list of coverage files | No | — |
directory | Folder to search for coverage files | No | . |
exclude | Comma-separated patterns to exclude | No | — |
coverage-format | Format hint: auto, clover, cobertura, jacoco, lcov, istanbul, go, codecov | No | auto |
disable-search | Disable auto-search, use only explicit files | No | false |
| Input | Description | Required | Default |
|---|---|---|---|
fail-ci-if-error | Fail if coverage processing errors (e.g., parsing failures, missing files) | No | false |
handle-no-reports-found | Don't fail if no coverage found | No | false |
verbose | Enable verbose logging | No | false |
| Input | Description | Required | Default |
|---|---|---|---|
target-project | Target project coverage % (or auto to use base branch coverage) | No | — |
threshold-project | Allowed project coverage drop % (only used when target is auto) | No | — |
target-patch | Target patch coverage % for changed lines | No | 80 |
fail-on-error | Fail CI if coverage thresholds are not met (distinct from fail-ci-if-error) | No | false |
When thresholds are not configured, status checks report coverage metrics without enforcing pass/fail.
| Input | Description | Required | Default |
|---|---|---|---|
flags | Comma-separated flags to tag coverage (e.g., unittests,frontend) | No | — |
name | Custom name for this coverage upload. Also used to differentiate artifacts in matrix builds | No | — |
| Input | Description | Required | Default |
|---|---|---|---|
junit-xml-pattern | Glob pattern for JUnit XML files | No | ./**/*.junit.xml |
| Output | Description |
|---|---|
total-tests | Total number of tests run |
passed-tests | Number of passed tests |
failed-tests | Number of failed tests |
test-pass-rate | Percentage of tests that passed |
tests-added | Tests added compared to base branch |
tests-removed | Tests removed compared to base branch |
tests-fixed | Tests changed from failing to passing |
tests-broken | Tests changed from passing to failing |
| Output | Description |
|---|---|
line-coverage | Line coverage percentage |
branch-coverage | Branch coverage percentage |
coverage-change | Change in line coverage vs base branch |
branch-coverage-change | Change in branch coverage vs base branch |
coverage-improved | Whether coverage improved (true/false) |
coverage-format | The detected/used coverage format |
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}files: ./coverage/lcov.info,./backend/coverage.xmldisable-search: trueflags: unittestsname: my-coverageverbose: true- name: Run testsrun: pytest --cov=src --cov-report=xml
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}directory: ./coverage-format: coberturafail-ci-if-error: true- name: Build and testrun: ./gradlew test jacocoTestReport
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}files: ./build/reports/jacoco/test/jacocoTestReport.xmlcoverage-format: jacoco- name: Run testsrun: go test -coverprofile=coverage.out ./...
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}files: ./coverage.outcoverage-format: go- name: Install cargo-llvm-covuses: taiki-e/install-action@cargo-llvm-cov
- name: Run tests with coveragerun: cargo llvm-cov --codecov --output-path codecov.json
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}files: ./codecov.jsoncoverage-format: codecov- name: Run testsrun: npm test -- --coverage --coverageReporters=lcov
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}directory: ./coveragecoverage-format: lcov- name: Frontend Coverageuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}directory: ./frontend/coverageflags: frontendname: frontend-coverage
- name: Backend Coverageuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}directory: ./backend/coverageflags: backendname: backend-coverageWhen running coverage in a matrix strategy (e.g., multiple Python versions), use the name input to give each matrix entry a unique artifact name and avoid upload conflicts:
jobs:
test:
runs-on: ubuntu-lateststrategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5with:
python-version: ${{ matrix.python-version }}
- name: Run testsrun: pytest --cov=src --cov-report=xml
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}files: coverage.xmlname: py${{ matrix.python-version }}Use built-in threshold enforcement with GitHub status checks:
- name: Codecov Actionuses: getsentry/codecov-action@v1with:
token: ${{ secrets.GITHUB_TOKEN }}target-project: auto # Use base branch coverage as targetthreshold-project: 1# Allow up to 1% coverage droptarget-patch: 80# Require 80% coverage on changed linesfail-on-error: true # Fail CI if thresholds not metThis creates two status checks (codecov/project and codecov/patch) that:
- Appear on commits and PRs
- Can be required via branch protection rules
- Provide clear pass/fail feedback
The action searches for coverage files using:
- Explicit files: If
filesinput is provided - Auto-discovery: Searches
directoryfor known coverage file patterns - Format detection: Auto-detects format from file content
Multiple coverage files are parsed and aggregated:
- Supports mixing formats (e.g., frontend LCOV + backend Cobertura)
- Calculates unified line, branch, and method coverage
Results are stored as GitHub Artifacts:
codecov-coverage-results-{branch}[-{name}][-{flags}]— Aggregated coverage datacodecov-test-results-{branch}— Aggregated test results
When using the name input, it is appended to the artifact name to avoid conflicts in matrix builds.
On PRs or feature branches:
- Auto-detects the repository's default branch (or uses the explicit
base-branchinput) - Downloads latest results from base branch
- Compares current vs baseline
- Calculates deltas
- Job Summary: Always generated in Actions UI
- PR Comment: Optional detailed comment on PRs
The action creates GitHub commit status checks that appear on commits and PRs:
| Status Context | Description |
|---|---|
codecov/project | Overall project coverage status (pass/fail based on target) |
codecov/patch | Coverage for changed lines in the PR |
These status checks:
- Show as green checkmarks or red X marks on commits and PRs
- Can be used in branch protection rules to require coverage thresholds
- Provide immediate feedback on coverage quality
Display your CI status in your README using GitHub's workflow badge:
| Format | Typical File | Common Test Frameworks |
|---|---|---|
| JUnit XML | *.junit.xml | Jest, Vitest, Mocha, pytest, JUnit, NUnit, PHPUnit |
Jest / Vitest:
{
"reporters": ["default", ["jest-junit", { "outputFile": "report.junit.xml" }]]
}pytest:
pytest --junitxml=report.junit.xml{
"coverageReporters": ["lcov", "clover", "json"]
}pytest --cov=src --cov-report=xml # Cobertura formatjacocoTestReport {
reports {
xml.required =true
}
}go test -coverprofile=coverage.out ./...# Install cargo-llvm-cov
cargo install cargo-llvm-cov
# Generate Codecov JSON format
cargo llvm-cov --codecov --output-path codecov.json
# Or generate LCOV format
cargo llvm-cov --lcov --output-path lcov.infodotnet test --collect:"XPlat Code Coverage"# or
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=coberturaYou can configure status check thresholds using a .github/coverage.yml file in your repository. Action inputs take precedence over the config file, allowing you to override settings per-workflow.
# .github/coverage.ymlcoverage:
status:
project:
target: 80# Target coverage percentage (or "auto" to use base branch)threshold: 1# Allowed coverage drop when using "auto" (supports "1%" or 1)informational: false # When true, status check reports but never fails the buildpatch:
target: 90# Target coverage for changed linesinformational: falseignore:
- "**/*.test.ts"# Patterns to exclude from coverage
- "**/fixtures/**"# Enable PR comments from config (alternative to post-pr-comment input)comment: true# Report display settingsconfig:
files: changed # all (default) | changed | none| Option | Description |
|---|---|
status.project.target | Target project coverage % (number or "auto") |
status.project.threshold | Allowed drop from base branch when target is "auto". Supports number (1) or string ("1%") |
status.project.informational | When true, status check reports but never fails the build (advisory mode) |
status.patch.target | Target coverage % for changed lines |
status.patch.informational | When true, patch status check is advisory-only |
ignore | Glob patterns to exclude from coverage calculations |
comment | Enable PR comments. Set to true or false |
config.files | Control file table scope in reports (Job Summary + PR comments). Values: all (default), changed, none |
This action also supports the standard Codecov YAML format with nested default keys:
# .github/codecov.yml (Codecov-compatible format)coverage:
status:
project:
default:
target: autothreshold: 10%# Percentage strings are supportedinformational: truepatch:
default:
target: 80ignore:
- "tests/**"comment: trueconfig:
files: changedBoth formats work identically—use whichever style you prefer.
Use config.files to control the "Files with missing lines" section in both Job Summary and PR comments:
all(default): show all files with missing/partial lineschanged: show only non-deleted files from the PR diffnone: hide the section entirely
permissions:
contents: read # Read repository contentsactions: read # Read workflow runs and artifactspull-requests: write # Post PR comments (if enabled)statuses: write # Create commit status checksNote: On pull requests from forks,
GITHUB_TOKENhas restricted permissions regardless of the workflow'spermissionsblock. PR comments and commit status checks will be skipped gracefully with a warning in this case. Coverage results and Job Summary are still generated.
If you're migrating from the official Codecov action:
| Codecov Input | This Action |
|---|---|
fail_ci_if_error | fail-ci-if-error |
files | files |
directory | directory |
exclude | exclude |
flags | flags |
name | name |
verbose | verbose |
handle-no-reports-found | handle-no-reports-found |
Note: This action doesn't require a Codecov token—it uses GitHub's native artifacts for storage.