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.
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
!importantselectively. It can help override site rules, but broad rules can also hide content you intend to capture. - Prefer
display: nonewhen an element and its layout space should disappear. Usevisibility: hiddenwhen 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
iframehides 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.


