daw
daw

Reputation: 2049

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

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.

Upvotes: 1

Views: 1109

Answers (3)

Mathias M&#252;ller
Mathias M&#252;ller

Reputation: 22617

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

Upvotes: 1

KunduK
KunduK

Reputation: 33384

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

Upvotes: 0

Dmitri T
Dmitri T

Reputation: 168052

  1. 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.

  2. 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.

Upvotes: 0

Related Questions