ScreenshotNeo

BlogHow-to

How to Screenshot a Webpage with SVG Animations Paused in Playwright

Pause inline SVG SMIL animations and use Playwright’s screenshot options to capture a stable frame. Includes runnable JavaScript and Python examples.

By the ScreenshotNeo team4 October 20267 min read

To screenshot a webpage with inline SVG animations paused in Playwright, call pauseAnimations() on each SVG root in the page, then take the screenshot. Also set Playwright’s animations: 'disabled' option to handle CSS animations, CSS transitions, and Web Animations.

await page.evaluate(() => {
  document.querySelectorAll('svg').forEach(svg => svg.pauseAnimations());
});
await page.screenshot({ path: 'page.png', animations: 'disabled' });

The two controls address different animation mechanisms. Playwright documents animations: 'disabled' for CSS animations, CSS transitions, and Web Animations. SVG’s pauseAnimations() freezes the SVG document fragment’s animation clock at its current position. Neither call guarantees a particular frame unless you first arrange the application or animation state you want.

1. Why SVG animations need a separate pause

SVG can animate declaratively with SMIL elements such as <animate>, <animateMotion>, and <animateTransform>. A reported Playwright issue reproduced inline SVG SMIL continuing during a locator screenshot with animations: 'disabled' in Playwright 1.41.1. That report is evidence for the separate pause call when SMIL matters; it does not establish behavior for every later Playwright version or browser engine.

For SVG, pauseAnimations() stops the animation clock at its current position. It does not rewind to time zero or select a canonical frame. Playwright’s disabled mode has other semantics: finite CSS/Web Animations are fast-forwarded to completion, while infinite ones are canceled to their initial state for the screenshot and resumed afterward.

Mechanism Control Frame behavior Scope
CSS animations, transitions, Web Animations animations: 'disabled' Finite animations fast-forward; infinite animations are canceled to their initial state for capture Playwright screenshot operation
SVG SMIL in inline SVG svg.pauseAnimations() Freezes the current SVG animation time SVG elements found in the page DOM
Reduced motion preference reducedMotion: 'reduce' Emulates a media preference; not documented as pausing the SVG clock Page context media feature

2. JavaScript: complete Playwright example

This runnable Node.js example starts Chromium, opens a page, pauses inline SVG animation clocks, disables the animation classes supported by the screenshot option, and saves a PNG. Install Playwright and its browser first using the official Playwright installation guide.

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

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });

  try {
    await page.goto('https://example.com', { waitUntil: 'load' });

    // Pause SMIL animation clocks on SVG roots present in the DOM.
    await page.evaluate(() => {
      document.querySelectorAll('svg').forEach((svg) => {
        if (typeof svg.pauseAnimations === 'function') {
          svg.pauseAnimations();
        }
      });
    });

    await page.screenshot({
      path: 'page.png',
      fullPage: true,
      animations: 'disabled',
    });
  } finally {
    await browser.close();
  }
})();

Replace https://example.com with the page you control or need to capture. If the SVG is inserted after navigation, wait for the relevant element before evaluating the pause script. For example, use await page.locator('svg').first().waitFor() when at least one inline SVG is expected.

3. Python: complete async example

The Python async API uses the same page-side SVG pause. Install Playwright and its browser according to the official Python installation guide.

import asyncio
from pathlib import Path
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": 1440, "height": 1000})
        try:
            await page.goto("https://example.com", wait_until="load")
            await page.evaluate("""() => {
                document.querySelectorAll('svg').forEach(svg => {
                    if (typeof svg.pauseAnimations === 'function') {
                        svg.pauseAnimations();
                    }
                });
            }""")
            await page.screenshot(
                path="page.png",
                full_page=True,
                animations="disabled",
            )
        finally:
            await browser.close()

asyncio.run(main())

For a locator screenshot, run the same page.evaluate() first, then call await page.locator('selector').screenshot(path='element.png', animations='disabled'). The page-side pause is needed before capturing the target locator if it contains inline SVG SMIL.

4. Choose a deliberate frame when repeatability matters

Pausing freezes the current SVG clock position, which can vary with load timing and runtime. If a visual test needs a specific frame, make the application state deterministic before pausing. For example, configure the page or test fixture to render a known state, wait for that state to appear, and then pause the SVG clocks immediately before capture. Do not assume that pause means “frame zero.”

For CSS and Web Animations, note that animations: 'disabled' does not mean “freeze whatever is on screen now”: finite animations are fast-forwarded and infinite animations are canceled to their initial state for capture. If the desired image depends on an intermediate point in one of those animations, drive the app into a stable state or control the animation through application code before taking the screenshot.

reducedMotion: 'reduce' is useful for testing the page’s response to the prefers-reduced-motion media feature, but it is not a documented general-purpose pause switch for SVG animation clocks. Use the explicit SVG pause call for inline SVG SMIL where needed.

5. Coverage limits and edge cases

  • Inline SVG: the loop finds SVG roots present in the page DOM and pauses each one that exposes pauseAnimations().
  • Late-rendered SVG: wait for the app’s render condition or SVG locator before evaluating the pause script. If the app replaces the SVG after the pause, pause the replacement too.
  • External SVG image: an SVG loaded through an <img> or another external resource is not demonstrated by this page-DOM loop as controllable. Consider loading it inline or use an application-specific capture strategy.
  • JavaScript-driven motion: scripts may update SVG attributes independently of the SVG animation clock. Pause or stabilize the page’s own animation logic as appropriate.
  • Cross-origin frames: the main page’s query does not reach into a separate frame’s document. Run the pause in the relevant frame context when permitted by the page and test setup.
  • No SVG animation: calling the method on a non-animated SVG is harmless; the feature check avoids errors where the method is unavailable.

6. Troubleshooting

Symptom Likely cause Fix
The SVG still moves in the screenshot The SVG root was not present when the pause ran, or JavaScript is changing it independently Wait for the rendered SVG, pause after rendering, and inspect app-driven updates separately.
pauseAnimations is not a function The selected node is not an SVG root, or the page context exposes a different element Select the actual <svg> root and keep the method feature check.
The captured frame varies between runs The SVG clock was paused at a timing-dependent current position Arrange a known app or animation state, wait for it, then pause immediately before capture.
CSS motion still appears The screenshot call omitted Playwright’s animation option or used a different capture path Pass animations: 'disabled' to the page or locator screenshot call.
The SVG in an image resource keeps moving The inline DOM query does not operate on an external SVG resource as a page SVG root Use an inline version or a capture method designed for that resource.
Reduced motion did not freeze the SVG Reduced motion emulates a media preference, not the documented SVG pause operation Call pauseAnimations() on inline SVG roots.

7. Performance, reliability, and cost

The pause loop runs a short DOM query and calls a method on each matching SVG root; its work grows with the number of SVG elements on the page. For ordinary pages, the main reliability concern is timing and state control rather than the loop itself. Wait for the actual content you need, pause after it has rendered, and use the same browser engine and controlled application state in repeatable screenshot workflows.

Playwright’s animation handling and SVG clock pausing do not guarantee identical rendering across operating systems, fonts, browser versions, or application data. Pin the browser and dependencies in visual regression environments and avoid treating the dated Playwright 1.41.1 issue as a statement about every current release. This method has no ScreenshotNeo API charge; its operational cost is the browser runtime and maintenance of your capture environment.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. The browser method above is appropriate when you need direct control over Playwright and SVG animation timing. For a one-call capture, use the API; see the ScreenshotNeo API docs for options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie banners, popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. The API provides PNG, JPEG, WebP, or PDF output and supports capture options, but use Playwright’s explicit SVG pause when you need to control a particular inline SVG animation frame.

Sign up free for 1,000 screenshots a month, with no card required.

9. FAQ

Does animations: 'disabled' pause every kind of animation?

No. Playwright documents it for CSS animations, CSS transitions, and Web Animations. Add the SVG pause call for inline SVG SMIL when needed.

Does pauseAnimations() reset SVG to the beginning?

No. It freezes the SVG document fragment’s animation clock at its current position.

Should I set reduced motion too?

Only when you want to test the page’s reduced-motion media preference. It is not a substitute for pausing an SVG clock.

Can the exact same frame be guaranteed across runs?

Not by pausing alone. Establish a known page and animation state first, then pause before capture.

Sources