This is the only documented way to get coverage badges with GitHub Actions. It took a few months of research, trial, and error; but eventually I got it to work, with the help of a GitHub user by the name of Schneegans.
The following is for Node.js and Jest, but you can tweak it to work with anything (if you are comfortable doing some shell script googling). Here is what the end result looks like:
Yep, just a simple coverage badge. At the top of your PR or README. There's a lot of setup required for this to work, but once in place it's pretty minor to set up other repos. Here's the instructions:
- Go to gist.github.com and create a new gist. You will need the ID of the gist (this is the long alphanumerical part of its URL) later.
- Go to github.com/settings/tokens and create a new token with the gist scope.
- Go to the Secrets page of the settings of your repo and add this token as a new secret with the name
GIST_SECRET
. -
Create your workflow file like this (comments to explain the code)
-
your-repo/.github/workflows/node.js.yml
```yml
This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Build Status
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]jobs:
build:runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] node-version: [14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} # basically npm install but only installs from package-lock - run: npm ci - run: npm run lint - run: npm t # Only run the coverage once - if: ${{ matrix.node-version == '14.x' }} name: Get Coverage for badge run: | # var SUMMARY = [ # '', # '=============================== Coverage summary ===============================', # 'Statements : 32.5% ( 39/120 )', # 'Branches : 38.89% ( 21/54 )', # 'Functions : 21.74% ( 5/23 )', # 'Lines : 31.93% ( 38/119 )', # '================================================================================', # '' # ]; # SUMMARY = SUMMARY.split('\n')[5]; // 'Lines : 31.93% ( 38/119 )' # SUMMARY = SUMMARY.split(':')[1].split('(')[0].trim(); // '31.93%' SUMMARY="$(npm test -- --coverageReporters='text-summary' | tail -2 | head -1)" TOKENS=($SUMMARY) # process.env.COVERAGE = '31.93%'; echo "COVERAGE=$(echo ${TOKENS[2]})" >> $GITHUB_ENV # var REF = 'refs/pull/27/merge.json'; REF=${{ github.ref }} # console.log('github.ref: ' + REF); echo "github.ref: $REF" # var PATHS = REF.split('/'); IFS='/' read -ra PATHS <<< "$REF" # var BRANCH_NAME = PATHS[1] + '_' + PATHS[2]; BRANCH_NAME="${PATHS[1]}_${PATHS[2]}" # console.log(BRANCH_NAME); // 'pull_27' echo $BRANCH_NAME # process.env.BRANCH = 'pull_27'; echo "BRANCH=$(echo ${BRANCH_NAME})" >> $GITHUB_ENV - if: ${{ matrix.node-version == '14.x' }} name: Create the Badge uses: schneegans/dynamic-badges-action@v1.0.0 with: auth: ${{ secrets.GIST_SECRET }} gistID: 7d4c25ef2e97e8de523ef7c1fee26e8e filename: your-repo-name__${{ env.BRANCH }}.json label: Test Coverage message: ${{ env.COVERAGE }} color: green namedLogo: jest
-
1. The above will run `npm test`, which for me is `jest --coverage`, then it does a double dash `--` which says the next arguments will be passed down and appended to the end of the command, then `--coverageReporters='text-summary'`. The result is the GitHub Actions CI will run `jest --coverage --coverageReporters='text-summary'`. The reporter being set to "text-summary" is important, as it will give us the correct string output to parse to get the coverage percent.
1. We do some shell script magic to grab the correct value from the result of the coverage command (comments written in JavaScript to help explain what the variables are equal to and what the shell script magic is doing).
1. We then store the coverage string in a secure GitHub Environment Variable.
1. Unfortunately, GitHub actions does not offer a way to get the current branch name from a PR, instead it gives the Pull Request ID (except sometimes it actually gives you the branch name, but... it doesn't really matter, just know that this is very annoying)
1. So we use more shell script nonsense to do string manipulation to get a usable representation of the branch or PR, and store that in an environment variable too.
1. Finally we use Schneegans' plugin to create a JSON file stored on the Gist we created earlier (Make sure you change the Gist ID from the above code to your own). Also change the `your-repo-name` to the name of your repo.
1. Then you can use this code to help set up your PR's.
* `your-repo/.github/PULL_REQUEST_TEMPLATE.md`
```md
<!-- Change the ## to your pull request number -->
![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/<YourUsername>/<gist_id>/raw/<your-repo>__pull_##.json)
**Notes for reviewer:**
*
- Change out the 3 items above wrapped in
<>
- From now on, every PR you make for this repo will come with a badge (though you will still have to create the PR first, then edit it to set the PR number in the badge), but it works!
-
If you want one for your
main
branch to put at the top of theREADME.md
you can use this:-
your-repo/README.md
```md
-
1. Now all you need to do to set this up in other repos is to add the GIST_SECRET to each, copy/paste your CI config and change the repo name in it. Since the JSON files created in the gist contain the repo name, it can be reused if you want.
* ![JSON files stored in gist](https://dev-to-uploads.s3.amazonaws.com/i/j1j9w7boi6aczae10rus.png)
Yes, this is very hacky, but I haven't found a better way yet, and I spent months trying different approaches. This is the first thing I've found that works. Still hoping that GitHub just adds this feature in, like every other major CI already does.
If you do not care about the badge itself, there is a simpler way of displaying coverage on PR's by adding this to your GitHub Actions file:
```yaml
# Main doesn't have a PR for comments so skip that branch
# We don't want multiple comments about code coverage, just just run it once on 14.x on Linux
- if: ${{ github.ref != 'refs/heads/main' && matrix.node-version == '14.x' && matrix.os == 'ubuntu-latest' }}
uses: romeovs/lcov-reporter-action@v0.2.16
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
lcov-file: ./tests/coverage/lcov.info
This results in a comment being added to the PR by a bot with the coverage percent and a expandable hidden table of all uncovered lines. Example. Though more detailed, this is often overkill, and can be spammy when pushing changes to a PR. These details can just as easily be seen from the results of the actions being ran from the "Checks" tab of a PR. Though these check logs may get deleted over time, based on retention settings. So the comments approach is better from a historical perspective.
- Cover image from gwendoline63 on Pixabay
- Photo from Jay & Silent Bob Strike Back
All IDs/Tokens in screenshots were modified in Photoshop.
Top comments (12)
I may be missing something here, but since this comes down to creating and stashing a .json file that can be accessed in the context of a viewer on the readme.md, couldn't a repo be used just as well (with a token generated having appropriate perms to commit changes to the repo/branch that will be used in the badge to retrieve that .json file?
That's another way, abusing Gist just has fewer steps. And lower risk (can't accidentally give permissions to the wrong repo, just to your gists).
You can get PR number easily:
You can update the pull request template after the coverage badge has been created with an additional step in your workflow:
Needed to make a few changes for Yarn (no need to add
--
, have to trim 3 lines on the tail)Made a tiny version to renew the gist file only on push to main branch (when you need to display only in Readme)
Instead of parsing
text-summary
output, you can use ajson-summary
reporter and then get values that you need withjq
tool, like so:You can see it in action at my repository.
There are more easiest way like this action Jest Coverage Comment
It works in PR/Push, can extract the coverage, and there are also example of how to update the Readme with
coverage
.@mishakav @thejaredwilcurt consider this action, no secrets config at all.
It's simple and fits simple projects,
with a continuously updated badge output to gh-pages.
github.com/we-cli/coverage-badge-a...
Great post. finally, something without any paid third-party usages.
Great article, I was able to implement this with a bit of tinkering. It might be good to note that the
your-repo/.github/workflows/node.js.yml
file needs to be configured with the gistID and filename (repo name):Since one or two weeks Shield.io display "domain is blocked" when using this technique. Am I the only one getting this error?
Just tested with
https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/TheJaredWilcurt/9c5d16fe3fa8f8ef414fe8b0eff17f7f/raw/red-perfume__pull_31.json
and it is working fine for me.