Селен вебдрайвер (chromedriver) и доступ к теневой дом

Я тестирую новое приложение, которое использует shadow dom следующим образом:

 #shadow-root (open)
    <div class="th_filePicker">
        <div class="th_fp_header">
            <div class="th_fp_title" role="heading" aria-level="1" data-l10n-id="th_fp_title">Select Image</div>
                <div class="th_fp_Close"><button class="close-popup" data-l10n-id="close_popup" title="Close"></button></div>
        </div>
    </div>

Кто-нибудь имеет представление о том, как я могу получить доступ к элементам в элементе управления выбора файлов - в частности, значок закрытия?

3 ответа

Вы можете попробовать этот "тяжелый" подход (C#, но в зависимости от вашего языка это может быть что-то вроде этого):

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);
}

Драйвер - это веб-драйвер (IWebDriver)

Выступления не так уж плохи, и это делает работу;) Надеюсь, что это может помочь

Это возможно, но это займет пару шагов. Предварительно проверьте эту страницу о доступе к теневому домену. Я нашел это действительно информативным.

Начнем с двух методов, чтобы получить элемент shadow dom:

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")));
}

Оттуда вы создаете методы как псевдо POM

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

По сути, первые два метода предназначены для создания начальной точки, а затем все остальные методы вызывают эти методы и говорят: "Из этой начальной точки найдите элемент под ней".

Тогда вы можете заявления, как:

findByShadowButton().click();

Одним из способов было бы использовать проникающий селектор CSS (/deep/ или же >>>). Хотя он поддерживается не всеми браузерами и может быть удален в будущем выпуске.

Этот должен дать вам кнопку закрытия с Chrome 62:

driver.findElement(By.css("* /deep/ button[title='Close']"))

Я взял информацию из ответа MivaScott и создал рекурсивный метод для своего решения, я подумал, что он может быть полезен другим людям, так что вот он. Я использовал это, чтобы нажать кнопку воспроизведения в видеоплеере.

Все, что вам нужно предоставить, - это строковый массив CSS-селекторов теневого корня. Метод вернет последний теневой корневой элемент, поэтому вы можете добавить еще один селектор в конец (в моем случае это svg). Пожалуйста, посмотрите мой пример:

Структура теневого корня игрока

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 {
        }

}

И сам рекурсивный метод:

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;

        }

Затем я нажал на кнопку так:

PlayButton.Click();
Другие вопросы по тегам