ScreenshotNeo

BlogHow-to

How to Set Up Loki Screenshot Tests in a CI Pipeline

Build Storybook in CI, compare Loki screenshots against committed references, and make visual changes reviewable without accepting missing baselines.

By the ScreenshotNeo team4 October 202610 min read

Loki screenshot tests run Storybook stories through a renderer, compare the resulting images with approved reference images, and return a failing exit code when a visual difference or missing reference needs review. In CI, build a static Storybook and run loki --requireReference --reactUri file:./storybook-static. Generate and review the initial references locally, commit the approved files, and let CI test without updating them.

This guide uses the documented static-build workflow. Loki’s setup and CLI documentation was last updated on August 27, 2024, and lists Node 16+ as a prerequisite. Check commands and runtime compatibility against the versions pinned by your project before adopting them. Loki CI guide, getting started.

1. Install Loki and initialize its configuration

Install Loki as a development dependency using the package manager already used by the project. The official getting-started guide shows Yarn:

yarn add loki --dev
yarn loki init

The initialization command detects the project type and adds a default loki configuration to package.json. Review the generated target and configuration rather than assuming they suit your CI runner. The guide lists Docker as an optional prerequisite for the chrome.docker target and GraphicsMagick for the gm diffing engine. See Loki’s setup requirements.

For npm, equivalent installation and initialization commands are:

npm install --save-dev loki
npx loki init

Pin Loki in the project lockfile so local runs and CI resolve the same dependency. Also pin the Storybook and Node versions through the project’s existing version-management setup.

2. Create and approve the reference images

Reference images are the expected output Loki compares against. Create them intentionally, inspect the result, and commit approved files alongside the code. Do this before enabling a CI job that requires every story to have a baseline.

# Build or start Storybook using the project's normal local workflow.
yarn loki update

The getting-started guide describes references in a loki folder; the CLI reference documents defaults of ./.loki/reference for references, ./.loki/current for generated output, and ./.loki/difference for diffs. Actual locations can depend on the version and configuration. Check the files created in your repository and adjust any ignore rules so approved references are tracked. Git LFS is an optional way to store image files. Reference setup guidance, CLI paths and flags.

  1. Run the update command with the renderer and Storybook configuration intended for the project.
  2. Inspect the captured images for missing fonts, unloaded data, incorrect viewport sizes, and unstable animation frames.
  3. Review changes with component owners and approve only the output that represents the intended design.
  4. Commit approved reference files. Avoid generating or approving new references automatically in the CI verification job.

3. Build Storybook and run Loki in CI

Loki’s CI guide gives this core command for a static Storybook build:

build-storybook && loki --requireReference --reactUri file:./storybook-static

--requireReference makes a missing baseline a failure instead of silently treating an unreviewed screenshot as the expected image. Since the build is static, this workflow points Loki at the build directory and does not need a Storybook server process. Confirm that build-storybook is the command available in your project and that its output directory is actually storybook-static. Loki’s CI guidance.

A representative script in package.json is:

{
  "scripts": {
    "build-storybook": "storybook build",
    "test:visual": "yarn build-storybook && loki --requireReference --reactUri file:./storybook-static"
  }
}

The script names above are examples; preserve the build command and output path used by your Storybook version. If the package manager does not forward flags as expected, invoke Loki directly through the package script or use its documented argument separator. The Loki CLI guide notes that Yarn or npm may need -- before arguments intended for Loki. For example:

yarn loki test -- --requireReference --reactUri file:./storybook-static

Check the exact syntax supported by the installed Loki version. The CI guide’s representative command uses the Loki CLI invocation shown above; the CLI page documents the test subcommand and argument forwarding. Loki CLI reference.

A CI job should check out the repository, install dependencies reproducibly from the lockfile, build Storybook, and run the visual script. For example, the essential shell steps are:

yarn install --frozen-lockfile
yarn test:visual

Use the corresponding lockfile-enforcing install option for your package manager and version. Make the job fail when the Loki command exits unsuccessfully. Do not run loki update or loki approve as part of routine verification: a pull request should reveal differences for review, not rewrite the expected output in place.

4. Choose a renderer the runner can reproduce

Loki documents Chrome in Docker, local Chrome, an iOS simulator, and an Android emulator. Its configuration examples include targets such as chrome.docker, chrome.app, ios.simulator, and android.emulator. Select the renderer based on the browser or device coverage you need and can support consistently in both CI and reference generation. Loki platform overview, configuration reference.

Target Use when CI consideration
chrome.docker You want Chrome in a containerized renderer. The runner needs Docker access; use a consistent image and environment.
chrome.app The runner has a compatible local Chrome installation. Browser availability and version must be managed on the runner.
ios.simulator You need iOS simulator rendering. The runner must support the simulator setup the project requires.
android.emulator You need Android emulator rendering. The runner must support the emulator setup the project requires.
chrome.aws-lambda You have a sufficiently large suite and are willing to operate remote rendering. Requires a renderer Lambda and a remotely reachable Storybook build; this adds AWS setup.

The configuration guide shows viewport dimensions, named presets, and device settings. A small illustrative configuration is:

{
  "loki": {
    "configurations": {
      "chrome.desktop": {
        "target": "chrome.docker",
        "width": 1366,
        "height": 768
      }
    }
  }
}

Use configuration keys and options supported by the version you have installed. Avoid introducing multiple viewports or device targets without a reason: every added configuration expands the amount of output to inspect and the rendering work required.

5. Keep stories deterministic

A screenshot test is useful only when repeated captures of unchanged code are comparable. Control the inputs that affect the rendered page: fixture data, fonts, viewport, time-dependent content, and network dependencies. Prefer self-contained stories with stable local data over stories that depend on live services.

Loki handles common web transitions by disabling CSS transitions and animations and requestAnimationFrame behavior by default. Its flakiness guide identifies looping requestAnimationFrame animations, GIFs, SVG animations, native Lottie animations, and React Native Animated as limitations. Disable or replace those effects in visual-test mode where appropriate. Loki also documents a chromeEnableAnimations setting; enabling animations can make captures time-sensitive. Handling flaky tests, configuration options.

For stories whose rendering completes asynchronously, Loki documents an explicit callback pattern:

import createAsyncCallback from '@loki/create-async-callback';

export const AsyncStory = () => (
  <MyComponent onDone={createAsyncCallback()} />
);

Use the callback where the component can signal that its final state is ready. Do not rely on an arbitrary delay when the application can expose a completion event. If a story cannot be meaningfully captured, the documented story parameter can skip it:

export const AnimatedStory = () => <AnimatedComponent />;

AnimatedStory.story = {
  parameters: {
    loki: { skip: true },
  },
};

Skipping should be an intentional coverage decision; it removes that story from visual checks. Loki’s configuration reference also documents fetchFailIgnore for selectively ignoring failed network requests that match a regular expression. Prefer fixing missing test data or stabilizing a request first, since ignoring failures can hide broken story output. Loki configuration reference.

6. Review differences and update references deliberately

When a comparison fails, inspect both the current image and the diff. A visual difference may be an intended design change, an unintended regression, or a rendering inconsistency. The getting-started guide describes reviewing current and difference images before approving updates. The CLI provides an approve command and documents a --diffOnly option for approving only failed tests. Review and approval workflow, CLI reference.

  1. Download or open the CI artifacts for current captures and differences, if your CI system retains them.
  2. Identify the affected story and configuration and check whether the change is expected.
  3. Fix unintended component or environment changes. If the change is intentional, regenerate the relevant references locally, inspect them, and commit the approved result.
  4. Rerun CI against the committed references.

Do not approve a visual diff solely because it is blocking a merge. The reference images are the test’s expected output; updating them without review removes the check’s value.

7. Scale the job when the suite grows

Start with the simplest renderer your CI runner can reproduce. Loki includes Chrome parallelization options in its CLI, but increasing concurrency can increase runner resource use and may expose resource-sensitive behavior. Raise it gradually and check whether captures remain stable.

For very large suites, Loki documents an AWS Lambda renderer. The guide requires creating a renderer Lambda and making the Storybook build remotely accessible, with S3 and HTTPS presented as an approach. This adds deployment, access, and runtime configuration, so it is a separate scaling path rather than a prerequisite for ordinary CI. The Lambda guidance is dated August 27, 2024; verify AWS runtime availability and packaging instructions before using it. Loki serverless renderer guide.

The sources do not provide a current benchmark or a universal concurrency setting. Measure your own CI duration and runner capacity. Keep rendering environments stable before optimizing for speed.

Common errors and fixes

Symptom Likely cause What to check
CI fails because a reference image is missing The story has no committed baseline, or Loki is looking in a different reference directory. Generate and review the reference locally, commit it, and confirm the configured/default reference path.
Storybook URI cannot be opened The static build directory or URI does not match the build output. Check that the build succeeds and that --reactUri points to the actual directory with the expected file: form.
The command runs locally but flags are ignored in CI Yarn or npm may not be forwarding arguments to Loki. Check the installed CLI syntax and use the argument separator documented for your package-manager command.
Chrome or Docker renderer fails to start The runner lacks the selected renderer prerequisite or does not support its configuration. Confirm Docker or local Chrome is available as required by the target; compare the runner setup with local reference generation.
Many unrelated screenshots differ The capture environments differ, or fonts, data, viewport, or assets are unstable. Align renderer and configuration, use stable fixtures, and ensure required assets load before capture.
A single story changes between runs It may depend on async updates, animation, current time, or an external request. Use Loki’s async callback pattern where applicable, disable unsupported animations, and make story inputs deterministic.
A network failure causes test failure Loki’s configuration guide says failed requests fail by default. Provide the request dependency reliably or, only when appropriate, configure fetchFailIgnore narrowly for known irrelevant failures.
Diffs are too sensitive or too permissive The diff engine or tolerance may not suit the project. Review the installed CLI/configuration documentation for supported engines and tolerance settings; keep thresholds narrow enough to catch meaningful changes.
CI runtime or AWS deployment instructions no longer work The referenced Loki docs are version-dated and external runtimes change. Check current compatibility for the pinned Loki, Storybook, Node, and AWS versions before changing the environment.

Performance, reliability, and cost notes

  • Performance: Build Storybook once per job and reuse that output for the visual run. Keep the suite focused on stories that provide useful regression coverage, and add render configurations based on an explicit coverage need.
  • Reliability: Keep Node, Loki, Storybook, renderer, viewport, and test data consistent between reference generation and CI. Commit only reviewed reference changes. Preserve CI artifacts for failed runs when the platform supports it.
  • Runner resources: Parallel capture can shorten elapsed time but uses more resources. The right setting depends on the runner and suite; the cited docs do not establish a generally optimal value.
  • Direct cost: Loki is a development dependency; the basic workflow uses your own CI runner and selected renderer. Docker, simulator, emulator, or remote Lambda operations may bring their own infrastructure and maintenance costs. No universal cost or runtime figure follows from the documented workflow.
  • Maintenance: Treat old setup snippets as version-sensitive. Keep the configuration close to the project and recheck supported options when upgrading Loki or Storybook.

Or skip the browser setup

Loki checks Storybook stories against committed references. For a one-off screenshot of a live page, ScreenshotNeo provides a screenshot API and MCP server. This does not replace Loki’s story-based visual regression workflow; it handles URL-based page captures.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests

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

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options and configuration. Cookie banners, popups, and chat widgets are removed before capture; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. Sign up free and try ScreenshotNeo.

FAQ

Should CI create new Loki references automatically?

No. Create and review references intentionally, then commit them. Use --requireReference in CI so a missing baseline is reported for review.

Do I need to start Storybook as a server in CI?

Not for the documented static-build workflow. Build Storybook and point Loki at its output with --reactUri.

Can I use Loki to screenshot arbitrary production URLs?

Loki’s documented workflow tests stories in a Storybook project. For standalone URL screenshots, use a URL-based screenshot tool such as ScreenshotNeo.

Where should approved reference images live?

Use the path configured for your Loki version; the CLI documentation lists ./.loki/reference as the default. Commit the files, or optionally store them with Git LFS.