ScreenshotNeo

BlogHow-to

How to Disable CSS Animations for Playwright Screenshots

Use Playwright’s screenshot option to suppress CSS animations, transitions, and Web Animations. Learn when to use reduced-motion emulation or a CSS override instead.

By the ScreenshotNeo team4 October 20267 min read

For a direct Playwright screenshot, pass animations: 'disabled' to page.screenshot():

await page.screenshot({ path: 'screenshot.png', animations: 'disabled' });

This tells Playwright to handle CSS animations, CSS transitions, and Web Animations for the capture. Direct screenshots default to animations: 'allow', so set the option explicitly when you need a still result. Finite animations are fast-forwarded to completion and fire transitionend; infinite animations are canceled to their initial state for the screenshot, then played over afterward. See the Playwright Page API.

1. Disable animations in a direct screenshot

Here is a complete TypeScript example for a standalone Playwright script. Install Playwright with npm install -D playwright, then run the script in an environment with its browser installed.

import { chromium } from 'playwright';

async function main() {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
    await page.goto('https://example.com', { waitUntil: 'networkidle' });
    await page.screenshot({
      path: 'screenshot.png',
      fullPage: true,
      animations: 'disabled',
    });
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Replace the example URL with the page you want to capture. If the site keeps long-running requests open, use a more suitable load condition such as domcontentloaded and wait for the page content you need before capturing.

JavaScript version

The option is the same in JavaScript:

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle' });
    await page.screenshot({
      path: 'screenshot.png',
      animations: 'disabled',
    });
  } finally {
    await browser.close();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

What the option does and does not do

  • animations: 'disabled' covers CSS animations, CSS transitions, and Web Animations.
  • Finite animations are fast-forwarded to completion. This fires transitionend, so application code listening for that event may change state before capture.
  • Infinite animations are canceled at their initial state for the screenshot, then resume after the screenshot.
  • It is screenshot-time handling; it does not permanently turn off motion in the page or test how the site responds to a reduced-motion preference.

If the final frame of a finite animation is not the state you want, wait for the intended application state explicitly, or use a targeted screenshot stylesheet to set the desired appearance.

2. Choose the right approach

Goal Use What to expect
Take a stable direct screenshot page.screenshot({ animations: 'disabled' }) Playwright handles CSS animations, transitions, and Web Animations for that capture.
Run a visual regression assertion expect(page).toHaveScreenshot() The assertion waits for two consecutive screenshots to match; its animations option defaults to disabled.
Test the site’s reduced-motion behavior page.emulateMedia({ reducedMotion: 'reduce' }) Emulates the prefers-reduced-motion media feature. The page must implement a response to it.
Make a capture-only visual adjustment page.screenshot({ style: '...' }) Applies a stylesheet for the screenshot, including through Shadow DOM and inner frames. The option was added in Playwright v1.41.

These approaches serve different purposes. Use the screenshot option for capture-time animation handling, reduced-motion emulation to exercise the user preference, and screenshot CSS when you need a specific visual override.

3. Use Playwright Test for visual assertions

When you are checking an expected screenshot with the Playwright test runner, use toHaveScreenshot(). It waits for two consecutive screenshots to produce the same result before comparing with the expectation, and animation handling defaults to disabled.

import { test, expect } from '@playwright/test';

test('home page matches its screenshot', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveScreenshot('home.png');
});

You can also set the option explicitly to make the test’s intent clear:

await expect(page).toHaveScreenshot('home.png', {
  animations: 'disabled',
});

toHaveScreenshot() belongs to Playwright Test’s assertion API. For a script that only saves an image, use page.screenshot() instead. See the official PageAssertions API and visual comparisons guide.

4. Test reduced-motion behavior

Use emulateMedia when the test is specifically about the site’s response to a visitor who prefers reduced motion. This sets the emulated prefers-reduced-motion media feature; it does not guarantee every page animation will stop.

await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto('https://example.com');
await page.screenshot({ path: 'reduced-motion.png' });

To emulate the default preference again, use no-preference. To clear the emulation, pass null:

await page.emulateMedia({ reducedMotion: 'no-preference' });
// Or clear the emulated preference:
await page.emulateMedia({ reducedMotion: null });

The page needs CSS or application behavior keyed to this preference for the rendered result to change. Check the site’s styles and behavior rather than assuming the preference suppresses motion automatically. The Page API documents the supported values.

5. Apply a targeted screenshot stylesheet

Use the style screenshot option when a specific element must be hidden or changed in the captured image. This is useful for dynamic content that is not solved by disabling animations, or for pinning an element to a known appearance.

await page.screenshot({
  path: 'screenshot.png',
  style: `
    .animated-banner {
      animation: none !important;
      transition: none !important;
    }
    .timestamp {
      visibility: hidden !important;
    }
  `,
});

The stylesheet is applied for the screenshot and reaches Shadow DOM and inner frames. Target selectors carefully: overriding layout, visibility, or animation properties can alter the captured page in ways that differ from what a visitor sees. The style option is available starting in Playwright v1.41. Check the Page screenshot API for your installed version.

6. Common problems and fixes

Symptom Likely cause Fix
The screenshot still changes between runs Time, network content, random data, canvas rendering, or application state may still vary; disabling animations only addresses animation behavior. Wait for the relevant content or app state, control variable data, and use Playwright Test’s screenshot assertion when comparing snapshots.
An element appears at its final animation state Finite animations are fast-forwarded to completion. Wait for the desired state before capture, or use screenshot style to set the appearance you need.
The application advances after capture starts Fast-forwarding a finite animation fires transitionend; an event handler may react to it. Inspect event-driven behavior and explicitly establish the state to capture before taking the screenshot.
Reduced-motion emulation has no visible effect The page may not implement prefers-reduced-motion behavior. Use animations: 'disabled' for screenshot-time handling, or add and test the page’s reduced-motion behavior if that is the goal.
style is rejected or ignored The installed Playwright version may predate v1.41, or the selector may not match the target. Check the installed version and selector. Upgrade if you need the screenshot style option.
Navigation waits indefinitely networkidle may not occur on pages with persistent connections or recurring requests. Choose an appropriate navigation wait condition and wait for a concrete selector or application-ready signal.

7. Reliability, performance, and cost

Disabling animations is a screenshot consistency control, not a complete visual test stabilization strategy. Your capture can still vary because of changing data, fonts, browser versions, viewport size, time, or asynchronous page content. Keep the browser version, viewport, device scale, test data, and readiness condition consistent when comparing screenshots.

Use the narrowest wait that reliably indicates the page is ready. Waiting for all network activity to stop can add latency or hang on sites that keep requests open. A selector or app-specific ready signal can be more predictable. Screenshot assertions add stabilization by checking consecutive captures, while direct screenshots do not perform that comparison step.

These approaches run in your Playwright environment, so their cost depends on your compute and browser runtime. No third-party screenshot API is required for the local Playwright method.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. A single GET request can return a PNG, JPEG, WebP, or PDF. Its clean-shot flow accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the page verdict and billing status in headers. AI agents can use its MCP server tools to take screenshots, get page information, and capture PDFs.

For one-call website capture, see the ScreenshotNeo API documentation:

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

Cookie banners, popups, and chat widgets are removed before the shot. 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; paid plans start at $5 for 3,000.

Start with 1,000 free screenshots a month; no card is required.

9. FAQ

Does animations: 'disabled' pause an animation on its current frame?

No. Finite animations are fast-forwarded to completion, while infinite animations are canceled at their initial state for the screenshot and resume afterward.

Does this option change the page permanently?

No. It controls animation handling for that screenshot capture.

Should I use reduced motion or disable animations?

Use reduced-motion emulation to test the page’s response to the visitor preference. Use the screenshot option to handle animation during a capture.

Can I use this with a full-page screenshot?

Yes. Pass fullPage: true and animations: 'disabled' in the same page.screenshot() options object.