Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To read text inside a Shadow DOM with Selenium WebDriver, first find the component’s host in the regular page, get its shadow root, find the target from that root, then call getText() on the target. In Selenium’s JavaScript binding, the essential sequence is asynchronous: await each lookup and root operation before using its result. Shadow-root methods require Selenium 4.0 or later.

How the host-to-root-to-element lookup works

A shadow tree is not searched like an ordinary descendant of the document. A page-level lookup finds the component that owns the tree—the shadow host. The host’s shadow root then becomes the search context for elements inside that tree. Read text from the resulting target element, not from the host, unless the host itself is what you intend to inspect.

  1. Locate the host with a selector that works in the regular document.
  2. Call getShadowRoot() on that host.
  3. Find the target using the returned root as the search context.
  4. Call getText() on the target and use the returned text.

This scoped lookup pattern is the practical interface Selenium documents for reaching descendants of a shadow tree. Selenium describes its JavaScript ShadowRoot as providing functions to retrieve elements that live in the DOM below the ShadowRoot.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

JavaScript example

The following shows the core operations using Selenium’s JavaScript API. It assumes driver is an initialized WebDriver instance and that the page has loaded the component.

const host = await driver.findElement(By.css('my-widget'));
const shadowRoot = await host.getShadowRoot();
const target = await shadowRoot.findElement(By.css('.message'));
const text = await target.getText();

console.log(text);

Import By from the Selenium WebDriver package in the surrounding test or script. The example deliberately awaits each operation: the JavaScript WebDriver methods are asynchronous, and a later call needs the element or root returned by the preceding one.

Replace my-widget and .message with selectors from the actual page. The host selector is evaluated against the document; the target selector is evaluated inside that host’s shadow root. A selector that finds the target in DevTools or in a regular page-level Selenium lookup may still fail when used in the wrong context.

Java binding pattern

The Java binding follows the same sequence. Selenium’s finding-elements guide describes obtaining a shadow root from a WebElement, then searching the returned SearchContext for descendants. With a configured WebDriver named driver, the essential operations are:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
WebElement host = driver.findElement(By.cssSelector("my-widget"));
SearchContext shadowRoot = host.getShadowRoot();
WebElement target = shadowRoot.findElement(By.cssSelector(".message"));
String text = target.getText();

Use the imports and driver setup appropriate to the Java project. The significant detail is that the second lookup is called on shadowRoot, not on driver. The returned text is then obtained from the target WebElement.

Reading nested shadow roots

When a component inside one shadow tree hosts another component, cross each boundary explicitly. Find the inner host from the current root, get that host’s root, and search within it. Repeat for every level between the document and the text-bearing element.

const outerHost = await driver.findElement(By.css('outer-widget'));
const outerRoot = await outerHost.getShadowRoot();
const innerHost = await outerRoot.findElement(By.css('inner-widget'));
const innerRoot = await innerHost.getShadowRoot();
const target = await innerRoot.findElement(By.css('.message'));
const text = await target.getText();

This is a sequence of scoped searches, not a special one-step selector into arbitrary component internals. If a host does not expose a shadow root to WebDriver, the sequence cannot proceed through that host. Confirm the component structure and what the page makes available before treating the final selector as the problem.

What getText() returns—and what it does not promise

For visible text, getText() is the documented choice. Selenium’s JavaScript API describes it as the element’s visible (not CSS-hidden) innerText, including sub-elements and without leading or trailing whitespace. That description is not a promise to return raw textContent, hidden text, or whitespace exactly as stored in the DOM.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Decide what the test is asserting before choosing a retrieval method:

  • Visible user-facing copy: use getText() and assert the text a user can see.
  • Hidden or exact DOM text: the documented getText() semantics may not match the requirement. Verify a method that exposes the specific DOM property you need against the binding and page behavior.
  • Whitespace-sensitive content: do not assume leading, trailing, or internal formatting is preserved as raw DOM text by getText(); check the returned value for the page and assertion in question.

Wait for the component’s actual readiness

A component may render its shadow root or its target asynchronously. A lookup performed before that work completes can fail even when the selectors are correct. Synchronize the test with a readiness condition that reflects the page—for example, the application state or element that indicates the component has finished rendering—rather than adding an arbitrary fixed pause.

Keep the readiness condition aligned with what is actually needed. If the host exists before the shadow tree is populated, waiting only for the host is insufficient. If the root exists before the target is added, the target lookup still needs to happen after the target is available. A useful diagnosis is to identify which stage is not ready: host, root, or target.

Common failures and how to diagnose them

Observed failure What it indicates What to check
NoSuchShadowRootError in the JavaScript API The selected host has no shadow root available through this operation. Confirm the selector matched the intended component host, that the component has rendered, and that it exposes a root WebDriver can access.
NoSuchElementError from ShadowRoot.findElement() The requested descendant was not found in the root used for that lookup. Check the target selector and search context. Verify that the element belongs to this root and has been rendered before the lookup.
The host lookup fails The first, document-level step did not locate a matching host. Check the page-level host selector and whether the component is present at lookup time. Do not troubleshoot the inner selector until the host is found.
A lookup works at one nesting level but not the next A host, root, or selector at a particular boundary may be wrong or not ready. Inspect the sequence one operation at a time. Find the inner host from its containing root, then obtain that host’s root before searching further.
Text is missing, trimmed, or differs from raw markup The expected value may not match visible-text semantics. Check whether the requirement is visible text, hidden text, raw DOM text, or whitespace-preserving text. getText() is documented as visible innerText without leading or trailing whitespace.
The method is unavailable in the project The installed Selenium version or language binding may not expose the documented operation. Shadow-root methods require Selenium 4.0 or later. Check the installed binding and its API documentation, and verify browser and driver compatibility for the versions used in the project.

The JavaScript API documents the distinction between a missing root and a missing descendant. Preserve that distinction in debugging: changing a descendant selector will not solve a failure to obtain the root, and changing the host selector will not fix a target selector scoped to the wrong root.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Version and portability notes

Selenium’s finding-elements documentation sets Selenium 4.0 as the threshold for shadow-root methods. The exact calls depend on the language binding, and the JavaScript API uses asynchronous operations. Use the method exposed by the Selenium version installed in the project rather than assuming that an older client or every browser-and-driver pairing behaves identically.

The WebDriver specification defines commands for retrieving an element’s shadow root and for retrieving element text. Selenium’s language APIs are the practical interfaces used in tests. If behavior differs across environments, verify the binding and browser/driver versions in that environment and confirm the component’s rendering and root availability.

Or skip the browser setup

If your goal is a visual record of a page rather than extracting DOM text for a WebDriver assertion, ScreenshotNeo provides a screenshot API. It is not a replacement for reading text from a shadow-root element; it returns a screenshot or PDF rather than a DOM text value. For an eligible page capture, one GET request can look like this:

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

See the ScreenshotNeo documentation for request options. Before capture, it accepts cookie or consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Try ScreenshotNeo for visual captures, or sign up free for 1,000 screenshots a month with no card.

Sources

Frequently Asked Questions

Can I use a CSS selector from the page directly with `driver.findElement()` to reach a shadow descendant?

No. Use the host’s shadow root as the search context for descendants inside that root; a normal document lookup is for the host.

Does `getText()` return hidden text in a shadow tree?

Its documented JavaScript semantics are visible text, not CSS-hidden text. For hidden or exact DOM text, verify a method that matches that requirement.

Can Selenium read text from nested shadow components?

Yes, when each host exposes a root WebDriver can access. Find each inner host from its containing root, then obtain and search its root.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.