114

I'm writing a Selenium testcase. And here's the xpath expression I use to match all 'Modify' buttons within a data table.

//img[@title='Modify']

My question is, how can I visit the matched node sets by index? I've tried with

//img[@title='Modify'][i]

and

//img[@title='Modify' and position() = i]

But neither works.. I also tried with XPath checker(One firefox extension). There're totally 13 matches found, then I have totally no idea how am I gonna select one of them.. Or does XPath support specified selection of nodes which are not under same parent node?

Kymair Wu
  • 1,275
  • 2
  • 9
  • 9

6 Answers6

240

This is a FAQ:

//someName[3]

means: all someName elements in the document, that are the third someName child of their parent -- there may be many such elements.

What you want is exactly the 3rd someName element:

(//someName)[3]

Explanation: the [] has a higher precedence (priority) than //. Remember always to put expressions of the type //someName in brackets when you need to specify the Nth node of their selected node-list.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • 1
    Thanks so much! Sorry I totally forgot the precedence things.. I just tried and it works! – Kymair Wu Sep 10 '10 at 13:03
  • 1
    @Kymair-Wu: I am glad this answer was useful to you. Here at SO the way of expressing gratitude is by accepting an answer (hint: click on the check-mark next to the answer). :) – Dimitre Novatchev Sep 13 '10 at 14:33
  • @DimitreNovatchev you are getting points for the same question over and over :p, thanks for the FAQ. – IPX Oct 29 '12 at 20:36
  • 3
    @Eytoss, You are welcome. And yes, I am getting most +1s for relatively simple answers -- not for the answers that I believe are my biggest achievements -- probably because everybody understands the former and almost nobody understands the latter :) – Dimitre Novatchev Oct 29 '12 at 22:26
  • I would clarify, that //someName[3] stands for "third occurence of someName on a level", not just a "someName that is a third child in a row of all children". Though I fully understand what you wanted to say, the original formula may sound a bit incorrect. – TEH EMPRAH Sep 02 '15 at 13:03
  • @TEHEMPRAH, Not exactly. The exact meaning, as per Spec is: "*Select all elements named 'someName', where each of which is the 3rd 'someName' child of its parent*" – Dimitre Novatchev Sep 02 '15 at 14:16
  • Which is now equal to what I'm saying. 3rd "somename" of childs. Not 3rd child which is by chance "somename". In any case, maybe I'm just not a native speaker, thus I feel a minor change of meaning behind the formula. – TEH EMPRAH Sep 02 '15 at 14:20
  • @TEHEMPRAH, No "on a level" means the 3rd 'someName' which is at a given level (from the top). Anyway, attempts to paraphrase the only normative document (the spec) can and often lead to incorrect interpretation and understanding. – Dimitre Novatchev Sep 02 '15 at 14:46
  • @TEHEMPRAH, I never said "that are the 3rd child of their parent". I said exactly what the spec says: that re the 3rd "someName" child of their parent. Please, try to understand this, or take a good XPath training. For XPath 1.0 I can recommend the 3rd module of this training course (which I happen to be the author of): http://www.pluralsight.com/courses/xslt-foundations-part1 – Dimitre Novatchev Sep 02 '15 at 15:39
  • 2
    @TEHEMPRAH, Actually I saw that in the answer I didn't say "the 3rd 'someName' child of its parent". Thanks for noticing this. Corrected now. – Dimitre Novatchev Sep 02 '15 at 15:42
  • And index start at 1. WHY? – Maxence Oct 14 '20 at 12:43
  • @Maxence, In XPath any sequence position (indexing) starts at 1. This is postulated in the WWW Consortium XPath Specifications. See for example: https://www.w3.org/TR/2002/WD-xpath20-20020430/#eval_context . It says in the 2nd bullet: "...The position of the first item in a sequence is always 1 (one). ..." – Dimitre Novatchev Oct 14 '20 at 14:21
  • @DimitreNovatchev What I meant was why start at 1 when almost everywhere else the indexes start at zero. – Maxence Oct 18 '20 at 09:45
  • @Maxence, When you say "almost everyone" you really skip all XML/XSLT/XPath developers. And not only them. If anyone thinks that they should use 0-based indexing in the "ordinary" programming languages like C#, Java, etc., do have a look at Skiena's "Algorithm Design Manual" book, where every (of the hundreds described) algoritm uses 1-based indexing... In fact, Skienna allocates an array of N+1 items when he needs to use N items, and "forgets" about the item at position 0. All his code references arrays starting at 1. The gains are in understandability and reducing errors in edge cases. – Dimitre Novatchev Oct 18 '20 at 16:41
  • 1
    "Note also, `index values in XPath predicates (technically, 'proximity positions' of XPath node sets) start from 1`, not 0 as common in languages like C and Java." – Ivan Chau May 26 '21 at 06:11
  • @IvanChau, Yes, as discussed at length in the comments above – Dimitre Novatchev May 26 '21 at 14:10
  • 1
    My especial thanks for this answer - the use of parentheses in this fashion has been a godsend to me, and until I found your answer I was starting to fear there was no workaround! – AJM Jul 26 '22 at 12:24
  • @AJM You are welcome. Yes, XPath is a wonderful language and starting to understand it is fun. Please, feel free to upvote :) – Dimitre Novatchev Jul 26 '22 at 13:25
  • 1
    @DimitreNovatchev Cheers - and I had already upvoted - I think near close-of-day Thursday after trying your answer. – AJM Jul 26 '22 at 13:44
  • 1
    @AJM, Thanks. If you are interested in XPath, maybe you will like my courses on Pluralsight :) For example, how to create a new datatype such as BinarySearchTree entirely in XPath 3. – Dimitre Novatchev Jul 26 '22 at 14:29
19

There is no i in XPath.

Either you use literal numbers: //img[@title='Modify'][1]

Or you build the expression string dynamically: '//img[@title='Modify']['+i+']' (but keep in mind that dynamic XPath expressions do not work from within XSLT).

Or does XPath support specified selection of nodes which are not under same parent node?

Yes: (//img[@title='Modify'])[13]


This //img[@title='Modify'][i] means "any <img> with a title of 'Modify' and a child element named <i>."

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • For some reason I needed to include the index before the attribute expression. For example, to find `td`s that were the sixth child of a `tr` and don't have empty content: `//tr/td[6][string-length(text()) > 0]` – Samir Aguiar Jun 07 '16 at 17:48
  • 1
    @kopranb For an explanation, see this answer http://stackoverflow.com/a/1006439/18771 – Tomalak Jun 08 '16 at 08:37
  • Thanks for explaining about '//img[@title='Modify']['+i+']' (+1) – undetected Selenium Jan 27 '18 at 11:00
4

There is no i in xpath is not entirely true. You can still use the count() to find the index.

Consider the following page

<html>

 <head>
  <title>HTML Sample table</title>
 </head>

 <style>
 table, td, th {
  border: 1px solid black;
  font-size: 15px;
  font-family: Trebuchet MS, sans-serif;
 }
 table {
  border-collapse: collapse;
  width: 100%;
 }

 th, td {
  text-align: left;
  padding: 8px;
 }

 tr:nth-child(even){background-color: #f2f2f2}

 th {
  background-color: #4CAF50;
  color: white;
 }
 </style>

 <body>
 <table>
  <thead>
   <tr>
    <th>Heading 1</th>
    <th>Heading 2</th>
    <th>Heading 3</th>
    <th>Heading 4</th>
    <th>Heading 5</th>
    <th>Heading 6</th>
   </tr>
  </thead>
  <tbody>
   <tr>
    <td>Data row 1 col 1</td>
    <td>Data row 1 col 2</td>
    <td>Data row 1 col 3</td>
    <td>Data row 1 col 4</td>
    <td>Data row 1 col 5</td>
    <td>Data row 1 col 6</td>
   </tr>
   <tr>
    <td>Data row 2 col 1</td>
    <td>Data row 2 col 2</td>
    <td>Data row 2 col 3</td>
    <td>Data row 2 col 4</td>
    <td>Data row 2 col 5</td>
    <td>Data row 2 col 6</td>
   </tr>
   <tr>
    <td>Data row 3 col 1</td>
    <td>Data row 3 col 2</td>
    <td>Data row 3 col 3</td>
    <td>Data row 3 col 4</td>
    <td>Data row 3 col 5</td>
    <td>Data row 3 col 6</td>
   </tr>
   <tr>
    <td>Data row 4 col 1</td>
    <td>Data row 4 col 2</td>
    <td>Data row 4 col 3</td>
    <td>Data row 4 col 4</td>
    <td>Data row 4 col 5</td>
    <td>Data row 4 col 6</td>
   </tr>
   <tr>
    <td>Data row 5 col 1</td>
    <td>Data row 5 col 2</td>
    <td>Data row 5 col 3</td>
    <td>Data row 5 col 4</td>
    <td>Data row 5 col 5</td>
    <td>Data row 5 col 6</td>
   </tr>
   <tr>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
   </tr>
  </tbody>
 </table>

 </br>

 <table>
  <thead>
   <tr>
    <th>Heading 7</th>
    <th>Heading 8</th>
    <th>Heading 9</th>
    <th>Heading 10</th>
    <th>Heading 11</th>
    <th>Heading 12</th>
   </tr>
  </thead>
  <tbody>
   <tr>
    <td>Data row 1 col 1</td>
    <td>Data row 1 col 2</td>
    <td>Data row 1 col 3</td>
    <td>Data row 1 col 4</td>
    <td>Data row 1 col 5</td>
    <td>Data row 1 col 6</td>
   </tr>
   <tr>
    <td>Data row 2 col 1</td>
    <td>Data row 2 col 2</td>
    <td>Data row 2 col 3</td>
    <td>Data row 2 col 4</td>
    <td>Data row 2 col 5</td>
    <td>Data row 2 col 6</td>
   </tr>
   <tr>
    <td>Data row 3 col 1</td>
    <td>Data row 3 col 2</td>
    <td>Data row 3 col 3</td>
    <td>Data row 3 col 4</td>
    <td>Data row 3 col 5</td>
    <td>Data row 3 col 6</td>
   </tr>
   <tr>
    <td>Data row 4 col 1</td>
    <td>Data row 4 col 2</td>
    <td>Data row 4 col 3</td>
    <td>Data row 4 col 4</td>
    <td>Data row 4 col 5</td>
    <td>Data row 4 col 6</td>
   </tr>
   <tr>
    <td>Data row 5 col 1</td>
    <td>Data row 5 col 2</td>
    <td>Data row 5 col 3</td>
    <td>Data row 5 col 4</td>
    <td>Data row 5 col 5</td>
    <td>Data row 5 col 6</td>
   </tr>
   <tr>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
    <td><button>Modify</button></td>
   </tr>
  </tbody>
 </table>

 </body>
</html>

The page has 2 tables and has 6 columns each with unique column names and 6 rows with variable data. The last row has the Modify button in both the tables.

Assuming that the user has to select the 4th Modify button from the first table based on the heading

Use the xpath //th[.='Heading 4']/ancestor::thead/following-sibling::tbody/tr/td[count(//tr/th[.='Heading 4']/preceding-sibling::th)+1]/button

The count() operator comes in handy in situations like these.

Logic:

  1. Find the header for the Modify button using //th[.='Heading 4']
  2. Find the index of the header column using count(//tr/th[.='Heading 4']/preceding-sibling::th)+1

Note: Index starts at 0

  1. Get the rows for the corresponding header using //th[.='Heading 4']/ancestor::thead/following-sibling::tbody/tr/td[count(//tr/th[.='Heading 4']/preceding-sibling::th)+1]

  2. Get the Modify button from the extracted node list using //th[.='Heading 4']/ancestor::thead/following-sibling::tbody/tr/td[count(//tr/th[.='Heading 4']/preceding-sibling::th)+1]/button

Eric Stanley
  • 71
  • 2
  • 7
  • thanks a lot! i use it, but I came to `td[count(../preceding-sibling::tr/th[.='Heading 4'])]/following-sibling::td` that don't fall into `td[1]` every time that i don't have some heading :) – mani Oct 21 '20 at 14:34
2
//img[@title='Modify'][i]

is short for

/descendant-or-self::node()/img[@title='Modify'][i]

hence is returning the i'th node under the same parent node.

You want

/descendant-or-self::img[@title='Modify'][i]
Nick Jones
  • 6,413
  • 2
  • 18
  • 18
  • 1
    Just `/descendant::img[@title='Modify'][$index]` will work fine. Also note that `[i]` predicate test for existence of `i` child element. –  Sep 09 '10 at 13:14
1

(//*[@attribute='value'])[index] to find target of element while your finding multiple matches in it

mahesh
  • 11
  • 1
0

Here is the solution for index variable

Let's say, you have found 5 elements with same locator and you would like to perform action on each element by providing index number (here, variable is used for index as "i")

for(int i=1; i<=5; i++)
{
    string xPathWithVariable = "(//div[@class='className'])" + "[" + i + "]";
    driver.FindElement(By.XPath(xPathWithVariable)).Click();
}

It takes XPath :

(//div[@class='className'])[1]
(//div[@class='className'])[2]
(//div[@class='className'])[3]
(//div[@class='className'])[4]
(//div[@class='className'])[5]
Maelig
  • 2,046
  • 4
  • 24
  • 49