ScreenshotNeo

BlogHow-to

How to Make Loki Screenshots Consistent by Disabling Animations

Loki disables common CSS animations by default. Learn how to confirm that setting, handle animation types Loki cannot stop, and troubleshoot inconsistent snapshots.

By the ScreenshotNeo team4 October 20266 min read

Loki disables common CSS transitions and animations by default. To make that behavior explicit in a Loki project, set chromeEnableAnimations to false in the loki configuration object. This controls common CSS animation cases, but it does not stop every kind of motion; looping JavaScript animations, GIFs, SVG animations, native Lottie, and React Native Animated may need application-level handling.

1. Set Loki to disable animations

Add the option to the loki object in your project configuration. For example, in package.json:

{
  "loki": {
    "chromeEnableAnimations": false
  }
}

If your project keeps Loki configuration in a separate supported configuration file, put the same property inside its loki configuration object. Loki documents that command-line options accepted by loki test can also be set there. See the Loki advanced configuration and CLI reference for the syntax applicable to your installed version.

The option name can be confusing: chromeEnableAnimations sounds like animations should be enabled, but its documented default is false. Keep it false to disable them. Setting it explicitly records the intended behavior in project configuration and makes it easier for teammates to see what the test run should do.

2. Run the visual test

After saving the configuration, run the same Loki test command you normally use, such as:

npx loki test

Loki’s CLI reference says the command-line options accepted by loki test can be supplied in the configuration object as well. This setting is not a substitute for making the story itself deterministic: it handles common browser CSS motion, while application state, data, fonts, and unsupported animation mechanisms can still affect a capture.

3. Know which animation types Loki handles

Loki describes its default handling as disabling CSS transitions and animations and requestAnimationFrame for common web transitions, with the screenshot paused at the end state. It also documents limitations. Do not assume this setting can freeze every animation system:

Animation source What to do
Common CSS transitions and animations Rely on Loki’s default, or explicitly set chromeEnableAnimations: false.
Looping requestAnimationFrame animation Use an application-level Loki state to render a stable variant, or skip the story if one still frame is not meaningful.
Animated GIF Provide a stable test asset or render a non-animated variant for Loki.
SVG animation Provide a static SVG or a Loki-specific non-animated rendering.
Native Lottie animation Render a still or non-animated state for the visual test.
React Native Animated Use application-level handling to render a stable state for Loki.

For stories where motion cannot produce a useful single screenshot, Loki documents a story parameter for skipping that story. Use that selectively: skip only cases whose moving state is inherently unsuitable for a still-image comparison, rather than hiding a visual regression in a component that should have a stable state.

4. Render a stable application state for unsupported motion

When a component uses an animation mechanism that Loki cannot disable, make the test state an explicit application concern. Loki documents using isLokiRunning() in a Storybook decorator so the component can render a non-animated version during Loki capture. The exact decorator and component API depend on your Storybook setup, but the pattern is:

import { isLokiRunning } from 'loki';

export const decorators = [
  (Story) => (
    <Story disableMotion={isLokiRunning()} />
  ),
];

Treat this as a pattern, not a drop-in decorator for every project: adapt the prop or context to the component that owns the animation. In normal Storybook viewing, leave the motion behavior as designed; in Loki, render a deterministic state. Avoid relying on timing alone, because a test that happens to capture the same animation frame today can capture a different frame on another run.

5. Troubleshoot snapshots that still vary

Symptom Likely cause Fix
The snapshot still changes while a CSS transition runs. The project may be using a different Loki version or configuration than expected. Confirm the installed version, check its matching CLI documentation, and verify that chromeEnableAnimations is false in the configuration actually used by the run.
A continuously moving component differs between runs. It may use a looping requestAnimationFrame animation or a library-managed animation. Render a Loki-specific stable state using isLokiRunning(), or skip the story if a still frame has no useful expected result.
An image or icon changes despite the CSS setting. Animated GIF or SVG content is not covered by the common CSS handling. Use a static asset or select a non-animated variant for Loki.
Only a React Native animated component is unstable. React Native’s Animated library is a documented limitation. Make the component render a static test state in the Loki environment.
A test waits for an action’s animation but the screenshot is still inconsistent. Waiting for animations during an action is not the same as stabilizing the entire page at screenshot time. Disable or finish the page animation using the visual snapshot tool’s capture behavior, and make unsupported component motion deterministic.

Cypress makes the same distinction in its visual-testing guidance: waitForAnimations and animationDistanceThreshold apply to action commands such as clicks; they do not stop an unrelated animation elsewhere on the page from appearing mid-motion in a screenshot. If you combine interaction testing with visual snapshots, control the animation at capture time as well as during the action.

6. Reliability, performance, and version notes

  • Reliability: Explicitly recording false documents the desired setting, but deterministic snapshots also depend on stable application data, rendering, and any motion outside Loki’s supported handling.
  • Capture state: Loki describes common transitions as being captured at their end state. If the end state itself changes with data or timing, stabilize that input in the story.
  • Performance: The documented option is about screenshot behavior. The cited Loki material does not provide a performance benchmark or quantify a runtime change from setting it.
  • Version behavior: The official CLI reference documents the default as false. The reviewed documentation does not establish that every Loki release behaves identically, so consult documentation matching the installed version when version differences matter.
  • Cost: This configuration answer introduces no paid dependency or additional screenshot service requirement. Any separate CI or infrastructure costs depend on your existing setup.

7. Or skip the browser setup

If you need screenshots of live websites rather than Storybook component snapshots, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its clean-shot steps accept cookie or consent banners like a visitor and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off.

For example, this cURL request captures a page as WebP. Replace the URL with the page you want to capture and use your API key:

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

Equivalent 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)

Equivalent 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

See the ScreenshotNeo API documentation for request options and response details. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and responses identify the page verdict and billing status in headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo to get 1,000 free screenshots a month with no card.

8. FAQ

Does chromeEnableAnimations: false mean Loki animations are disabled?

Yes. Despite the option’s name, its documented default is false, and Loki documents disabling common CSS transitions and animations by default.

Should I add the option if Loki already defaults to it?

It is optional for the documented default. Adding it makes the desired behavior visible in project configuration.

Can I use this to freeze every animated element?

No. It does not cover every JavaScript, image, SVG, or native animation mechanism. For listed limitations, render a stable application state or skip a story that cannot be represented meaningfully as one still.

Will waiting for a click animation stabilize the screenshot?

Not by itself. An action-level wait can finish the animation involved in that action while other page motion continues during capture.

Sources