I\'m testing out a new application that uses shadow dom like so:
#shadow-root (open)
-
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)
-
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)
-
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)
-
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)