How to prevent blinking cursors from triggering screenshot change alerts
Hide the text caret in Playwright screenshot assertions to stop blink-only diffs. See options for Cypress and other visual testing tools.
For Playwright Test, set caret: "hide" on the screenshot assertion:
await expect(page).toHaveScreenshot({ caret: "hide" });
This hides the text insertion caret during capture, so its blink does not create a screenshot difference. Playwright already defaults this option to "hide"; setting it explicitly makes the test’s intent clear. If the alert persists, check whether it comes from a different capture path, an integration that compares screenshots separately, or another dynamic part of the page.
Why a blinking caret causes screenshot alerts
A text caret alternates between visible and invisible states while an input, textarea, or editable region has focus. If a screenshot is captured in different phases of that blink cycle, the two images can differ by a small region even when the page has not meaningfully changed. A visual regression system may then report a change.
Screenshot stabilization and caret suppression solve different problems. Playwright’s screenshot assertion waits for two consecutive page screenshots to be identical, then compares the last capture with the expected snapshot. That helps with transient rendering, but explicitly hiding the caret is the direct control for caret variation. See the Playwright PageAssertions API.
Playwright Test: hide the caret in screenshot assertions
Minimal fix
import { test, expect } from "@playwright/test";
test("profile form matches its visual baseline", async ({ page }) => {
await page.goto("https://example.com/profile");
await page.getByLabel("Display name").fill("Ada Lovelace");
await expect(page).toHaveScreenshot({ caret: "hide" });
});
Replace the example URL and locator with those from your app. The caret option accepts "hide" or "initial". "hide" hides the caret in the screenshot; "initial" leaves its behavior unchanged. The documented default for toHaveScreenshot is "hide". If your test already uses this assertion and still reports changes, verify the installed Playwright version and the actual screenshot call that produces the alert.
Apply it to a specific screenshot assertion
Pass the option where you compare the page or a locator. For a focused comparison of one element:
await expect(page.getByTestId("editor")).toHaveScreenshot({
caret: "hide",
});
Use the page assertion when the relevant visual state spans the page, and the locator assertion when the test only needs to protect one component. In either case, hide the caret while preserving the rest of the input’s appearance, including its value, border, focus outline, and surrounding layout.
When you need the caret visible
Some tests intentionally verify a focused editor or cursor state. In that case, caret: "initial" leaves normal caret behavior unchanged. A blink-dependent expected image is inherently timing-sensitive, so consider whether the test can assert focus or editor behavior separately and keep the screenshot comparison focused on stable visual output.
Other screenshot instability controls in Playwright
Use these controls for other sources of variation; they are not substitutes for the caret option when the unwanted pixels are the text caret.
| Control | Use it for | Tradeoff or behavior |
|---|---|---|
animations: "disabled" |
CSS animations, CSS transitions, and Web Animations that make the page vary. | Finite animations are fast-forwarded to completion and fire transitionend. Infinite animations are canceled to their initial state and played over after capture. |
mask |
Uncontrolled regions whose changing content is irrelevant to the screenshot. | Mask only the smallest relevant locator; a large mask can hide real regressions. |
style or stylePath |
Screenshot-only CSS changes, such as hiding a known volatile region. | Confirm that the injected style targets only content you intend to suppress. |
| Screenshot thresholds | Small pixel differences that are expected for a particular comparison. | Broad tolerance can allow real visual changes through. Prefer a targeted caret or region control when it addresses the cause. |
Playwright documents screenshot-only styles that can hide dynamic elements and affect Shadow DOM and inner frames. For example, if a screenshot tool lacks a native caret option, this CSS can hide carets in common editable controls:
input, textarea, [contenteditable] {
caret-color: transparent !important;
}
This is a fallback suggestion, not a Playwright-specific caret recipe. Apply it only at screenshot time, and only if hiding all carets in those controls is acceptable for the visual states being tested. Prefer caret: "hide" when the assertion supports it. See the screenshot assertion options for the available masking, style, animation, and comparison controls.
Cypress and other visual testing integrations
Cypress’s cy.screenshot() captures an image; Cypress does not perform image comparison itself. A plugin or visual testing integration handles the comparison and may provide its own caret setting, masking, or screenshot-time CSS controls. Check that integration’s documentation for the capture options it actually supports. Cypress discusses this distinction in its visual testing documentation.
If there is no caret-specific setting, use a supported screenshot-time style or mask, scoped to the relevant editable area. Keep the rendering environment and test data stable as well: otherwise a caret fix can remove one source of difference while unrelated dynamic content continues to trigger alerts.
- Identify which command or integration captures and compares the image.
- Check whether it has a native caret suppression option.
- If not, apply a narrow screenshot-time CSS rule or mask, if supported.
- Keep focus and other meaningful editor states covered by separate assertions where needed.
Choosing the right fix
| Approach | Scope | Good fit |
|---|---|---|
| Native caret suppression | Text caret during capture | The alert is caused by a blinking insertion point and the screenshot API supports a caret option. |
| Screenshot-time CSS | Elements matched by a CSS rule | The capture tool has no native caret option and can inject styles at screenshot time. |
| Masking | A locator or visual region | A region is uncontrolled and its pixels are not part of the assertion’s purpose. |
| Threshold adjustment | Pixel differences across the comparison | A small amount of noise is acceptable and the sensitivity impact is understood. |
This scope guidance follows from the tools’ documented controls: a targeted treatment preserves more of the screenshot for detecting real changes than a broad mask or permissive threshold.
Troubleshooting
The alert remains after setting caret: "hide"
- Cause: The alerting capture is made by a different API or integration. Fix: Find the code path that creates the compared image and configure caret handling there.
- Cause: The changing pixels are a blinking cursor drawn by a custom editor, not the browser text caret. Fix: Use that editor or integration’s supported screenshot style or mask for its cursor, keeping the scope limited.
- Cause: The difference is another changing element. Fix: Inspect the diff image and stabilize or mask only the unrelated dynamic region.
The screenshot hides a caret the test needs to show
Cause: The assertion uses caret: "hide", explicitly or through the default. Fix: Use caret: "initial" for that assertion, or verify caret behavior with a dedicated interaction test rather than a blink-sensitive visual baseline.
CSS does not affect the captured image
Cause: The CSS is applied to the page at the wrong time, or the capture integration does not support screenshot-time styles. Fix: Check the tool’s documented injection mechanism and confirm the selector matches the actual editable element. Use the native option when available.
A broad mask or threshold stops useful alerts
Cause: The suppression covers more pixels than the caret or accepts too much difference. Fix: Narrow the locator or style rule, or restore the prior threshold and use caret-specific suppression.
Performance, reliability, and maintenance
Hiding the caret is a screenshot-time rendering option; it does not require changing the application’s production CSS. Its practical benefit is fewer alerts caused solely by caret phase. It does not make other dynamic content stable, so continue to control data, timing, animations, and environment as appropriate for the comparison.
For reliability, make the intended option explicit in assertions that matter, use the same browser and rendering setup for baseline and comparison runs, and avoid depending on a particular blink phase. Revisit tool documentation when upgrading: screenshot API options and integration behavior can vary by installed version. No benchmark or quantified reduction in alerts is implied by this fix.
Or skip the browser setup
If you need a screenshot from a URL rather than a local visual regression assertion, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns an image or PDF. This does not replace a baseline comparison in Playwright or Cypress, but it can remove common page noise from URL captures.
Use the ScreenshotNeo API docs for the request options. Example with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The Python and Node.js equivalents are:
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. 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 a month with no card; paid plans start at $5 for 3,000.
Get 1,000 free screenshots a month with no card.
FAQ
Does Playwright already hide the caret by default?
Yes. The documented default for toHaveScreenshot is caret: "hide". Setting it explicitly can make test intent easier to see.
Does waiting for stable screenshots solve caret blinking?
Playwright waits for two consecutive identical screenshots before comparing, but caret suppression is the direct option for preventing the text caret from appearing in the capture.
Does Cypress have a built-in visual diff assertion?
No. Cypress captures screenshots; a plugin or integration supplies image comparison and its associated controls.
Should I increase the screenshot diff threshold?
Only if the comparison should tolerate the additional differences. For a blinking text caret, first use a targeted caret option or narrow screenshot-time treatment.


