Storybook · GitHub · axe-core

UI regression testing for the AI era

More UI is being written, faster, by more agents than ever, and few of them can see what they shipped. Merrykat gives you eyes into your user interface: it renders every story on both sides of a pull request, works out what actually changed, and tells you whether it looks intentional.

Install the GitHub App, add one line to CI, open a pull request.

A CI agent for the visual parts of your app

Think of it the way you think of an AI reviewer for your code — except this one looks at pixels. It reads the diff, renders the components, and writes up what it found in the pull request, with pictures.

  1. 1

    Both sides, one browser, one moment

    Every run is a pair of static Storybook builds — your pull request’s head and its merge base — re-rendered side by side in the same Chromium, back to back. No stored screenshots to go stale, no renderer drift, and no baseline approval queue to keep tidy.

  2. 2

    Differences grouped, not listed

    Diffs are clustered by what kind of change they are before anything is examined. One token edit touching four hundred stories is one finding, not four hundred, so the review spends its attention on distinct changes instead of samples of the same one.

  3. 3

    A verdict you can act on

    Each examined change gets a labelled before/after/overlay composite and a verdict: intentional, needs review, or likely bug. A colour shift that matches a token edit in your diff reads differently from the same shift with nothing to explain it.

What you get

Accessibility baked in

Every story is run through axe-core on both sides, in the same browser as the screenshot. The comment reports what your pull request changed — elements that newly violate a rule, and ones it fixed — never the hundred violations you inherited.

Only renders what could have changed

If your build can say which stories are byte-identical between the two commits, Merrykat skips them entirely. A sample of the skipped stories is compared anyway on every run, so a generator that quietly stops following a dependency is caught rather than trusted forever.

Dark mode, light mode, every viewport

A viewport can be a size, or a media feature under test: prefers-color-scheme, prefers-contrast and forced-colors each render and diff as their own comparison. Dark mode is a viewport, not a separate test suite.

Flakiness tracked automatically

A story that differs between two captures moments apart is recorded as flaky and treated as unchanged — no tolerance threshold that would also hide a real one-pixel regression. The rate is tracked across runs, and repeat offenders are quarantined: longer settle time, an extra attempt, and a named list, because a story that flakes every run is a bug in the story.

Summarised in the pull request

One sticky comment and one check run, updated in place: what changed, what broke, what is new, which stories were examined and why, and a recommendation on each. The check outcome is decided by the engine, never by the model — genuine breakage fails it whether the model agrees or not.

Secure by design

The GitHub App holds no permission to read your repository’s contents at all. Stories render with no network access whatsoever. Builds are encrypted at rest, and the pull request diff used for context is encrypted with a key unique to your installation and deleted by the one run that reads it.

Getting started

Three steps. There is no baseline to record first, no screenshots to approve, and nothing to install in your test runner.

1. Install the GitHub App

Pick the repositories it should watch. The page it lands you on shows the CI workflow, ready to copy.

2. Upload your Storybook build from CI

Run it on pull requests and on pushes to your default branch — those default-branch builds are what baselines resolve to.

3. Configure it, if you want to

The defaults are a desktop and a mobile viewport with the accessibility pass on. Everything else is optional.

merrykat.config.ts
export default {
  viewports: [
    { name: 'desktop', width: 1280, height: 800, accessibility: true },
    { name: 'mobile', width: 420, height: 700 },
    {
      name: 'dark',
      width: 1280,
      height: 800,
      media: { colorScheme: 'dark' },
    },
  ],
  accessibility: { enabled: true },
};

See what your pull requests have been changing