概要
Chromatic による Visual testing (a.k.a VRT) を、GitHub Actions から都度実行しているプロジェクトにて、その実行タイミングを必要十分になるように制限することで課金額を半分未満にまで削減した事例です。
背景
Chromatic は VRT の導入をコンフィグレスで即座にでき、メンテコストも低い便利なツールです。
ただ撮影したスクリーンショット枚数に応じる従量課金額は(個人の感覚ですが)非常に高額です。
私が担当しているプロダクトでも、無邪気に Chromatic を導入した結果、これはまずいというレベル請求になったため、急遽コスト削減の取組みを行いました。
前提
- GitHub Actions を使用して Chromatic へのデプロイを行っている
- リポジトリでは Chromatic をパスしていることが、PRをマージに必須
- バックエンドとフロントエンドが混在したリポジトリだが、フロントエンドのコードは
client/
ディレクトリ以下に切り出されている
改善前のワークフロー
最初に Chromatic を導入した際のワークフローは以下のようなものでした。
name: Chromatic Deployment
on:
- push
- pull_request
jobs:
deploy:
runs-on: ubuntu-latest
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
steps:
# (環境セットアップのステップは省略)
- name: Run Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
onlyChanged: true
exitZeroOnChanges: true
無邪気にすべてのコードプッシュで Chromatic を実行しているため、初月からとんでもない請求が来てしまいました。
流石にまずいので、明らかに実行する必要がない場合には実行しないように改善を重ねました。
PRの状態で制限する
まず、Chromatic はリリース対象となる master ブランチへのコードマージ前にデグレを検知する役目を果たしてくれば良いので、プルリクエストがある場合のみをトリガーにします。
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
また、PRがドラフト状態の場合や、クローズ済みのPRはマージされる心配がないためスキップします。
jobs:
deploy:
runs-on: ubuntu-latest
if: |
github.event.pull_request.draft == false && github.event.pull_request.state == 'open'
PR がドラフトからオープンされた場合には ready_for_review
がトリガーとなって実行されます。
PR にフロントエンドの差分がない場合はスキップ
VRT の差分は、原理上フロントエンド関連のコードに差分があるときしか発生しません。バックエンドのみ変更したPRで Chromatic を実行するのは完全に無意味です。
そこで、PRのベースブランチとの差分にクライアントコードが含まれていないかを確認します。
- name: Check for frontend changes
id: check_changes
run: |
# PRにフロント差分が含まれていなければスキップする
CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -E '^client/|package.json|pnpm-lock.yaml|^\.storybook/' || true)
echo "$CHANGED_FILES"
if [ -z "$CHANGED_FILES" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
クライアントコードは基本的に client/
ディレクトリに集約されているので、それに加えて package.json
など、VRTに影響を与えるパスをチェック対象としています。
クライアントコードの差分の有無を元に、Chromatic の Skip オプションを有効化します。
- name: Run Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
onlyChanged: true
exitZeroOnChanges: true
skip: ${{ steps.check_changes.outputs.skip }} # これを追加
これは Chromatic の実行自体はスキップするが、コミットステータス上では成功として通知してくれる便利なオプションです。
これによって、実際は Chromatic を実行していなくても、ブランチ保護ルールを突破してPRをマージできるようになります。
renovate/dependabot でも動かせるようにする
renovate/dependabot が作成した、ライブラリバージョンアップのPRでも、master ブランチにマージさせるには Chromatic を通す必要があります。
しかし、bot ユーザーが作成したPRでは、以下の GitHub Secrets を参照できないという問題がありました。
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
これはいくつかの方法で回避可能ですが、今回は最も簡単な、pull_request_target
をトリガーとする方法を採用しました。
on:
- pull_request:
+ pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
これによって、ワークフローを実行するコンテキストがベースブランチ側となり、secrets にアクセスできるようになります。
ただし、ワークフローが実行されるブランチもベースブランチ(≒master)となってしまうため、そのままだと意図した VRT 結果を得ることができません。
そこで、コードをチェックアウトするステップで、プルリクエストを作成したブランチ側のコミットをチェクアウトするように明示しました。
steps:
- name: Checkout code
uses: 'actions/checkout@4'
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
これで renovate/dependabot が作成したPRでも、ライブラリバージョンアップ後のコードを使って VRT を行えるようになりました。
ちなみに dependabot のほうは npm のバージョンアップを行っていないため、常にフロントエンドの差分が無い判定となりスキップされます。
その他
本記事では、GitHub Actions での実行条件の見直しについて紹介しましたが、Chromatic のコスト削減のために、他にも以下のような取り組みもしました。
- VRT専用のストーリーを用意し、通常のストーリーをVRT対象外にする
- renovate の設定で、複数パッケージをまとめてバージョンアップするようにし、PR数自体を減らす
- TurboSnapを有効化し、キャッシュを活用する