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

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)'))

Related

Protractor getting DOM element and using their properties

I'm running e2e tests with Protractor and Selenium on a Polymer/Angular App.
Most of the elements are in the shadow DOM (I managed to handle it with custom css locators but it made my tests unstable as any change in the dom breaks them). The problem is that the devs have implemented a virtual scroll list :
Only a few items are loaded in the scroll list, next ones are loaded when you scroll.
I asked my devs how to deal with it and they told me I had to get the web-component and test it without protractor aka running commands like this :
document.querySelector('virtual-scroll').baseList
Where baseList is a public property of the tag.
I tried
var element = browser.executeScript('return document.querySelector("virtual-scroll")');
element.then(function (el){
console.log(el.$.baseList);
});
but the command above returns a WebElement. Is there any way to return the HTML DOM element as a javascript querySelector command does ?
EDIT : as requested the source code with the property of the scrollList that I want to get
If "virtual-scroll" is name of the shadow root element and baseList is an attribute of the element. You can get it with command below.
var baseList = browser.executeScript('return document.querySelector("virtual-scroll").baseList');
By the way, you can test the shadow DOM with Selenium. See an example below.
var shadowRootElement = browser.executeScript('return document.querySelector("virtual-scroll").shadowRoot');
var element = shadowRootElement.findElement(......);

click only visible element selenium webdriverjs

I have multiple div like below
<div class="one">send Message</div>
<div class="one">send Message</div>
<div class="one">send Message</div>
I have a web page where there is send Message buttons like above, in which only one button is visible at a time.Other two buttons are hidden via some javascript codes.So for example if 2nd button is visible , I should be able to click only that element.But in my selenium code , its trying to click first hidden div and its failing
driver.findElements(by.className(".one")).then((els) => {
var element = els[index];
element.click();
});
So basically I wanna convert below javascript code to Selenium nodejs code,If some one guide me that will be helpful
var all = document.getElementsByTagName("*");
for (var i = 0, max = all.length; i < max; i++) {
if (isHidden(all[i]))
// hidden
else
// visible
}
function isHidden(el) {
var style = window.getComputedStyle(el);
return ((style.display === 'none') || (style.visibility === 'hidden'))
}
Do you want to click the button ( basically a div as far as code is concerned ) which is visible ?
If that is your main agenda, then the code you've written will fail to find desired element. As you are selecting the element by it's classname not its visibility.
Your code will find all the matched class element. As it's a basic element selector and all your buttons have the same class, so they are basically rendered on the page.
Approach 1
driver.findElements(by.className(".one")).then((els) => {
for(var key in els){
var element = els[key];
if(element.isDisplayed()){ //if visible element
element.click();
}
}
});
The Key here is to check if the element you are trying to click is visible on the page.
Approach 2
By giving a unique class to the target button. Some class for eg 'clickable' or 'active'. So it will be a more optimized solution to select the target element using the Css selector. The Key here is to give uniqueness to your target element to be more identifiable.
Usually many Java Scripts are run in the node Js without the convert.
Have you try it in the node Js without converting ???
** Remember to import selenium

Shadow-dom support for 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()');

How to get the value of an attribute using XPath

I have been testing using Selenium WebDriver and I have been looking for an XPath code to get the value of the attribute of an HTML element as part of my regression testing. But I couldn't find a good answer.
Here is my sample html element:
<div class="firstdiv" alt="testdiv"></div>
I want to get the value of the "alt" attribute using the XPath. I have an XPath to get to the div element using the class attribute which is:
//div[#class="firstdiv"]
Now, I am looking for an XPath code to get the value of the "alt" attribute. The assumption is that I don't know what is the value of the "alt" attribute.
You can use the getAttribute() method.
driver.findElement(By.xpath("//div[#class='firstdiv']")).getAttribute("alt");
Using C#, .Net 4.5, and Selenium 2.45
Use findElements to capture firstdiv elements into a collection.
var firstDivCollection = driver.findElements(By.XPath("//div[#class='firstdiv']"));
Then iterate over the collection.
foreach (var div in firstDivCollection) {
div.GetAttribute("alt");
}
Just use executeScript and do XPath or querySelector/getAttribute in browser. Other solutions are wrong, because it takes forever to call getAttribute for each element from Selenium if you have more than a few.
var hrefsPromise = driver.executeScript(`
var elements = document.querySelectorAll('div.firstdiv');
elements = Array.prototype.slice.call(elements);
return elements.map(function (element) {
return element.getAttribute('alt');
});
`);
Selenium Xpath can only return elements.
You should pass javascript function that executes xpaths and returns strings to selenium.
I'm not sure why they made it this way. Xpath should support returning strings.

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.