Shadow-dom support for selenium - selenium

I am working on an automation project which uses shadow DOMs extensively.
I use the execute_script function to access shadow root elements.
For example:
root = driver.execute_script('return document.querySelector(".flex.vertical.layout").shadowRoot')
Then I use the root element to access the elements within.
Since we have shadow root elements at many levels, this is annoying me a lot.
Is there any better solution to access elements within shadow root elements?
I am using Chrome 2.20 driver.

By googling I found another workaround for this problem - which is using the "/deep/ combinator".
For example, I was able to access all the shadow roots elements with
driver.find_elements_by_css_selector('body/deep/.layout.horizontal.center')
This will have access to the element with the compound class name layout horizontal center regardless of the number of shadow roots it has.
But this only works for the chromedriver and /deep/ is a deprecated approach.

The WebDriver spec still doesn't have anything specific to say about Shadow DOM.
Nor the Selenium project pages - which is understandable, as they closely follow the spec. Yet there is some low-level code in existence.
So, the short answer is: no, there is no special support in the spec, in Selenium's WebDriver API or implementation code at present.
Yes, the capability seems to exist in ChromeDriver 2.14 (as a wrapper around Chrome). However, as far as I can tell there are no Selenium or WebDriver-level bindings to let you use it.
But for more detail and possible workarounds, see also: Accessing Shadow DOM tree with Selenium, also: Accessing elements in the shadow DOM, and especially: Finding elements in the shadow DOM

You can write extension methods to operate on IWebElement to expand the root as below.
public static class SeleniumExtension
{
public static IWebElement ExpandRootElement(this IWebElement element, IWebDriver driver)
{
return (IWebElement)((IJavaScriptExecutor)driver)
.ExecuteScript("return arguments[0].shadowRoot", element);
}
}
You can use the above extension method to traverse through the element's hierarchy to reach the element of interest.
By downloads_manager_ShadowDom= By.TagName("downloads-manager");
By downloadToolBarShadowDom = By.CssSelector("downloads-toolbar");
By toolBarElement = By.CssSelector("cr-toolbar");
IWebElement ToolBarElement = driver.FindElement(downloads_manager_ShadowDom).ExpandRootElement(driver).FindElement(downloadToolBarShadowDom).ExpandRootElement(driver).FindElement(toolBarElement);

Trying to have this automated on Chrome I came up with an inelegant solution of recursively searching through each shadow dom explicitly using:
driver.executeScript(scriptToRun, cssSelector);
Here's the javascript (passed as a string):
function recursiveSearch(element, target) {
let result = element.querySelector(target);
if (result) { return result; }
let subElements = element.querySelectorAll("*");
for (let i = 0; i < subElements.length; i++) {
let subElement = subElements[i];
if (subElement && subElement.shadowRoot) {
let result = recursiveSearch(subElement.shadowRoot, target);
if (result) return result;
}
}
}
return recursiveSearch(document, arguments[0]);
Since the contents of a shadowRoot might be empty initially one can use driver.wait and until.elementIsVisible to avoid returning a null element.
Async example:
return await driver.wait(until.elementIsVisible(await driver.wait(async () => {
return await driver.executeScript(scriptToRun, cssSelector);
}, timeOut)));
Alternatively
My previous solution was to traverse the elements with shadowdoms explicitly, but is less autonomous. Same as above but with this script:
let element = document.querySelector(arguments[0][0]);
let selectors = arguments[0].slice(1);
for (i = 0; i < selectors.length; i++) {
if (!element || !element.shadowRoot) {return false;}
element = element.shadowRoot.querySelector(selectors[i]);
}
return element;
Where selectors would be something like:
['parentElement1', 'parentElement2', 'targetElement']
Sidenote
I found that running my automation tests on Firefox Quantum 57.0 doesn't suffer from hidden shadow doms, and any element can be found with a simple:
driver.findElement(searchQuery);

Since you use often that you may create a function, then the above becomes:
def select_shadow_element_by_css_selector(selector):
running_script = 'return document.querySelector("%s").shadowRoot' % selector
element = driver.execute_script(running_script)
return element
shadow_section = select_shadow_element_by_css_selector(".flex.vertical.layout")
shadow_section.find_element_by_css(".flex")
on the resulting element you can put any of the methods:
find_element_by_id
find_element_by_name
find_element_by_xpath
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector
To find multiple elements (these methods will return a list):
find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector
later edit:
Sometime the shadow host elements are hidden withing shadow trees that's why the best way to do it is to use the selenium selectors to find the shadow host elements and inject the script just to take the shadow root: :
def expand_shadow_element(element):
shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
return shadow_root
#the above becomes
shadow_section = expand_shadow_element(find_element_by_tag_name("neon-animatable"))
shadow_section.find_element_by_css(".flex")
To put this into perspective I just added a testable example with Chrome's download page, clicking the search button needs open 3 nested shadow root elements:
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
selenium.__file__
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()

Not sure it works in all browsers, but for me
::shadow works fine in chromedriver 2.38 For example:
div::shadow div span::shadow a

Maybe you may use IJavaScriptExecutor?
IWebDriver driver;
IJavaScriptExecutor jsExecutor = (IJavaScriptExecutor)driver;
jsExecutor.ExecuteScript('yourShadowDom.func()');

Related

Find elements in webpage using Selenium in Jmeter

I want to do testing using selenium webdriver in Jmeter. And i was using By.linkText to find an element, and assert whether the element exists or not.
var elements = WDS.browser.findElements(pkg.By.linkText("Tools"));
eval(elements.length != 0);
But it seems if replace 'Tools' with any other string like 'asfasdsa' it will return True, and my test is passing. It seems By.linkText doesnt work in JMeter. Is there any other alternate way to find an element in webpage other than By.id??
Also, is this a good way to verify whether an element is present?
Selenium works just fine, I'm not sure what you're trying to do with eval(elements.length != 0); function call, it will return false but fail to see where and how you're using this value
If you want to fail the WebDriver Sampler when the number of returned elements is 0 you need to do this a little bit differently, in particular conditionally call WDS.sampleResult.setSuccessful() function
Suggested code change:
WDS.sampleResult.sampleStart()
WDS.browser.get('http://example.com')
var elements = WDS.browser.findElements(org.openqa.selenium.By.linkText('More information...'))
if (elements.length == 0) {
WDS.sampleResult.setSuccessful(false)
WDS.sampleResult.setResponseMessage('Failed to find any element matching the criteria')
}
WDS.sampleResult.sampleEnd()
The above code will pass as long as you don't change More information... to something else.
See The WebDriver Sampler: Your Top 10 Questions Answered for more WebDriver Sampler tops and tricks
You can use xpath:
Using text():
var elements = WDS.browser.findElements(pkg.By.xpath("//*[text()='Tools']"));
eval(elements.length != 0);
Using contains():
var elements = WDS.browser.findElements(pkg.By.xpath("//*[contains(., 'Tools')]"));
eval(elements.length != 0);

How can to locate descendant elements with selenium web driver API using JavaScript?

I'm new in Selenium web driver and I facing some problems trying to locate DOM elements.
Let's say I have a bunch of <div class="column">...</div>, and inside them, I have a bunch of <div class="text">...</div>.
My question is: What is the better way to get a specific descendant and click it?
Below my code
var driver = new webdriver.Builder()
.forBrowser('chrome')
.build();
driver.get('http://www.localhost:4000/');
var columns = [];
driver.findElements(By.css('.column')).then(function(list) {
columns = list.slice();
columns[1].findElements(By.css('.text')).then(function(textList) {
textList[0].click();
});
});
You could combine the selectors and do:
driver.findElements(By.css('.column .text'))
which would locate all the elements with class text inside elements with class .column.
And, you could use the nth-child(), nth-of-type() or other pseudo classes to get to the elements by index inside the selectors, for instance:
driver.findElements(By.css('.column:nth-of-type(1) .text:nth-of-type(2)'))

selenium - how to find xpath

can any one help me for finding the correct xpath for the given link Logout
What you could do is to locate all links in your page, and then file the one corresponding to what you're searching for.
Here is the code converted to java
public static IWebElement GetLinkContainingText(string textToBeContained) {
// Here Driver is my ChromeDriver instance. You can replace a, by whatever tag your href is in.
ArrayList<WebElement> allTags = Driver.FindElements(By.Xpath("//a"));
for (WebElement v : allTags) {
if (v.GetAttribute(href).contains(textToBeContained)) {
return v;
}
}
return null;
}
calling the method would result for you in that.
WebElement elementYouSeachFor = GetLinkContainingText("http://ec2-34-210-163-161.us-west-2.compute.amazonaws.com:8094/login/index/logout");
Most likely
WebElement elementYouSeachFor = GetLinkContainingText("/logout");
would work too since there's probably not many links with logout on your page.
Hope this helps.
driver.findElement(By.xpath("//a[text()='Logout']"))
Is one way.
driver.findElement(By.linkText("Logout"));
Both could be problematic if you have more than 1 logout link on the page.
More resources on selecting elements: https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/By.html

Selenium Xpath Not Matching Items

I am trying to use Selenium's Xpath ability to be able to find an set of elements. I have used FirePath on FireFox to create and test the Xpath that I have come up with and that is working just fine but when I use the Xpath in my c# test with Selenium nothing is returned.
var MiElements = this._driver.FindElements(By.XPath("//div[#class='context-menu-item' and descendant::div[text()='Action Selected Jobs']]"));
and the Html looks like this:-
Can Anyone please point me right as everything that I have read the web says to me that this Xpath is correct.
Thanking you all in-advance.
Please post the actual HTML, so we can simply "drop it in" into a HTML file and try it ourselves but I noticed that there is a trailing space at the end of the class name:
<div title="Actions Selected Jobs." class="context-menu-item " .....
So force XPath to strip the trailing spaces first:
var MiElements = this._driver.FindElements(By.XPath("//div[normalize-space(#class)='context-menu-item' and descendant::div[text()='Action Selected Jobs']]"));
Perhaps you don't take into consideration the time that the elements need to load and you look for them when they aren't yet "searchable". UPDATE I skipped examples regarding this issue. See Slanec's comment.
Anyway, Selenium recommends to avoid searching by xpath whenever it is possible, because of being slower and more "fragile".
You could find your element like this:
//see the method code below
WebElement div = findDivByTitle("Action Selected Jobs");
//example of searching for one (first found) element
if (div != null) {
WebElement myElement = div.findElement(By.className("context-menu-item"));
}
......
//example of searching for all the elements
if (div != null) {
WebElement myElement = div.findElements(By.className("context-menu-item-inner"));
}
//try to wrap the code above in convenient method/s with expressive names
//and separate it from test code
......
WebElement findDivByTitle(final String divTitle) {
List<WebElement> foundDivs = this._driver.findElements(By.tagName("div"));
for (WebElement div : foundDivs) {
if (element.getAttribute("title").equals(divTitle)) {
return element;
}
}
return null;
}
This is approximate code (based on your explanation), you should adapt it better to your purposes. Again, remember to take the load time into account and to separate your utility code from the test code.
Hope it helps.

Scroll Element into View with Selenium

Is there any way in either Selenium 1.x or 2.x to scroll the browser window so that a particular element identified by an XPath is in view of the browser? There is a focus method in Selenium, but it does not seem to physically scroll the view in FireFox. Does anyone have any suggestions on how to do this?
The reason I need this is I'm testing the click of an element on the page. Unfortunately the event doesn't seem to work unless the element is visible. I don't have control of the code that fires when the element is clicked, so I can't debug or make modifications to it, so, easiest solution is to scroll the item into view.
Have tried many things with respect to scroll, but the below code has provided better results.
This will scroll until the element is in view:
WebElement element = driver.findElement(By.id("id_of_element"));
((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", element);
Thread.sleep(500);
//do anything you want with the element
You can use the org.openqa.selenium.interactions.Actions class to move to an element.
Java:
WebElement element = driver.findElement(By.id("my-id"));
Actions actions = new Actions(driver);
actions.moveToElement(element);
actions.perform();
Python:
from selenium.webdriver.common.action_chains import ActionChains
ActionChains(driver).move_to_element(driver.sl.find_element_by_id('my-id')).perform()
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("javascript:window.scrollBy(250,350)");
You may want to try this.
If you want to scroll on the Firefox window using the Selenium webdriver, one of the ways is to use JavaScript in the Java code. The JavaScript code to scroll down (to bottom of the web page) is as follows:
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight));");
Targeting any element and sending down keys (or up/left/right) seems to work also. I know this is a bit of a hack, but I'm not really into the idea of using JavaScript to solve the scrolling problem either.
For example:
WebElement.sendKeys(Keys.DOWN);
In Selenium we need to take the help of a JavaScript executor to scroll to an element or scroll the page:
je.executeScript("arguments[0].scrollIntoView(true);", element);
In the above statement element is the exact element where we need to scroll. I tried the above code, and it worked for me.
I have a complete post and video on this:
http://learn-automation.com/how-to-scroll-into-view-in-selenium-webdriver/
webElement = driver.findElement(By.xpath("bla-bla-bla"));
((JavascriptExecutor)driver).executeScript("arguments[0].scrollIntoView();", webElement);
For more examples, go here. All in Russian, but Java code is cross-cultural :)
I found that the bounding rect of my element was not correct, leading to the browser scrolling well off the screen. However, the following code works rather well for me:
private void scrollToElement(WebElement webElement) throws Exception {
((JavascriptExecutor)webDriver).executeScript("arguments[0].scrollIntoViewIfNeeded()", webElement);
Thread.sleep(500);
}
You can use this code snippet to scroll:
C#
var element = Driver.FindElement(By.Id("element-id"));
Actions actions = new Actions(Driver);
actions.MoveToElement(element).Perform();
There you have it
This worked for me:
IWebElement element = driver.FindElements(getApplicationObject(currentObjectName, currentObjectType, currentObjectUniqueId))[0];
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", element);
Use the driver to send keys like the pagedown or downarrow key to bring the element into view. I know it's too simple a solution and might not be applicable in all cases.
From my experience, Selenium Webdriver doesn't auto scroll to an element on click when there are more than one scrollable section on the page (which is quite common).
I am using Ruby, and for my AUT, I had to monkey patch the click method as follows;
class Element
#
# Alias the original click method to use later on
#
alias_method :base_click, :click
# Override the base click method to scroll into view if the element is not visible
# and then retry click
#
def click
begin
base_click
rescue Selenium::WebDriver::Error::ElementNotVisibleError
location_once_scrolled_into_view
base_click
end
end
The 'location_once_scrolled_into_view' method is an existing method on WebElement class.
I apreciate you may not be using Ruby but it should give you some ideas.
The Ruby script for scrolling an element into view is as below.
$driver.execute_script("arguments[0].scrollIntoView(true);", element)
sleep(3)
element.click
Selenium 2 tries to scroll to the element and then click on it. This is because Selenium 2 will not interact with an element unless it thinks that it is visible.
Scrolling to the element happens implicitly so you just need to find the item and then work with it.
Sometimes I also faced the problem of scrolling with Selenium. So I used javaScriptExecuter to achieve this.
For scrolling down:
WebDriver driver = new ChromeDriver();
JavascriptExecutor js = (JavascriptExecutor)driver;
js.executeScript("window.scrollBy(0, 250)", "");
Or, also
js.executeScript("scroll(0, 250);");
For scrolling up:
js.executeScript("window.scrollBy(0,-250)", "");
Or,
js.executeScript("scroll(0, -250);");
This is a repeated solution with JavaScript, but with an added waiting for element.
Otherwise ElementNotVisibleException may appear if some action on the element is being done.
this.executeJavaScriptFunction("arguments[0].scrollIntoView(true);", elementToBeViewable);
WebDriverWait wait = new WebDriverWait(getDriver(), 5);
wait.until(ExpectedConditions.visibilityOf(elementToBeViewable));
I have used this way for scrolling the element and click:
List<WebElement> image = state.getDriver().findElements(By.xpath("//*[contains(#src,'image/plus_btn.jpg')]"));
for (WebElement clickimg : image)
{
((JavascriptExecutor) state.getDriver()).executeScript("arguments[0].scrollIntoView(false);", clickimg);
clickimg.click();
}
def scrollToElement(element: WebElement) = {
val location = element.getLocation
driver.asInstanceOf[JavascriptExecutor].executeScript(s"window.scrollTo(${location.getX},${location.getY});")
}
Something that worked for me was to use the Browser.MoveMouseToElement method on an element at the bottom of the browser window. Miraculously it worked in Internet Explorer, Firefox, and Chrome.
I chose this over the JavaScript injection technique just because it felt less hacky.
You may want to visit page Scroll Web elements and Web page- Selenium WebDriver using Javascript:
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
FirefoxDriver ff = new FirefoxDriver();
ff.get("http://toolsqa.com");
Thread.sleep(5000);
ff.executeScript("document.getElementById('text-8').scrollIntoView(true);");
}
If you think other answers were too hacky, this one is too, but there is no JavaScript injection involved.
When the button is off the screen, it breaks and scrolls to it, so retry it... ¯\_(ツ)_/¯
try
{
element.Click();
}
catch {
element.Click();
}
In most of the situation for scrolling this code will work.
WebElement element = driver.findElement(By.xpath("xpath_Of_Element"));
js.executeScript("arguments[0].click();",element);
JAVA
Try scroll to element utilize x y position, and use JavascriptExecutor with this is argument: "window.scrollBy(x, y)".
Following import:
import org.openqa.selenium.WebElement;
import org.openqa.selenium.JavascriptExecutor;
First you need get x y location the element.
//initialize element
WebElement element = driver.findElement(By.id("..."));
//get position
int x = element.getLocation().getX();
int y = element.getLocation().getY();
//scroll to x y
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollBy(" +x +", " +y +")");
I am not sure if the question is still relevant but after referring to scrollIntoView documentation from https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView.
The easiest solution would be
executor.executeScript("arguments[0].scrollIntoView({block: \"center\",inline: \"center\",behavior: \"smooth\"});",element);
This scrolls the element into center of the page.
Javascript
The solustion is simple:
const element = await driver.findElement(...)
await driver.executeScript("arguments[0].scrollIntoView(true);", element)
await driver.sleep(500);
The default behavior of Selenium us to scroll so the element is barely in view at the top of the viewport. Also, not all browsers have the exact same behavior. This is very dis-satisfying. If you record videos of your browser tests, like I do, what you want is for the element to scroll into view and be vertically centered.
Here is my solution for Java:
public List<String> getBoundedRectangleOfElement(WebElement we)
{
JavascriptExecutor je = (JavascriptExecutor) driver;
List<String> bounds = (ArrayList<String>) je.executeScript(
"var rect = arguments[0].getBoundingClientRect();" +
"return [ '' + parseInt(rect.left), '' + parseInt(rect.top), '' + parseInt(rect.width), '' + parseInt(rect.height) ]", we);
System.out.println("top: " + bounds.get(1));
return bounds;
}
And then, to scroll, you call it like this:
public void scrollToElementAndCenterVertically(WebElement we)
{
List<String> bounds = getBoundedRectangleOfElement(we);
Long totalInnerPageHeight = getViewPortHeight(driver);
JavascriptExecutor je = (JavascriptExecutor) driver;
je.executeScript("window.scrollTo(0, " + (Integer.parseInt(bounds.get(1)) - (totalInnerPageHeight/2)) + ");");
je.executeScript("arguments[0].style.outline = \"thick solid #0000FF\";", we);
}
I've been doing testing with ADF components and you have to have a separate command for scrolling if lazy loading is used. If the object is not loaded and you attempt to find it using Selenium, Selenium will throw an element-not-found exception.
If nothing works, try this before clicking:
public void mouseHoverJScript(WebElement HoverElement) {
String mouseOverScript = "if(document.createEvent){var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseover', true, false); arguments[0].dispatchEvent(evObj);} else if(document.createEventObject) { arguments[0].fireEvent('onmouseover');}";
((JavascriptExecutor) driver).executeScript(mouseOverScript, HoverElement);
}
In Java we can scroll by using JavaScript, like in the following code:
driver.getEval("var elm = window.document.getElementById('scrollDiv'); if (elm.scrollHeight > elm.clientHeight){elm.scrollTop = elm.scrollHeight;}");
You can assign a desired value to the "elm.scrollTop" variable.
A solution is:
public void javascriptclick(String element)
{
WebElement webElement = driver.findElement(By.xpath(element));
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].click();", webElement);
System.out.println("javascriptclick" + " " + element);
}