selenium webdriver (chromedriver) and accessing shadow dom

前端 未结 4 2057
孤城傲影
孤城傲影 2020-12-12 02:34

I\'m testing out a new application that uses shadow dom like so:

 #shadow-root (open)
    
相关标签:
4条回答
  • 2020-12-12 03:20

    You can try this "heavy" approach (C# but depending on your language it can be something like that):

    public IWebElement DeepFind(By search)
    {
        try
        {
            // search a result in the main dom
            return Driver.FindElement(search);
        }
        catch (NoSuchElementException)
        {
            // if nothing we will take a look to the shadow dom(s)
            var shadowRoots = new List<IWebElement>();
            try
            {
                // will use the recursive method that search for all shadow roots
                ListShadowRoots(search, Driver.FindElements(By.XPath("//*")), shadowRoots);
            }
            catch (NoSuchElementException)
            {
                //
            }
            // return the first element that match the By search
            return shadowRoots.FirstOrDefault(s => s.FindElement(search) != null);
        }
    }
    
    private void ListShadowRoots(By search, ReadOnlyCollection<IWebElement> elements, List<IWebElement> shadowRoots)
    {
        elements.ToList().ForEach(e =>
        {
            var jsResult = (IWebElement)ExecuteJavascript("return arguments[0].shadowRoot", new object[] { e });
            if (jsResult != null)
            {
                shadowRoots.Add(jsResult);
                try
                {
                    ListShadowRoots(search, jsResult.FindElements(By.XPath("//*")), shadowRoots);
                }
                catch (NoSuchElementException)
                {
                    //
                }
            }
        });
    }
    
    private object ExecuteJavascript(string code, object[] args)
    {
        IJavaScriptExecutor js = (IJavaScriptExecutor)Driver;
        js.ExecuteScript(code, args);
    }
    

    Driver is the web driver (IWebDriver)

    Performances are not so bad and it does the job ;) Hope that it can help

    0 讨论(0)
  • 2020-12-12 03:28

    One way would be to use a piercing CSS selector (/deep/ or >>>). Though it's not supported by all the browsers and it may be removed in a future release.

    This one should give you the close button with Chrome 62:

    driver.findElement(By.css("* /deep/ button[title='Close']"))
    
    0 讨论(0)
  • 2020-12-12 03:28

    I took the information from MivaScott's answer and created a recursive method for my solution, I thought it could be of use to other people so here it is. I was using this to click a play button in a video player.

    All you need to provide is a string array of the CSS selectors of the shadow-root. The method will return the final shadow root element, so you can add another selector onto the end (in my case, an svg). Please see my example:

    The Player's Shadow-Root Structure

    public IWebElement PlayButton {
    
            get {
    
                string[] shadowRootSelectors = { "apc-controls", "apc-control-footer", "apc-toggle-play", "apc-icon-play" };
    
                return FindShadowRootElementRecursive(shadowRootSelectors).FindElement(By.CssSelector("svg"));
    
            }
            set {
            }
    
    }
    

    And the recursive method itself:

    public IWebElement FindShadowRootElementRecursive(string[] selectors = null, IWebElement element = null) {
    
                IWebElement root = null;
                IWebElement selectorElement = null;
                bool baseCase = false;
    
                //Get the first selector from the array
                string selector = selectors[0];
    
                if (selectors.Length == 1)
                {
    
                    baseCase = true;
    
                }
                else {
    
                    //If there are more selectors, then remove this selector and recurse with the rest
                    selectors = selectors.Where(w => w != selectors[0]).ToArray();
    
                }
    
                //If this is the first call...
                if (element == null)
                {
    
                    //Use the driver to select the element
                    selectorElement = Driver.FindElement(By.CssSelector(selector));
    
                }
                else {
    
                    //Otherwise, use the previously found element
                    selectorElement = element.FindElement(By.CssSelector(selector));
    
                }
    
                //Get the shadow root
                root = (IWebElement)((IJavaScriptExecutor)Driver).ExecuteScript("return arguments[0].shadowRoot", selectorElement);
    
                if (baseCase)
                {
    
                    return root;
    
                }
                else {
    
                    //Recurse
                    root = FindShadowRootElementRecursive(selectors, root);
    
                }
    
                return root;
    
            }
    

    I then clicked the button like so:

    PlayButton.Click();
    
    0 讨论(0)
  • 2020-12-12 03:35

    It's possible, but it will take a couple steps. As a preliminary, check out this page about accessing shadow dom. I found it really informative.

    Start with two methods to get the shadow dom element:

    private WebElement shadowDom;
    
    private WebElement expandRootElement(WebElement element) {
        WebElement ele = (WebElement) ((JavascriptExecutor) driver).executeScript("return arguments[0].shadowRoot",element);
        return ele;
    }
    private void findByShadowRoot(WebDriver driver) {
        shadowDom = expandRootElement(driver.findElement(By.id("whatEverTheShadowDomIdIs")));
    }
    

    From there, you create methods as a pseudo POM

    private WebElement findByShadowButton() {
        findByShadowRoot(driver);
        return shadowDom.findElement(By.cssSelector("div.th_fp_Close"));
    }
    

    Basically the first two methods are for creating a starting point, and then all the other methods call those methods and say, "from this starting point, find the element beneath it".

    Then you can statements like:

    findByShadowButton().click();
    
    0 讨论(0)
提交回复
热议问题