How to Reduce Flaky Happo Screenshot Tests Caused by Animations
Learn how Happo and Playwright handle animations in screenshot tests, when to disable or hide changing elements, and how to investigate persistent visual diffs.
To reduce animation-driven screenshot flakes, first identify what is changing, then make the captured state deterministic. Happo says its Playwright integration automatically silences animations and waits for asynchronous assets and fonts. If you use Playwright screenshot assertions directly, set animations: 'disabled' when the reference should show the completed animation state. For other changing content, establish a stable application state or hide only the volatile region with a screenshot stylesheet. Use pixel tolerance only for small rendering noise.
1. Find the source of the changing pixels
Start with the diff, not an arbitrary delay. Determine whether the changed area comes from a CSS animation, CSS transition, Web Animation, or unrelated dynamic content such as a rotating banner, timestamp, embedded frame, or data that changes between runs.
- Open the diff and note the exact region that changes.
- Identify the component and its state at capture time. Check whether the screenshot is intended to show an initial, final, or intermediate state.
- Check the capture route: Happo’s integration, a Playwright
toHaveScreenshotassertion, or another screenshot path may have different controls and defaults. - Make the state repeatable before tuning comparison tolerance.
A delay can move the capture to a different point in an animation rather than make it stable. Prefer an explicit state, such as opening a menu in a known state or waiting for a meaningful selector, over sleeping for a guessed number of milliseconds.
2. Understand Happo’s documented animation behavior
Happo says its Playwright integration automatically silences animations and waits for asynchronous assets and fonts. Its public guidance does not specify an animation-silencing configuration setting or implementation details, so do not add an assumed Happo option to your configuration. If a Happo screenshot still flakes, investigate other dynamic regions, loading state, and whether the intended UI state is deterministic.
Happo also documents color-delta tolerance to account for small visual noise such as image compression and anti-aliasing. Tolerance addresses minor pixel differences; it does not stop an element from moving or make application state repeatable. See Happo’s Playwright visual testing guide.
3. Disable animation for a Playwright screenshot assertion
For Playwright Test, use the screenshot assertion option when the expected image represents the post-animation state:
import { test, expect } from '@playwright/test';
test('captures the stable completed page', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.locator('h1')).toBeVisible();
await expect(page).toHaveScreenshot({ animations: 'disabled' });
});
toHaveScreenshot waits until two consecutive page screenshots produce the same result, then compares the last one with the expected screenshot. This helps with capture stability but does not guarantee that application data or every external resource is stable. The screenshot assertion option animations defaults to 'disabled'; setting it explicitly makes the test’s intent clear and avoids relying on an assumed default in a different capture API.
Playwright’s animations: 'disabled' behavior is specific:
- Finite CSS animations, CSS transitions, and Web Animations are fast-forwarded to completion. A transition may fire
transitionend. - Infinite animations are canceled to their initial state while the screenshot is taken, then played over afterward.
animations: 'allow'leaves animations running, which is useful only when the moving or animated state itself is what the test needs to capture.
If the design reference is the initial frame, disabling animations may produce the wrong frame because finite animations jump to completion. Put the page into the intended state through the app or test setup rather than expecting the screenshot option to preserve an arbitrary animation timestamp.
For API details, see Playwright PageAssertions.
4. Stabilize non-animation content narrowly
When the difference comes from volatile content rather than animation, choose between making that content deterministic and excluding it from the visual assertion. If it matters to the page’s visual contract, control it in the test or application setup. If it is genuinely irrelevant to this screenshot, use a narrowly scoped screenshot stylesheet.
Playwright’s visual comparison guide demonstrates a custom screenshot stylesheet for filtering volatile elements, including hiding iframes. The page screenshot API also accepts inline stylesheet text that can hide or alter dynamic elements, including content in shadow DOM and inner frames. For example, for a locator matched by .changing-ad:
import { test, expect } from '@playwright/test';
test('ignores a volatile region in this visual assertion', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot({
stylePath: './visual-test.css',
animations: 'disabled',
});
});
/* visual-test.css */
.changing-ad {
visibility: hidden !important;
}
Use a selector that targets only the unstable region. Hiding a header, menu, status indicator, or other element whose appearance users rely on can conceal a real regression. See Playwright visual comparisons and the Page screenshot API.
5. Set tolerance only after the state is stable
Color-delta or pixel tolerance can help with small differences from anti-aliasing, compression, or rendering noise. First make the intended visual state repeatable; then choose a tolerance that accepts only noise your project is willing to ignore. A broad threshold can also accept genuine visual regressions, so review the changed region before increasing it.
6. Troubleshoot persistent flakes
| Symptom | Likely cause | What to do |
|---|---|---|
| The animated element differs between runs | The capture path allows the animation to continue, or the expected frame is ambiguous. | For Playwright assertions, set animations: 'disabled' when the final state is intended. Otherwise establish the intended app state explicitly. |
| The screenshot changes even though animation is disabled | The variation may come from data, a timer, a third-party embed, a loading state, or another non-animation region. | Inspect the diff and isolate the region. Wait for a meaningful readiness condition or make its data deterministic. |
| The screenshot shows the completed transition, but the expected image is the starting state | Playwright fast-forwards finite animations when animations are disabled. | Decide which state is part of the assertion and set up that state directly before capture. |
| A Happo screenshot still flakes | Happo’s documented integration behavior silences animations, but other changing content or page state can still vary. | Inspect the volatile pixels and app readiness. Do not assume an undocumented Happo animation setting exists. |
| Increasing tolerance makes the failure disappear | The threshold may be masking meaningful differences along with rendering noise. | Review the diff, stabilize the underlying state, then keep tolerance as narrow as practical. |
| Fonts or images differ or appear late | The page may be captured before those resources are ready in the route being used. | Use the relevant tool’s documented readiness behavior and wait for an application-specific signal where needed. Happo says its Playwright integration waits for asynchronous assets and fonts. |
7. Reliability, runtime, and cost considerations
Disabling animations can make a screenshot deterministic, but it changes the captured frame according to Playwright’s fast-forward and cancellation rules. Choose the reference state intentionally. A screenshot assertion’s wait for consecutive identical captures is useful for visual stability, but it is not a substitute for deterministic application data or explicit readiness conditions.
Repeated arbitrary sleeps add runtime and can still miss the desired state. A selector or app-level readiness condition usually communicates the test’s intent more clearly. Narrow stylesheets reduce unrelated visual noise, but every hidden region is also removed from that assertion’s coverage.
Happo’s and Playwright’s cited documentation does not establish a general runtime or cost figure for a particular suite. Those depend on your project and execution setup; measure your own CI run and keep the screenshot state and comparison policy consistent.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. A single request captures a URL as an image or PDF; for this task, use a representative stable page and remember that a one-shot API capture does not replace a repeatable visual assertion in your test suite. See the ScreenshotNeo API documentation.
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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', bytes));
Cookie and consent banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response reports the page verdict and billing status. AI agents can use the MCP server’s take_screenshot, get_page_info, and capture_pdf tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Should I disable animations for screenshot tests?
Yes, when the expected image should show the completed state. If the initial or another intermediate state matters, put the application into that state deliberately.
Does Happo expose a setting to control animation silencing?
The cited Happo page says its Playwright integration automatically silences animations, but does not document a specific setting for that behavior.
Will screenshot tolerance fix a moving element?
No. Stabilize or narrowly exclude the changing content first; tolerance is for minor rendering differences.
Does Playwright wait until the whole application is deterministic?
No. The screenshot assertion waits for two consecutive identical screenshots, but changing external data or application state may still need explicit setup.


