English | 日本語
A Chromatic-like Storybook visual regression testing GitHub Action with no external SaaS. storycap capture → pixelmatch green/red diff → PR inline comment (chroma + magic).
A replacement for reg-actions. The biggest difference is the two-color green/red diff (🟢 = added pixels / 🔴 = removed pixels), which makes position shifts obvious at a glance.
Every cacheable step (Chrome for Testing binary, npm dependencies, the 61MB Noto CJK font package) is cached automatically — no config needed.
| Run | chromagic step duration |
|---|---|
| Cold (first run / no cache yet) | ~31–42s |
| Warm (cache hit, typical PR run) | ~16s |
Measured on a minimal Storybook sample repo, ubuntu-latest, 2 vCPU.
When you open a PR, every changed story gets expected / actual / difference side by side in a PR comment:
Here's a closer look — a QtyStepper whose "+" was turned green and given wider spacing (difference: 🔴 = old position / 🟢 = new position):
This is all the calling workflow needs:
# .github/workflows/vrt.yamlname: VRTon:
pull_request:
push: { branches: [main] } # ← required for baseline updatespermissions:
contents: write # pushes the baseline/report branchespull-requests: write # PR commentsjobs:
vrt:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2 # or actions/setup-node
- run: bun install --frozen-lockfile
- run: bun run build-storybook # → storybook-static/
- uses: sgash708/chromagic@v1with:
github-token: ${{ secrets.GITHUB_TOKEN }}- On PRs: captures every story → compares against
vrt-baseline→ posts green/red diffs as a PR comment - On push to main (= merge): saves the current screenshots to
vrt-baseline(= the next baseline; equivalent to Chromatic's Accept)
On the first run there is no
vrt-baselineyet, so every story is "new". Merge once to establish the baseline; real diffs appear from the next PR.
Ready-to-copy workflows for .github/workflows/vrt.yaml:
| File | Use case |
|---|---|
examples/vrt.yaml | bun (recommended) |
examples/vrt-npm.yaml | npm / Node.js (no bun) |
examples/vrt-custom.yaml | Customized inputs (viewport, sensitivity, branch names, etc.) |
| name | default | description |
|---|---|---|
github-token | (required) | GITHUB_TOKEN. Used for posting comments and pushing branches |
storybook-static-path | storybook-static | Built Storybook static directory |
viewport | 390x844 | Capture viewport WxH. Comma-separated for multiple viewports (e.g. 390x844,1280x800) — filenames get a _WxH suffix only when more than one is given, so single-viewport setups keep their existing baseline |
port | 6006 | Local port for serving the static build |
matching-threshold | 0.05 | pixelmatch sensitivity (0-1, smaller = more sensitive) |
threshold-pixel | 50 | Stories with more changed pixels than this count as "changed" |
baseline-branch | vrt-baseline | Branch holding the baseline images |
report-branch | vrt-reports | Branch hosting the images referenced by PR comments |
install-fonts | true | Install Noto CJK (prevents tofu for CJK text on Linux) |
Counts usable in later steps (e.g. fail the job when there are diffs).
| name | description |
|---|---|
changed | Number of stories with diffs |
new | Number of new stories (absent from the baseline) |
deleted | Number of deleted stories |
pass | Number of matching stories |
total | Total number of captured stories |
- uses: sgash708/chromagic@v1id: vrtwith:
github-token: ${{ secrets.GITHUB_TOKEN }}
- if: ${{ steps.vrt.outputs.changed != '0' }}run: echo "::warning::${{ steps.vrt.outputs.changed }} visual diff(s) found"- Capture:
storycap(Chrome for Testing provisioned viasetup-chrome; Noto CJK bundled so CJK text renders correctly). - Compare:
pixelmatch(diffColor=red/diffColorAlt=green) generates the two-color diff. - Baseline / image hosting: no external storage. Baselines live on the
vrt-baselinebranch; PR images are pushed tovrt-reports/<run_id>branches and referenced from comments via?raw=true. - Approval: for intended changes, just merge the PR into main and the baseline updates.

