116

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?

0

6 Answers 6

247

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.

Sign up to request clarification or add additional context in comments.

16 Comments

Thanks so much! Sorry I totally forgot the precedence things.. I just tried and it works!
@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). :)
@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 :)
@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.
"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."
|
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>."

2 Comments

For some reason I needed to include the index before the attribute expression. For example, to find tds that were the sixth child of a tr and don't have empty content: //tr/td[6][string-length(text()) > 0]
@kopranb For an explanation, see this answer stackoverflow.com/a/1006439/18771
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

1 Comment

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

1 Comment

Just /descendant::img[@title='Modify'][$index] will work fine. Also note that [i] predicate test for existence of i child element.
1

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

1 Comment

Can you explain a little bit more?
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] 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.