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を呼び出すだけで良い。
# ...jobs: steps: # ... - run: npx vitest run --coverage - uses: davelosert/vitest-coverage-report-action@v2// ...export default defineConfig({ // ... test: { // ... coverage: { reporter: ["text", "json-summary", "json"], }, },});テストが失敗した場合も添付する
いずれかのテストが失敗した場合でも、その他のカバレッジを確認したい場合は次のようにする。
GitHub Actionsでは通常ステップがすると後続のステップが実行されないため、if: always()を利用してカバレッジレポートを添付するステップを常に実行されるようにする。
# ...jobs: steps: # ... - run: npx vitest run --coverage - uses: davelosert/vitest-coverage-report-action@v2 if: always()またVitestではreportOnFailureをtrueにすることで、テストが失敗した場合でもカバレッジレポートが生成されるようにする。
// ...export default defineConfig({ // ... test: { // ... coverage: { reporter: ["text", "json-summary", "json"], reportOnFailure: true, }, },});しきい値未満である場合でもエラーにしない
テストを後から導入するケースなど、しきい値を目標値として利用し、エラーにしたくない場合がある。
次のようにcoverage.thresholdsの代わりにdavelosert/vitest-coverage-report-actionのthreshold-iconsを利用することで、エラーにせずに目標値として分かりやすく表示することが出来る。
# ...jobs: steps: # ... - run: npx vitest run --coverage - uses: davelosert/vitest-coverage-report-action@v2 threshold-icons: "{0: '🔴', 40: '🟠', 70: '🟢'}"エラーにして問題がない場合は、davelosert/vitest-coverage-report-actionがcoverage.thresholdsを自動的に参照するためthreshold-iconsを指定する必要はない。
テストがないコードも含めてカバレッジを計測する
Vitest v4ではテストが書かれていないコードをカバレッジに含めない挙動がデフォルトなため、coverage.includeでカバレッジレポートの対象にするファイルを指定する必要がある。
import { defineConfig } from "vite";
export default defineConfig({ // ... test: { // ... coverage: { include: ["src/**"], reporter: ["text", "json-summary", "json"], reportOnFailure: true, }, },});逆に含めたくないファイルはcoverage.excludeで指定する。
デフォルトブランチとの比較を行う
カバレッジレポートと比較を行う場合、json-summary-compare-pathに比較対象のカバレッジレポートを指定することで行えるが、何らかの方法で比較対象のカバレッジレポートを取得する必要がある。
公式などでは、デフォルトブランチでのテストも毎回実行することで比較対象を取得する方法が紹介されているがコストが高い。ここでは、デフォルトブランチで実行された結果をダウンロードして利用する方法を紹介する。
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.jsonpushトリガーでも添付する
davelosert/vitest-coverage-report-actionはpr-numberを指定しない場合、pull_requestトリガーでのみ添付を行う。
特にpr-number: autoのように指定すると、pushトリガーでも自動的にPull Requestが特定され添付される。例えば、次のようにするとpushとpull_requestトリガーどちらでも添付されるようになる。
# ...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に切り出すことで扱いやすくすることが出来る。
name: vitest coverage reportdescription: 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: '🟢'}"