Ignoring the parent element when finding child element

Hello,
I am trying to find a child element using the parent element, but it is only taking the xpath in child element.

 List <WebElement> numOfExploreMoreCards = driver.findElements(By.xpath('//section//div[@class=\'card-exposed\']'))
 for (WebElement ExploreCard : numOfExploreMoreCards){
 	String ExploreCardHeader = ExploreCard.findElement(By.xpath('//h4')).getText() //I am getting staleElement error at this place when returned back to this page
 	println(ExploreCardHeader)
    WebElement learnMoreelement = ExploreCard.findElement(By.xpath('//a'))
    println(learnMoreelement.getText())
   clickonLearnMore(learnMoreelement, ExploreCardHeader, 'Exposed Cards')
   navigateToPage(homePage)
   numOfExploreMoreCards = driver.findElements(By.xpath(GlobalVariable.exploreMoreCards))
}

This below script is working fine when changing between pages

List <WebElement> numOfExploreMoreCards = driver.findElements(By.xpath('//section//div[@class=\'card-exposed\']'))
for (int i = 0; i < numOfExploreMoreCards.size(); i++) {
String ExploreCardHeader = ExploreCard.findElement(By.xpath('descendant::h4')).getText()
println(ExploreCardHeader)
WebElement learnMoreelement = ExploreCard.findElement(By.xpath('descendant::a/h5'))
println(learnMoreelement.getText())
clickonLearnMore(learnMoreelement, ExploreCardHeader, 'Exposed Cards')
navigateToPage(homePage)
numOfExploreMoreCards = driver.findElements(By.xpath(GlobalVariable.exploreMoreCards))
}

the html code in ss

when I run this the output I expected for println is Our Mission and Learn more. But the actual output I got was another H4 at the top of the page
“Protect the Nation.
Skip to main content”

It seems like its skipping the parent element. Any help would be greatly appreciated.

XPath //h4 selects all the h4 descendants of the document root and thus selects all h4 elements in the same document as the context node.

But you want only h4 descendants of <div class="card-exposed">. Then you want:

String ExploreCardHeader = 
    ExploreCard.findElement(By.xpath('descendant::h4')).getText()

Reference:

  1. W3 School XPath Axes
  2. W3C XPath 1.0 sepc “Abbreviated Syntax”
1 Like

@kazurayam, thanks for the fix. it worked. in the same code, I am getting another error which is StaleElementExeption. I have updated my actual code above to include a few more steps. in that script, after I navigate to another page and returned to the previous page I am finding the elements again so the staleElement error should be gone.
But on the second iteration I am still getting this error at the line String ExploreCardHeader = ExploreCard.findElement(By.xpath(’//h4’)).getText(). The error is thrown only when I use for each statement.
If I use standard for loop with initiating i then its working as expected.

A StaleElementException is always a risk when you try to find a child element of an already-located WebElement like that:

ExploreCard.findElement(By.xpath(’//h4’))

As such, I would recommend that you try and avoid this approach at all costs. The alternative is to compile xpath snippets into one final xpath that you use to locate the target element once (and only once). So instead of something like this:

WebElement parentElement = driver.findElement(By.xpath("//parent/path"))
WebElement childElement = parentElement.findElement(By.xpath(".//relative/path/to/child"))

It’s much better to do something like this:

String parentXPath = "//parent/path"
String childXPath = "//relative/path/to/child"
WebElement childElement = driver.findElement(By.xpath(parentXPath + childXPath))

@Brandon_Hein, Thanks for your quick response. Also do I need to assign those string variables out side of the for each loop? Also do I still need to findelements after returning to the homepage?

It doesn’t really matter, as they would just be assigned to the same value with each iteration. But it makes more sense to have them outside of the loop, yes.

Right, if the html is reloaded at any time, you need to relocate all elements involved in your loop.

Overall, looking at your code, I wouldn’t even bother with the parent/child relationship logic at all. Here’s how I would approach it:

1.) Count the number of “explore more cards”:

int count = driver.findElements(By.xpath("//div[@class=‘card-exposed’]")).size()

2.) Using the count, loop and locate the header and link elements with an index (and do whatever you want with them). As you said, if you navigate away from the page within the loop, you’ll need to get the count again once you return:

for(int i = 1; i <= count; i++) {
    WebElement header = driver.findElement(By.xpath("(//div[@class='card-exposed'])[" + i + "]//h4"))
    WebElement link = driver.findElement(By.xpath("(//div[@class='card-exposed'])[" + i + "]//a"))
    // do stuff...
    count = driver.findElements(By.xpath("//div[@class='card-exposed']")).size()
}

There is zero chance of a StaleElementException with this approach, however you could still get a WebElementNotFound exception. But this exception is much easier to understand/deal with.

1 Like

@Brandon_Hein, Thanks that’s working fine for me if I use for loop and not for the for each. I am going to use for loop

1 Like