How to Hide Sticky Headers in a Playwright Full-Page Screenshot
Hide sticky headers in Playwright full-page screenshots with targeted capture-time CSS. Learn when to use masking, stabilize captures, and troubleshoot common issues.
To hide a sticky header in a Playwright full-page screenshot, pass a targeted CSS rule through the screenshot-time style option and set fullPage: true. Use a selector that matches the page’s actual header:
await page.screenshot({
path: 'page.png',
fullPage: true,
style: 'header { display: none !important; }',
});
Replace header with a stable, specific selector if the page has multiple header elements. The fullPage option captures the full scrollable page; it does not hide sticky elements by itself. Playwright’s screenshot API documents both full-page capture and screenshot-time CSS. The style option was added in Playwright v1.41, so check your installed version if it is unavailable.
Complete JavaScript example
This runnable example opens a page, waits for it to load, hides the selected header only while taking the screenshot, and saves the result:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'load' });
const screenshotCss = `
.site-header,
#sticky-navigation {
display: none !important;
}
`;
await page.screenshot({
path: 'page.png',
fullPage: true,
style: screenshotCss,
});
} finally {
await browser.close();
}
})();
Install Playwright in a project with npm install playwright, then run the script with Node.js. The selectors are examples, not universal class names. Inspect the target site and use its real header selector. For ordinary screenshot capture, style accepts CSS text directly.
Find a reliable header selector
- Inspect the page in browser developer tools and identify the element that stays fixed or sticky while scrolling.
- Prefer a stable ID, page-specific class, or attribute selector over a broad selector such as
header. - Check whether the page has more than one matching element, such as a global header and a separate sticky navigation bar.
- Use the selector in the screenshot stylesheet and inspect the saved image to verify that only the intended element disappeared.
For example, if the actual element is nav.primary-nav, use:
await page.screenshot({
path: 'page.png',
fullPage: true,
style: 'nav.primary-nav { display: none !important; }',
});
Screenshot-time CSS is applied for the capture. It does not require changing the site’s source code or leaving the element hidden in later browser interactions.
Choose between hiding, preserving space, and masking
| Desired result | Approach | What happens |
|---|---|---|
| Remove the header and its space | display: none |
The matched element is removed from layout for the capture; content below may shift upward. |
| Keep its space but make it invisible | visibility: hidden |
The element remains in layout, so the reserved area stays. |
| Keep layout and cover the header’s pixels | Playwright’s mask option |
A colored overlay covers the matched element’s bounding box; it does not remove the element from layout. |
Use display: none when the screenshot should look as if the header is absent and page content should move into its place. If keeping the original vertical spacing matters, use a visibility rule instead:
await page.screenshot({
path: 'page.png',
fullPage: true,
style: '.site-header { visibility: hidden !important; }',
});
To cover rather than remove a matched element, Playwright supports screenshot masking:
await page.screenshot({
path: 'page.png',
fullPage: true,
mask: [page.locator('.site-header')],
});
Masking is useful when an overlay is acceptable. It is not equivalent to hiding: the header still takes up its layout space. Playwright documents a pink default mask and a configurable mask color in the screenshot options.
Make captures more stable
Animated or transitioning headers can make screenshots vary between runs. Disable animations separately from hiding the header:
await page.screenshot({
path: 'page.png',
fullPage: true,
style: '.site-header { display: none !important; }',
animations: 'disabled',
});
Playwright’s animations: 'disabled' option affects CSS animations, transitions, and Web Animations. Finite animations are fast-forwarded to completion; infinite animations are canceled for the screenshot and played again afterward. This can reduce visual variation, but it does not select or hide the header for you. See the API documentation for the behavior and options.
For Playwright Test visual assertions, use stylePath to apply a stylesheet during screenshot comparison. This is distinct from the inline style option used by page.screenshot():
import { test, expect } from '@playwright/test';
test('page screenshot without sticky header', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('page.png', {
fullPage: true,
stylePath: './screenshot.css',
});
});
Put a targeted rule in screenshot.css:
.site-header {
display: none !important;
}
Playwright describes stylePath as a way to filter dynamic or volatile page elements for screenshot comparisons. See the visual comparison API for its options.
Options and edge cases
- Multiple headers: A broad selector may hide unrelated page sections. Target a specific class, ID, or attribute; combine selectors only when each is known to match an unwanted header.
- Content shifts:
display: nonecollapses the header’s layout space, which can move content upward. Usevisibility: hiddenif the empty space should remain. - Full-page behavior: Full-page capture requests the full scrollable document. How a particular browser version renders sticky positioning across that capture is not specified as one universal outcome, so apply explicit CSS and inspect the target output.
- Frames and shadow DOM: Playwright says screenshot-time styles pierce Shadow DOM and apply to inner frames. The selector still needs to match the element in its context; verify pages with embedded or cross-origin content in the resulting image.
- Version and binding differences: The documented
styleoption was added in v1.41. Check documentation matching your installed Playwright version and language binding when using Python, .NET, or Java. - Late-loading headers: If the header is inserted after navigation, wait for a page-specific ready condition before taking the screenshot. The screenshot stylesheet can match elements present at capture time, but your capture should also wait for the page content you need.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
style is rejected or ignored |
The installed Playwright version or language binding does not support the option as used. | Check the API docs for your installed version; the current API marks screenshot style as added in v1.41. Upgrade or use a supported capture-time stylesheet mechanism. |
| The header remains visible | The selector does not match the real element, or a different header is rendered in a frame or shadow root. | Inspect the DOM, use the correct selector, and verify the output. Screenshot styles are documented to pierce Shadow DOM and apply to inner frames, but page structure still matters. |
| More page content disappears | The selector is too broad, for example header matches multiple sections. |
Replace it with a page-specific class, ID, or attribute selector and capture again. |
| Content jumps upward | display: none removes the header from layout. |
Use visibility: hidden when preserving its space is important, or accept the collapse when that is the desired output. |
| A colored rectangle appears instead of removal | The screenshot uses mask. |
Masking covers pixels but preserves layout. Use screenshot-time CSS with display: none to remove the element. |
| Header position or appearance varies between captures | Animation or transition state changes between runs. | Set animations: 'disabled' and use a targeted hide rule. Animation control does not replace the CSS selector. |
| Screenshot is missing lower-page content | The page may not have finished rendering or loading the content expected in the capture. | Wait for a meaningful page condition before capture, then use fullPage: true and check the resulting image. |
Performance, reliability, and cost
A screenshot-time stylesheet avoids a separate page mutation and cleanup step, and it limits the visual change to capture time. The capture still depends on the page loading and rendering successfully. Full-page images can be much taller and larger than viewport screenshots, so account for image size and processing time when capturing long documents. No universal timing or cost figure applies across pages and browser environments.
For repeatable visual checks, use stable selectors, wait for the page state your test requires, disable animations when they create unwanted variation, and review changes to the site’s markup. Keep the hide rule scoped to the capture so ordinary browsing and other tests are unaffected.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Make one GET request with a URL to receive a PNG, JPEG, WebP, or PDF. Its capture can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status.
For a screenshot of a page with its sticky header hidden, provide a targeted CSS rule with the request’s custom CSS option. Check the ScreenshotNeo documentation for the API parameters and options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
--data-urlencode 'css=.site-header { display: none !important; }' \
-o shot.webp
The header selector is site-specific. For programmatic integrations, the same endpoint can be called from Python or Node.js; use the documented parameter names for custom CSS and output format:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://example.com",
"css": ".site-header { display: none !important; }",
},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com',
css: '.site-header { display: none !important; }',
});
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(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. All features are available on every plan. The free plan includes 1,000 screenshots a 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
Does fullPage: true hide a sticky header automatically?
No. It requests a capture of the full scrollable page. Add screenshot-time CSS to hide the specific header.
Can I hide the header only in a visual regression test?
Yes. Use stylePath with toHaveScreenshot() to apply a stylesheet during the comparison capture.
Should I use masking to remove a header?
Use masking only when covering the header’s pixels is sufficient. To remove the element and collapse its layout space, use display: none in screenshot-time CSS.


