1

I want to print text of each div with class="Name". The code below prints Name1 three times instead of Name1, Name2 and Name3.

  1. Why does my code print Name1 three times?
  2. Why is dateInput.FindElement even able to find the Root div at all? Root div is located in completely different level than the date element. And since I'm doing //div..., which means find the div in the current node (right?), on dateInput.FindElement it should NOT even find the Root div, right?

CODE

var dateInput = driver.FindElement(By.Id("date"));
var rootElement = dateInput.FindElement(By.XPath("//div[contains(@class,'Root')]"));
var boxes = rootElement.FindElements(By.XPath("//div[contains(@class,'Box)]"));
foreach (var box in boxes)
{
    var nameElement = box.FindElement(By.XPath("//div[contains(@class,'Name')]"));
    Console.WriteLine(nameElement.Text);
}

HTML

<div>
  <div>
    <input id="date"></div>
  </div>
  <div class="__Root">
    <div>
      <div class="__Box">
        <div class="__Name">Name1</div>
      </div>
      <div class="__Box">
        <div class="__Name">Name2</div>
      </div>
      <div class="__Box">
        <div class="__Name">Name3</div>
      </div>
    </div>
  </div>
</div>
theateist
  • 13,879
  • 17
  • 69
  • 109
  • This is not well-formed ... but I don't think that's the problem. – Fildor Feb 14 '23 at 08:50
  • There's also a typo: `"//div[contains(@class,'Box)]"` should be `"//div[contains(@class,'Box')]"` – Fildor Feb 14 '23 at 08:53
  • 1
    If you want to make an element search "starting from" current element you need to specify the *[context node](https://stackoverflow.com/questions/1022345/current-node-vs-context-node-in-xslt-xpath)*: `var rootElement = dateInput.FindElement(By.XPath(".//div[contains(@class,'Root')]"))` – JaSON Feb 14 '23 at 11:39

1 Answers1

1

You are evaluating an XPath expression starting with // relative to a particular context node, but the meaning of // is to search the document from the document's root, ignoring the context node altogether (except of course that the context does provide the document which is being searched). So you execute the same query three times. Each time, your query expression matches all 3 div elements in the document, but because the findElement method is defined to return a single element, it is returning the first one each time.

To search within a subtree rooted at the context node, your expression should start with .//.

Secondly, you could just search directly for the "Name" div elements with a single XPath expression (broken onto multiple lines for readability), and simplify your c# code drastically:

//div[contains(@class,'Root')]
   //div[contains(@class,'Box')]
      //div[contains(@class,'Name')]
Conal Tuohy
  • 2,561
  • 1
  • 8
  • 15
  • Use `.//` if you look for any descendant node of the current node, use `./` if you look for a node which is a direct child of the current – sound wave Feb 14 '23 at 14:09
  • I'm confused. So, `//` is indeed relative to a particular context node, but it ignores it and searches from document's root? – theateist Feb 14 '23 at 19:23
  • Let me put it this way: the XPath expression is evaluated relative to a context node (because you've called the `FindElements` method of a particular node; that means that a context node _is_ provided to the XPath evaluator), but the XPath expression itself, because it starts with `/`, searches from the root of the document. The XPath expression isn't ignoring the context node altogether, though, because the document root node where the search begins is precisely the root of the document that contains the context node. – Conal Tuohy Feb 14 '23 at 22:46
  • NB `//` is short for `/descendant-or-self::node()/` – Conal Tuohy Feb 14 '23 at 22:50
  • In my mind, once `//` is used from `FindElements` the XPath should consider the context node as the root and do everything relative to that context node and not the root of the document. – theateist Feb 15 '23 at 06:27
  • Well that misconception is the root of your problem, here, obviously enough. NB it would have been a mistake for the developers of the C# XPath API to make `FindElements` construct a new document that contains the context node as its root element, and execute the XPath over that document, because that would have made it impossible to execute XPath expressions such as e.g. `ancestor::foo`, since all ancestor elements would be lost. – Conal Tuohy Feb 15 '23 at 13:52