Question

I've setup a function of my Selenium (v 2.3) tests to check for the presence of certain error messages which has worked perfectly until an element I'm checking has an acronym in.

My function gets all errors in the page and then searches that div for the message expected;

# el is the error div element
error_el = el.find_element_by_xpath('p[contains(text(), "{0}")]'.format(error_text))
...
self.assertIsNotNone(error_el) # make sure it actually found something

I'm calling this from my tests with self.assertFieldError('My error message') and it works perfectly, until 'My error message' contains an acronym. If an acronym is present, Selenium doesn't match my string to the paragraph.

My current solution to this is to check for the string in the source for the page, but this isn't ideal if it's a string that appears more than once.

Is it possible to ignore acronym tags or improve my xpath to allow text or text with acronyms? I'd always assumed that the text() method operated exactly as things are seen by the user and it'd automatically ignore acronyms.

edit

The error messages are attached to the field's using Javascript which gets the field label from the Django form via it's field name. Although this shouldn't be relevant to Selenium as far as I'm aware.

HTML:

<div class="field">
    <div class="error">
        <p>My <acronym title="Error">ERR</acronym> message</p>
    </div>
    <label for="id_myField">My <acronym title="Error">ERR</acronym> message</label>
    <input id="id_myField" type="text">
</div>
Was it helpful?

Solution

I think you're confusing the behavior of text() with that of converting an element node to a string value. The difference shows up in cases where <p> has child elements with text, such as <acronym>.

What text() does is select individual text nodes. With no explicit namespace axis, it selects text nodes that are children of the context node. Inside p[...], the context node is a <p> element. So text() inside that predicate selects only nodes that are children (not grandchildren) of the context node. In your example, the <p> element has three child nodes:

  • text node "My "
  • element node named acronym
  • text node " message"

Therefore, text() (in the context of p element) would return a nodeset of two text nodes, whose values are My and message.

When evaluated as a string, as in contains(text(), ...), a nodeset is treated as follows: the string value of the first node is returned, and the rest are ignored. So in your example, the nodeset returned by text() evaluates to "My ", that is, the content of the first child text node. Your XPath expression then is equivalent to p[contains("My ", "My error message")] for the p you're trying to match. That of course fails. The predicate is false, so no p element is returned.

You wanted the string content of all descendant text nodes to be concatenated, but that's not what text() does. To get that, evaluate the <p> element itself as a string. E.g.

p[contains(., "...")]

. means the context node, which inside p[...] is the p element. When you evaluate the p element as a string, the result is the concatenation of all descendant text nodes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top