Configuration

Configuration lives in merrykat.config.ts — or .js, .json, or a merrykat key in package.json. The CLI reads and validates it in your CI job.

Everything, with its default

merrykat.config.ts
export default {
  viewports: [
    // `accessibility: true` on the first viewport and nowhere else is the explicit
    // spelling of the default: unstated everywhere means the first one gets the pass.
    { name: 'desktop', width: 1280, height: 800, accessibility: true },
    { name: 'mobile', width: 420, height: 700 },
  ],

  include: ['**'],
  exclude: [],

  delay: 250,
  play: true,
  playTimeout: 5000,

  failOn: ['error', 'blank', 'likely_bug'],
  baselineBranch: null,

  ai: { enabled: true, maxExamined: 10, codeContext: true, depth: 'standard' },
  accessibility: { enabled: true },
  compare: { fingerprints: 'auto', audit: 0.02, alwaysCompare: [] },
};

Every field is optional. If you don’t have a Merrykat configuration, you get the configuration above.

Viewports

A viewport is a name, a width and a height, plus an optional device pixel ratio.

A viewport can also be a media feature under test rather than a size. Three are supported, each mapping onto the browser’s own emulation: colorScheme (prefers-color-scheme), contrast (prefers-contrast) and forcedColors (forced-colors). Anything you leave out keeps the run’s usual default.

viewports: [
  { name: 'desktop', width: 1280, height: 800 },
  { name: 'mobile', width: 420, height: 700, dpr: 2 },

  // Same size as 'desktop', rendered under prefers-color-scheme: dark
  { name: 'desktop-dark', width: 1280, height: 800, media: { colorScheme: 'dark' } },

  // prefers-contrast: more, and forced-colors: active
  { name: 'high-contrast', width: 1280, height: 800, media: { contrast: 'more' } },
  { name: 'forced-colors', width: 1280, height: 800, media: { forcedColors: 'active' } },
],

prefers-reduced-motion is deliberately not on that list: it is pinned to reduce for every capture as a determinism control, alongside disabled animations and transitions, a hidden caret and hidden scrollbars.

A viewport can also carry accessibility: true, which changes where the axe pass runs — see Accessibility testing.

Choosing stories

OptionDefaultMeaning
include['**']Story id / title globs to compare.
exclude[]Story id / title globs to leave out, applied after include.
delay250Settle time, in ms, before a story that differs from the baseline is re-shot.
playtrueRun each story’s play() function before capturing.
playTimeout5000Ceiling on one play(), in ms.
baselineBranchnullOverrides the pull request’s base branch when resolving a baseline. For repositories whose stable branch is not the branch pull requests target.

Failure policy

failOn decides what turns the check run red. Everything not listed is reported and left neutral. A visual difference is usually the point of the pull request rather than a fault in it.

ValueFails when
errorA story newly throws — Storybook’s own error overlay, not a console warning. On by default.
blankA story newly renders nothing. On by default.
likely_bugAn examined difference comes back as a high-severity likely bug. On by default.
any_diffAny visual difference at all. For repositories that want every change acknowledged.
new_a11y_violationThe pull request introduces an accessibility violation. Off by default.

error and blank both mean newly broken. If the baseline was already broken or blank, Merrykat reports it but doesn’t fail the check.

The AI layer

OptionDefaultMeaning
ai.enabledtrueOff still produces the full diff report, one representative story per change class instead of a chosen shortlist.
ai.maxExamined10Hard ceiling on stories sent to the vision stage. Differences and newly added stories draw on this one budget, differences first.
ai.codeContexttrueSend the pull request’s changed-file list, and the hunks of style and token files, so Merrykat can better explain results.
ai.depth'standard''deep' spends more of the model’s attention per examined story.

The code context is your source, so it is handled as such: computed locally by the CLI, encrypted with a key unique to your installation, stored encrypted and only for a short time, and deleted as soon as it has been used. Using --code-context=false on the CLI turns it off for a single run without editing the file.

Per-story parameters

You can configure Merrykat for individual stories through Storybook parameters.

export const Slow = {
  parameters: { merrykat: { delay: 900, viewports: ['desktop'] } },
};

export const WaitsForSomething = {
  parameters: { merrykat: { waitFor: '[data-loaded="true"]' } },
};

export const NotWorthChecking = {
  parameters: { merrykat: { disable: true } },
};
ParameterMeaning
disableSkip this story entirely.
delaySettle time for this story, in ms, up to 10000.
viewportsRender this story only at the named viewports.
waitForWait for a selector to appear before capturing.
play(Boolean) Override the run’s play() setting for this story.
accessibilityfalse switches the axe pass off for this story. A story can only ever turn it off, never on.

Command line

FlagMeaning
--dir <dir>Storybook build directory.
--project <key>Project key, for repositories with several Storybooks.
--config <file>Path to the configuration file.
--api <url>API base URL. MERRYKAT_API_URL overrides it.
--sha / --branch / --refOverride the git context detected from CI.
--code-context [bool]Send the pull request’s diff as context for this run.
--accessibility [bool]Run the axe pass for this run.
--quietOnly print warnings and errors.
NextFlakiness and the quarantine