When developing in the Ionic framework, the generated html sometimes will contain duplicate DOM elements, where all but one tree of elements is hidden with a class="ion-page-hidden" at the top level.
When using webdriverio to try and locate an element inside this tree, it find duplicated elements. Since the ion-page-hidden class is at the top of the tree, and not on the element itself, how can we use Xpath to locate only the displayed element.
I couldn't figure out any way to modify the XPath selector with a second condition since the elements are exactly the same!
So instead I have tried to use the webdriverio isDisplayed() function:
get openHamburgerMenu() { return Utils.visibleElement($$("//ion-button[#name='button-open-menu']")); }
where the Utils function is:
async visibleElement(elementArray) {
let returnElement;
elementArray.forEach(element => {
if (element.isDisplayed()) {
returnElement = element;
}
});
return returnElement;
}
but no elements are passed into the function. In the chrome browser, I can see two that match the xpath //ion-button[#name='button-open-menu']. I need the one not in the ion-page-hidden block!
tree
The tree looks like this:
app-for-homes[1]/ion-header/ion-toolbar/ion-buttons[1]/ion-button
app-for-homes[2]/ion-header/ion-toolbar/ion-buttons[1]/ion-button
where app-for-homes[2] happens to have the ion-page-hidden class.
I think it should be possible to use ancestors to identify which of the two elements, matching the xpath, does not have a 4th level ancestor with that class? But I'm way out of my depth on day one of working with xpaths...
Quick and Dirty
The desired outcome can be achieved using this Xpath:
//app-for-homes[1]/ion-header/ion-toolbar/ion-buttons/ion-button[#name='button-open-menu']
However, this only works where the order in which the elements appears is known.
Better Answer
When you have exactly 1 element that is not hidden, Xpaths allow you to look at an elements ancestors as far back as you want to identify the presence / or absence of the hidden class. In this case, we start by finding the ancestor app-for-homes which does not include the ion-page-hidden class:
//app-for-homes[not(contains(#class,'ion-page-hidden'))]
and then simply append the remainder of the path to the desired element. Full answer =
//app-for-homes[not(contains(#class,'ion-page-hidden'))]/ion-header/ion-toolbar/ion-buttons/ion-button[#name='button-open-menu']
Related
Selenium FindElement:
driver.FindElement(By.XPath($"//*[contains(text(), '{text}')]"));
Throws:
no such element: Unable to locate element:
{
"method":"xpath",
"selector":"//*[contains(text(), '269424ae-4d74-4a68-91e0-1603f2d674a0')]"
}
(Session info: chrome=74.0.3729.169)
(Driver info:
chromedriver=74.0.3729.6 (255758eccf3d244491b8a1317aa76e1ce10d57e9-refs/branch-heads/3729#{#29}),
platform=Linux 4.18.0-20-generic x86_64)
But it's definitely there and the xpath is valid because I can use AngleSharp to parse the driver's page source with the same xpath expression:
new HtmlParser()
.ParseDocument(driver.PageSource)
.SelectSingleNode($"//*[contains(text(), '{text}')]");
The target element is a div containing a guid:
<div class="home-project-title-text"> 269424ae-4d74-4a68-91e0-1603f2d674a0 </div>
This is with
dotnet core 2.2
chrome webdriver
Chrome 74
Ubuntu 18.04
EDIT1
Interestingly the document.evaluate in the browser console also fails with this xpath expression. I use this as a helper function for running xpath:
selectSingle = xpath => document.evaluate(xpath, document).iterateNext()
and then find that this returns null:
> selectSingle("//*[contains(text(), '269424ae-4d74-4a68-91e0-1603f2d674a0')]")
> null
but it's definitely there and has the expected text, e.g. I can use a different xpath expression to manually locate and check it's text content:
> selectSingle("//*[#id='app']/div/div[1]/div[3]/div/div[1]/div/div[1]/div")
.textContent
.trim()
== "269424ae-4d74-4a68-91e0-1603f2d674a0"
> true
EDIT2
So the cause was that the div was being created in react like this:
React.createElement(
"div",
{className = "home-project-title-text"},
" ",
"269424ae-4d74-4a68-91e0-1603f2d674a0",
" ");
I think this roughly means that the div has three textnodes as children (is that valid?). The result looks 100% normal - it renders perfectly and inspecting the element with devtools looks like a single text node and .textContent returns the concatenated string.
Now that you gave some more information (how this element is created):
Yes, it is possible that an XML element has as its children several separate text nodes. However, this is usually not the case if the text nodes are adjacent to each other, instead of separated by child elements.
If '269424ae-4d74-4a68-91e0-1603f2d674a0' is indeed the second text node, then
//*[contains(text(), '269424ae-4d74-4a68-91e0-1603f2d674a0')]
will indeed not return this div element. You should not think of this as "breaking XPath", it is just that the precise semantics of the expression are:
Find an element with any name whose first text node contains '269424ae-4d74-4a68-91e0-1603f2d674a0'.
text() actually selects all text nodes of an element, but XPath functions such as contains() silenty select only the first one.
What you actually would like to select is
an element with any name where any text node contains '269424ae-4d74-4a68-91e0-1603f2d674a0'
And an expression to achieve exactly that is:
//*[text()[contains(.,'269424ae-4d74-4a68-91e0-1603f2d674a0')]]
You can test those expressions with a small toy document such as:
<div className="home-project-title-text">
<other/>
269424ae-4d74-4a68-91e0-1603f2d674a0
<other/>
</div>
Where other elements are forcing the div element to contain three separate text nodes, two of them containing whitespace only.
Finally, if you already know that the element you are looking for is a div, then you should look specifically for that:
//div[text()[contains(.,'269424ae-4d74-4a68-91e0-1603f2d674a0')]]
It might be the case the element lives in an iframe, if this is the case - you will have to use IWebDriver.SwitchTo() function in order to switch to the required iframe prior to attempting locating the element.
It might be the case the element is not immediately available, i.e. it's being loaded by an AJAX request, in that case you will need to use WebDriverWait class so Selenium could wait till the element appears in DOM prior to interacting with it.
Try the following xpath.See if you get any luck.
//div[#class='home-project-title-text'][contains(.,'269424ae-4d74-4a68-91e0-1603f2d674a0')]
EDIT
//div[contains(.,'269424ae-4d74-4a68-91e0-1603f2d674a0')]
I am facing an issue where I am unable to locate Element on webpage with any type of locator expect Absolute xpath. Here are details:
URL : https://semantic-ui.com/modules/dropdown.html#selection
Required Element Screen shot:
Manually created Xpath Screen shot( Please note that I am able to recognize Element in web application with manually created xpath but Same xpath is not working in selenium code)
But Same xpath is not working in selenium script.
PLEASE NOTE THAT I AM ABLE TO IDENTIFY SAME OBJECT WITH Absolute xpath
Please help to me to understand reason for this.
Here is code:
public static WebDriver driver;
public static void main(String[] args) {
driver= new FirefoxDriver();
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("https://semantic-ui.com/modules/dropdown.html");
//Selection
driver.findElement(By.xpath("//div[#class='ui selection dropdown upward']")).click();
driver.findElement(By.xpath("//div[#class='menu transition visible']/div[text()='Female']")).click();
System.out.println("Done");
This may be issue with your first x-path. You may try the following code. It may work.
driver.findElement(By.xpath("//div[#class='ui selection dropdown']").click();
driver.findElement(By.xpath("//div[#class='menu transition visible']/div[text()='Male']").click();
You are missing a preceding wildcard in you driver.FindElement XPath.
Instead of driver.findElement(By.xpath("//div..."));, do driver.findElement(By.xpath("//*div..."));.
Currently, what your code is doing is telling the XPath locator to find a first level div (not easily possible, as the first item is almost always the document body), but the wildcard of "*" tells it that it can look for the div at any level.
As an aside, please edit your answer up top with actual code instead of pictures so others with the same problem can find their solution easier.
Longshot:
You are using Chrome to look at the source, but Selenium is using Firefox.
There is a chance that the source is rendered differently between the two browsers. Specifically, your xpath is relying on an exact match of class. I do know that FireFox is notorious for modifying source output.
I haven't done any testing but I would not be surprised if the class is in a different order and it's failing on an exact match.
There are two solutions if that is the case:
Don't do a single exact match (xpath = "") but instead do a mix of contains joined with && (contains ui && contains selection && dropdown)
Don't rely on the output from the console tab. Instead "View Page Source" and view what is actually being sent instead of what is being interpreted by the browser
Find the dropdown container element firstly, then use it to find self descendant, like option etc.
driver.get("https://semantic-ui.com/modules/dropdown.html");
// find dropdown container element
WebElement dropdownWrapper = driver.findElement(
By.xpath("//div[#class='html']/div[input[#name='gender']]"));
// expand drop down options by click on the container element
dropdownWrapper.click();
// or click the down arrow
dropdownWrapper.findElement(By.css('i')).click();
// select option
dropdownWrapper.findElement(By.xpath(".//div[text()='Female']")).click();
To locate the element with text as Gender and select the option Female you can use the following Locator Strategy :
Code Block :
System.setProperty("webdriver.chrome.driver", "C:\\path\\to\\geckodriver.exe");
WebDriver driver = new FirefoxDriver();
driver.get("https://semantic-ui.com/modules/dropdown.html#selection");
driver.findElement(By.xpath("//div[#class='another dropdown example']//div[#class='ui dropdown selection']")).click();
driver.findElement(By.xpath("//div[#class='another dropdown example']//div[#class='ui dropdown selection active visible']//div[#class='menu transition visible']//div[#class='item' and contains(.,'Female')]")).click();
System.out.println("Gender Female selected.");
Console Output :
Gender Female selected.
This might be helpful to clearify how selectors are working in general:
.(dot): Dot at starting represents the current node. It tells us that the processing will start from the current node.
. .(double dot): It will select parent of current node. For example, //table/. . will return div element because div is the parent of table element.
‘*’: is used to select all the element nodes descending from the current node. For example:
/table/*: It will select all child elements of a table element.
//*: It will select all elements in the document.
//*[#id = ‘username’]: It will select any element in document which has an attribute named “id” with the specified value “username”.
#: It represents an attribute which selects id, name, className, etc. For example:
#id: It will select all elements that are defined with the id attribute in the document. No matter where it is defined in the document.
//img/#alt: It will select all the img elements that are defined with the #alt attribute.
//td[#*]: It will select all td elements with any attribute.
Here is a link to the article:
https://www.scientecheasy.com/2020/07/selenium-xpath-example.html/
i'm in day 2 of my selenium class, need help in finding an efficient way of looping over elements and if matches click the link address.
I want to navigate from classFrame to navList and loop over to find the match and click.
public void switchFrames() {
driver.navigate().to("https://seleniumhq.github.io/selenium/docs/api/java/");
driver.switchTo().frame("classFrame");
/* List<WebElement> elements = driver.findElements(By.className("navList"));
for (WebElement element : elements) {
System.out.println(element.findElement(By.xpath(".//li/a")).getText());
}
*/
List<WebElement> items = driver.findElements(By.cssSelector("ul li"));
if ( items.size() > 0 ) {
for ( WebElement we: items ) {
we.findElement(By.linkText("Deprecated")).click();
}
}
driver.findElement(By.linkText("Deprecated")).click();
driver.close();
}
The main part you are missing and the reason you can't find the element you are looking for is because it's in a frame. In order to access elements in a frame with Selenium, you need to switch the driver context to the frame. You do that using driver.switchTo().frame(). Once you are done interacting with the frame, switch back to the default context using driver.switchTo().defaultContent().
Having said that... let me offer you some more advice since you are just starting out. There are several ways to do this. One way is like what you attempted... grab an element, find a child, loop through those children looking for the link you want. I prefer the more direct approach since we can search for the exact link using an XPath. What you want to do is to click the DEPRECATED link on the navbar. You could just use the By.linkText() locator and that will work but you want to be careful, especially with a page like this that has so many links, to not click on a link you didn't intend to. The way you do that is to narrow the search to the specific area you expect the link to be in, the navbar. Once you narrow the search there, you can quickly and safely find the link you are looking for. I prefer to do it in a single search using an XPath but you could use say a CSS selector to find the navbar area and then use By.linkText() to find the link, e.g.
driver.findElement(By.cssSelector("ul[title='Navigation']").findElement(By.linkText("Deprecated").click();
In that case, you will be scraping the page twice. It's not likely a big performance hit, I just prefer to use a single locator when it makes sense. I would suggest that since you are likely to use this code over and over that you put it in a function and pass it the link name, e.g.
public void clickNavbar(String linkName)
{
driver.switchTo().frame(driver.findElement(By.cssSelector("frame[name='classFrame']")));
driver.findElement(By.xpath("//ul[#title='Navigation']//a[.='" + linkName + "']")).click();
driver.switchTo().defaultContent();
}
Then you can call it like, clickNavbar("Deprecated");
Basically I am searching for a syntax that I can use to select an element that shares the same name, properties and attributes.
I was thinking of selecting them via Index. (Unfortunately Xpath wont work since it's a dynamic element.)
So, I have a page where the element Add is shown twice, both of them adds/throws a different value. But both of them has the same ID, Attributes and Name. In my test I need to select the first Add and then the other.
${add attributes row} //*[#data-bind="click: function() {
$parents[1].addItem($parents[1]
.attributes()[$parentContext.$index()]) }", index=1]
${add attributes row_2} //*[#data-bind="click: function() {
$parents[1].addItem($parents[1]
.attributes()[$parentContext.$index()]) }", index=2]
Is there a way to select them by Index?
If you find an XPath that selects both of them, you can apply a predicate to the entire XPath by putting the XPath in parentheses. For instance, //a selects all anchors throughout the DOM. (//a)[4] selects the 4th anchor found in the DOM. You can also use the last and position functions to select relative indices such as the penultimate anchor (//a)[last()-1]
Try a locator like this for the 1st:
xpath=(//*[#data-bind="click: function() {$parents[1].addItem($parents[1].attributes()[$parentContext.$index()]) }"])[1]
Try a locator like this for the 2nd:
xpath=(//*[#data-bind="click: function() {$parents[1].addItem($parents[1].attributes()[$parentContext.$index()]) }"])[2]
See this related question
Is there a way in Dojo to find all widget descendants in a DOM element? I used the example below it will only list the children of the element, in this case any widgets that are children of the document object, but not all the descendants or nested widgets. Hopefully that's clear.
var widgets = dijit.findWidgets(dojo.doc);
dojo.forEach(widgets, function(w) {
console.log(w);
});
I could just write my own recursive function but I want to make sure I'm not missing out on a Dojo method which already does this.
Many thanks
Hmm, dijit.findWidgets(parentWidget.domNode) ?
Edit Oh, now I nee findWidgets doesn't search recursively.
I checked the dijit.findWidgets source code and all it does is check for nodes with a widgetid attribute that are represented in the dijit registry. The following version uses dojo.query to do this same search recursively:
function findEvenTheNestedWidgets(innitialNode){
return dojo.query("[widgetid]", innitialNode)
.map(dijit.byNode)
.filter(function(wid){ return wid;}) //filter invalid widget ids that yielded undefined
}