Get last element from several results with cssselector - selenium

So I have this CSS-selector query that returns several results:
div[class=b-folders] span[class=b-folders__folder]
And I want to get only the last one, I tried;
div[class=b-folders] span[class=b-folders__folder]:last-of-type
But nothing was found.

:last-of-type, :last-child and other relevant pseudo-classes/selectors check the element's position inside it's parent:
The :last-of-type CSS pseudo-class represents the last sibling with the given element name in the list of children of its parent element.
Instead, solve it using your selenium language bindings - get all elements matching the selector and get the last found element. For instance, in Python:
driver.find_elements_by_css_selector("div[class=b-folders] span[class=b-folders__folder]")[-1]

You can have an array list of elements and then select the last element by:
List<WebElement> elements= driver.findElements(By.css("css"));
element = elements.get(list.size() - 1);

Related

Protractor get the second element of an ocurrence

I have an issue and I'm quite new with protractor.
I need to get an element by its text, right now it is not possible to change how the UI is built to use better selectors.
So what I have right now is a span tag with the text Summary inside of it, to retrieve the element using XPath I do the following:
const myElement = element(by.xpath("//span[text()='Summary']"));
That works for the first occurrence when there's only one element on the screen, however, when there are two elements, it fails with the following error:
Failed: element not interactable
Possibly because it is trying to access the first element which is toggled.
So I tried accessing the element index as I read around with:
element(by.xpath("//span[text()='Summary']")[1]);
And got the following error:
Failed: Invalid locator
I read somewhere that the values start on 1 and not on 0 so I tried with:
element(by.xpath("//span[text()='Summary']")[2]);
Yet, I'm still getting the same error:
Failed: Invalid locator
Is there a way I can get to these second element?
When you are using list slicing then there exist more then one elements. So you can try to use elements instead of element
element(by.xpath("(//span[text()='Summary'])[2]");
OR
element(by.xpath("(//span[contains(text(),'Summary')])[2]");
You can try this XPath, it has additional brackets, you have been almost there.
(//span[text()='Summary'])[2]
Reference:
XPath query to get nth instance of an element

locate displayed element with webdriverio

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

Selenium xpath failing to find element (other xpath tools prove it's there)

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

Need help regarding Run Keyword if xpath matches and then click element

I am trying to validate xpath expression and if it is true click element .Following is the code that I am trying to do .
Xpath_combined variable returns Boolean : true . Please help me in correcting the syntax or valid expression
Variable
${xpath_combined} //div[text()='00:04:56:AC:41:F6'] AND //div[contains(text(),'Device-77')]
Keyword
Run keyword if ${xpath_combined} == "true" Click Element //i[#class='fa fa-lg fa-file-text-o grow']
Error from console output
Evaluating expression '//div\[text()='00:04:56:AC:41:F6'\] AND //div\[contains(text(),'Gambit-77')\] == "true"' failed: SyntaxError: invalid syntax (<string>, line 1)]
You can create different scenarios how to solve this question. But, you can try this solution:
Xpath = With xpath, find the the parent element which contains both elements. But this depends on your code structure f.e. Try to find body element, with two child elements containing specific text conditions:
xpath=//body[//div[text()='00:04:56:AC:41:F6'] AND //div[contains(text(),'Device-77')]]
Set variable the right way or use scalar. Note: we will use Get Matching Xpath Keyword, so we don't need to use 'xpath=' prefix
${xpath_combined}= Set Variable //body[//div[text()='00:04:56:AC:41:F6'] AND //div[contains(text(),'Device-77')]]
Get Matching Xpath Count It will return 1 occurence of xpath. There is only one body element which passes the xpath expression
${count}= Get Matching Xpath Count ${xpath_combined}
Run Keyword If If the xpath count is exactly 1 time, the elements are present on the page and you are allowed to click on icon. Note: don't forget to add xpath= prefix to Click Element locator
Run keyword if ${count} == 1 Click Element xpath=//i[#class='fa fa-lg fa-file-text-o grow']
Though the other solution is fully viable one, here's an alternative which is an it more universal.
It combines two keywords - Get Webelement which returns a selenium object if it exists, and Run Keyword And Return Status - which returns True if a keyword succeeds, False otherwise:
${locator}= Set Variable //body[//div[text()='00:04:56:AC:41:F6'] AND //div[contains(text(),'Device-77')]]
${target}= Set Variable //i[#class='fa fa-lg fa-file-text-o grow']
${the element is present}= Run Keyword And Return Status Get Webelement ${locator}
Run Keyword If ${the element is present} Click Element ${target}
Another benefit is that you are not constrained by an xpath locator - it'll work with css, id or whatever other strategy.
Alternatively, one could use Element Should Be Visible instead of Get Webelement, but that works only for elements actuaĺly visible, not just present in the DOM.

Selenium WebDriver WebTable getData: How to get a value of "td" if it is depends on another "td"

I am facing one issue where the value of a one particular cell(td) in a webtable depends on another cell (td)..
Example:
Use http://www.espncricinfo.com/new-zealand-v-australia-2015-16/engine/match/914239.html and click on Full score...
Now I want to get a batsmen name who is unbeaten (Need "Not out" batsman from New Zealand 1st Innings. In this case "TA Boult")
But "TA Boult" and "Not out" are 2 different td's of a same "tr".
Can someone please guide me how to achieve this?
The following XPath would solve this issue for you:
//table[contains(#class,"batting-table")][1]//td/a[.="TA Boult"]/../../td[#class="dismissal-info" and contains(text(), "not out")]
You may want to make the selector for the top table a bit more specific, but essentially this XPath is doing the following:
Find the first table that contains the classname "batting-table"
Get all the td elements within this table
Get all the a elements within the td elements
Specify that the a element's text must be the player you are searching form, in this case, TA Boult.
Use /../.. to select the parent of the parent of the a element that has the text "TA Boult". So if the parent of the a tag is td, the parent of the td tag is tr.
Now that we are in the row containing any matching a tags, we can search within this row for the td tag with the "dismissal-info" class that contains the text "not out".
From there you could then use /../.. again to return the entire row as the result of the XPath.
So essentially, you are searching for a row, then searching for a child, then searching for a parent, then searching for a different child and then returning a correct row.
Hope that helps!
Ok so...
//all tr elements.
IList<IWebElement> tableTRCollection = driver.FindElement(By.Xpath("//table[#class='batting-table innings']")).FindElements(By.TagName("tr"))
//in every tr all td elements will be:
IList<IWebElement> tableTDCollection;
foreach(IWebElement tr in tableTRCollection )
{
tableTDCollection = tr.FindElements(By.TagName("td"));
}
//in td collection take all td
foreach(IWebElement td in tableTDCollection )
{
string tdText = td.Text;
}
and so on...
after you get all the collections you can get everything.
Thank you all for answers.. I was able to find an answer after a lot of struggle, but found solution is almost same that Ben has suggested.
I did not try another suggested solution though..
Below is the code...
WebElement name = driver.findElement(By.xpath(".//*[#id='full-scorecard']/div[2]/div/table[1]//td[#class='dismissal-info'][contains(text(),'not out')]/ancestor::tr/td[#class='batsman-name']"));