ChatGPT解决这个技术问题 Extra ChatGPT

How to select specified node within Xpath node sets by index with Selenium?

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?


D
Dimitre Novatchev

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.


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."
T
Tomalak

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


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
Thanks for explaining about '//img[@title='Modify']['+i+']' (+1)
E
Eric Stanley

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 Sample table

Heading 1 Heading 2 Heading 3 Heading 4 Heading 5 Heading 6
Data row 1 col 1 Data row 1 col 2 Data row 1 col 3 Data row 1 col 4 Data row 1 col 5 Data row 1 col 6
Data row 2 col 1 Data row 2 col 2 Data row 2 col 3 Data row 2 col 4 Data row 2 col 5 Data row 2 col 6
Data row 3 col 1 Data row 3 col 2 Data row 3 col 3 Data row 3 col 4 Data row 3 col 5 Data row 3 col 6
Data row 4 col 1 Data row 4 col 2 Data row 4 col 3 Data row 4 col 4 Data row 4 col 5 Data row 4 col 6
Data row 5 col 1 Data row 5 col 2 Data row 5 col 3 Data row 5 col 4 Data row 5 col 5 Data row 5 col 6

Heading 7 Heading 8 Heading 9 Heading 10 Heading 11 Heading 12
Data row 1 col 1 Data row 1 col 2 Data row 1 col 3 Data row 1 col 4 Data row 1 col 5 Data row 1 col 6
Data row 2 col 1 Data row 2 col 2 Data row 2 col 3 Data row 2 col 4 Data row 2 col 5 Data row 2 col 6
Data row 3 col 1 Data row 3 col 2 Data row 3 col 3 Data row 3 col 4 Data row 3 col 5 Data row 3 col 6
Data row 4 col 1 Data row 4 col 2 Data row 4 col 3 Data row 4 col 4 Data row 4 col 5 Data row 4 col 6
Data row 5 col 1 Data row 5 col 2 Data row 5 col 3 Data row 5 col 4 Data row 5 col 5 Data row 5 col 6

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:

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

Note: Index starts at 0

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


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 :)
N
Nick Jones
//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]

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

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


Can you explain a little bit more?
M
Maelig

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]