ScreenshotNeo

BlogHow-to

How to Take Screenshots of a Website That Uses Shadow DOM with Playwright

Use Playwright locators to capture Shadow DOM components or whole pages. Learn selector limits, screenshot options, troubleshooting, and a browser-free API alternative.

By the ScreenshotNeo team4 October 20267 min read

Direct answer: Playwright can screenshot supported Shadow DOM content with its regular locator and screenshot APIs. Use locator.screenshot() to capture one component, page.screenshot() for the current viewport, or page.screenshot({ fullPage: true }) for the full scrollable page. Playwright locators pierce open shadow roots by default; XPath and closed-mode shadow roots are exceptions. Playwright Locators documentation

1. Install Playwright and capture a Shadow DOM component

This runnable TypeScript example uses Playwright Test. Replace the example URL and accessible role/name with values that match your page. A user-facing role locator is a good starting point when the component exposes an accessible name.

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

test('capture a component rendered in Shadow DOM', async ({ page }) => {
  await page.goto('https://example.com');

  const component = page.getByRole('button', { name: 'Details' });
  await component.screenshot({ path: 'details.png' });

  // Capture the current viewport instead:
  await page.screenshot({ path: 'viewport.png' });

  // Capture the full scrollable document instead:
  await page.screenshot({ path: 'page.png', fullPage: true });
});

To run it in a new project, install the test package and its browser binaries, save the code in a test file, then run the Playwright test command:

npm init playwright@latest
npx playwright test

The role and name above are illustrative. If the component is not exposed accessibly, use a stable CSS selector. Playwright CSS selectors can pierce open Shadow DOM; XPath cannot. Avoid selectors that depend on generated class names if the site offers a more stable locator. Playwright selector guidance

2. Choose the screenshot scope

Goal Call What it captures
One component await locator.screenshot({ path: 'component.png' }) The matched element’s visible bounds, clipped to its size and position.
Current viewport await page.screenshot({ path: 'viewport.png' }) The current browser viewport.
Full scrollable page await page.screenshot({ path: 'page.png', fullPage: true }) The full scrollable document rather than only the viewport.

Playwright’s screenshot guide documents both page and locator screenshots. Playwright Screenshots guide

3. Find the element inside an open shadow root

Playwright’s locator methods work with elements in Shadow DOM by default. Prefer a role or text locator when it describes what a user sees; CSS is useful when the page’s stable contract is a selector. For example:

// User-facing locator, when available:
const saveButton = page.getByRole('button', { name: 'Save' });
await saveButton.screenshot({ path: 'save-button.png' });

// CSS locator can pierce open shadow roots:
const status = page.locator('account-panel .status');
await status.screenshot({ path: 'status.png' });

CSS piercing does not mean every selector strategy can cross the boundary. XPath does not pierce shadow roots, and Playwright’s documented locator support does not cover closed-mode roots. If the component uses a closed root, look for an accessible element or another exposed page-level target; Playwright does not provide a locator route into that closed root. Locator behavior and limits

4. Wait for the component and prepare the capture

Navigation completion alone may not mean a client-rendered component is ready. Wait for the target locator before taking the screenshot. A locator screenshot itself waits for the element to be actionable, but an explicit wait makes the intended readiness condition clear:

const panel = page.getByRole('region', { name: 'Account details' });
await panel.waitFor({ state: 'visible' });
await panel.screenshot({ path: 'account-details.png' });

Use a locator that identifies the actual target rather than a broad ancestor when isolating a component. If a cookie dialog, modal, sticky header, or chat overlay covers it, the screenshot can show the obstruction. Dismiss or suppress that UI according to the page’s behavior before capture.

5. Screenshot options and deterministic styling

For this task, the core choices are capture scope and output path. The page screenshot API includes the fullPage option for the full scrollable document. The screenshot API also supports a style string for applying CSS during capture; the documented stylesheet pierces Shadow DOM and inner frames. This is useful for hiding volatile elements in repeatable captures.

await page.screenshot({
  path: 'stable-page.png',
  fullPage: true,
  style: '.live-clock, .rotating-promo { visibility: hidden !important; }'
});

Use selectors that actually match the page, and check the API reference for options supported by your installed Playwright version. Playwright Page API reference

6. Edge cases that change what appears

  • Closed shadow roots: Playwright’s documented locators cannot reach into closed-mode roots. Target accessible content outside that boundary or use another supported capture scope.
  • XPath: XPath does not cross shadow roots. Change to a role, text, or CSS locator where appropriate.
  • Overlays: A component screenshot reflects what is rendered in its bounds; an overlay can obscure the component. Dismiss or hide the overlay before capture.
  • Scrollable components: A locator screenshot captures the element’s visible area. For a scrollable element, it shows the content at its current scroll position, not every item in the component’s internal scroll area.
  • Lazy-loaded content: Full-page capture is intended to include the full scrollable page, but page content may depend on scrolling or application-specific loading. Ensure the content has loaded before relying on the image.
  • Visual comparisons: Browser and host conditions can affect rendering. Keep operating system, browser version, settings, hardware, power source, and headless mode consistent when comparing baselines. Playwright visual comparison guidance

7. Troubleshooting

Symptom Likely cause Fix
Locator reports no matching element The selector is wrong, the component has not rendered yet, or the target is inside a closed shadow root. Check the locator against the rendered page, wait for the component, and verify that the root is open. Prefer an accessible role/name when available.
XPath finds the host but not its internal element XPath does not pierce shadow roots. Use a supported role, text, or CSS locator that reaches the element in an open root.
Screenshot shows the wrong area The locator matched a wrapper or multiple elements, or the desired scope is the page rather than the component. Narrow the locator to the intended element; use a page screenshot for viewport or full-page output.
Component is covered in the image A modal, sticky element, consent prompt, or other overlay obscures it. Dismiss the overlay or apply a capture style to hide the relevant selector.
Only part of a component’s list appears The component has its own scroll position; element screenshots show its currently visible content. Scroll the component to the needed position before capture, or capture separate states as required.
Pixel comparison changes between runs Rendering environment or dynamic page content changed. Control browser and host environment, and suppress or stabilize dynamic content with a screenshot style.

8. Performance, reliability, and cost

A locator screenshot keeps the output focused on one component and avoids producing an unnecessarily large full-page image. Use full-page capture only when the entire document is needed; long pages naturally produce larger output. For repeatable results, wait for the specific component and stabilize changing content. Keep browser versions and execution environments consistent for visual comparisons. Playwright’s documentation notes that operating system, browser version, settings, hardware, power source, and headless mode can affect rendering. The research sources do not establish a universal timing or cost figure, so measure capture time and resource use in the environment where the script will run.

9. cURL, Python, and Node.js options

Playwright is the direct browser-based solution in this guide. If your workflow instead needs a screenshot API call without managing browser setup, ScreenshotNeo accepts a URL and returns an image or PDF. The following examples capture the target page; they do not expose a selector-level Shadow DOM locator, so use Playwright when the exact target is an individual component.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
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())));

See the ScreenshotNeo API documentation for request options and setup. Its API supports full-page capture, element selection by CSS selector, custom CSS and JavaScript, wait conditions, output formats, and other capture controls.

10. Or skip the browser setup

For a whole-page capture, one GET request can return the screenshot:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Learn more at ScreenshotNeo, or sign up for the free plan.

11. FAQ

Do I need a special Playwright API for Shadow DOM screenshots?

No. Use the regular locator screenshot method for a component or the page screenshot method for page scope.

Can Playwright capture a closed shadow root?

Playwright’s documented locator behavior does not support closed-mode roots. Use content exposed outside the closed boundary or capture a broader page target.

Does a full-page screenshot capture a component’s entire internal scroll area?

fullPage covers the page’s scrollable document. It does not turn a component locator screenshot into a capture of every position in that component’s own scroll container.