ScreenshotNeo

BlogHow-to

Playwright Full-Page Screenshot Cuts Off a Sticky Footer: Fix

Diagnose why a sticky footer is clipped in a Playwright full-page screenshot, then choose a CSS or capture fix that matches the page’s intended behavior.

By the ScreenshotNeo team4 October 20268 min read

fullPage: true asks Playwright to capture the page’s full scrollable document instead of only the visible viewport. It does not repair the page’s CSS or guarantee that a sticky or fixed footer will appear at the document’s end. Inspect the footer’s position, its ancestors’ height and overflow, and whether it lives inside a nested scroller. Then choose a fix that matches the image you need: a document image with the footer once at the bottom, or a faithful capture of a persistent on-screen footer.

1. Confirm what Playwright is capturing

Playwright defines a full-page screenshot as capturing the full scrollable page, as if it were displayed on a very tall screen. That describes the capture area; it does not diagnose a particular layout. A footer cutoff alone is not enough evidence to conclude that Playwright has a bug.

First reproduce the issue with the same browser, viewport, and page state as the failing run. Save both a viewport screenshot and a full-page screenshot. Compare where the footer is clipped and whether the page’s actual document extends beyond the captured image.

Inspect the footer’s computed position and bounding box, the document’s scroll height, and each ancestor that can constrain or clip it. A sticky footer can behave differently when an ancestor establishes a scrolling container, has a fixed height, or clips overflow. Also check whether the page uses a nested scroller: fullPage refers to the page’s scrollable document, so a separate scrollable panel may need to be handled on its own.

// Run in a Playwright test after the page has reached the state you want to capture.
const footer = page.locator('.footer');
console.log('footer count:', await footer.count());
if (await footer.count()) {
  console.log('footer box:', await footer.boundingBox());
  console.log('footer styles:', await footer.evaluate(el => {
    const s = getComputedStyle(el);
    return {
      position: s.position,
      bottom: s.bottom,
      height: s.height,
      overflow: s.overflow,
      parent: el.parentElement?.tagName,
      parentOverflow: el.parentElement
        ? getComputedStyle(el.parentElement).overflow
        : null,
    };
  }));
}
console.log('document dimensions:', await page.evaluate(() => ({
  scrollHeight: document.documentElement.scrollHeight,
  clientHeight: document.documentElement.clientHeight,
  bodyScrollHeight: document.body.scrollHeight,
  bodyClientHeight: document.body.clientHeight,
}))); 
await page.screenshot({ path: 'viewport.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });

Replace .footer with the selector used by your application. If the selector matches multiple elements, inspect the intended footer specifically. For a full ancestor audit, evaluate the footer’s parent chain and record each element’s position, height, overflow, and bounding box. This helps find the first ancestor that clips the footer or creates the relevant scrolling context.

3. Choose the fix based on the intended image

If the output should represent a long document with the footer after the content, the simplest layout is usually a footer in normal document flow. Correct the application CSS if the inspected page layout is wrong for users. If only the screenshot should change, test a screenshot-only override:

await page.screenshot({
  path: 'page.png',
  fullPage: true,
  style: '.footer { position: static !important; }',
});

The screenshot style option applies CSS while capturing. Making this footer static is a diagnostic and presentation choice, not a universal Playwright fix: confirm the selector and check that the result matches the desired document. If the footer is clipped by an ancestor, changing the footer’s own position may not be enough; fix or override the clipping ancestor as well.

For screenshot assertions, Playwright also documents stylePath for applying a stylesheet. Put the verified capture-only rules in that file when you want to reuse them in assertions. Screenshot-only styling is useful when production styling should stay intact but the test artifact should use a document-style footer.

If the footer is meant to stay visible while a user scrolls, preserve that behavior and capture the viewport. A full-page image is a tall document representation, not a sequence of viewport states. For a visual regression test of the persistent UI, capture the relevant viewport and assert the footer there.

Nested scrolling region

If the content is inside a panel with its own scroll bar, the panel’s contents are not necessarily part of the main document’s scroll height. Decide whether the artifact should include the entire panel. If so, either use a test-specific style that expands the panel and removes its clipping, or scroll and capture the panel separately. Verify the resulting dimensions and avoid assuming that fullPage expands every nested scroller.

4. Keep the screenshot test reproducible

Visual output can vary across operating systems, browser versions, browser settings, hardware, power source, and headless mode. Keep the browser and host conditions stable when investigating a regression. Also wait for the page state your test expects before capturing: late content, fonts, or layout changes can move the footer after an early screenshot.

For a visual comparison, use the same viewport, browser build, content state, and capture styling for the baseline and actual image. If the footer is dynamic, decide whether the test should include that variation or apply a documented screenshot stylesheet to make the capture deterministic.

5. Complete runnable Playwright example

This JavaScript example opens a page, checks the footer and document dimensions, and saves both screenshots. Install Playwright in your project and set PAGE_URL to the page under test.

import { chromium } from 'playwright';

const url = process.env.PAGE_URL;
if (!url) throw new Error('Set PAGE_URL to the page under test.');

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

const footer = page.locator('footer').first();
if (await footer.count()) {
  console.log('footer box:', await footer.boundingBox());
  console.log('footer computed position:', await footer.evaluate(
    el => getComputedStyle(el).position
  ));
} else {
  console.warn('No footer element matched; replace the selector with your footer selector.');
}

console.log('document:', await page.evaluate(() => ({
  scrollHeight: document.documentElement.scrollHeight,
  clientHeight: document.documentElement.clientHeight,
})));

await page.screenshot({ path: 'viewport.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
// Diagnostic capture only: verify this override suits your page before using it.
await page.screenshot({
  path: 'full-page-static-footer.png',
  fullPage: true,
  style: 'footer { position: static !important; }',
});
await browser.close();

networkidle is a possible readiness condition, not a guarantee that every application has finished rendering; pages with persistent network activity may not reach it. Use the application’s own ready signal or wait for a meaningful selector when appropriate. The footer override is included as an experiment so you can compare the result, not as an assumed production correction.

6. Troubleshooting common symptoms

Symptom Likely cause What to check or change
Footer is absent from the full-page image Footer is positioned relative to a viewport or constrained ancestor, or the page capture ends before the relevant content is in the document. Check its computed position, bounding box, document scroll height, and ancestor overflow. Try a verified screenshot-only normal-flow style.
Footer appears halfway down or overlaps content Sticky or fixed positioning interacts with the tall capture layout, or the footer’s containing block differs from what the page design assumes. Compare viewport and full-page captures. Decide whether the image should preserve persistent UI or show a document footer once at the end.
Footer is cut at a panel boundary An ancestor has constrained dimensions or clipping overflow, or the footer is inside a nested scroller. Walk the ancestor chain and inspect height and overflow. Expand the relevant container for the capture or capture the panel separately.
Footer appears in the viewport screenshot but not the full-page one The viewport may show a persistent footer that is not in normal document flow. Use a viewport capture if persistent behavior is the subject of the test; use a verified static positioning override for a document image.
Footer position changes between runs Content or layout may still be changing, or browser and host rendering conditions differ. Wait for a meaningful application-ready condition and stabilize the browser, viewport, and test environment.
Screenshot stylesheet has no effect The selector may not match, the footer may be inside a different document or component boundary, or another rule/layout constraint may control the result. Verify the selector against the captured page and inspect the footer and clipping ancestors before adjusting the override.

7. Performance, reliability, and cost

A full-page image can be much taller and larger than a viewport image, so use it only when the test needs document length. For a persistent footer regression, a viewport capture is smaller and directly represents the behavior being checked. Avoid adding arbitrary long delays as a default: wait for a concrete ready condition, then capture. Stable browser and host conditions improve visual comparison reliability.

With self-hosted Playwright, account for the browser execution environment and the time and storage needed for large artifacts; the research sources provide no universal timing or cost figures for this particular layout issue. Keep generated screenshots with the test artifacts you actually need.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It accepts a URL in one GET request and returns an image or PDF. Its screenshot options include full-page capture, custom CSS and JavaScript, and waiting for a selector, delay, or network idle. See the ScreenshotNeo API documentation.

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,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

For this footer issue, use the API’s custom CSS option to test a page-specific positioning override and compare it with the original capture. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.

9. FAQ

No. It requests the full scrollable page for the screenshot. It does not change the page’s layout rules.

Should I remove position: sticky from the application?

Only if inspection shows the application layout itself is wrong for users. For a test-only document image, first try a screenshot-only style and verify the result.

No conclusion follows from the symptom alone. Check the page’s CSS, scroll containers, browser, and capture environment before assigning a cause.

Can a full-page screenshot include everything inside a nested scroller?

Do not assume so. A nested scrolling region may need a capture-specific layout change or a separate capture, depending on the artifact you need.

Where can I see Playwright’s screenshot options?

Read the official Playwright Screenshots guide and Page screenshot API. For screenshot assertions and stylesheet configuration, see Visual comparisons.