'XPath / Selenium can't locate an element using a partial id with contains / start-with

I have the following HTML generated with an AjaxFormLoop.

<div id="phones">
    <div class="t-forminjector tapestry-forminjector" id="rowInjector_13b87fdd8b6">
        <input id="number_13b87fdd8b6" name="number_13b87fdd8b7" type="text"/>
        <a id="removerowlink_13b87fdd8b6" href="#" name="removerowlink_13b87fdd8b6">remove</a>
    </div>
    
    <div class="t-forminjector tapestry-forminjector" id="rowInjector_13b87fdda70" style="background-image: none; background-color: rgb(255, 255, 251);">
        <input id="number_13b87fdda70" name="number_13b87fdda70" type="text" />
        <a id="removerowlink_13b87fdda70" href="#" name="removerowlink_13b87fdda70">remove</a>
    </div>
</div>

I'm trying to access the second input field in child 2 using a partial ID, however I have not been successful in getting this to work.

What I've tried thus far.

String path = "//input[contains(@id,'number_')][2]";
String path = "(//input[contains(@id,'number_')])[2]";

I can't even access input 1 using 1 instead of 2, however if I remove [2] and only use

String path = "//input[contains(@id,'number_')]";

I'm able to access the first field without issue.

If I use the exact id, I'm able to access either field without issue.

I do need to use the id if possible as there is many more fields in each t-forminjector row that are not present in this example.

Implementation with Selenium.

    final String path = "(//input[starts-with(@id,'quantity_')])[2]";
    
    new Wait() {
        @Override
        public boolean until() {
            return isElementPresent(path);
        }
    }.wait("Element should be present", TIMEOUT);

Resolved

I'm noticing I can't seem to use the following starts-with / contains to locate any element within to dom, however if I use a complete id, it works.

//Partial ID - fails
//*[starts-with(@id,"quantity_")]

//Exact ID - works
//*[starts-with(@id,"quantity_-112409575185705")]


Solution 1:[1]

The generated output you pasted here simply does not contain the string number_ anywhere in it. It does contain Number_ -- note the capital N -- but it's not the first part of the string. Perhaps you meant something like this (which at least selects something):

(//input[contains(@id, 'Number_')])[2]

Or:

(//input[starts-with(@id,'catalogNumber_')])[2]

Solution 2:[2]

As Iwburk stated, this was a namespace issue. According to the Selenium API,

http://release.seleniumhq.org/selenium-remote-control/0.9.0/doc/java/com/thoughtworks/selenium/Selenium.html

while using an xpath expression, I needed to used xpath=xpathExpression changing my query string to:

String path = "xpath=(//input[starts-with(@id,'quantity_')])[2]";

I found a related post here, Element is found in XPath Checker but not in Selenium

Solution 3:[3]

you can't access it because you are not locating the element as to be unique in the page. use an xpath that makes it unique , - you're xpath look ok . more info here http://www.seleniumhq.org/docs/appendix_locating_techniques.jsp

Solution 4:[4]

Besides the selenium syntax problem there's an xpath issue related to markup structure.
xpath 1: //input[starts-with(@id,'number_')][1]
xpath 2: (//input[starts-with(@id,'number_')])[1]

In the sample below xpath 1 will return 2 nodes (incorrect) and xpath 2 will be correct because input nodes are not siblings so surrounding parenthesis are needed to refer to the resulting nodeset

<div id="phones">
    <div>
        <input id="number_1" name="number_1" type="text"/>
    </div>
    <div>
        <input id="number_2" name="number_2" type="text" />
    </div>
</div>

Result without parenthesis

/ > xpath //input[starts-with(@id,'number_')][1]
Object is a Node Set :
Set contains 2 nodes:
1  ELEMENT input
    ATTRIBUTE id
    TEXT
        content=number_1
    ATTRIBUTE name
    TEXT
        content=number_1
    ATTRIBUTE type
    TEXT
        content=text
2  ELEMENT input
    ATTRIBUTE id
    TEXT
        content=number_2
    ATTRIBUTE name
    TEXT
        content=number_2
    ATTRIBUTE type
    TEXT
        content=text

In this next sample, parenthesis will not make a difference because nodes are siblings

<div id="other">
<input id="pre_1" type="text"/>
<input id="pre_2" type="text" />
<div>a</div>
</div>

With parenthesis

/ > xpath (//input[starts-with(@id,'pre_')])[1]
Object is a Node Set :
Set contains 1 nodes:
1  ELEMENT input
    ATTRIBUTE id
    TEXT
        content=pre_1
    ATTRIBUTE type
    TEXT
        content=text

Without parenthesis

/ > xpath //input[starts-with(@id,'pre_')][1]
Object is a Node Set :
Set contains 1 nodes:
1  ELEMENT input
    ATTRIBUTE id
    TEXT
        content=pre_1
    ATTRIBUTE type
    TEXT
        content=text

Testing was done with xmllint shell xmllint --html --shell test.html

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Wayne
Solution 2 Community
Solution 3 Ionut Emilian Moldovan
Solution 4 LMC