michal
michal

Reputation: 103

Selenium Webdriver with Shadow DOM

While using Selenium Webdriver in C#, I get an exception when trying to select an element that exists under Shadow DOM.

The exception I am getting is: NoSuchElementException

How would you suggest to use Selenium with Shadow DOM?

Thanks,

Michal

img

Upvotes: 2

Views: 11732

Answers (6)

KR Akhil
KR Akhil

Reputation: 1017

Rules to find element inside Shadow DOM

  1. First find parent of Shadow Root element (also called Shadow Host) using normal Find Element method.
  2. Call the below helper methods to find the Shadow Root element by passing Shadow Host Element. Use below helper methods.
  3. In case of nested Shadow root. Keep repeating the above two steps. i.e. again find Shadow Host and then Shadow Root.
  4. If you have ID of the element. Then using the Shadow Root element. Find the element on which you want to perform action and perform desired action on it.
  5. If ID is not available - You can use any other identifier except XPath (XPath doesn't work in Shadow DOM).

You can also use SelectorsHub plugin to get the locator and sample code

Helper method 1

    /// <summary>
    /// Method 1 to fetch Shadow Root using Selenium's in build method
    /// </summary>
    /// <param name="shadowHostElement">IWebElement - Which is parent element of Shadow Root element. Also called Shadow Host</param>
    /// <returns>Shadow Root element in the form of ISearchContext</returns>
    public static ISearchContext GetShadowRootElement(IWebElement shadowHostElement)
    {
        ISearchContext shadowRootElement = shadowHostElement.GetShadowRoot();
        return shadowRootElement;
    }

Helper method 2

    /// <summary>
    /// Method 2 to fetch Shadow Root using Selenium's IJavaScriptExecutor
    /// </summary>
    /// <param name="driver">Instance of IWebDriver</param>
    /// <param name="shadowHostElement">IWebElement - Which is parent element of Shadow Root element. Also called Shadow Host</param>
    /// <returns>Shadow Root element in the form of ISearchContext</returns>
    public static ISearchContext GetShadowRootElementUsingJavaScript(IWebDriver driver, IWebElement shadowHostElement)
    {
        IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
        ISearchContext shadowRootElement = (ISearchContext)js.ExecuteScript("return arguments[0].shadowRoot", shadowHostElement);

        return shadowRootElement;
    }

Sample Code - Website with Shadow DOM

//1. Finding shadow host first - normally using FindElement            
IWebElement ShadowHostElement = Driver.FindElement(By.TagName("book-app"));
        
//2. Finding Shadow Root using Shadow Host element - use either one from below

ISearchContext shadowRoot = GetShadowRootElementUsingJavaScript(Driver, ShadowHostElement);
        ISearchContext shadowRoot = GetShadowRootElement(ShadowHostElement);

        IWebElement node = shadowRoot.FindElement(By.Id("input"));
        node.SendKeys("Something");

Upvotes: 0

GreenThus
GreenThus

Reputation: 1

That gives an error Andre, I don't know what to do now: OpenQA.Selenium.JavaScriptException : javascript error: missing ) after argument list (Session info: MicrosoftEdge=104.0.1293.63)

changed it to:

var element = js.ExecuteScript("return document.querySelector('#shadow-root').shadowRoot.querySelector('#private-host');");

but that resulted in: OpenQA.Selenium.JavaScriptException : javascript error: Cannot read properties of null (reading 'shadowRoot')

Upvotes: 0

MargaritaLadybird
MargaritaLadybird

Reputation: 11

You can create a Method, that accepts the list of ShadowDom root locators, and builds js script to execute and get shadow element:

public static IWebElement GetElementFromShadowDom(this IWebDriver driver, params string[] selectors)
    {
        IJavaScriptExecutor js = (IJavaScriptExecutor)driver;

        var scriptString = "return document.querySelector";
        var selectorIndex = 0;
        var stopIndex = selectors.Length - 1;

        foreach (var selector in selectors)
        {
            var root = "('" + selector + "')";
            root += (selectorIndex != stopIndex && selectors.Length != 1) ? ".shadowRoot.querySelector" : null;                
            selectorIndex++;
            scriptString += root;
        }

        var webElement = (IWebElement)js.ExecuteScript(scriptString);
        return webElement;
    }
}

Upvotes: 1

Anoop
Anoop

Reputation: 156

A very good repository having Selenium shadow DOM interactions written as Java Binding : https://github.com/sukgu/shadow-automation-selenium

This repo uses a bunch of JQueries to expand and identify elements in shadow dom.Please have a look.Should be easy to port it to C#

Upvotes: 0

Alex
Alex

Reputation: 1

I had the same problem. I found some values in the

Inspect -> Properties -> value (it can be something else)

Try:

WebElement element = driver.findElement(By.cssSelector("div"));
System.out.println(element.getAttribute("value"));

Upvotes: 0

Andrei
Andrei

Reputation: 5637

Try to locate your element like this:

driver.FindElement(By.CssSelector('selector_otside_shadow_root /deep/ selector_inside_shadow_root')); 

in your case it would be:

driver.FindElement(By.CssSelector('app-home /deep/ #itemName1'));

You can test this approach in chrome://downloads/ link with this css_selector:

downloads-manager /deep/ downloads-item /deep/ [id=file-link]

in dev tools. As you can see there was needed to pass two shadow-root elements, so make sure that you have only one shadow-root element or use multiple /deep/ like in example above.

or you can use JavasciptExecutor like this:

IJavaScriptExecutor js = (IJavaScriptExecutor)_driver;
var element = js.ExecuteScript("return document.querySelector('selector_outside_shadow_root').shadowRoot.querySelector('selector_inside_shadow_root');");
  • Note: the first suggestion as far I know works only in Chrome, if you want a cross browser solution - use the second one.

Upvotes: 5

Related Questions