XPath trouble with Selenium XPath trouble with Selenium selenium selenium

XPath trouble with Selenium


There is no ' in your HTML document. There is a '.

The ' only informs the HTML parser to insert a single quote into the document tree at this position, it does not actually end up as something you could search for.

You can do this:

self.wait_for(lambda: self.assertEqual(    self.browser.find_element_by_xpath(        '//span[contains(text(), "You can\'t have an empty list item")]'        )    ))

but this only works as long as the quotes are exactly this way. When your search text contains a double quote, the above breaks and you must escape things the other way around. That's feasible as long as the search text is predefined.

As long as the resulting XPath is valid, you're good to go. In this case, the above results in this perfectly valid XPath expression:

//span[contains(text(), "You can't have an empty list item")]

But if the search text is variable (e.g. user-defined) then things get hairy. Python knows string escape sequences, you can always use \" or \' to get a quote into a string. XPath knows no such thing.

Assume the search text is given as You can't have an "empty" list item. This is easy to generate with Python, but it won't work:

//span[contains(text(), "You can't have an "empty" list item")]-------------------------------------------^ breaks here

and this XPath won't work either:

//span[contains(text(), 'You can't have an "empty" list item')]--------------------------------^ breaks here

and neither will this one, because XPath has no escape sequences:

//span[contains(text(), 'You can\'t have an "empty" list item')]---------------------------------^ breaks here

What you can do in XPath to work around this is concatenate differently quoted strings. This:

//span[contains(text(), concat('You can', "'" ,'t have an "empty" list item'))]

is perfectly valid and will search for the text You can't have an "empty" list item.

And what you can do in Python is create this very structure:

  1. split the search string at '
  2. join the parts with ', "'", '
  3. prepend concat(', append ')
  4. insert into the XPath expression

The following would allow a string search that can never throw a run-time error because of malformed XPath:

search_text = 'You can\'t have an "empty" list item'concat_expr = "', \"'\", '".join(search_text.split("'"))concat_expr = "concat('" + concat_expr + "')"xpath = "//span[contains(text(), %s)]" % concat_expr

xpath, as a Python string literal (what you would see when you print it to the console):

'//span[contains(text(), concat(\'You can\', "\'", \'t have an "empty" list item\'))]'

The way the XPath engine gets to see it (i.e. the actual string in memory):

//span[contains(text(), concat('You can', "'", 't have an "empty" list item'))]

The lxml library allows XPath variables, which is a lot more elegant than that, but I doubt that Selenium's find_elements_by_xpath supports them.


@Tomalak answer gives us a great insight on text() of xpath. However, as you are using find_element_by_xpath() you will be at ease clubbing up the class attribute and you can use the following xpath based solution:

self.wait_for(lambda: self.assertEqual(    self.browser.find_element_by_xpath(    "//span[@class='help-block' and contains(., 'have an empty list item')]"    )  ))