Accessing shadow DOM elements in headless Chrome fails while working in standard browser.

I’m testing a Chrome extension using Selenium 4.3.0 with Java, and I need to dynamically get the extension ID because it changes with each release. Instead of hardcoding it, I’m attempting to retrieve it from the Chrome extensions page, specifically from the shadow root element.

Here’s how the HTML structure looks, focusing on the highlighted area I want to access:

In standard Chrome, my code below successfully fetches the ID:

JavascriptExecutor js = (JavascriptExecutor) driver;
WebElement webElement = (WebElement) js.executeScript("return document.querySelector('body > extensions-manager').shadowRoot.querySelector('#items-list').shadowRoot.querySelector('extensions-item')");

However, when I run it in headless mode with this code:

ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments('--headless');
chromeOptions.addExtensions(new File('Chrome_Extension.crx'));
WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver(chromeOptions);

It breaks and I receive this error:
org.openqa.selenium.JavascriptException: javascript error: Cannot read properties of null (reading 'shadowRoot')

Why does it work in normal mode but fail in headless? Any ideas on how to address this issue would be great. Also, if anyone has a different method for fetching the extension ID that might be more reliable, I’d appreciate your suggestions.

The issue is that headless Chrome initializes DOM elements differently than regular Chrome. Shadow DOM components in chrome://extensions/ aren’t fully loaded when your script runs, which causes the null shadowRoot error. Don’t bother fighting timing issues - just use the chrome.management API instead. Inject a content script into any page that calls chrome.management.getAll() to get all extension details including IDs. This skips the extensions page completely and works the same in both headless and regular modes. Or you can check the Chrome user data directory where extension manifests live. Each extension folder uses its ID as the name, so scan the filesystem for your extension’s name in the manifest files. Way more reliable than trying to parse shadow DOM elements that act weird across different Chrome modes.

I’ve hit this same issue with shadow DOM elements in headless Chrome. Shadow DOM takes longer to load in headless mode than regular Chrome, so the extensions-manager component probably isn’t ready when your script runs - hence the null reference.

Add a proper wait before your shadow DOM query. Use WebDriverWait with a custom ExpectedCondition that checks if the shadow root exists. Or throw in a quick Thread.sleep() to test if timing’s really the problem.

Here’s what worked for me: skip shadow DOM completely. Just grab the chrome://extensions/ page source and use regex to find extension IDs. They follow a specific format, so you can pull them straight from the page content without dealing with shadow root headaches.

headless chrome can be tricky. try using --disable-web-security and --disable-features=VizDisplayCompositor for better results. also, the shadow dom may not load fast enough, so putting your script in a try-catch loop with a delay could help until it finally works.