web-dev-qa-db-ja.com

Seleniumを使用したShadowDOMツリーへのアクセス

Selenium/Chrome Webdriverを使用してShadowDOM内の要素にアクセスすることは可能ですか?

予想どおり、通常の要素検索方法を使用しても機能しません。 w3cで switchToSubTree 仕様への参照を見ましたが、実際のドキュメントや例などを見つけることができませんでした。

誰もがこれで成功しましたか?

18
lambinator

残念ながら、webdriver仕様はまだこれをサポートしていないようです。

私の詮索が明らかになりました:

http://www.w3.org/TR/webdriver/#switching-to-hosted-shadow-doms

https://groups.google.com/forum/#!msg/Selenium-developers/Dad2KZsXNKo/YXH0e6eSHdAJ

4
Damen TheSifter

受け入れられた回答は無効になり、他の回答のいくつかにはいくつかの欠点があるか、実用的ではありません(_/deep/_セレクターは機能せず、非推奨です。document.querySelector('').shadowRootは最初のシャドウ要素でのみ機能しますシャドウ要素がネストされている場合)、シャドウルート要素がネストされ、2番目のシャドウルートがドキュメントルートに表示されない場合がありますが、親がアクセスするシャドウルートでは使用できます。 Seleniumセレクターを使用して、シャドウルートを取得するためだけにスクリプトを挿入する方が良いと思います。

_def expand_shadow_element(element):
  shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
  return shadow_root

outer = expand_shadow_element(driver.find_element_by_css_selector("#test_button"))
inner = outer.find_element_by_id("inner_button")
inner.click()
_

これを理解するために、Chromeのダウンロードページでテスト可能な例を追加しました。検索ボタンをクリックするには、ネストされた3つのシャドウルート要素を開く必要があります。 enter image description here

_import Selenium
from Selenium import webdriver
driver = webdriver.Chrome()


def expand_shadow_element(element):
  shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
  return shadow_root

driver.get("chrome://downloads")
root1 = driver.find_element_by_tag_name('downloads-manager')
shadow_root1 = expand_shadow_element(root1)

root2 = shadow_root1.find_element_by_css_selector('downloads-toolbar')
shadow_root2 = expand_shadow_element(root2)

root3 = shadow_root2.find_element_by_css_selector('cr-search-field')
shadow_root3 = expand_shadow_element(root3)

search_button = shadow_root3.find_element_by_css_selector("#search-button")
search_button.click()
_

他の回答で提案されているのと同じアプローチを実行すると、クエリがハードコーディングされ、読みにくくなり、他のアクションに中間選択を使用できないという欠点があります。

_search_button = driver.execute_script('return document.querySelector("downloads-manager").shadowRoot.querySelector("downloads-toolbar").shadowRoot.querySelector("cr-search-field").shadowRoot.querySelector("#search-button")')
search_button.click()
_
12

SeleniumバイナリChromeドライバーがShadowDOMをサポートするようになりました(2015年1月28日以降): http://chromedriver.storage.googleapis.com/2.14/ notes.txt

5
djangofan

私はC#とSeleniumを使用しており、Javaスクリプトを使用して、ネストされたシャドウDOM内の要素を見つけることができました。これは私のhtmlツリーです。

htmlツリー

最後の行にURLが必要です。それを取得するには、最初に「downloads-manager」タグを選択し、次にそのすぐ下にある最初のシャドウルートを選択します。シャドウルート内に入ったら、次のシャドウルートに最も近い要素を見つけたいと思います。その要素は「downloads-item」です。これを選択すると、2番目のシャドウルートを入力できます。そこから、id = "file-icon"のURLを含むimgアイテムを選択します。ついに、探しているURLを含む属性「src」を取得できます。

トリックを実行するC#コードの2行:

IJavaScriptExecutor jse2 = (IJavaScriptExecutor)_driver;
var pdfUrl = jse2.ExecuteScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-item').shadowRoot.getElementById('file-icon').getAttribute('src')");
3
Erik Selin

SeleniumがShadowDOMをすぐにサポートするまで、Javaで次の回避策を試すことができます。 Byクラスを拡張するクラスを作成します。

import org.openqa.Selenium.By;
import org.openqa.Selenium.JavascriptExecutor;
import org.openqa.Selenium.SearchContext;
import org.openqa.Selenium.WebDriverException;
import org.openqa.Selenium.WebElement;
import org.openqa.Selenium.WrapsDriver;
import org.openqa.Selenium.internal.FindsByCssSelector;

import Java.io.Serializable;
import Java.util.List;

public class ByShadow {
    public static By css(String selector) {
        return new ByShadowCss(selector);
    }

    public static class ByShadowCss extends By implements Serializable {

        private static final long serialVersionUID = -1230258723099459239L;

        private final String cssSelector;

        public ByShadowCss(String cssSelector) {
            if (cssSelector == null) {
                throw new IllegalArgumentException("Cannot find elements when the selector is null");
            }
            this.cssSelector = cssSelector;
        }

        @Override
        public WebElement findElement(SearchContext context) {
            if (context instanceof FindsByCssSelector) {
                JavascriptExecutor jsExecutor;
                if (context instanceof JavascriptExecutor) {
                    jsExecutor = (JavascriptExecutor) context;
                } else {
                    jsExecutor = (JavascriptExecutor) ((WrapsDriver) context).getWrappedDriver();
                }
                String[] subSelectors = cssSelector.split(">>>");
                FindsByCssSelector currentContext = (FindsByCssSelector) context;
                WebElement result = null;
                for (String subSelector : subSelectors) {
                    result = currentContext.findElementByCssSelector(subSelector);
                    currentContext = (FindsByCssSelector) jsExecutor.executeScript("return arguments[0].shadowRoot", result);
                }
                return result;
            }

            throw new WebDriverException(
                    "Driver does not support finding an element by selector: " + cssSelector);
        }

        @Override
        public List<WebElement> findElements(SearchContext context) {
            if (context instanceof FindsByCssSelector) {
                JavascriptExecutor jsExecutor;
                if (context instanceof JavascriptExecutor) {
                    jsExecutor = (JavascriptExecutor) context;
                } else {
                    jsExecutor = (JavascriptExecutor) ((WrapsDriver) context).getWrappedDriver();
                }
                String[] subSelectors = cssSelector.split(">>>");
                FindsByCssSelector currentContext = (FindsByCssSelector) context;
                for (int i = 0; i < subSelectors.length - 1; i++) {
                    WebElement nextRoot = currentContext.findElementByCssSelector(subSelectors[i]);
                    currentContext = (FindsByCssSelector) jsExecutor.executeScript("return arguments[0].shadowRoot", nextRoot);
                }
                return currentContext.findElementsByCssSelector(subSelectors[subSelectors.length - 1]);
            }

            throw new WebDriverException(
                    "Driver does not support finding elements by selector: " + cssSelector);
        }

        @Override
        public String toString() {
            return "By.cssSelector: " + cssSelector;
        }
    }
}

また、追加の関数やラッパーを記述せずに使用できます。これは、あらゆる種類のフレームワークで機能するはずです。たとえば、純粋なSeleniumコードでは、これは次のようになります。

WebElement searchButton =
    driver.findElement(ByShadow.css(
        "downloads-manager >>> downloads-toolbar >>> cr-search-field >>> #search-button"));

またはセレニドを使用する場合:

SelenideElement searchButton =
    $(ByShadow.css("downloads-manager >>> downloads-toolbar >>> cr-search-field >>> #search-button"));
2

ShadowDomから要素を取得するはるかに簡単な方法を見つけました。 Chromeダウンロードページ検索アイコンについて、上記と同じ例を使用しています。

IWebDriver driver;

public IWebElement getUIObject(params By[] shadowRoots)
        {
            IWebElement currentElement = null;
            IWebElement parentElement = null;
            int i = 0;
            foreach (var item in shadowRoots)
            {
                if (parentElement == null)
                {
                    currentElement = driver.FindElement(item);
                }
                else
                {
                    currentElement = parentElement.FindElement(item);
                }
                if(i !=(shadowRoots.Length-1))
                {
                    parentElement = expandRootElement(currentElement);
                }
                i++;
            }
            return currentElement;
        }

 public IWebElement expandRootElement(IWebElement element)
        {
            IWebElement rootElement = (IWebElement)((IJavaScriptExecutor)driver)
        .ExecuteScript("return arguments[0].shadowRoot", element);
            return rootElement;
        }

Google Chromeダウンロードページ

画像に示されているように、検索アイコンを取得するには、3つのシャドウルート要素を展開する必要があります。アイコンをクリックするために必要なのは:-

  [TestMethod]
        public void test()
        {
           IWebElement searchButton= getUIObject(By.CssSelector("downloads-manager"),By.CssSelector("downloads-toolbar"),By.Id("search-input"),By.Id("search-buton"));
            searchButton.Click();
        }

したがって、1行だけでWeb要素が得られます。関数「getUIObject」の最初の引数として最初のシャドウルート要素を渡すようにしてください。関数の2番目の引数として2番目のシャドウルート要素を渡す必要があります。など、最後に関数の最後の引数は実際の要素の識別子になります(この場合は '検索ボタン'

2
Akash Jha

通常、これを行います。

element = webdriver.find_element_by_css_selector(
    'my-custom-element /deep/ .this-is-inside-my-custom-element')

そしてうまくいけば、それは機能し続けるでしょう。


ただし、/deep/::shadow非推奨であることに注意してください(OperaおよびChrome)。静的プロファイルでそれらを許可することについては多くの話があります。つまり、それらのクエリは機能しますが、スタイル設定は機能しません。

/deep/または::shadowに依存したくない場合は、それらの将来が少し不確実であるため、またはより良いクロスブラウザーで作業したいため、または非推奨の警告が嫌いであるため、別の方法があるので喜んでください。

# Get the shadowRoot of the element you want to intrude in on,
# and then use that as your root selector.
shadow_root = webdriver.execute_script('''
    return document.querySelector(
        'my-custom-element').shadowRoot;
    ''')
element = shadow_root.find_element_by_css_selector(
    '.this-is-inside-my-custom-element')

これについての詳細:

2

これは私にとってはうまくいきました(Selenium javascriptバインディングを使用):

driver.executeScript("return $('body /deep/ <#selector>')")

それはあなたが探している要素を返します。

1
jeremysklarsky

Chromeで最新のダウンロードファイルのファイル名を取得するため

def get_downloaded_file(self):
  filename = self._driver.execute_script("return document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList downloads-item').shadowRoot.querySelector('div#content  #file-link').text")
  return filename

使用法:

driver.get_url('chrome://downloads')
filename = driver.get_downloaded_file()

また、Seleniumのデフォルトのダウンロードディレクトリをchromeブラウザに設定するためのオプションを設定します。対応するファイルを取得できます。

..
chrome_options = webdriver.ChromeOptions()
..
prefs = {'download.default_directory': '/desired-path-to-directory'} # unix
chrome_options.add_experimental_option('prefs', prefs)
..
0
Arnab Das