Getting started
Install the App, add one step to CI, open a pull request. There is no baseline to record first, no screenshots to approve, and nothing to install in your test runner.
1. Install the GitHub App
Sign in with GitHub and install the App on the repositories it should watch. Straight after installation you land on a page that shows the workflow below.
2. Upload your Storybook build from CI
Add a workflow that builds Storybook and uploads it. Run it on pull requests and on pushes to your default branch: those default-branch builds are what baselines resolve to, so without them every pull request looks like a first run.
name: merrykat
on:
pull_request:
push:
branches: [main]
concurrency:
group: merrykat-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
visual:
runs-on: ubuntu-latest
permissions:
contents: read # lets actions/checkout clone the repo
id-token: write # lets the CLI authenticate with no configured secret
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build-storybook
- name: Upload the build to Merrykat
run: npx merrykat upload --dir ./storybook-staticThe uploader packs the build and sends it; your CI does no rendering and needs no browser. Authentication comes from the job’s own OIDC token, which is why id-token: write is in the permissions block and why there is no secret to configure.
A note on actions/checkout settings
ref: ${{ github.event.pull_request.head.sha }} builds the pull request’s own head commit. On pull_request runs, checkout otherwise gives you GitHub’s ephemeral merge of your branch into its base which can lead to confusing results.
fetch-depth: 0 gives the CLI the history it needs. Baseline resolution and the pull request’s diff both come from local git merge-base and git diff.
3. Read the first run
The first pull request after installation has no baseline to compare against, so nothing can be diffed. Instead of a dead end you get an inventory: how many stories there are, which of them failed to render or rendered nothing, and a gallery of sampled renders, chosen for breadth across components, plus anything that looks unusual, each judged on whether the screenshot plausibly depicts the story it came from.
From the second pull request onwards you get comparisons with the baseline: one sticky comment and one check run, updated in place as the run progresses, with what changed, what broke, what is new, and a verdict on each examined difference.
Several Storybooks in one repository
Upload each one under its own project key. Builds and runs are identified by project, so two Storybooks in a monorepo never resolve to each other’s baselines.
- run: npx merrykat upload --dir ./packages/design-system/storybook-static --project design-system
- run: npx merrykat upload --dir ./apps/admin/storybook-static --project adminNext
The defaults are a desktop and a mobile viewport with the accessibility pass on, which is a reasonable place to stay. When you want to change it, everything lives in one file — see Configuration. If your builds are large, the biggest win available is not rendering the stories that cannot have changed; see Fingerprinting.