How to Capture a Screenshot of a Page with a CSS Animation Paused in Selenium
Pause animations through Selenium’s JavaScript execution, then capture the page. This guide covers runnable examples, timing, caveats, and fixes.
To capture a Selenium screenshot with animations paused, run document.getAnimations().forEach(animation => animation.pause()) in the page, then call your Selenium binding’s screenshot method. This pauses animations at their current time; it does not choose a particular keyframe. Wait for the page to reach the state you want before pausing, and take the screenshot promptly afterward.
Python: pause animations and save a screenshot
This example uses Selenium’s Python binding. It assumes you have a working WebDriver and have navigated to the target page.
from selenium import webdriver
options = webdriver.ChromeOptions()
# Uncomment to run without opening a visible browser window.
# options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com")
# Wait for your page-specific readiness condition here, if needed.
driver.execute_script(
"document.getAnimations().forEach(animation => animation.pause());"
)
driver.save_screenshot("page.png")
finally:
driver.quit()
The readiness condition is application-specific. For a dynamic page, wait for the relevant element or content to appear before pausing. The code above is a general recipe, not a guarantee that every site has finished rendering when navigation returns.
What the pause script does
document.getAnimations() returns animations associated with descendants of the document that are in effect. This includes CSS Animations, CSS Transitions, and Web Animations. Calling pause() suspends each at its current animation time. It does not seek to the beginning, end, or a named keyframe. See the [MDN documentation for getAnimations()](https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations), [Animation.pause()](https://developer.mozilla.org/en-US/docs/Web/API/Animation/pause), and the [Web Animations API overview](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
Because the pause preserves the current point in playback, two runs can capture different frames if the page reaches the pause operation at different times. For a precisely predetermined frame, the page or test needs a controlled animation-time setup or a test mode designed for deterministic rendering.
Use the screenshot method for your Selenium language
Run the same JavaScript pause operation through the binding’s script execution API, then use its screenshot method.
Python
driver.execute_script("document.getAnimations().forEach(animation => animation.pause());")
driver.save_screenshot("page.png")
JavaScript
Selenium’s JavaScript API returns a Base64 PNG string from takeScreenshot(). Decode it to write the image file:
const { Builder } = require('selenium-webdriver');
const fs = require('node:fs');
(async () => {
const driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('https://example.com');
await driver.executeScript(
'document.getAnimations().forEach(animation => animation.pause());'
);
const base64 = await driver.takeScreenshot();
fs.writeFileSync('page.png', Buffer.from(base64, 'base64'));
} finally {
await driver.quit();
}
})();
Java
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.com");
((org.openqa.selenium.JavascriptExecutor) driver).executeScript(
"document.getAnimations().forEach(animation => animation.pause());"
);
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
Files.copy(screenshot.toPath(), new File("page.png").toPath(),
StandardCopyOption.REPLACE_EXISTING);
} finally {
driver.quit();
}
See the [Selenium screenshot documentation](https://www.selenium.dev/documentation/webdriver/interactions/screenshots/) and your binding’s API reference for the method available in your Selenium version.
Choose the right timing
- Navigate to the page.
- Wait for the specific content and visual state you need. Use a page-specific explicit wait for dynamic content instead of relying on a fixed delay where possible.
- Execute the pause script.
- Capture the screenshot immediately after the script completes.
If you pause too early, the screenshot may show a loading state or an incomplete layout. If you pause too late, the animation may already have advanced to a different frame. A delay can help when the desired state appears after a known interval, but delays alone are fragile when network or rendering time varies.
Important edge cases
- Unresolved animation time:
Animation.pause()can throwInvalidStateErrorwhen the current time is unresolved and the animation’s end time is positive infinity. If this occurs, identify the animation and handle that page-specific case rather than assuming every animation can be paused uniformly. - Shadow DOM: The documented call is scoped to animations associated with document descendants. Pages that place animated content in shadow roots need page-specific verification; universal coverage of open and closed shadow trees is not established here.
- Exact frame requirements: Pausing freezes the current time. It does not provide a cross-run, pixel-identical frame guarantee. Use a controlled application or test setup when a particular frame is required.
- Other motion: The API covers more than CSS
animationdeclarations: it also covers CSS transitions and Web Animations in effect on document descendants. - Capture scope: The pause script affects animations in the page context where it runs. If your workflow captures another frame, tab, or browsing context, run the script in that context too.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| The screenshot still appears animated or shows an unexpected frame | The screenshot captured a different moment than intended, or the visible motion is not covered by the animations returned in that page context. | Wait for the intended UI state first, run the pause script immediately before capture, and inspect the page-specific animation setup. |
InvalidStateError while pausing |
An animation has unresolved current time and an infinite end time. | Identify the problematic animation and account for its state in a page-specific test setup. Do not assume a blanket pause loop can handle every possible animation state. |
| The screenshot shows a loading screen or missing content | The page was captured before its relevant content was ready. | Add an explicit wait for the element or condition that indicates the page is ready, then pause and capture. |
| The pause script has no effect on an element inside a shadow root | The document-level query may not cover the animation as expected in that page’s shadow DOM structure. | Verify the page’s shadow-root behavior and use a page-specific approach where required. |
| The screenshot file is missing or cannot be opened | The binding-specific screenshot result may have been returned in memory, as Base64, or as a temporary file rather than saved at the path you expected. | Use the screenshot API for your language binding and explicitly write or copy its result to the desired path. |
Performance, reliability, and cost
The pause operation is a short browser-side JavaScript call followed by the screenshot capture. Its main reliability concern is timing: page readiness and animation progress vary by site and run. Prefer an explicit readiness condition and capture directly after pausing. A fixed sleep can add unnecessary waiting and still fail to select the same frame.
For repeated captures, keep the page state and browser configuration controlled where possible, and treat exact visual output as something to verify in your own browser and site. This method uses Selenium and a browser you operate; its costs depend on your browser and execution environment. The supplied documentation does not establish a universal capture-time benchmark or cross-browser pixel identity.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. A single GET request captures a URL; see the API documentation for request options. ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides screenshot tools for AI agents and MCP clients.
For a straightforward capture, request an image from the API:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo offers 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 screenshots. Learn about ScreenshotNeo, then sign up for 1,000 free screenshots a month, no card required.
FAQ
Does this pause CSS transitions too?
Yes. The document animation collection includes CSS Transitions as well as CSS Animations and Web Animations in effect on document descendants.
Does pausing reset an animation?
No. It suspends playback at the current time; it does not rewind or select a keyframe.
Will this make screenshots identical across runs?
Not by itself. The result depends on when the page is ready and when the pause takes effect. A deterministic frame needs a controlled page or test setup.


