sodcalculator

10 Tech How-To

Disable CSS Animations in Playwright Screenshots

Guide 10-15478 · Updated · Step by step

This guide shows you how to stop CSS animations, transitions, and web animations from affecting your Playwright screenshots. This is useful for visual regression testing, creating stable documentation images, or capturing a UI in a consistent state. You’ll learn three reliable methods, from a simple one-line CSS injection to a more robust approach using Playwright’s built-in features.

What You’ll Need

  • A Node.js project with Playwright installed (npm init playwright@latest).
  • A basic understanding of writing Playwright test scripts.
  • Access to the web page you want to screenshot.

Step-by-Step

Choose the method that best fits your workflow. All three work with both page.screenshot() and locator.screenshot().

Method 1: Inject CSS via addStyleTag (Simplest)

  1. Open your Playwright script (e.g., test.spec.js).
  2. Before taking the screenshot, navigate to the page and wait for it to load: await page.goto('https://example.com');
  3. Add the following code to inject a style tag that disables animations:
  4. await page.addStyleTag({ content: `
      *, *::before, *::after {
        animation: none !important;
        transition: none !important;
      }
    `});
  5. Take your screenshot as normal: await page.screenshot({ path: 'screenshot.png' });
  6. Run your script. The screenshot will now show elements in their final, static state.

Method 2: Use page.emulateMedia() (For CSS Media Queries)

This method is useful if your animations are triggered by prefers-reduced-motion media queries. It tells the browser the user prefers reduced motion, which many sites respect.

  1. In your script, after navigating to the page, add:
  2. await page.emulateMedia({ reducedMotion: 'reduce' });
  3. Now take your screenshot. This works best when the website itself has implemented @media (prefers-reduced-motion: reduce) rules.
  4. If the site does not respect this setting, combine it with Method 1 for full coverage.

Method 3: Disable Animations via CDP (Chrome DevTools Protocol)

This is the most thorough approach for Chromium-based browsers. It disables animations at the browser level, not just via CSS.

  1. Launch a Chromium browser context with the necessary CDP session:
  2. const browser = await chromium.launch();
    const context = await browser.newContext();
    const page = await context.newPage();
  3. Create a CDP session and enable the Emulation domain:
  4. const cdp = await context.newCDPSession(page);
    await cdp.send('Emulation.setEmulatedMedia', {
      features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
    });
  5. Additionally, you can force the animation to finish by pausing it and then seeking to the end. This is more advanced:
  6. await cdp.send('Animation.enable');
    // Later, before screenshot:
    const animations = await cdp.send('Animation.getCurrentAnimations');
    for (const anim of animations.animations) {
      await cdp.send('Animation.seekAnimations', { animations: [anim.id], currentTime: 10000 });
    }
  7. Proceed with your screenshot. This method is best for complex animations that don’t respond to CSS overrides.

Common Mistakes

  • Only disabling animation, not transition: Many UI elements use CSS transitions for hover states and color changes. Your CSS must target both.
  • Forgetting !important: Some CSS frameworks or inline styles have high specificity. Without !important, your override might be ignored.
  • Taking the screenshot too early: Even with animations disabled, lazy-loaded images or fonts might still be loading. Always wait for the network to be idle: await page.waitForLoadState('networkidle');
  • Using page.addStyleTag after navigation but before rendering: The style tag must be injected before the page paints. If you inject it after a delay, you might still catch a frame mid-animation.

Tips

  • Create a reusable helper function: Wrap the CSS injection in a function like async function disableAnimations(page) { ... } and call it in every test that needs screenshots.
  • Use a global setup: If you need this for all tests, add the code to a beforeEach hook or a global setup file in your Playwright config.
  • Test both states: For visual regression, consider taking one screenshot with animations disabled (for the baseline) and one with them enabled (for the live state) if you want to ensure the final state looks correct.
  • Combine methods: For maximum reliability, use both addStyleTag and emulateMedia together. This covers sites that use either approach.
  • Check for Web Animations API: Some sites use JavaScript animations via the Web Animations API (e.g., element.animate()). The CSS method won’t stop these. For those, use the CDP method (Method 3) or consider mocking the requestAnimationFrame function.

Troubleshooting

Screenshot still shows an animation frame. First, verify that your CSS selector is correct. Use page.evaluate() to check if the style tag was applied: await page.evaluate(() => getComputedStyle(document.body).animationName); should return none. If it doesn’t, your selector might be too specific or the animation is set via a CSS variable. Try the CDP method instead.

The page layout shifts after disabling animations. Some animations (like carousels or sliders) move elements. When you disable them, the elements might revert to their initial position, which could be off-screen. In this case, you might need to wait for the animation to finish naturally (using page.waitForTimeout()) or use the CDP method to seek to the end state.

Animations are disabled but the screenshot is blank. This usually means the page hasn’t finished loading. Ensure you have await page.waitForLoadState('networkidle'); before taking the screenshot. Also, check for JavaScript errors in the console that might prevent rendering.

Method 3 (CDP) throws an error. The Animation domain might not be available in all browser builds. Ensure you are using Chromium (not Firefox or WebKit) for this method. Also, wrap the CDP calls in a try-catch block to avoid failing the entire test if the domain is unavailable.

Frequently Asked Questions

Does this work for all types of animations?

No. CSS animations, transitions, and most requestAnimationFrame loops are covered by the CSS injection method. However, animations driven by WebGL, canvas, or complex JavaScript libraries that directly manipulate pixels may not be affected. The CDP method is more comprehensive but still may not stop GPU-accelerated animations.

Will this affect the layout of the page?

Generally, no. Disabling animations only affects the visual transition between states. The final state of the element (its position, size, color) remains the same. However, if an animation is used to reveal content (like a dropdown), the content might remain hidden if the animation is disabled before it completes.

Can I use this with Playwright’s visual regression testing?

Yes, this is a common practice. By disabling animations, you ensure that your baseline screenshots are stable and do not differ from run to run due to timing. This makes your visual regression tests more reliable.

Is there a performance impact?

Injecting a CSS style tag is very lightweight and has negligible performance impact. The CDP method, especially seeking animations, can be slightly heavier but is still fast enough for testing purposes.

Does this work on mobile emulation?

Yes, all three methods work regardless of the device emulation settings. Just make sure to apply the code after the page is created and before the screenshot.

Can I disable animations only for a specific element?

Yes. Instead of using the universal selector *, target the specific element or class. For example: .my-carousel { animation: none !important; }. This is useful if you want to keep some animations but stabilize others.

Related

General steps for current mainstream versions; menus can differ by device and version. See the disclaimer or report an error.

More in this bed

The whole Tech How-To bed →