1261 words
6min read
Edit

VitestのカバレッジレポートをGithub Actionsで添付する実装例

davelosert/vitest-coverage-report-actionを利用することで簡単にVitestのカバレッジレポートを添付することが出来る。

#動作確認環境

次の環境で動作を確認している。

  • Vitest v4.0.16
  • vitest-coverage-report-action v2.9.0

#最小実装

ただカバレッジレポートを添付するだけの場合は、設定ファイルにreporterを指定し、カバレッジレポートを取得するステップの後ろでdavelosert/vitest-coverage-report-actionを呼び出すだけで良い。

.github/workflows/test.yml
# ...
jobs:
steps:
# ...
- run: npx vitest run --coverage
- uses: davelosert/vitest-coverage-report-action@v2
vitest.config.ts
// ...
export default defineConfig({
// ...
test: {
// ...
coverage: {
reporter: ["text", "json-summary", "json"],
},
},
});

#テストが失敗した場合も添付する

いずれかのテストが失敗した場合でも、その他のカバレッジを確認したい場合は次のようにする。

GitHub Actionsでは通常ステップがすると後続のステップが実行されないため、if: always()を利用してカバレッジレポートを添付するステップを常に実行されるようにする。

.github/workflows/test.yml
# ...
jobs:
steps:
# ...
- run: npx vitest run --coverage
- uses: davelosert/vitest-coverage-report-action@v2
if: always()

またVitestではreportOnFailuretrueにすることで、テストが失敗した場合でもカバレッジレポートが生成されるようにする。

vitest.config.ts
// ...
export default defineConfig({
// ...
test: {
// ...
coverage: {
reporter: ["text", "json-summary", "json"],
reportOnFailure: true,
},
},
});

#しきい値未満である場合でもエラーにしない

テストを後から導入するケースなど、しきい値を目標値として利用し、エラーにしたくない場合がある。

次のようにcoverage.thresholdsの代わりにdavelosert/vitest-coverage-report-actionthreshold-iconsを利用することで、エラーにせずに目標値として分かりやすく表示することが出来る。

.github/workflows/test.yml
# ...
jobs:
steps:
# ...
- run: npx vitest run --coverage
- uses: davelosert/vitest-coverage-report-action@v2
threshold-icons: "{0: '🔴', 40: '🟠', 70: '🟢'}"

エラーにして問題がない場合は、davelosert/vitest-coverage-report-actioncoverage.thresholdsを自動的に参照するためthreshold-iconsを指定する必要はない。

#テストがないコードも含めてカバレッジを計測する

Vitest v4ではテストが書かれていないコードをカバレッジに含めない挙動がデフォルトなため、coverage.includeでカバレッジレポートの対象にするファイルを指定する必要がある。

vitest.config.ts
import { defineConfig } from "vite";
export default defineConfig({
// ...
test: {
// ...
coverage: {
  include: ["src/**"],
reporter: ["text", "json-summary", "json"],
reportOnFailure: true,
},
},
});

逆に含めたくないファイルはcoverage.excludeで指定する。

#デフォルトブランチとの比較を行う

カバレッジレポートと比較を行う場合、json-summary-compare-pathに比較対象のカバレッジレポートを指定することで行えるが、何らかの方法で比較対象のカバレッジレポートを取得する必要がある。

公式などでは、デフォルトブランチでのテストも毎回実行することで比較対象を取得する方法が紹介されているがコストが高い。ここでは、デフォルトブランチで実行された結果をダウンロードして利用する方法を紹介する。

.github/workflows/test.yml
on:
push:
branches:
- main
pull_request:
jobs:
steps:
# ...
- run: npx vitest run --coverage
- uses: actions/upload-artifact@v6
if: github.ref == 'refs/heads/main'
with:
name: coverage-main
path: coverage/
retention-days: 7
- name: Get main run ID
if: github.ref != 'refs/heads/main'
continue-on-error: true
run: |
RUN_ID=$(gh run list --workflow "${{ github.workflow }}" --branch ${{ github.event.repository.default_branch }} --status success --limit 1 --json databaseId --jq '.[0].databaseId')
echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV
- name: Download artifact
if: env.IS_DEFAULT_BRANCH == 'false' && env.RUN_ID != ''
uses: actions/download-artifact@v7
continue-on-error: true
with:
name: coverage-main
run-id: ${{ env.RUN_ID }}
github-token: ${{ github.token }}
path: coverage-main
- name: Ensure coverage files exist
if: github.ref != 'refs/heads/main'
# デフォルトブランチのカバレッジレポートが取得できない場合、
# 現在のブランチにあるカバレッジレポートで代替する。
run: |
if [ ! -f coverage-main/coverage-summary.json ]; then
echo "Main branch coverage not found, using current coverage for comparison."
mkdir -p coverage-main
cp coverage/coverage-summary.json coverage-main/coverage-summary.json
fi
- name: Report Coverage
if: github.ref != 'refs/heads/main'
uses: davelosert/vitest-coverage-report-action@v2
with:
json-summary-compare-path: coverage-main/coverage-summary.json

#pushトリガーでも添付する

davelosert/vitest-coverage-report-actionpr-numberを指定しない場合、pull_requestトリガーでのみ添付を行う。

特にpr-number: autoのように指定すると、pushトリガーでも自動的にPull Requestが特定され添付される。例えば、次のようにするとpushpull_requestトリガーどちらでも添付されるようになる。

.github/workflows/test.yml
# ...
jobs:
steps:
# ...
- uses: davelosert/vitest-coverage-report-action@v2
with:
pr-number: ${{ github.event_name == 'pull_request' && github.event.number || 'auto' }}

#全てを組み合わせたComposite Action

davelosert/vitest-coverage-report-actionは便利だが比較を行いたい場合など、 かなり冗長な記述になる。そこで次のようにComposite Actionに切り出すことで扱いやすくすることが出来る。

action.yml
name: vitest coverage report
description: Generate vitest coverage report.
runs:
using: composite
steps:
- name: Detect default branch
shell: bash
run: |
echo "IS_DEFAULT_BRANCH=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}" >> $GITHUB_ENV
- name: Upload coverage to artifacts
if: env.IS_DEFAULT_BRANCH == 'true'
uses: actions/upload-artifact@v6
with:
name: coverage-main
path: coverage/
retention-days: 7
- name: Download coverage from main
shell: bash
if: env.IS_DEFAULT_BRANCH == 'false'
env:
GH_TOKEN: ${{ github.token }}
run: |
RUN_ID=$(gh run list --workflow "${{ github.workflow }}" --branch ${{ github.event.repository.default_branch }} --status success --limit 1 --json databaseId --jq '.[0].databaseId')
echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV
continue-on-error: true
- name: Download artifact
if: env.IS_DEFAULT_BRANCH == 'false' && env.RUN_ID != ''
uses: actions/download-artifact@v7
with:
name: coverage-main
run-id: ${{ env.RUN_ID }}
github-token: ${{ github.token }}
path: coverage-main
continue-on-error: true
- name: Ensure coverage files exist
if: env.IS_DEFAULT_BRANCH == 'false'
shell: bash
continue-on-error: true
run: |
if [ ! -f coverage-main/coverage-summary.json ]; then
echo "Main branch coverage not found, using current coverage for comparison."
mkdir -p coverage-main
cp coverage/coverage-summary.json coverage-main/coverage-summary.json
fi
- name: Report Coverage
if: env.IS_DEFAULT_BRANCH == 'false'
uses: davelosert/vitest-coverage-report-action@v2
with:
json-summary-compare-path: coverage-main/coverage-summary.json
pr-number: ${{ github.event_name == 'pull_request' && github.event.number || 'auto' }}
threshold-icons: "{0: '🔴', 20: '🟠', 60: '🟢'}"