ScreenshotNeo

BlogHow-to

Fix Playwright Screenshots That Include Scrollbars in the Wrong Place

Diagnose missing or misplaced scrollbars in Playwright screenshots by checking capture mode, scroll ownership, viewport size, and CSS overflow.

By the ScreenshotNeo team4 October 20268 min read

To fix a scrollbar that appears missing or misplaced in a Playwright screenshot, first identify whether the document or a nested element owns the scroll, then choose the matching capture method. page.screenshot() captures the current viewport; page.screenshot({ fullPage: true }) captures the document as a tall image, but does not expand nested overflow: auto panels. For an inner scroller, capture the element or scroll it to the desired position before taking a viewport screenshot.

Why the scrollbar appears in the wrong place

“Wrong place” can describe several different results: the document scrollbar is absent in a full-page image, an inner panel is clipped at its visible bounds, a fixed or sticky element looks odd in the tall capture, or CSS constrains the content to the viewport. These are different layout and capture situations, so there is no single scrollbar option that fixes all of them.

A normal viewport screenshot preserves the browser’s viewport geometry at one moment. A full-page screenshot has different geometry: it renders the full scrollable document as though displayed on a very tall screen. A viewport scrollbar therefore may not look like it does in a user-visible viewport screenshot.

Choose the capture that matches the content

What you need to show Use Tradeoff
The page as a user sees it at one moment page.screenshot() Preserves viewport dimensions and current scroll position; content outside the viewport is not included.
The whole document page.screenshot({ fullPage: true }) Creates a tall document image. It does not expand nested scroll areas, and the viewport scrollbar may not appear as expected.
Content inside a scrolling panel locator.screenshot() on the panel Targets the element. Check whether its current scroll position and dimensions show the intended content.
A particular view within a panel Scroll the panel, then take a viewport screenshot Shows the chosen panel state in its surrounding page context.

Diagnose the scroll owner

  1. Record the exact screenshot call. Note whether it uses fullPage, a locator screenshot, or a viewport capture, and whether the page or panel has already been scrolled.
  2. Check which element scrolls. Inspect document.scrollingElement and look for fixed-height wrappers or elements with overflow: auto or overflow: scroll.
  3. Inspect the layout constraints. Check height, max-height, 100vh, position: fixed, and position: sticky. These can affect whether content extends below the fold and how it looks in a tall image.
  4. Match viewport setup to the target. If responsive CSS matters, set the intended viewport before navigation. Changing viewport size can change the layout and resets the screen size.
  5. Reproduce with a minimal page. Keep the relevant container CSS and content, and record the Playwright version, browser, operating system, and screenshot options.
const scrollInfo = await page.evaluate(() => {
  const root = document.scrollingElement;
  return {
    documentScroller: root?.tagName,
    documentClientHeight: root?.clientHeight,
    documentScrollHeight: root?.scrollHeight,
    documentScrollTop: root?.scrollTop,
    candidates: [...document.querySelectorAll('*')]
      .filter((el) => {
        const style = getComputedStyle(el);
        return /(auto|scroll)/.test(style.overflowY) &&
          el.scrollHeight > el.clientHeight;
      })
      .map((el) => ({
        tag: el.tagName,
        id: el.id,
        className: String(el.className),
        clientHeight: el.clientHeight,
        scrollHeight: el.scrollHeight,
        scrollTop: el.scrollTop,
        overflowY: getComputedStyle(el).overflowY,
      })),
  };
});
console.log(scrollInfo);

This inspection is a diagnostic aid: a page can contain multiple scrolling regions, and the intended one depends on the page’s CSS and interaction state.

Runnable Playwright fixes

Capture the current viewport

Use this when the goal is to show the page as it appears at the current viewport and scroll position.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
  await page.goto('https://example.com', { waitUntil: 'load' });
  await page.screenshot({ path: 'viewport.png' });
  await browser.close();
})();

Capture the whole document

Use fullPage for document content that extends below the fold. It captures the document’s scrollable content; it does not turn every nested panel into a full-height region.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
  await page.goto('https://example.com', { waitUntil: 'load' });
  await page.screenshot({ path: 'document.png', fullPage: true });
  await browser.close();
})();

Capture a nested scrolling panel

To show a particular portion of a panel in its page context, scroll the panel first, then take the viewport screenshot. To isolate the panel itself, use a locator screenshot.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
  await page.goto('https://example.com', { waitUntil: 'load' });

  const panel = page.locator('.scroll-panel');
  await panel.evaluate((el) => { el.scrollTop = 400; });
  await page.screenshot({ path: 'panel-in-page.png' });
  await panel.screenshot({ path: 'panel.png' });

  await browser.close();
})();

Replace .scroll-panel with the actual selector and choose a scroll offset appropriate to the content. A locator screenshot targets the element’s rendered box; do not assume it expands the element to include all of its scrollable contents. If you need the entire panel’s content, test how the page’s CSS and the chosen Playwright version render that specific element.

Set a responsive viewport before navigation

Choose the viewport when creating the context or page, before loading the site. A taller viewport can be useful as a diagnostic experiment, but it changes responsive layout and may not represent the target view.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({
    viewport: { width: 1280, height: 1000 },
    screen: { width: 1280, height: 1000 },
  });
  const page = await context.newPage();
  await page.goto('https://example.com', { waitUntil: 'load' });
  await page.screenshot({ path: 'tall-viewport.png' });
  await browser.close();
})();

Keep visual screenshot tests consistent

Playwright’s toHaveScreenshot() assertion supports fullPage. It waits for two consecutive screenshots to match before comparing the last capture with the expectation. Keep the browser, viewport, screenshot mode, and page state consistent between runs; otherwise, a difference can reflect changed geometry or rendering state rather than an application regression.

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

test('document screenshot', async ({ page }) => {
  await page.setViewportSize({ width: 1280, height: 800 });
  await page.goto('https://example.com');
  await expect(page).toHaveScreenshot('document.png', { fullPage: true });
});

For panel-specific visual tests, position the panel deterministically before the assertion and decide whether the expected image represents the panel or the whole viewport. Avoid comparing a viewport capture in one run with a full-page capture in another.

Common errors and fixes

Symptom Likely cause Fix
Body scrollbar is absent in a tall screenshot The capture is fullPage, which has tall-document geometry rather than a normal viewport. Use a regular viewport screenshot if the visible scrollbar is part of the requirement. Keep full-page mode for document content.
Panel content is cut off in a full-page screenshot The panel is a nested scroller with its own fixed dimensions and overflow. Scroll and capture the panel’s desired state, or take a locator screenshot. Full-page capture does not expand nested scrollers.
Content expected below the fold is not present CSS or page layout may constrain content to viewport height; Playwright does not change the page layout. Inspect height and overflow rules. Try a taller viewport to reveal the layout assumption, then capture at the viewport that matches your intended output.
Sticky or fixed UI looks displaced in a full-page image Its positioning is tied to viewport behavior while the screenshot has full-document geometry. Capture a viewport if the user-visible fixed state matters. For an archive image, consider whether that UI should be hidden or restyled for the capture.
Layout differs after resizing Responsive breakpoints or viewport-relative CSS changed; changing viewport also resets screen size. Set viewport and screen dimensions before navigation, and use the same values in each run.
Visual assertion is flaky Viewport, scroll position, browser, content, or screenshot mode is inconsistent. Stabilize those inputs and make the assertion target explicit: viewport, full document, or panel.

Performance, reliability, and cost

A viewport screenshot is generally the smaller capture because it covers only the visible viewport; a full-page screenshot may create a much taller image and take more time or memory to process. Nested panels add another state to control: their scroll offset and dimensions must be repeatable. Keep the viewport and scroll position explicit, and avoid using a very tall viewport as a production fix unless that is the output you actually want.

For reliable automation, use the same browser and viewport settings, wait for the page state needed by the screenshot, and record the exact capture mode. Historical issue reports describe missing scrollbar behavior on particular Playwright versions and platforms; those reports establish that the symptom has been encountered, not that every current browser and operating system behaves the same way. When behavior remains unclear, reduce the page to a reproducible example before treating it as a Playwright defect.

Playwright itself does not add a per-screenshot service charge. The practical cost is the browser runtime and infrastructure used to run it, plus storage or processing for the resulting image in your system. A full-page capture can produce larger files than a viewport capture. No general performance benchmark is implied; measure with your own page and runtime if throughput or memory is a constraint.

Or skip the browser setup

If you need a clean website screenshot without managing browser capture code, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. The API supports full-page captures, CSS selector element captures, custom viewports, and other capture settings. See the ScreenshotNeo API documentation for parameters and examples.

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 accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, 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 report the page verdict and billing status. Its MCP server gives AI agents tools for taking screenshots, getting page information, and capturing PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.

FAQ

Does fullPage: true capture every scrollable element?

No. It captures the document’s scrollable page; nested scroll containers keep their own dimensions and overflow behavior.

How do I include the scrollbar exactly as a user sees it?

Capture the viewport with page.screenshot() at the intended viewport size and scroll position. A full-page image has different geometry.

Can increasing the viewport fix a scrollbar?

It can expose a viewport-height layout assumption, but it changes responsive layout and is not a universal fix. Set the target viewport before navigation when matching a specific view.

What information should I include in a bug report?

Provide a minimal page, the exact screenshot call, Playwright version, browser and operating system, viewport dimensions, scroll position, and the CSS for relevant scrolling containers.

Primary references