How to Fix Playwright Screenshots That Cut Off Sticky Headers
Diagnose viewport cropping, sticky positioning, and moving headers in Playwright. Choose full-page, element, or region capture and apply targeted screenshot-time styles.
Playwright page screenshots capture the visible viewport by default. If the image stops at the viewport height, set fullPage: true (Python: full_page=True) to capture the full scrollable page. That changes the capture area; it does not decide whether a sticky header should remain sticky, move into document flow, or disappear. Choose the intended header appearance and, if needed, use a narrowly scoped screenshot-time CSS rule.
There is no documented Playwright option that specifically disables sticky headers. The documented style screenshot option lets you add CSS during capture; the positioning examples below are practical applications of that mechanism, and their layout effect depends on the page.
1. Check whether the screenshot is cropped to the viewport
A normal page screenshot captures the viewport. Playwright’s fullPage option defaults to false. Set it to true when you want the full scrollable document. The Playwright guide describes it as capturing the full page “as if you had a very tall screen and the page could fit it entirely.” See the official Playwright screenshots guide and Page screenshot API.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'page.png', fullPage: true });
await browser.close();
If you use Playwright Test, the core call is the same after navigating:
await page.screenshot({ path: 'page.png', fullPage: true });
For Python’s async API:
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": 1440, "height": 900})
await page.goto("https://example.com", wait_until="load")
await page.screenshot(path="page.png", full_page=True)
await browser.close()
Use the synchronous Python API by replacing async_playwright with sync_playwright and removing await and async. Install the language package and browser binaries as described in the Playwright installation guide.
2. Decide how the sticky header should look
First determine whether you want the header retained in its sticky state, placed in normal document flow, or omitted. Full-page capture alone does not make that decision. If the sticky header overlays content in an unwanted way, try a targeted override using the actual header selector.
await page.screenshot({
path: 'page.png',
fullPage: true,
style: '.site-header { position: static !important; }',
});
The screenshot style option applies a stylesheet for the capture. It can change properties or hide dynamic elements. The rule above is an example, not a guaranteed sticky-header recipe: changing positioning may change layout, spacing, or overlap depending on the site’s CSS. Inspect the resulting image and adjust the selector and declarations to match the intended output.
If the header should not appear in the image, a targeted hide rule is another option:
await page.screenshot({
path: 'page.png',
fullPage: true,
style: '.site-header { display: none !important; }',
});
Keep the selector narrow. A broad rule such as header { display: none } may also hide article section headers or other content you need.
3. Choose full-page, element, or region capture
| What you need | Use | What it captures |
|---|---|---|
| Entire scrollable document | page.screenshot({ fullPage: true }) |
The full page rather than only the current viewport |
| One element, such as the header | locator.screenshot() |
The target element’s rendered bounds |
| A specific rectangle | clip |
A region defined by coordinates and dimensions |
Element capture is useful when the header itself is the deliverable. It is not a replacement for a full-page capture if you need the document.
await page.locator('.site-header').screenshot({ path: 'header.png' });
For a defined region, set the clip rectangle relative to the page screenshot coordinates:
await page.screenshot({
path: 'header-region.png',
clip: { x: 0, y: 0, width: 1440, height: 160 },
});
See the screenshot API options for the current option definitions.
4. Make captures stable when the header moves
If repeated screenshots show different header states, check whether transitions or animations are running during capture. Playwright’s screenshot animation handling can make captures more repeatable. With animations disabled, finite animations are fast-forwarded so transition-end events fire; infinite animations are canceled to their initial state during capture and resume afterward. This helps with moving UI, but does not correct a wrong capture extent or layout.
await page.screenshot({
path: 'page.png',
fullPage: true,
animations: 'disabled',
});
If only one effect needs suppression, add a narrow rule with style rather than changing unrelated page styles. Keep the viewport, navigation state, and any relevant scroll position consistent across runs.
For Playwright Test visual comparisons, toHaveScreenshot() records a baseline and compares later runs. Screenshot styles can filter volatile elements. Review a new or changed baseline before accepting it; style filtering improves consistency but does not by itself fix sticky positioning. See Playwright visual comparisons.
5. Handle nested scrolling and lazy content
fullPage: true refers to the page’s scrollable document. It does not establish that every nested element with its own scrolling area will be expanded to show all of its contents. If the missing content lives in a nested scroll container, capture that container or deliberately set its scroll position and size as a separate step, then inspect the output.
For lazy-loaded content, make sure the page has reached the state you intend to capture before taking the screenshot. A screenshot cannot include content that has not loaded or been rendered. If a site loads sections only after scrolling, scroll through the relevant content and wait for it to appear before capturing. Keep this page-specific preparation separate from the screenshot’s choice of viewport or full-page extent.
6. Troubleshoot common results
| Symptom | Likely cause | Fix |
|---|---|---|
| Image ends at viewport height | The screenshot uses the default viewport capture | Set fullPage: true in JavaScript or full_page=True in Python on the page screenshot call. |
| Header covers content or appears in the wrong position | Full-page extent and sticky layout are separate concerns | Decide whether the header should remain, move into flow, or be hidden. Try a targeted style rule and inspect the result. |
| Only the header is needed | A page screenshot captures more than the requested component | Use page.locator('.site-header').screenshot(). |
| Only a fixed area is needed | The requested framing is a rectangle, not a document or element | Set an appropriate clip rectangle. |
| Header position changes between captures | Animation, transition, or inconsistent page state | Use animations: 'disabled' where available, suppress only the relevant effect, and keep viewport and page state consistent. |
| Nested panel content is missing | The content is inside an independently scrolling container | Capture that container or control its scroll position and verify the result. |
| CSS override has no visible effect | The selector may not match, a more specific rule may win, or the page layout may respond differently | Inspect the actual element and computed styles, narrow the selector correctly, and test one change at a time. |
7. Performance, reliability, and cost
Full-page screenshots can be larger and take longer to produce than viewport or element screenshots because they include more page area. Use the smallest capture that meets the requirement: an element or clip for focused output, and full-page capture for the complete document. Large pages, lazy loading, and page-specific preparation can add work; keep navigation and readiness conditions consistent when comparing results.
For repeatable visual checks, pin the viewport and page state, handle animations deliberately, and inspect snapshot changes before updating a baseline. The research sources provide no general benchmark or fixed runtime for these choices, so measure on the pages and environment that matter to your workflow.
Running Playwright yourself has the cost and operational work of the browser environment you choose. There is no Playwright screenshot API charge established by the documentation cited here; hosting, compute, and maintenance costs depend on your setup.
8. Or skip the browser setup
If you need a screenshot API instead of managing a browser capture flow, ScreenshotNeo takes a screenshot or PDF with one GET request. Its API documentation lists the request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Does Playwright have a sticky-header screenshot setting?
The reviewed screenshot API documents full-page capture, styles, element capture, clipping, and animation handling; it does not document a dedicated sticky-header switch.
Will fullPage: true remove a sticky header?
No. It changes the capture extent to the full scrollable page. The header’s appearance is a separate layout decision.
Should I hide the header or set it to static?
Choose based on the image you need: hide it if it should be absent, or try a positioning override if it should appear in document flow. Inspect the output because page CSS determines the result.
Can a locator screenshot capture a whole page?
It captures the selected element. Use a page screenshot with fullPage: true when you need the full scrollable document.


