Auto-Publish Your Test Coverage Report on GitHub Pages
Tags: github, tutorial, testing, javascript
Published: 2022-10-28 13:27:01

In this post join me as I publish a project's unit tests coverage report to GitHub Pages using GitHub actions.

Your project probably has a coverage report. If you’re using Jest as your unit test runner, generating a coverage report is embedded in it. It is done with [Istanbul](https://istanbul.js.org/) under the hood, which generates a nice HTML page presenting the entire project unit test coverage.

I too have this kind of coverage, but it's a little bit more advanced since I wanted to have a coverage report for the entire packages under a single Monorepo. You can read how I achieved that in [this post](https://dev.to/mbarzeev/aggregating-unit-test-coverage-for-all-monorepos-packages-20c6).

It’s lovely that there’s a coverage report for all packages, which I can trigger manually in my local env, but I would like it to be accessible to anyone who wishes to check up the coverage status of the project. 
In other words, I would like a way to publish my report somewhere public.

Luckily GitHub offers “[GitHub pages](https://pages.github.com/)” which simply put is “Websites for you and your projects”. I wonder if I can harness its powers for this goal.

It’s always a good idea to set the requirement before rushing into coding, so here they are:

* Automate the creation of a GitHub page displaying the current coverage
* Have this automation triggered when pushes are made to “master” branch
* Grab a beer

Here we go!

***

## Prerequisites
Just before we start, you should have a script which generates a coverage report. In a simple project, using Jest, generating the coverage can be done by running Jest with the `--coverage` option. I’m counting that you have such a script that can be triggered from a GitHub action.

The project I’m about to implement this on is a Monorepo called “[Pedalboard](https://github.com/mbarzeev/pedalboard)” which I maintain. In it I’ve created a script which aggregates coverage reports from all nested packages into a single report residing at the project’s root. You can read more about it [here](https://dev.to/mbarzeev/aggregating-unit-test-coverage-for-all-monorepos-packages-20c6).
The command used to generate this report is 

```bash
yarn coverage:combined 
```

## GitHub pages
How do we enable GitHub pages in our project to begin with?
Go to your repo’s settings, and under the “Pages” menu item you will see the GitHub pages “wizard”. Here is an image which will help us with the following explanations : 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/inegg4a4fx5vfrlbjvh6.png)

As you can see you can choose whether you’d like the site to be generated by GitHub action or deploy from a branch. Since I need to generate the report before deploying it I will choose the GitHub action option (note that this option is Beta).

As you can see in the image above there are 2 options under it as well - I will choose the Static HTML since my script creates a “coverage” directory and in it you will have a static `index.html` which is the main doc of the report. I click the “Configure” button and move on.

## The Action’s YAML
GitHub now presents the YAML file for the new workflow. Here it is:

```yaml
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["master"]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow one concurrent deployment
concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  # Single deploy job since we're just deploying
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Pages
        uses: actions/configure-pages@v2
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v1
        with:
          # Upload entire repository
          path: '.'
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1

 ```

Obviously we need to do some modifications to it. Let’s start - 

We change the name of the YAML file to “coverage-report” and we change the description to fit our intention.

The next lines describe when this action should be triggered, and OOTB it triggers when push is made to the “master” branch. This means that the build-and-publish workflow and the coverage-report workflow will start together and run in parallel. I know that it is not the most optimized way of synching between the 2 workflows, but for now it will do fine.

Next we would like to generate our report, before we can deploy it. For this purpose I’m adding 2 commands to the workflow - installing Yarn dependencies and running the `coverage:combine`

```yaml
- run: yarn
- run: yarn coverage:combined
```

Now, let’s have a look at another important line which tells the action which path to deploy from:

```yaml
with:
          # Upload entire repository
          path: '.'
```

Currently it points to the project’s root, and this is not what we’re aiming for. We want the site content to be taken from the generated coverage directory. I change it into this:

```yaml
 with:
          # Upload the coverage directory from the repository
          path: './coverage/lcov-report'

```

Let’s check if this action does what it should. I’m committing the new workflow file (from GitHub). And sure enough when we commit it to master the action is triggered:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hxzlgt161wq263wsnbyu.png)

It completes successfully and if we navigate to [https://mbarzeev.github.io/pedalboard/](https://mbarzeev.github.io/pedalboard/) we will get our combined coverage report!

Here is how the final workflow’s YAML looks like:

```yaml
# A workflow for deploying the Unit Tests coverage report to GitHub Pages
name: Deploy coverage report to Pages
 
on:
   # Runs on pushes targeting the default branch
   push:
       branches: ['master']
 
   # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
 
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
   contents: read
   pages: write
   id-token: write
 
# Allow one concurrent deployment
concurrency:
   group: 'pages'
   cancel-in-progress: true
 
jobs:
   # Single deploy job since we're just deploying
   deploy:
       environment:
           name: github-pages
           url: ${{ steps.deployment.outputs.page_url }}
       runs-on: ubuntu-latest
       steps:
           - name: Checkout
             uses: actions/checkout@v3
           - run: yarn
           - run: yarn coverage:combined
           - name: Setup Pages
             uses: actions/configure-pages@v2
           - name: Upload artifact
             uses: actions/upload-pages-artifact@v1
             with:
                 # Upload the coverage directory from the repository
                 path: './coverage/lcov-report'
           - name: Deploy to GitHub Pages
             id: deployment
             uses: actions/deploy-pages@v1
``` 

## Wrapping up
Exposing reports such as this coverage report is very helpful for stakeholders to understand where projects stand in terms of quality and where are the parts that are more prone to regressions. 
Having the coverage report as the main page of your project at GitHub though, is not the most elegant way of doing it, so you might wanna generate a “real” main HTML page which links to the report (and other generated reports), but that’s out of the scope of this post :)

As always, if you have any questions or ideas on how this can be done better, please share them with us in the comments section below.

*Hey! If you liked what you've just read check out <a href="https://twitter.com/mattibarzeev?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">@mattibarzeev</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> on Twitter* :beers:

<small><small><small>Photo by <a href="https://unsplash.com/@christinhumephoto?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Christin Hume</a> on <a href="https://unsplash.com/s/photos/report?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></small></small></small>