Animations
Chromatic proactively pauses CSS animations/transitions, SVG animations, and videos to prevent false positives. We do this because multiple variables outside of our control make it impossible to guarantee consistent animation painting down to the millisecond.
CSS animations
By default, CSS animations are paused at the end of their animation cycle (i.e., the last frame) when your tests run in Chromatic. This behavior is useful, specifically when working with animations that are used to “animate in” visible elements. If you want to override this behavior and pause the animation at the first frame, add the pauseAnimationAtEnd
configuration option to your tests. For example:
// Adjust this import to match your framework (e.g., nextjs, vue3-vite)
import type { Meta, StoryObj } from "@storybook/your-framework";
/*
* Replace the @storybook/test package with the following if you are using a version of Storybook earlier than 8.0:
* import { within } from "@storybook/testing-library";
* import { expect } from "@storybook/jest";
*/
import { expect, within } from "@storybook/test";
import { Product } from "./Product";
const meta: Meta<typeof Product> = {
component: Product,
title: "Product",
parameters: {
// Overrides the default behavior and pauses the animation at the first frame at the component level for all stories.
chromatic: { pauseAnimationAtEnd: false },
},
};
export default meta;
type Story = StoryObj<typeof Product>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("Product details")).toBeInTheDocument();
},
};
chromatic.pauseAnimationAtEnd
parameter can be set at story,
component, and project levels. This enables you to set project wide
defaults and override them for specific components and/or stories. Learn more » import { test, expect } from "@chromatic-com/playwright";
test.describe("Products Page", () => {
// Overrides the default behavior and pauses the animation at the first frame for this test.
test.use({ pauseAnimationAtEnd: false });
test("Successfully loads the page", async ({ page }) => {
await page.goto("/products");
await expect(page.getByText("Product details")).toBeVisible();
});
});
pauseAnimationAtEnd
configuration option can be set at the
project level or the test level. This enables you to set project wide
defaults and override them for specific tests. Learn more » describe("Not found", () => {
it("Successfully loads the page", { env: {
pauseAnimationAtEnd: false // Overrides the default behavior and pauses the animation at the first frame for this test.
}}, () => {
cy.visit("/products");
cy.get("h3").should("contain", "Product details");
});
});
pauseAnimationAtEnd
configuration option can be set at the
project level or the test level. This enables you to set project wide
defaults and override them for specific tests. Learn more » JavaScript animations
If you’re working with JavaScript animations libraries (e.g., framer-motion), Chromatic will not have the same level of control over the animations as CSS animations and will not disable them by default. We recommend toggling off the animation library when running in Chromatic to ensure consistent visual tests and avoid false positives.
Storybook
You can conditionally disable animations using the isChromatic()
utility function. For example, to turn off animations globally in framer-motion (v10.17.0 and above), you can set the MotionGlobalConfig.skipAnimations
option as follows:
import { MotionGlobalConfig } from "framer-motion";
import isChromatic from "chromatic/isChromatic";
MotionGlobalConfig.skipAnimations = isChromatic();
ℹ️ For more information on disabling animations in other libraries, refer to the library’s documentation or community resources to learn how to achieve this.
Playwright and Cypress
When using Playwright or Cypress, you can assert that specific elements are visible in the DOM to confirm an animation has finished. Alternatively, use the wait
or waitForTimeout
functions to wait for the animation to complete.
Another strategy is to inject a variable into the window object to disable animations. For example, in your E2E test, set the disableAnimations
property on the window
object to true
.
import { test, expect } from "@chromatic-com/playwright";
test.describe("Products Page", () => {
test("Successfully loads the page", async ({ page }) => {
// Set a property on the the window object to disable animations.
await page.addInitScript(() => {
window.disableAnimations = true
});
await page.goto("/products");
await expect(page.getByText("Product details")).toBeVisible();
});
});
describe("Not found", () => {
// Set a property on the the window object to disable animations.
Cypress.on('window:before:load', (win) => {
win.disableAnimations = true
})
it("Successfully loads the page", () => {
cy.visit("/products");
cy.get("h3").should("contain", "Product details");
});
});
Then read the value of the disableAnimations
property in your application code to conditionally disable animations.
// @ts-ignore
if (window.disableAnimations) {
MotionGlobalConfig.skipAnimations = true;
}
GIFs and Videos
Chromatic automatically pauses videos and animated GIFs at their first frame, ensuring consistent visual tests without the need for custom workarounds. If you specify a poster attribute for videos, Chromatic will use that image instead.
Animations that cannot be disabled
If you cannot turn off animations (for example, if disabling JavaScript animations is complex or your library doesn’t support it), you can use a delay to allow the animation to complete before taking the snapshot.
Alternatively, ignore an element to omit a visible area of your component when comparing snapshots.
Troubleshooting
Why are my animations working differently in Chromatic?
If you’re experiencing issues with animations not being paused as expected, it is likely due to an infrastructure update. With Capture Stack’s version 6 general availability (released in February 2024), the pauseAnimationAtEnd
feature was enabled by default, leading to a change in behavior. If your tests relied on the previous behavior, you need to update your tests and configure the pauseAnimationAtEnd
option to false
.