How to Take a Screenshot After CSS Animations Finish in Playwright
Use Playwright’s `animations: 'disabled'` screenshot option to capture finite CSS animations at their completed state. Learn how it handles infinite animations, elements, and visual tests.
To capture a finite CSS animation after it finishes, pass animations: 'disabled' to Playwright’s screenshot method:
await page.screenshot({ path: 'after-animation.png', animations: 'disabled' });
Playwright fast-forwards finite CSS animations, CSS transitions, and Web Animations to their end state for the capture. This also fires transitionend. Use page.screenshot() for the page or locator.screenshot() for one element. See the official Page screenshot API and Locator screenshot API.
1. Capture a page after finite animations finish
This runnable example uses Playwright Test and TypeScript. Save it as tests/animation-screenshot.spec.ts, replace the URL with your app route, and run it with npx playwright test.
import { test } from '@playwright/test';
test('captures the completed animation state', async ({ page }) => {
await page.goto('http://127.0.0.1:3000/animated-page');
await page.screenshot({
path: 'artifacts/after-animation.png',
animations: 'disabled',
});
});
The destination directory must exist. The option affects the screenshot operation; it does not permanently turn off animations in the page.
2. Capture one animated element
Use a locator screenshot when the full page is unnecessary. Locate the element by a role, label, or other stable selector, then pass the same animation option:
import { test } from '@playwright/test';
test('captures the completed button state', async ({ page }) => {
await page.goto('http://127.0.0.1:3000/animated-page');
const button = page.getByRole('button', { name: 'Submit' });
await button.screenshot({
path: 'artifacts/submit-button.png',
animations: 'disabled',
});
});
This is useful for focused component captures and visual checks where surrounding page content would add noise.
3. Choose how the screenshot treats animation
| Option | Behavior | Use it when |
|---|---|---|
animations: 'disabled' |
Finite animations are fast-forwarded to completion. Infinite animations are canceled to their initial state for the screenshot and replayed afterward. | You want the completed state of finite motion, or a stable capture without ongoing infinite motion. |
animations: 'allow' |
Animations are left running during capture, so the frame can depend on timing. | You intentionally want to capture motion in progress. |
“Disabled” does not mean Playwright waits for an infinite animation to end: an infinite animation has no completion point. The documented behavior for disabled is to cancel it at its initial state for capture, then restart it afterward. If you need a particular intermediate frame or application-specific final state, control that state in your app or test before taking the screenshot.
4. Use the option in a visual regression test
Playwright Test’s toHaveScreenshot() waits until two consecutive screenshots match, then compares the captured image with the stored expectation. Its animation option defaults to disabled; spelling it out makes the intended capture behavior clear:
import { test, expect } from '@playwright/test';
test('matches the completed animation state', async ({ page }) => {
await page.goto('http://127.0.0.1:3000/animated-page');
await expect(page).toHaveScreenshot('finished-state.png', {
animations: 'disabled',
});
});
This assertion is part of the Playwright Test runner. If you are using Playwright without that runner, use page.screenshot() or locator.screenshot() and manage any image comparison separately. See the official PageAssertions API.
5. Troubleshoot unexpected captures
| Symptom | Cause | Fix |
|---|---|---|
| The animation is not at the state you expected. | The animation is infinite, or the desired state is an application-specific frame rather than its finite endpoint. | Set the required state in the app or test before capture. animations: 'disabled' does not wait for an infinite animation to finish. |
| The screenshot still differs between runs. | Other page content may still be changing, such as data, timers, or asynchronous rendering. | Wait for the relevant app state or selector before capture. For visual assertions, toHaveScreenshot() waits for two consecutive matching screenshots, but the page still needs predictable content. |
| The element screenshot fails or captures the wrong area. | The locator may match the wrong element, or the target may not yet be present in the expected state. | Use a specific locator, check that it identifies the intended element, and wait for the relevant state before calling screenshot(). |
| The output file cannot be written. | The path’s parent directory may not exist or may not be writable. | Create the directory first or use a writable path. |
animations is rejected or unavailable. |
The installed Playwright version or API usage may not match the current documentation. | Check the project’s installed Playwright version and the API for the method you are calling. The release notes list screenshot animation options in version 1.20; consult the release notes and current API docs rather than assuming behavior from an old install. |
6. Reliability, speed, and cost considerations
- Reliability: Disabling finite animations removes animation timing as a source of screenshot variation. It does not freeze other dynamic page behavior, so stabilize data and app state as needed.
- Capture scope: Prefer a locator screenshot when only one component matters; use a page screenshot when the whole page is part of the check.
- Performance: The option fast-forwards finite animation effects for capture rather than requiring you to sleep for their full duration. Avoid fixed delays as a substitute for controlling the target state.
- Cost: Playwright is the browser automation method shown here. This article makes no claim about infrastructure costs for running your own browser tests; those depend on where and how you run them.
7. Or skip the browser setup
If you need a website screenshot without managing a browser test environment, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. The call below captures a page as WebP; see the ScreenshotNeo API documentation for parameters and configuration.
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}`);
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Learn more and sign up for 1,000 free screenshots a month, no card required.
8. Frequently asked questions
Does this work for CSS transitions as well as keyframe animations?
Yes. The screenshot option applies to CSS animations, CSS transitions, and Web Animations.
Does Playwright leave animations disabled after the screenshot?
The option controls capture behavior. Infinite animations canceled for the screenshot are replayed afterward.
Should I use a fixed timeout before taking the screenshot?
Use the animation option for finite animations. For app-specific states, wait for a meaningful condition or set the state directly instead of relying on a guessed delay.


