I'm using the Java Selenium WebDriver implementation. I'm searching for a string literal in an tag which has a tag in the middle, i.e.

<h4>Some regular text <strong>followed by strong text</strong> followed by regular text</h4>

I'm able to create an xpath selector for the first 2/3 (i.e., through the end of the strong tag), but not for the rest:

By.xpath("//h4[contains(text(),'Some regular text ')]" +
            "/descendant::strong[contains(text(), 'followed by strong text')]"

But I cannot finish the xpath path selector. Perhaps matching the first two-thirds is sufficient, but I wanted to find out for my own edification / completeness.

1 Answer 1

//h4[contains(text(),'Some regular text ')]

You are starting correct, though the first part, //h4[contains(text(),'Some regular text ')], looks for any h4 at any depth, with among its immediate children a text node containing Some regular text. Meaning, if that text appears after the strong element, it will also match the h4.

/descendant::strong[contains(text(), 'followed by strong text')]

You chose descendant::, which means it will search at any depth from h4 for a strong element. If you had <span><div><strong>...</strong></div></span> it would also find it. If that's the intend, leave it as is. If not, and you want an immediate child, then use just /strong[...].

Your XPath as a whole will select that strong element containing the text followed by strong text, with an ancestor h4 as specified before.

Perhaps matching the first two-thirds is sufficient

You say this is ⅔ of what you wanted to achieve. Do you mean you need to select the text node? In that case, append the expression with /text().

Essentially, this XPath returns something, or nothing. It will return nothing if it can't find both texts the way you specified it. Otherwise it will return the strong element. If you just need to verify its existence, you are done.

through the end of the strong tag

This is not quite right, but I understand the confusion. XPath does not see tags, it sees elements (or more generally: nodes), which is the whole of the opening tag and the closing tag, all its descendants, processing instructions, attributes and namespaces. As such, and as suggested, you can then query whatever is inside the strong element.

Update (selecting following sibling)

After your comment, I think the rules are as follows:

  • Any h4 with an immediate child strong
  • Before strong contains text X
  • Inside strong, contains text Y
  • After strong, contains text Z

This can be written as:

//h4[text()[1][contains(., 'X')]]
       [contains(., 'Y')]
       [following-sibling::text()[1][contains(., 'Z')]]

Written in such a way that it still returns the strong element. If you want the whole h4 to be returned, write everything as a predicate of it (though for a boolean test whether this returns anything, this obviously won't matter):

    [text()[1][contains(., 'X')] 
        and text()[last()][contains(', 'Z')]]
    [strong[contains(., 'Y')]]

(for illustration, it uses a slightly different approach for the third text match)

  • Thanks for the response, specifically on demonstrating how to signal for an immediate child, which is what I did want. However the last part isn't clear. I want to include the text (still within the h4 node), i.e. <h4>first <strong> second </strong> third</h4>. I want to include the text 'third' in my xpath selector.
    – douglas
    Sep 11, 2015 at 19:27
  • @douglas, I have updated with that in mind. Note, if you want an exact match, use = instead of contains().
    – Abel
    Sep 11, 2015 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.