Skip to content

Create GitHub Actions Workflows for Publishing Code Coverage Reports

Tiffany Forkner edited this page Apr 24, 2025 · 5 revisions

Important

All of the files in this section should be created in the main branch.

Configure Code Coverage

Note

My project is using Vitest for our tests, but most testing frameworks use Istanbul for reporting code coverage. If you are not using Istanbul, you will need to adjust these instructions to work for your report.

In a branch from main, open your test configurations. Set the reporter to use at least the html and json-summary reporters. Specify the sub-directory for the html report because we need all of those files contained separately from the other reports. Also, specify the thresholds that you want for your coverage. You can set whatever you want for your project, these are just what we are using at the moment.

Note

The coverage thresholds set here should match your coverage values in the _config.yml file on the gh-pages branch.

import { configDefaults, defineConfig } from "vitest/config";
import { join } from "path";

export default defineConfig({
  test: {
    globals: true,
    include: ["**/*.test.ts?(x)"],
    coverage: {
      provider: "istanbul",
      reportsDirectory: join(__dirname, "coverage"),
      reporter: [
        ["html", { subdir: "html-report" }], // required for this setup, must be nested within its own folder to avoid getting confused with the other reports
        ["json-summary"], // required for this setup
        ["text"], // OPTIONAL, you can use any additional reports that you like
      ],
      thresholds: {
        lines: 90,
        branches: 90,
        functions: 90,
        statements: 90,
      },
      reportOnFailure: true,
      exclude: [
        ...configDefaults.exclude,
        ".github/**",
        "node_modules/**",
        "playwright-reports/**",
        "test-results/**",
        "tests/**",
        ".gitignore",
        "package.json",
        "playwright.config.ts",
        "pnpm-lock.yaml",
        "README.md",
        "vitest.config.ts",
      ],
    },
  },
});

Create a GitHub Actions Workflow for Code Coverage

Create .github/workflows/code-coverage-report.yml that will run the tests with the coverage flag, upload the artifacts for the html and json-summary reports, then call the publish workflow.

name: Code Coverage Report

on:
  push:
    branches:
      - main # run this whenever someone merged into main
  schedule:
    - cron: "30 01 * * *" # run this every morning at 1:30
  workflow_dispatch: # allow manual triggering workflow

# ensures that currently running Code Coverage Report workflow is canceled if another job starts
concurrency:
  group: ${{ github.workflow }}-main
  cancel-in-progress: true

# this action needs the same permissions as the Push to GitHub Pages action because it is calling it
permissions:
  actions: read
  contents: write

jobs:
  coverage-report:
    runs-on: ubuntu-latest
    env:
      JSON_ARTIFACT_ID: json-main-coverage-report
      HTML_ARTIFACT_ID: html-main-coverage-report
    outputs:
      json_artifact_id: ${{ env.JSON_ARTIFACT_ID }}
      html_artifact_id: ${{ env.HTML_ARTIFACT_ID }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: main
#### this is our set up for installing and running the tests, update this with your set up and test run ####
      - uses: pnpm/action-setup@v4
      - name: Install dependencies
        run: pnpm install
      - name: Run Tests
        continue-on-error: true
        run: pnpm exec vitest --coverage
#### end set up and test run ####
      - name: Upload Coverage Summary
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        id: upload-json
        with:
          name: ${{ env.JSON_ARTIFACT_ID }}
          # path should point to the location of your json coverage report
          path: coverage/coverage-summary.json
          overwrite: true
          retention-days: 90
      - name: Upload Coverage Report
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        id: upload-html
        with:
          name: ${{ env.HTML_ARTIFACT_ID }}
          # path should point to the location of your html coverage report
          path: coverage/html-report/*
          overwrite: true
          retention-days: 90

  publish:
    needs: [coverage-report]
    # call the push-to-gh-pages workflow to add the artifacts to gh-pages
    uses: ./.github/workflows/push-to-gh-pages.yml
    with:
      FILE_1_ARTIFACT_ID: ${{ needs.coverage-report.outputs.json_artifact_id }}
      FILE_1_DEPLOY_TO: _data/coverage/
      FILE_2_ARTIFACT_ID: ${{ needs.coverage-report.outputs.html_artifact_id }}
      FILE_2_DEPLOY_TO: coverage/
      COMMIT_MSG: update code coverage report for main
      URL_PATH: coverage/
    secrets: inherit # allow called workflow to use GitHub secrets

OPTIONAL Create a GitHub Actions Workflow for PR Code Coverage

Create .github/workflows/pr-coverage-annotation.yml that will run on PRs to main. For this setup, we are not reporting coverage for each branch. However, you can add the coverage report to the PR itself. This workflow also refers to the main coverage report to show whether the coverage is going up or down in the PR.

name: PR Coverage Annotation

on:
  pull_request:
    branches:
      - main

# ensures that currently running PR Coverage Annotation workflow is canceled if another one starts
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref }}
  cancel-in-progress: true

permissions:
  actions: read
  contents: write
  pull-requests: write

jobs:
  coverage-report:
    runs-on: ubuntu-latest
    outputs:
      actualResult: ${{ steps.test.conclusion }}
    steps:
      - uses: actions/checkout@v4
#### this is our set up for installing and running the tests, update this with your set up and test run ####
      - uses: pnpm/action-setup@v4
      - name: Install dependencies
        run: pnpm install
      - name: Run Tests
        id: test
        continue-on-error: true
        run: pnpm exec vitest --coverage
#### end set up and test run ####
      - name: Get run ID of Code Coverage Report workflow
        # gets the run id of the last time Code Coverage Report ran so that it can be used to download that artifact
        id: get-run-id
        continue-on-error: true
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          WF_NAME="Code Coverage Report"
          RUN_ID=`gh run list --workflow "${WF_NAME}" --json databaseId --jq .[0].databaseId`
          echo "Detected latest run id of ${RUN_ID} for workflow ${WF_NAME}"
          echo "run-id=${RUN_ID}" >> "$GITHUB_OUTPUT"
      - uses: actions/download-artifact@v4
        if: steps.get-run-id.conclusion == 'success'
        # if we were able to get the run id, download that json summary report using the run id
        id: get-main-coverage
        continue-on-error: true
        with:
          name: json-main-coverage-report
          path: main-coverage
          github-token: ${{ secrets.GITHUB_TOKEN }}
          run-id: ${{ steps.get-run-id.outputs.run-id }}
      - name: Report Coverage (compared with Main)
        if: ${{ steps.get-run-id.conclusion == 'success' && steps.get-main-coverage.conclusion == 'success' }}
        # if we were able to get the run id and download the main json summary report, then compare it to the current json summary report and add that to the PR
        uses: davelosert/vitest-coverage-report-action@v2
        with:
          json-summary-compare-path: main-coverage/coverage-summary.json
          file-coverage-mode: all
      - name: Report Coverage (only PR)
        if: ${{ failure() }}
        # if we were not able to get the run id or download the main json summary report, then just add the current json summary report to the PR
        uses: davelosert/vitest-coverage-report-action@v2
        with:
          file-coverage-mode: all

Clone this wiki locally