How to Screenshot a Web Page with Web Components in Selenium
Capture a whole page or a web component with Selenium. Learn to locate content inside Shadow DOM, wait for rendering, save screenshots, and fix common issues.
To screenshot a page that uses web components, use Selenium’s WebDriver screenshot method for the current browsing context. To capture only a component, locate its host as a WebElement and use that element’s screenshot method. For content inside Shadow DOM, first locate the host, get its shadow root, then find the internal element there. These Shadow DOM methods require Selenium 4 or later. [Selenium: Finding web elements]
The key distinction is capture scope: WebDriver captures the current browsing context, while an element screenshot captures the visible region within that element’s bounding rectangle. Page screenshots are best-effort; confirm full-page behavior for your browser and driver if the entire scrollable document matters. [Selenium: Working with windows and tabs] [Selenium JavaScript WebElement API]
1. Choose the screenshot scope
| Need | Use | What the image covers |
|---|---|---|
| The page around the component | Driver-level screenshot | The current browsing context, subject to best-effort browser and driver behavior |
| One component or a particular rendered part | Element screenshot | The visible region encompassed by that element’s bounding rectangle |
| One node inside Shadow DOM | Find it from the component’s shadow root, then take its element screenshot | The selected internal element’s visible region |
Shadow DOM is a search boundary, not a screenshot mode. First find the target in the right DOM context; then choose page-level or element-level capture. Selenium describes Shadow DOM as “an encapsulated DOM tree hidden inside an element.” [Selenium: Finding web elements]
2. Python: page, component, and Shadow DOM screenshots
Install Selenium with python -m pip install selenium and use a compatible browser and driver setup. This example uses Selenium Manager’s driver handling where supported by the installed Selenium version and local environment.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
URL = "https://example.com"
OUT = Path("screenshots")
OUT.mkdir(exist_ok=True)
options = webdriver.ChromeOptions()
# Uncomment to run without a visible browser window:
# options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
driver.get(URL)
wait = WebDriverWait(driver, 20)
# Replace this with a page-specific condition that means the component
# has rendered and its data is ready.
host = wait.until(lambda d: d.find_element(By.CSS_SELECTOR, "my-widget"))
# Whole current browsing context. Selenium describes this as best-effort.
if not driver.save_screenshot(str(OUT / "page.png")):
raise RuntimeError("WebDriver did not save the page screenshot")
# The visible region of the component host.
host.screenshot(str(OUT / "component.png"))
# Selenium 4+: locate a node inside the host's Shadow DOM.
shadow = host.shadow_root
inner = shadow.find_element(By.CSS_SELECTOR, ".chart")
inner.screenshot(str(OUT / "chart.png"))
finally:
driver.quit()
Replace my-widget and .chart with selectors from the page. The wait shown only waits for the host to exist; if the component fills asynchronously, wait for a meaningful internal state too, such as a rendered chart or a page-specific ready marker. Navigation completing does not establish that asynchronous component content is ready.
3. Other Selenium bindings
The same sequence applies in other bindings: wait for the component, capture the driver or host, and traverse the shadow root when the target is inside it. Check the current documentation for the Selenium version and binding used by your project. [Selenium: Working with windows and tabs]
JavaScript
const { Builder, By, until } = require('selenium-webdriver');
const fs = require('node:fs/promises');
(async () => {
const driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('https://example.com');
const host = await driver.wait(
until.elementLocated(By.css('my-widget')),
20000
);
// WebDriver screenshot returns Base64 PNG data.
const pageBase64 = await driver.takeScreenshot();
await fs.writeFile('page.png', Buffer.from(pageBase64, 'base64'));
// Element screenshot also returns Base64 image data.
const hostBase64 = await host.takeScreenshot();
await fs.writeFile('component.png', Buffer.from(hostBase64, 'base64'));
// Selenium 4+: traverse the host's Shadow DOM.
const shadow = await host.getShadowRoot();
const chart = await shadow.findElement(By.css('.chart'));
const chartBase64 = await chart.takeScreenshot();
await fs.writeFile('chart.png', Buffer.from(chartBase64, 'base64'));
} finally {
await driver.quit();
}
})();
Java
import java.nio.file.Path;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.com");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
WebElement host = wait.until(
ExpectedConditions.presenceOfElementLocated(By.cssSelector("my-widget"))
);
((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE)
.renameTo(Path.of("page.png").toFile());
host.getScreenshotAs(OutputType.FILE)
.renameTo(Path.of("component.png").toFile());
WebElement chart = host.getShadowRoot().findElement(By.cssSelector(".chart"));
chart.getScreenshotAs(OutputType.FILE)
.renameTo(Path.of("chart.png").toFile());
} finally {
driver.quit();
}
For Java production code, prefer moving the temporary screenshot file to its destination with Java NIO rather than relying on File.renameTo; the concise snippet shows the capture calls. Selenium’s official examples cover screenshot saving across bindings. [Selenium: Working with windows and tabs]
C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
var driver = new ChromeDriver();
try
{
driver.Navigate().GoToUrl("https://example.com");
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(20));
var host = wait.Until(d => d.FindElement(By.CssSelector("my-widget")));
((ITakesScreenshot)driver).GetScreenshot().SaveAsFile("page.png");
host.GetScreenshot().SaveAsFile("component.png");
var shadow = host.GetShadowRoot();
var chart = shadow.FindElement(By.CssSelector(".chart"));
chart.GetScreenshot().SaveAsFile("chart.png");
}
finally
{
driver.Quit();
}
4. Wait for the component’s rendered state
- Navigate to the page.
- Wait for the host element to exist.
- Wait for the component’s content or application-specific ready state if it renders asynchronously.
- Locate the internal target through the shadow root when needed.
- Capture the page or chosen element and verify that the output file is nonempty and readable.
Use a condition that corresponds to the screenshot you need. Waiting only for the host to appear can still capture a loading state if the component hydrates, fetches data, or paints later. A fixed sleep can be used for a known animation delay, but a state-based wait is usually more reliable because it ends when the expected condition is met.
5. Shadow DOM locator details and edge cases
- Open Shadow DOM: find the host with a normal page locator, call Selenium 4’s shadow-root method, and search from that root.
- Nested components: repeat the host → shadow root → child host sequence at each boundary.
- Slot content: slotted nodes may belong to the light DOM even though they render inside a component. Locate them in the DOM context where they are defined, and check the rendered result before choosing the screenshot target.
- Multiple matching hosts: use a stable distinguishing attribute or wait for the intended host; selecting the first match can capture the wrong instance.
- Closed Shadow DOM: ordinary WebDriver shadow-root traversal may not expose a closed root. If the internal target cannot be located, capture the host or page instead, or use an application-provided test hook.
- Frames: switch into the relevant frame before finding its component. A locator in the top-level browsing context cannot directly find frame content.
- Element visibility: element screenshots concern the visible region. Scroll the element into view if necessary, and account for clipping or overlays in the page.
- Full-page expectations: driver screenshots are best-effort, with behavior depending on browser and driver. Verify the exact environment rather than assuming the whole scrollable document is included.
6. Saving and checking the image
Python’s save_screenshot(path) writes the driver image and reports success as a boolean; a WebElement has screenshot(path). Selenium’s JavaScript WebDriver screenshot returns Base64 PNG data, which must be decoded to bytes before writing. Element screenshots likewise return image data in the JavaScript API. [Selenium: Working with windows and tabs] [Selenium JavaScript WebElement API]
Use PNG when you need lossless output or reliable pixel comparison. Keep the browser version, viewport, device scale, fonts, and page state consistent for visual regression work. Screenshot APIs in the examples save PNG; convert formats separately if your pipeline requires another image type.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
NoSuchShadowRootException or missing shadow root |
The element is not a shadow host, the component has not initialized, or the installed binding/version lacks the API. | Confirm the host selector and component readiness; use Selenium 4 or later and inspect the page’s component structure. |
NoSuchElementException inside the component |
The selector is wrong, content is not rendered yet, or the search starts at the wrong shadow root. | Wait for the relevant state, verify the selector, and traverse each nested shadow boundary. |
| Screenshot shows a loading skeleton | The screenshot was taken after navigation but before asynchronous rendering finished. | Wait for a page-specific ready marker or the actual target element, not just navigation or host presence. |
| Screenshot is cropped or not full page | Element capture is bounded to the element’s visible region; driver-level capture is best-effort. | Choose the correct scope and verify full-page support in the browser/driver combination. For a component, capture its host or intended internal element. |
| Element screenshot fails because it is offscreen | The target is outside the visible area or obscured. | Scroll it into view, wait for layout to settle, and check for overlays or clipping. |
| Frame content cannot be found | WebDriver is still in the top-level context. | Switch to the frame first, then locate the component within that frame. |
| Output file is empty or invalid | Screenshot bytes were not saved or Base64 data was written as text. | Check the save result; in JavaScript decode Base64 into a buffer before writing. |
| Browser session hangs or screenshot times out | The page or driver is stalled, the wait is unbounded, or browser resources are exhausted. | Set bounded waits, close each driver in a finally block, and capture only after the required state is reached. |
8. Performance, reliability, and cost
Capturing a single element is often operationally simpler when only that component is needed because it avoids storing unrelated page pixels; the API’s defined boundary is the visible element rectangle. This is a workflow consideration, not a speed benchmark. Reusing a browser session across captures can avoid repeated startup work, while ensuring each capture waits for its own page state and cleans up after errors.
For stable automated output, pin the browser and Selenium environment used by the job, set the viewport explicitly, wait on application state, and retry only transient navigation or readiness failures. Avoid blindly retrying a deterministic missing selector: it adds delay without changing the result. Selenium itself has no per-screenshot charge described in these APIs; account for your browser execution, compute, storage, and maintenance costs.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a screenshot or PDF; for this workflow, point it at the page URL. It captures a page rather than using Selenium to traverse a component’s Shadow DOM, so use Selenium when you need to select an internal node or run browser automation around the capture.
Before the shot, ScreenshotNeo accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets. Each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers identify the page verdict and whether the request was billed. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. Every feature is on every plan: 1,000 shots/month free with no card; Starter is $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.
See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
await Bun.write('shot.webp', res);
For Node.js without Bun, save the response bytes with fs.writeFile after reading the response body as an ArrayBuffer. These examples use the supplied request shape; check the response headers and API documentation for verdict and billing details. Sign up free for 1,000 screenshots a month, with no card required.
10. Frequently asked questions
Can Selenium screenshot a web component directly?
Yes. Find its host as a WebElement and take an element screenshot. That captures the visible region of the host’s bounding rectangle.
Can Selenium screenshot an element inside a Shadow DOM?
Yes, when its shadow root is accessible: obtain the host’s shadow root, find the internal element there, then capture that element. Selenium documents this flow for Selenium 4 and later.
Does a driver screenshot always include the entire scrollable page?
No. Selenium describes page screenshots as best-effort. Verify the behavior for the browser and driver your project uses.
Which should I save: the page or the component?
Choose the page when surrounding context matters. Choose the element when the output should be limited to the component or a specific rendered part.


