How to Screenshot a Web Page with Shadow DOM Content in Playwright
Capture open Shadow DOM content with Playwright locators, choose between page and element screenshots, and understand the limits of closed roots.
Playwright locators can find elements inside open Shadow DOM by default. Use page.screenshot() to capture the viewport or the full scrollable page, and locator.screenshot() to capture one matched element. XPath does not pierce shadow roots, and Playwright does not support closed-mode shadow roots. Playwright locator documentation
The examples below use TypeScript with Playwright Test. Replace the example URL and target text or accessible name with values from your page.
1. Capture a page containing open Shadow DOM
This runnable Playwright Test example waits for text inside an open shadow root, then saves both a full-page screenshot and an element screenshot:
import { test, expect } from '@playwright/test';
test('capture open Shadow DOM content', async ({ page }) => {
await page.goto('https://example.com');
// Playwright text locators pierce open shadow roots.
const details = page.getByText('Details', { exact: true });
await expect(details).toBeVisible();
// Capture the whole scrollable document.
await page.screenshot({ path: 'page.png', fullPage: true });
// Capture only the matched element's bounds.
await details.screenshot({ path: 'details.png' });
});
Install and run the test with the project’s Playwright Test setup. For a standalone script, use the same locator and screenshot calls inside an async function after launching a browser, creating a page, and navigating to the target URL.
Prefer locators that describe the content or purpose, such as getByRole() and getByText(). For example, if a web component exposes an accessible button:
const save = page.getByRole('button', { name: 'Save settings' });
await save.waitFor({ state: 'visible' });
await save.screenshot({ path: 'save-button.png' });
See the Playwright screenshots guide and Page screenshot API reference for the current API details.
2. Choose the screenshot scope
| Goal | Method | What the image contains |
|---|---|---|
| Visible viewport | page.screenshot({ path: 'page.png' }) |
The currently visible page area. |
| Full scrollable document | page.screenshot({ path: 'page.png', fullPage: true }) |
The full page as a tall screenshot. |
| One component or matched element | locator.screenshot({ path: 'component.png' }) |
The matched element’s bounds. It does not mean the entire page. |
A locator screenshot of a scrollable element includes only the content at that element’s current scroll position. If you need the whole document, use a full-page page screenshot. If you need a particular region, locate that element and capture it. Locator screenshot API reference
3. Make captures repeatable with screenshot options
Playwright’s screenshot options let you control the output and adjust the page during capture. Check the API reference for the full list and version-specific details. These options are useful when the page includes animation, volatile content, or a component that needs consistent styling:
fullPage: truecaptures the full scrollable document rather than only the viewport.typeselects an image format supported by the API, such as PNG or JPEG.clipcaptures a specified page region.animationscontrols animation handling during capture.maskcan cover selected locators in the screenshot.styleapplies a stylesheet during capture; the API documents that this stylesheet pierces Shadow DOM and inner frames.
For example, apply temporary CSS through shadow roots to hide a volatile element while taking a full-page image:
await page.screenshot({
path: 'page-clean.png',
fullPage: true,
style: `
.volatile-widget {
visibility: hidden !important;
}
`,
});
Review selectors against the page’s actual component structure. The screenshot style option can reach through Shadow DOM, but selectors still need to match the elements you intend to alter. Refer to the Page screenshot API reference for supported option names and values.
4. Know the Shadow DOM boundaries
Playwright’s locator guide documents two important limits:
- XPath does not pierce shadow roots. If a target is inside an open root, use a supported locator such as a role, text, or CSS locator instead of XPath.
- Closed-mode shadow roots are unsupported. Playwright does not provide a documented locator workaround for reaching into a closed root. If you control the application, expose an appropriate public or testable interface. Otherwise, capture a higher-level page area if that meets the need.
Open roots are traversable by Playwright locators by default; that does not mean every selector strategy works across them. See the locator guide’s Shadow DOM behavior and exceptions.
5. Troubleshoot common capture problems
| Symptom | Likely cause | What to do |
|---|---|---|
| The locator cannot find text or a control inside a component. | The content may not have rendered yet, the locator may not match the actual text or accessible name, or the component may use a closed root. | Wait for the intended content, check the locator against the page, and confirm the root is open. Use role or text locators where possible. |
| An XPath locator cannot reach the target. | XPath does not pierce Shadow DOM. | Replace it with a role, text, or suitable CSS locator. |
| The element screenshot is smaller than expected. | locator.screenshot() clips to the matched element’s bounds. |
Use page.screenshot({ fullPage: true }) for the document, or target a larger element. |
| A scrollable component’s screenshot omits some content. | The locator screenshot includes only the element’s currently scrolled content. | Set the component’s scroll position before capture, or capture the full document if that is the required scope. |
| Temporary CSS does not hide the intended content. | The selector does not match the component’s actual structure, or the stylesheet targets a different element. | Inspect the page structure and adjust the selector; the screenshot style option can pierce Shadow DOM and inner frames. |
| The image captures an intermediate page state. | The target had not reached the state required for the screenshot. | Wait for a meaningful locator or visibility condition before capturing, as in the test example. |
6. Or skip the browser setup
If you need a screenshot without setting up and running a browser, ScreenshotNeo accepts a URL in one GET request and returns an image or PDF. See the ScreenshotNeo API documentation. This captures the rendered page; it does not provide Playwright locator access to a particular element inside Shadow DOM.
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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
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 about ScreenshotNeo, then sign up for 1,000 free screenshots a month, no card required.
7. Performance, reliability, and cost
Playwright screenshots run as part of your browser workflow, so the capture depends on navigating to the page and waiting for the target state you need. Keep waits specific to the content being captured; a locator wait makes the intended condition clearer than taking an image immediately after navigation. Full-page images cover more content than viewport or element images, so choose the smallest scope that answers your use case.
ScreenshotNeo offers caching with a configurable TTL, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, and a usage API. Its stated billing rule is that only clean shots are billed; cache hits and bot checks, blank pages, timeouts, and failed loads cost nothing, with verdict and billing information in response headers. Plans are Free (1,000 shots/month), Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000); yearly billing gives two months free, and every feature is available on every plan.
8. Frequently asked questions
Does a page screenshot include open Shadow DOM content?
Playwright locators can locate elements in open roots. Use a page screenshot for the page image or a locator screenshot for one matched element.
Can Playwright screenshot content inside a closed shadow root?
Closed-mode roots are not supported by Playwright locators. Use an application interface that exposes what you need, if available, or capture a broader page area.
Does fullPage: true capture every component’s internal scroll area?
It captures the full scrollable document. A locator screenshot of a scrollable element includes only that element’s current scroll position; handle nested scrolling separately when it matters.
Can ScreenshotNeo target a Shadow DOM component like a Playwright locator?
The ScreenshotNeo facts provided here describe URL-based page capture, not locator-based selection of a Shadow DOM element. Use Playwright when the capture must target a particular matched element.


