ScreenshotNeo

BlogHow-to

How to Mask Elements in Playwright Screenshot Tests

Mask dynamic elements in Playwright screenshots with Locators, choose a mask or stylesheet, and fix common visual test failures.

By the ScreenshotNeo team4 October 20267 min read

Pass a Playwright Locator in the screenshot call’s mask array. For a visual regression assertion, use expect(page).toHaveScreenshot(); for a standalone image, use page.screenshot(). By default Playwright covers each matching element’s bounding box with pink (#FF00FF); set maskColor to choose another CSS color.

1. Mask an element in a screenshot assertion

This runnable Playwright Test example masks a test fixture’s changing content and compares the result with a stored snapshot. It assumes a Playwright Test project is already configured and the page at /dashboard contains an element with data-testid="live-metric".

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

test('dashboard snapshot ignores the live metric', async ({ page }) => {
  await page.goto('/dashboard');

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

Run it with npx playwright test. On the first run, Playwright creates the expected snapshot; review and commit that baseline using your team’s normal snapshot workflow. Later runs compare new captures with the baseline. The toHaveScreenshot assertion retries capture until two consecutive screenshots match, then compares the final capture with the expected image. [PageAssertions API]

2. Mask a standalone page or element screenshot

The same Locator-based option works for direct screenshots. These calls save a file without making a visual assertion.

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

test('save a masked page screenshot', async ({ page }) => {
  await page.goto('/dashboard');

  await page.screenshot({
    path: 'artifacts/dashboard.png',
    fullPage: true,
    mask: [page.getByTestId('live-metric')],
    maskColor: '#808080',
  });
});

To capture one component, call screenshot() on its Locator and provide the mask option there:

const card = page.getByTestId('account-card');
await card.screenshot({
  path: 'artifacts/account-card.png',
  mask: [page.getByTestId('live-metric')],
  maskColor: '#808080',
});

Page, Locator, and screenshot assertion APIs document masking. See the Page API and Screenshots guide for the installed version’s full options.

3. Select the right target

mask accepts an array of Locators. Prefer a locator that identifies the volatile element precisely, such as a test ID or a role and accessible name. Avoid broad selectors that might cover stable content too.

await expect(page).toHaveScreenshot({
  mask: [
    page.getByTestId('live-metric'),
    page.getByRole('img', { name: 'Rotating promotion' }),
  ],
});
Locator strategy Example Use when
Test ID page.getByTestId('live-metric') The application exposes a stable test hook for the specific region.
Accessible role and name page.getByRole('img', { name: 'Rotating promotion' }) The element has a useful accessible role and name.
CSS selector page.locator('.live-metric') A CSS class or selector reliably identifies only the intended content.

Use one Locator per region you want covered. When a Locator matches multiple elements, Playwright applies the mask to matching elements; narrow the selector if that is not intended. The API documents that invisible matching elements are also masked. If that behavior is undesirable, use a locator that matches only visible elements, as shown in the Page API.

4. Choose between a mask and screenshot CSS

A mask paints over the target’s bounding box and leaves its layout space in place. Use it when the element’s dimensions and surrounding layout should remain represented, but its changing pixels should not affect the image. Use screenshot CSS when you want to hide or alter the content instead.

Approach Screenshot effect Good fit
mask and optional maskColor Solid overlay over each matched element’s bounding box Suppress volatile pixels while retaining the region’s space and page layout.
style or stylePath CSS can hide or modify content Remove dynamic content or make it render in a stable form.

For a direct screenshot, pass inline CSS with style:

await page.screenshot({
  path: 'artifacts/dashboard.png',
  style: '[data-testid="live-metric"] { visibility: hidden !important; }',
});

For a screenshot assertion, provide a stylesheet file with stylePath:

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

test('dashboard snapshot hides the live metric', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page).toHaveScreenshot('dashboard.png', {
    stylePath: './tests/screenshot-overrides.css',
  });
});

For example, tests/screenshot-overrides.css can contain:

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

Playwright’s screenshot stylesheet support can affect content inside Shadow DOM and inner frames. Direct screenshot options use style; screenshot assertions use stylePath. These options were added in v1.41. maskColor was added in v1.35. Check the API reference for your installed version: Page API and PageAssertions API.

5. Control other sources of screenshot variation

Masking addresses pixels within the selected elements’ bounding boxes. It does not stabilize unrelated parts of the page. A changing timestamp outside the mask, a different font, or a changed viewport can still cause a visual comparison to fail.

  • Use the same browser version, operating system, browser settings, and headless mode for creating and comparing baselines.
  • Keep viewport and device scale consistent with the baseline.
  • Use screenshot assertion defaults deliberately: assertions disable animations by default. Finite animations are fast-forwarded; infinite animations are canceled to their initial state for capture and resumed afterward.
  • Mask only the unstable regions. If you mask a large container, meaningful regressions inside it will be hidden from the comparison.
  • Use a stylesheet when the element should be absent or consistently modified in the image rather than painted over.

Playwright documents environment variation and screenshot assertion behavior in its Visual comparisons guide and PageAssertions API. Choosing whether to cover or alter a region is an application-specific decision based on those behaviors.

6. Troubleshooting

Symptom Likely cause Fix
The volatile content still appears The Locator does not match the rendered element, or identifies a different node. Check the selector against the live DOM and use a precise test ID, role/name, or CSS selector for the target.
A stable area is unexpectedly covered The locator matches more elements than intended, or its container is too broad. Narrow the locator to the changing region and inspect every match before accepting the baseline.
An invisible element still has a mask Playwright masks invisible matching elements too. Make the Locator identify only visible targets when that is the desired behavior, or use screenshot CSS to control what appears.
The screenshot assertion fails despite a mask Variation remains outside the masked bounding box, or the capture environment differs from the baseline environment. Identify the actual differing region, mask only legitimate volatile content, and align browser, OS, viewport, and rendering settings.
The mask color option is rejected or ignored The installed Playwright version may predate maskColor (added in v1.35). Check the installed version and upgrade if appropriate; otherwise use the default mask color.
style or stylePath is unavailable Screenshot stylesheet support was added in v1.41, or the option is being passed to the wrong screenshot API. Use style for direct screenshots and stylePath for screenshot assertions, with a version that supports the option.
A stylesheet rule has no effect The selector may not match the element, or the target may be in a frame or Shadow DOM with different markup. Verify the selector and use screenshot stylesheet support, which can pierce Shadow DOM and inner frames.

7. Performance, reliability, and cost

Masking is a screenshot presentation option: it does not change the page’s data or make the underlying content deterministic. Keep selectors specific and the number of masked regions limited so the screenshot still catches meaningful regressions. Screenshot assertions may capture repeatedly while seeking two consecutive matching images, so a page that never settles can take longer to produce an assertion result. Treat that retry behavior as part of test runtime and investigate persistent instability at its source.

Rendering differs with operating system, browser version, settings, hardware, power source, and headless mode. For reliable comparisons, create and compare baselines in a consistent environment. A mask can reduce noise inside a region, but cannot correct rendering differences elsewhere. [Visual comparisons guide]

8. Or skip the browser setup

If you need a clean screenshot of a live page rather than a Playwright visual assertion, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its API returns a screenshot or PDF with one GET request. The examples below save a WebP screenshot of Stripe; see the ScreenshotNeo API documentation for request options.

cURL

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
  writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);

ScreenshotNeo accepts cookie and consent banners as 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, blank pages, and failed loads are never billed. Response headers say which page verdict occurred and whether the request was billed. 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.

Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card.

9. FAQ

Does a mask remove the element from the page?

No. It covers the matched element’s bounding box in the screenshot; it is not documented as removing the element or its layout space.

Can I mask more than one region?

Yes. Pass multiple Locators in the mask array.

Can I change the overlay color?

Yes. Set maskColor to a CSS color. The default is #FF00FF.

Should I mask or hide a changing element?

Mask it to retain its space under a solid overlay. Use screenshot CSS to hide or alter it in the capture.

Primary Playwright references