ScreenshotNeo

BlogHow-to

How to Fix a Dropdown Menu Cut Off in a Playwright Screenshot

Find out whether Playwright's capture boundary or your page's CSS is clipping a dropdown, then fix the cause and capture the right state.

By the ScreenshotNeo team4 October 20268 min read

If a dropdown is cut off in a Playwright screenshot, first determine whether the screenshot boundary is too small or the browser layout is clipping the menu. Use fullPage: true only when you need the whole scrollable document; it does not undo CSS clipping. If an ancestor clips the dropdown with overflow, fix the layout, move the menu outside that clipping ancestor, or make the panel intentionally scrollable. If the menu is behind another element, inspect stacking contexts before changing z-index.

This guide uses Playwright’s JavaScript API. The same diagnosis applies to other Playwright language bindings. For the official screenshot options, see Playwright’s Page API and screenshot guide.

1. Identify what is actually being clipped

Open the menu in the browser and classify the failure before changing the screenshot call:

What you see Likely cause Where to look
The screenshot ends at the viewport edge, while the page continues below it The capture is viewport-sized Choose viewport or full-page capture deliberately
The menu is cut off exactly at a card, panel, or scroll area edge An ancestor’s overflow or constrained dimensions clip it Inspect overflow, height, max-height, and containment on the menu’s ancestors
The menu’s box exists, but another element paints over it A stacking-context boundary or layer order issue Inspect stacking contexts up the ancestor chain
The menu appears in an unexpected position after applying fixed positioning An ancestor changes the fixed-position containing block Check for transform, filter, perspective, or containment
The screenshot catches a closed, half-open, or transitioning menu The test captured before the intended open state was ready Open through the real trigger and wait for a visible state

A useful first check is the menu’s bounding box and the viewport dimensions. A menu whose box extends beyond the viewport needs placement or viewport-aware behavior. A box inside the viewport whose pixels disappear at a panel boundary points toward clipping or paint order.

2. Capture the intended area

Playwright’s page screenshot captures the viewport by default. Set fullPage: true when the artifact should include the entire scrollable document. Use a locator screenshot when the artifact should contain just the menu. A locator screenshot does not escape CSS clipping imposed by ancestors, and it captures only the element’s current scroll position.

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

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
await page.goto('http://localhost:3000', { waitUntil: 'domcontentloaded' });

const trigger = page.getByRole('button', { name: 'Products' });
const menu = page.getByRole('menu');
await trigger.click();
await expect(menu).toBeVisible();

// Viewport screenshot (the default page screenshot behavior)
await page.screenshot({ path: 'viewport.png' });

// Entire scrollable document; this changes capture extent, not CSS layout.
await page.screenshot({ path: 'full-page.png', fullPage: true });

// The menu element itself. Ancestor clipping still applies.
await menu.screenshot({ path: 'menu.png' });

await browser.close();

To capture a specific rectangle, use the clip option with the desired x, y, width, and height. Expanding a clip rectangle is appropriate when the desired image area is genuinely larger; it will not reveal pixels that CSS has clipped from rendering.

3. Fix CSS overflow clipping

CSS overflow values such as hidden, clip, auto, and scroll can clip descendants at an ancestor’s box. Inspect the menu and every ancestor, including panel wrappers that may have a fixed height or maximum height. MDN documents how the overflow property controls content outside a box.

If the menu should extend beyond a panel, choose the layout fix that matches the interface:

  • Remove or adjust the clipping overflow rule if the panel does not need to clip its contents.
  • Render the menu in a portal or overlay container outside the clipping ancestor, then position it relative to its trigger.
  • Keep the menu inside the panel and make that panel scrollable if containment is the intended interaction.

Do not apply overflow: visible blindly: it can change nearby layout and scrolling behavior. Also, overflow: hidden can still permit programmatic scrolling even though the overflowed content is visually clipped.

4. Correct positioning and stacking

An absolutely positioned menu is placed relative to its containing block. A fixed element is generally positioned relative to the viewport, but ancestor properties such as transforms, filters, perspective, or containment can establish a different containing block. See MDN’s guide to containing blocks.

A large z-index on the menu does not necessarily put it above content outside its parent stacking context. Stacking contexts are layered as units; a child cannot out-rank a sibling context of its parent just by using a larger number. Inspect the menu’s ancestors for properties that establish stacking contexts, then adjust the relevant parent layers or render the menu in an appropriate overlay. See MDN’s stacking context reference.

If the menu should escape a scroll panel or other clipping region, a portal or overlay pattern may fit the existing app. The browser Popover API can place content in the top layer and can be combined with invoker anchoring. Check target browser support and implement the menu’s keyboard and accessibility behavior separately; using Popover does not automatically provide complete menu semantics.

5. Make the Playwright capture deterministic

  1. Set a viewport that reflects the layout you need to inspect.
  2. Navigate to the page and open the dropdown through its actual trigger.
  3. Wait for the expected menu state with a locator assertion instead of relying on an arbitrary sleep.
  4. Inspect the menu bounding box, its ancestors’ styles, and the viewport if it is still clipped.
  5. Choose a viewport, locator, or full-page screenshot based on the intended artifact.

Playwright screenshot assertions disable animations by default in the documented assertion options. When using a direct screenshot call or a different API/version, check that API’s options and defaults instead of assuming the assertion behavior applies. See PageAssertions screenshot options.

const box = await menu.boundingBox();
const viewport = page.viewportSize();
console.log({ box, viewport });

// Inspect computed styles on the menu and its ancestors in the page.
const ancestors = await menu.evaluate((element) => {
  const rows = [];
  for (let node = element; node instanceof HTMLElement; node = node.parentElement) {
    const style = getComputedStyle(node);
    rows.push({
      tag: node.tagName,
      className: node.className,
      overflow: style.overflow,
      overflowX: style.overflowX,
      overflowY: style.overflowY,
      position: style.position,
      zIndex: style.zIndex,
      transform: style.transform,
      contain: style.contain
    });
  }
  return rows;
});
console.table(ancestors);

6. Troubleshooting common failures

Symptom Cause Fix
fullPage: true still shows a menu cut at the card edge The page capture includes more document area but CSS still clips the menu Fix the clipping ancestor or move the menu outside it
A locator screenshot has only part of a menu The menu is clipped in the rendered page or the locator itself is scrollable Fix ancestor overflow; scroll the element intentionally if more content should appear
z-index: 999999 changes nothing A parent stacking context is below another context, or overflow clips the menu Inspect ancestor stacking contexts and overflow; correct the parent layer or overlay placement
The fixed menu moves with a transformed panel The ancestor establishes the fixed descendant’s containing block Remove or relocate the containing-block property, or portal the menu to a suitable root
The screenshot sometimes catches the closed state The click or animation has not reached the expected state Assert menu visibility after clicking; avoid arbitrary sleeps when a state assertion is possible
The full-page image has an unexpected large blank region The page has a tall scrollable document or lazy content changed page dimensions Check document dimensions and capture after the intended content has loaded
Menu goes off the right or bottom of the viewport Placement does not account for available viewport space Use viewport-aware positioning or flip/shift the menu, then verify with the same viewport as the test

7. Performance, reliability, and cost

Choose the smallest capture scope that answers the test: a menu locator image is usually a more focused artifact than a full-page image, while a full-page capture may take more work for long documents. Keep viewport and device scale consistent across runs so layout changes are easier to interpret. Wait on a meaningful visible state rather than a fixed delay; this reduces timing-sensitive captures.

For reliable visual checks, stabilize dynamic page content that is irrelevant to the menu, ensure the correct fonts and assets have loaded, and capture after the menu reaches its intended state. If the issue occurs only in CI, compare the browser version, viewport, device scale, fonts, and page data between CI and local runs before changing CSS.

Running Playwright yourself means maintaining browser installation, runtime dependencies, and capture infrastructure. A local or CI screenshot has no per-call ScreenshotNeo charge; its cost comes from the compute and maintenance you use. The browser setup is useful when you need direct control over your application and test environment.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. One GET request returns an image or PDF; see the API documentation. For a visual reference of the page after you have fixed its layout, you can capture the target URL with one call:

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
  • Cookie banners, newsletter popups, and chat widgets are removed before the screenshot.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers say which page verdict applied and whether it was billed.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan.

Sign up for 1,000 free screenshots a month, with no card.

9. Frequently asked questions

Does a screenshot fix a dropdown that is broken in the live page?

No. A screenshot records the rendered result. Fix the page’s layout or placement logic first, then capture it.

Should I always use full-page screenshots for dropdowns?

No. Use full-page capture for a document-length artifact, a locator capture for an isolated menu, and the viewport capture when that is what users see.

Can a higher device scale factor fix clipping?

No. Device scale changes rasterization density, not CSS overflow or stacking behavior.

Is a portal always the right fix?

No. It is one option when the menu must escape an ancestor’s clipping boundary. Consider the interface’s scrolling, placement, keyboard behavior, and accessibility requirements.