ScreenshotNeo

BlogHow-to

How to capture a Playwright screenshot with custom CSS

Use Playwright’s screenshot-time CSS, a reusable stylesheet in Playwright Test, or page.addStyleTag() when CSS should persist on the page.

By the ScreenshotNeo team4 October 20267 min read

For a one-off Playwright screenshot, pass CSS text to page.screenshot({ style }). For a reusable CSS file in a Playwright Test visual assertion, use expect(page).toHaveScreenshot({ stylePath }). If the CSS should be installed on the page before capture and remain in effect for subsequent work, inject it with page.addStyleTag().

The examples below use JavaScript with Playwright. The screenshot-time style option is available starting in Playwright 1.41. See the official Page API and visual comparisons guide.

1. Add inline CSS to a direct screenshot

Use style when the CSS is specific to the capture. This complete Node.js example hides a cookie banner and sets a predictable background without adding a persistent stylesheet to the page:

import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
  await page.goto('https://example.com', { waitUntil: 'networkidle' });

  await page.screenshot({
    path: 'screenshot.png',
    fullPage: true,
    style: `
      .cookie-banner { display: none !important; }
      body { background: #fff !important; }
    `,
  });
} finally {
  await browser.close();
}

The style value is CSS text, not a file path. Playwright applies it while making the screenshot. This is useful for hiding volatile elements, such as a banner, or normalizing styles that would otherwise make captures inconsistent. It is not a permanent page stylesheet. For details and supported screenshot options, see the official API reference.

Capture only the visible viewport

Omit fullPage or set it to false to capture the current viewport. Set it to true to capture the full scrollable document:

await page.screenshot({
  path: 'viewport.png',
  fullPage: false,
  style: '.cookie-banner { display: none !important; }',
});

Stabilize animations

Animations and transitions can produce different pixels across captures. Disable them for a screenshot when motion is not what you are testing:

await page.screenshot({
  path: 'stable.png',
  fullPage: true,
  animations: 'disabled',
  style: `
    .cookie-banner { display: none !important; }
    *, *::before, *::after { caret-color: transparent !important; }
  `,
});

Playwright documents animations: 'disabled' for screenshot capture; it affects CSS animations, transitions, and Web Animations during the capture. See the Page API.

2. Use a CSS file with Playwright Test snapshots

For visual regression tests, put screenshot-specific rules in a stylesheet and pass its path to toHaveScreenshot() as stylePath. This API belongs to the Playwright Test runner, imported from @playwright/test; it is not an option for the direct page.screenshot() method.

Create screenshot.css:

.cookie-banner {
  display: none !important;
}

.live-chat-widget,
iframe.advertisement {
  visibility: hidden !important;
}

/* Prevent a blinking insertion point from changing snapshots. */
input,
textarea {
  caret-color: transparent !important;
}

Then use the stylesheet in a test:

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

 test('page visual snapshot', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveScreenshot({
    fullPage: true,
    stylePath: path.join(process.cwd(), 'screenshot.css'),
    animations: 'disabled',
  });
});

Install the test runner with npm install --save-dev @playwright/test and install its browser binaries with npx playwright install if they are not already installed. Run the test with npx playwright test. The assertion captures until two consecutive screenshots match, then compares the result with the expected snapshot. The official visual comparisons guide describes this process and the stylePath option.

Use a stable path that resolves from the test process. process.cwd() is convenient when you run tests from the project root; use a path relative to the test file if your project layout calls for it. Keep the CSS file under version control so teammates and CI use the same rules.

3. Inject a stylesheet before capture

Use page.addStyleTag() when the page itself should receive a style element, for example because later interactions or multiple captures should use the same injected rules:

import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com');

  await page.addStyleTag({
    content: `
      .cookie-banner { display: none !important; }
      body { background: white !important; }
    `,
  });

  await page.screenshot({ path: 'injected-style.png', fullPage: true });
} finally {
  await browser.close();
}

addStyleTag() inserts a style element into the page. It accepts CSS through content, or a stylesheet through path or url. Unlike screenshot-time style, this is a page mutation. Use the Page API for the method details.

4. Choose the right CSS method

Method CSS input When it applies Best fit
page.screenshot({ style }) Inline CSS string During that screenshot One-off captures and screenshot-only overrides
toHaveScreenshot({ stylePath }) CSS file path During the visual assertion Reusable Playwright Test snapshot rules
page.addStyleTag() CSS content, path, or URL Installed on the page before capture Rules needed for later page interactions or captures

Do not interchange the option names: direct screenshots take CSS text as style; Playwright Test assertions take a stylesheet path as stylePath.

5. Write CSS that behaves predictably

  • Target the actual element. Inspect the page or query it in Playwright before writing a selector. A class name may differ between pages or change as the site updates.
  • Use !important selectively. It can help override site rules, but broad rules can also hide content you intend to capture.
  • Prefer display: none when an element and its layout space should disappear. Use visibility: hidden when preserving the element’s space is useful. Either can alter layout or leave a gap, so choose based on the desired image.
  • Scope rules tightly. A broad selector such as iframe hides every iframe, including embedded content that may matter.
  • Account for shadow DOM and cross-origin frames. Ordinary page CSS selectors do not necessarily style content inside a shadow root or another frame. Apply styles in the relevant context or use Playwright locators and frame APIs where appropriate.
  • Wait for the page state you need. Screenshot CSS does not make late-loading content appear. Wait for a selector, application state, or other explicit readiness condition before capturing.

For example, wait for the page’s main content before taking a capture:

await page.goto('https://example.com');
await page.locator('main').waitFor({ state: 'visible' });
await page.screenshot({
  path: 'ready.png',
  style: '.cookie-banner { display: none !important; }',
});

6. Keep visual snapshots reliable

Pixel output can vary with operating system, browser version, settings, hardware, power state, and headless mode. Generate and compare baselines in a consistent environment, especially in CI. Pinning the Playwright version and using the same browser and operating system for baseline updates and comparisons reduces avoidable differences. Playwright’s visual comparison guidance explains environment consistency and snapshot behavior.

  • Use the same viewport, device scale factor, browser, and operating system when generating and comparing baselines.
  • Wait for fonts, images, and application data that affect the expected result.
  • Disable animations when motion is irrelevant to the assertion.
  • Hide timestamps, rotating ads, live counters, and other changing content with narrow screenshot CSS rules.
  • Review baseline changes as code changes; CSS that hides too much can make a test pass while important content is missing.

7. Troubleshooting

Symptom Likely cause Fix
style is ignored or rejected The installed Playwright version predates support, or the option is being used on the wrong API. Use Playwright 1.41 or later for screenshot-time style. Use stylePath only with Playwright Test’s toHaveScreenshot().
stylePath is not recognized It was passed to page.screenshot(), or the test runner/version does not support the option. Use style with direct screenshots. For snapshot assertions, check the installed @playwright/test version and its API documentation.
The banner is still visible The selector is wrong, the banner appears after capture, or the banner is inside a frame or shadow root. Inspect the live DOM, wait until the banner appears, and check its frame or shadow-root context. Make the CSS selector match the actual element.
The screenshot has a blank area where a hidden element was visibility: hidden preserves layout space. Use display: none if the space should collapse, or keep the space if preserving layout is intentional.
Snapshots fail intermittently Content, animations, fonts, images, or the rendering environment is changing. Wait for stable content, disable animations, mask or hide only the volatile parts, and keep baseline and comparison environments consistent.
Styles work with addStyleTag() but not in a frame The CSS was installed in the main document, while the target lives in a separate frame. Locate the frame and inject the stylesheet in that frame’s page context.
CSS changes page behavior before a later assertion addStyleTag() permanently changed the current document for the lifetime of that page. Use screenshot-time style for a capture-only override, or remove the injected style element when finished.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request captures a URL as PNG, JPEG, WebP, or PDF, and custom CSS is available as an API option. See the ScreenshotNeo documentation for the request parameters.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  --data-urlencode css='.cookie-banner { display: none !important; }' \
  -o shot.webp

With ScreenshotNeo, cookie banners, popups, and chat widgets are removed 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 per month with no card, and paid plans start at $5 for 3,000.

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

9. FAQ

Can I use CSS to hide one element without changing the live site?

Yes. Pass a targeted rule through page.screenshot({ style }), or use stylePath for a Playwright Test screenshot assertion. These apply for the capture rather than installing a lasting stylesheet on the page.

Does stylePath work with plain Playwright?

stylePath is an option for Playwright Test’s toHaveScreenshot() assertion. For a direct Page API screenshot, pass CSS text through style.

Should I use a screenshot stylesheet to test the site’s actual appearance?

Only when the rules are part of the test setup, such as hiding nondeterministic content. If the purpose is to verify that a banner or widget is styled correctly, leave it visible in the snapshot.