ScreenshotNeo

BlogEngineering

Visual Regression Testing with Loki

Set up Loki with Storybook, create reviewable baselines, run visual tests in CI, fix failures, and compare Loki with ScreenshotNeo.

By the ScreenshotNeo team29 September 20269 min read

Visual Regression Testing with Loki

Direct answer: Loki performs visual regression testing by capturing screenshots of your Storybook stories, comparing them with committed reference images, and showing differences for human review. The normal workflow is yarn loki update to create or refresh references, yarn loki test to compare current renders, and yarn loki approve only after you decide a visual change is intentional. Loki does not decide whether a change is correct by itself; the baseline is a review artifact owned by your team.

This guide covers local setup, browser targets, baseline organization, CI, flaky captures, debugging, performance, maintenance, and an API alternative when you do not want to manage a browser runner.

What Loki tests

Loki is an open-source development dependency for visual regression testing of Storybook. It launches a supported browser or simulator, visits each selected story, captures an image, and compares that image with a reference stored in your repository. A changed pixel produces a diff that a developer or reviewer must inspect.

The project README describes aims of easy setup, low maintenance, reproducible tests across operating systems, CI execution, and support for Storybook platforms. Those are project aims, not independently measured guarantees. The surfaced setup documentation lists Node 16 or newer as a prerequisite and documents Chrome in Docker, Chrome in AWS Lambda, local Chrome, an iOS simulator, and an Android emulator as possible targets. Check the current repository before choosing exact versions, flags, or simulator combinations because the setup pages used for this article were updated in 2024.

Install Loki and initialize a project

  1. Start with a working Storybook project. Run its normal development command once and open a story in a browser.
  2. Install Loki as a development dependency with your package manager.
  3. Initialize Loki from the project root.
  4. Confirm that the Storybook version, Node version, browser, and optional target dependencies match the current Loki release.
# Yarn
 yarn add --dev loki
 yarn loki init

# npm equivalent
 npm install --save-dev loki
 npx loki init

Initialization creates or updates Loki configuration. Keep that configuration in version control. The exact generated fields vary by release and target, so prefer the configuration produced by your installed version over copying an old example.

Loki captures a story, compares it with a committed reference, and leaves approval to a reviewer.
Loki captures a story, compares it with a committed reference, and leaves approval to a reviewer.

Create your first visual baselines

Run Storybook, then generate references:

yarn storybook
# In another terminal
yarn loki update

Loki stores reference files in a loki directory by default. Commit this directory to Git; Git LFS is an option when the image volume is large. Treat every reference as an explicit review artifact. A baseline update should be part of the same pull request as the component change, with a reviewer checking the rendered result.

After changing a component or story, capture and compare again:

yarn loki test

When a test reports a difference, inspect the current screenshot and the diff. If the change is expected, approve the new image:

yarn loki approve

Do not run approve as an automatic replacement for review. A changed snapshot can indicate a genuine regression, a deliberate redesign, a font change, a browser update, or an unstable story.

A practical Storybook baseline workflow

  1. Make stories deterministic. Use fixed fixture data, stable dates, predictable random seeds, and controlled loading states.
  2. Define the visual contract. Decide which viewport, theme, locale, and state each story represents.
  3. Generate references. Run yarn loki update on the agreed environment.
  4. Commit references with code. Keep image changes visible in the pull request.
  5. Run comparisons for every change. Use yarn loki test locally before pushing.
  6. Review diffs. Check whether the changed region is intentional and whether unrelated stories moved.
  7. Approve deliberately. Run yarn loki approve only after review, then commit the updated references.

For design systems, keep stories focused. A story that renders one component state is easier to diagnose than a page containing several unrelated components. Include stories for empty, loading, error, long-content, keyboard-focus, disabled, dark-mode, and responsive states when those states are part of the component contract.

Configuration and capture targets

Loki can run against several environments documented by the project: local Chrome, Chrome in Docker, Chrome in AWS Lambda, an iOS simulator, and an Android emulator. Docker and simulator targets may require additional software such as Docker, Chrome, or GraphicsMagick. Availability depends on your operating system and the current Loki release.

Decision What to standardize Why it matters
Browser Browser family and version Font rendering and layout can change between versions.
Viewport Width, height, and device scale Responsive breakpoints and rasterization depend on them.
Story state Fixtures, network responses, loading status Uncontrolled data creates false diffs.
Fonts Installed fonts and load completion Fallback fonts change line wrapping and component height.
Platform Operating system or container image Native rendering differences can create broad diffs.

Use one canonical environment for baseline generation and CI comparison. If developers update references on different operating systems, you may see diffs caused by rendering rather than code. A containerized browser can reduce that variation, but it adds image maintenance and startup time.

Running Loki in continuous integration

CI should fail when a visual test differs or when a reference is missing. The Loki CI guide documents --requireReference for this purpose. Without that guard, a missing baseline may be created silently, allowing a new story to bypass review.

One documented approach builds a static Storybook and points Loki at the resulting files with a file URI. This avoids starting Storybook as a long-running server in that setup. Verify the exact flag names against your installed version.

# Example flow; confirm the current Loki flags for your release
 yarn build-storybook -o storybook-static
 yarn loki test --requireReference --storybook-url=file://$PWD/storybook-static

A CI job should install dependencies with a lockfile, prepare the documented browser target, build Storybook, run Loki with missing-reference protection, and upload current screenshots and diffs as artifacts. Keep baseline changes in pull requests instead of replacing them only in CI. If your pipeline runs parallel jobs, ensure each job has access to the same reference directory and does not write approvals concurrently.

Diagnosing visual differences

Large areas changed

Check browser and operating-system versions first. Then check fonts, device scale, color profile, and whether the page loaded a different theme or locale. Re-run the same commit in the canonical environment before approving anything.

Only text changed

Look for dates, generated IDs, randomized content, translated strings, and API responses. Replace them with fixed fixtures or mock the request at the Storybook layer.

Images are missing

Wait for image loading in the story, use stable local fixtures, and verify that the CI environment can reach required assets. A screenshot taken before fonts or images finish loading is not a useful baseline.

Animations create intermittent diffs

Disable transitions and animations for visual tests or make the story render at a stable point in time. Avoid approving a diff that disappears on the next run.

A new story passes without a baseline

Use --requireReference in CI. Generate the reference locally, review it, commit it, and then rerun CI.

Loki cannot launch the browser

Confirm Node compatibility, browser installation, Docker availability, simulator configuration, and optional dependencies listed for your target. Read the command output for the failing launcher rather than changing image files first.

Diffs appear after a dependency upgrade

Record the browser, Storybook, Loki, operating-system, and font changes in the pull request. Upgrade deliberately, inspect the full diff set, and regenerate references only when the rendering change is accepted.

Reliability and maintenance practices

  • Keep stories independent of live production APIs.
  • Use fixed time and timezone settings for date components.
  • Provide deterministic mock data for every network request.
  • Wait for fonts, images, and asynchronous state before capture.
  • Keep one baseline owner for large browser or OS upgrades.
  • Review image diffs as code, not as generated files to be auto-accepted.
  • Prune obsolete stories and references when components are removed.

Visual tests are most valuable when failures are actionable. A smaller set of stable stories is preferable to a huge set of flaky captures that developers learn to ignore. Run the complete suite in CI and a focused subset locally while iterating.

Performance and cost considerations

Loki’s runtime is shaped by the number of stories, browser startup, Storybook build time, asset loading, and the chosen target. Local Chrome is often simpler to iterate with; Docker or remote environments can improve consistency while adding setup overhead. Mobile simulators generally require more preparation than a desktop browser.

A hosted capture service can remove consent clutter and overlays before returning the image.
A hosted capture service can remove consent clutter and overlays before returning the image.

The project materials do not provide independent speed, accuracy, adoption, or cost benchmarks. Budget CI time from your own runs, cache package and browser layers where your CI provider allows it, and split large suites only when parallelization does not compromise shared state. Store image artifacts selectively because references and diffs can consume substantial repository and CI storage.

Or skip the browser setup

If you need screenshots of arbitrary URLs rather than Storybook stories, ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and returns PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and whether the request was billed.

See the ScreenshotNeo API documentation for the complete option list. The service supports full-page captures with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or custom viewports, retina scale, PDF paper sizes and page ranges, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for 100 URLs per call, usage data, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://storybook.example.com/button \
  -o story.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://storybook.example.com/button",
    },
    timeout=90,
)
r.raise_for_status()
open("story.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://storybook.example.com/button',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('story.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Its Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account to try it.

Loki versus an API capture workflow

Question Loki ScreenshotNeo
Primary target Storybook stories in your test workflow Web URLs, elements, images, and PDFs
Execution Your local or CI browser/simulator One API request or MCP tool
Baselines Reference images committed to Git You manage returned assets and comparisons
Review Diff and approve with Loki commands Use your existing review or storage process
Maintenance Browser, simulator, and dependency setup Request options and API key management

Choose Loki when Storybook stories are the system under test and you want references reviewed alongside component code. Choose ScreenshotNeo when you need hosted URL capture, PDF output, clean pages without consent clutter, or screenshots requested by AI agents. You can also use both: Loki for component regressions and ScreenshotNeo for deployed-page checks.

FAQ

Does Loki understand whether a visual change is good?

No. It captures, compares, and reports differences. A person decides whether to fix the code or approve the new reference.

Where should Loki references live?

The default location is the project’s loki directory. Commit it to Git; Git LFS is optional for large image sets.

How do I prevent missing references in CI?

Use the documented --requireReference option and verify its spelling for your installed release.

Can Loki test mobile layouts?

The project documents iOS simulator and Android emulator targets, along with desktop and containerized Chrome targets. Confirm current platform prerequisites before adoption.

Should every Storybook story have a screenshot?

No. Prioritize stable states that represent your visual contract, then expand coverage where regressions would be costly.