ScreenshotNeo

BlogHow-to

How to Hide Dynamic Content in Playwright Screenshot Tests

Stabilize Playwright visual tests by masking or hiding volatile page content, controlling animation, and keeping baselines in a consistent environment.

By the ScreenshotNeo team4 October 20268 min read

To keep changing page content from making a Playwright screenshot assertion fail, either pass a locator in the assertion’s mask option or apply capture-only CSS with stylePath. Use mask when a colored block over the changing region is acceptable; use CSS when the content should be hidden or normalized. Disable animation when motion causes variation, and generate and compare baselines in the same browser and host environment.

This guide uses Playwright Test’s expect(page).toHaveScreenshot(). Its screenshot assertions retry captures until two consecutive screenshots match, then compare the result with the stored snapshot. That can settle capture-time changes, but it does not make live content deterministic or eliminate differences between rendering environments. Playwright visual comparisons and the PageAssertions API document these behaviors.

1. Mask a volatile element with a locator

Use mask for a region whose appearance is irrelevant to the assertion, such as a live timestamp, rotating avatar, or personalized recommendation tile. The locator identifies the element; Playwright covers its bounding box in the screenshot. By default, the cover is pink (#FF00FF); set maskColor to change it.

import { test, expect } from '@playwright/test';

test('dashboard layout stays stable', async ({ page }) => {
  await page.goto('https://example.com/dashboard');

  await expect(page).toHaveScreenshot('dashboard.png', {
    mask: [page.getByTestId('live-timestamp')],
    maskColor: '#808080',
  });
});

The locator should be specific and stable. Prefer a test ID or a semantic locator that identifies only the volatile area. A broad locator can hide a real layout regression along with the changing data. Masks also apply to matched invisible elements, so verify that the locator matches only the intended target.

Mask multiple regions

Pass an array of locators when several independent regions vary. Keep each one as small as practical:

await expect(page).toHaveScreenshot('dashboard.png', {
  mask: [
    page.getByTestId('live-timestamp'),
    page.getByTestId('profile-avatar'),
    page.getByTestId('market-price'),
  ],
  maskColor: '#808080',
});

Masking replaces the region with an overlay; it does not remove the element or its layout box. If the box’s dimensions or position change, those changes can still affect the screenshot around it. If you want the region to disappear while retaining its layout space, use visibility: hidden in a capture stylesheet instead.

2. Hide or normalize content with capture-only CSS

Use stylePath when you want the screenshot to omit volatile content rather than display a colored mask, or when a CSS rule can normalize its appearance. It accepts one stylesheet path or an array of paths. For page screenshot assertions, this stylesheet is applied during capture, pierces Shadow DOM, and applies to inner frames.

Create screenshot.css alongside the test:

[data-testid="live-timestamp"] {
  visibility: hidden !important;
}

[data-testid="rotating-promo"] {
  display: none !important;
}

Then pass the path to the assertion:

import { test, expect } from '@playwright/test';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));

test('dashboard screenshot ignores volatile content', async ({ page }) => {
  await page.goto('https://example.com/dashboard');

  await expect(page).toHaveScreenshot('dashboard.png', {
    stylePath: path.join(here, 'screenshot.css'),
  });
});

visibility: hidden keeps the element’s layout space, while display: none removes it from layout and can shift surrounding content. CSS can also normalize a changing visual property, but avoid rules that suppress the behavior or region the test is meant to verify. Review the resulting screenshot and diff after introducing a rule.

Page assertion and locator screenshot APIs differ

For expect(page).toHaveScreenshot(), use stylePath. Locator screenshot capture has a related style option that takes stylesheet text. Do not pass style to the page assertion or assume stylePath is the locator screenshot option. See the official page assertion API and element screenshot API.

3. Disable animation when motion causes variation

For page screenshot assertions, animations are disabled by default. Setting the option explicitly makes the intent clear and protects against changes in the assertion setup:

await expect(page).toHaveScreenshot('dashboard.png', {
  animations: 'disabled',
});

With animations disabled, finite animations are fast-forwarded to completion and fire transitionend; infinite animations are canceled to their initial state during capture, then played again afterward. Standalone locator screenshot capture documents allow as its default, so set animations: 'disabled' explicitly there if motion matters.

Animation control does not stabilize arbitrary live data. A clock, changing price, random identifier, or asynchronously refreshed message can still vary when rendered. Mask or style the specific volatile region as well.

4. Choose the right method for the change

Situation Use What the screenshot shows
Known element changes, and a solid cover is acceptable mask with a precise locator A colored rectangle over the element’s bounding box
Content should be invisible but preserve its layout space stylePath with visibility: hidden The element’s space remains, but its contents are hidden
Content and its layout space should be removed stylePath with display: none Surrounding layout may shift
Only animation or transition frames vary animations: 'disabled' The capture uses Playwright’s disabled-animation behavior
Rendering differs across CI and local runs Use a consistent rendering environment Baselines and test captures share browser and host conditions

Often the most robust setup combines a narrow mask or stylesheet with disabled animation. Do not use a larger mask or more permissive pixel threshold as a substitute for identifying the source of variation.

5. Keep baseline generation and comparison consistent

Screenshot rendering can vary with operating system, browser version, browser settings, hardware, power source, and headless mode. Generate baselines and run comparisons in the same environment wherever possible. If local and CI rendering environments differ, treat their baselines as separate environments rather than repeatedly updating snapshots to accommodate unexplained differences. Playwright’s visual comparison guide describes these sources of variation.

After changing a mask, stylesheet, or environment, inspect the actual screenshot and diff. A snapshot update changes the expected image; it does not prove that the visual change is intended. Keep screenshot assertions focused on stable, meaningful page regions.

6. Troubleshooting

Symptom Likely cause Fix
The assertion still fails around a masked element The locator matched the wrong node, matched multiple unexpected regions, or the element’s box/layout changed. Inspect the locator and screenshot; make it more specific. If the changing geometry itself is irrelevant, consider a stylesheet rule and verify the resulting layout.
A pink rectangle appears in the baseline This is the default mask overlay. Set maskColor to a different CSS color, or use capture-only CSS if the content should be hidden instead.
CSS has no effect on the screenshot The path may be wrong, or the wrong option was used for the capture API. Resolve the stylesheet path from the test file location and use stylePath for toHaveScreenshot. Locator screenshot capture uses style text.
The page shifts after hiding a region display: none removes the element from layout. Use visibility: hidden to preserve its space, or mask its bounding box.
The screenshot changes despite disabled animations Live data, random content, asynchronous updates, or rendering-environment differences remain. Mask or normalize the changing element; keep baseline and comparison environments aligned.
Only CI differs from the local baseline OS, browser build, headless mode, fonts, settings, or hardware can alter rendering. Generate and compare snapshots in the same CI image/browser configuration and review changes before updating the baseline.
The locator screenshot still contains motion Locator screenshot capture allows animations by default. Set animations: 'disabled' explicitly on that screenshot call.
A mask hides a real regression The masked region is too broad or contains behavior that should be asserted. Narrow the locator. Add a separate assertion for the dynamic element’s meaningful behavior, such as its presence or accessible name.

7. Performance, reliability, and maintenance

  • Performance: Masking and applying a small capture stylesheet avoid adding a separate wait loop or changing application data. Large or complex CSS rules can affect what is rendered, so keep the stylesheet focused. Screenshot assertions may take multiple captures while waiting for two consecutive images to match.
  • Reliability: A stable locator and narrow rule make it easier to see genuine layout regressions. Animation control addresses motion; it does not replace deterministic test data or a consistent browser environment.
  • Maintenance: Keep screenshot-only styles near the test or shared screenshot configuration. When a selector changes in the application, revisit the mask/style rule and inspect the new baseline.
  • Cost: Playwright’s documented approach uses the test runner and browser environment already used for visual assertions. No separate screenshot API is required for this fix. CI resource and execution costs depend on your own infrastructure; the cited Playwright documentation does not specify a cost benchmark for these techniques.

Or skip the browser setup

If you need rendered screenshots outside a Playwright test, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF; see the ScreenshotNeo API documentation for its request options.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com/dashboard \
  -o dashboard.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/dashboard"},
    timeout=90,
)
open("dashboard.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/dashboard',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('dashboard.webp', res);
  • Cookie banners are accepted and removed before capture; newsletter popups and chat widgets are removed, and each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Can I mask an element without using a test ID?

Yes. Any Playwright locator can be used. Choose a semantic or structural locator that identifies only the changing region and remains stable as the page evolves.

Does masking remove the element from the page?

No. It covers the matched element’s bounding box in the screenshot. Use screenshot-only CSS when you want to hide or alter rendered content.

Should I hide a changing element or assert it separately?

If its changing value is irrelevant to visual appearance, hide or mask it in the screenshot and add a separate functional assertion when its presence or meaning matters.

Do page screenshot assertions work outside Playwright Test?

toHaveScreenshot() is a Playwright Test assertion. For a standalone capture, use the relevant screenshot API and its documented options; locator screenshot capture uses a style string for capture CSS.

Primary references: Playwright PageAssertions API, Playwright visual comparisons, and Playwright element screenshot API.