1

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.

3 Answers 3

1

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')]]
Sign up to request clarification or add additional context in comments.

1 Comment

Excellent thanks, that all makes sense. The things that threw me were that i) text() does not behave like .textContent returning a concatenated string ii) if a list of text nodes are coerced to a string, only the first is chosen and iii) roundtripping the underlying html to test in another xpath tool changes the structure of the textnodes.
0
  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.

2 Comments

1. not an iframe 2. The code has waited until the string appears in the driver.pagesource so I don't think it's that. I have now found that document.evaluate() also fails to find it.... will update the question.
I can only think of Shadow DOM
0

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

6 Comments

yes that finds it... however this is test code replicating a human clicking on piece of visible text without knowing anything about the DOM and it's classes so... gah... what to do?
definitely found something... just trying to verify it's the right element... strangely hard to work out what the IWebElement it's found actually is - I'm concerned it's matched something in the parent hierarchy
suspicion confirmed - it's matched the top-level div - . is basically converting all child elements to text so it then finds the string on top-level parent
can you post more html for clarity?
thanks @kunduk for your help - I've found the very weird cause in how the html was generated - see the answer I have added.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.