1

I encountered the below error in my robot framework script.

InvalidSelectorException: Message: invalid selector: Unable to locate an element with the xpath expression //ul[@id='xyz-ul']/li[contains(text(),'Épargne en vue d\'un objectif précis')] because of the following error:
SyntaxError: Failed to execute 'evaluate' on 'Document': The string '//ul[@id='xyzul']/li[contains(text(),'Épargne en vue d\'un objectif précis')]' is not a valid XPath expression.

I am trying to fetch the French data "Épargne en vue d'un objectif précis" from an excel and passing in an xpath to identify and click the element.

The element is a dropdown. This is one of the scenarios. I have many other such scenarios which are failing. Could someone help how to handle this?

Todor Minakov
  • 19,097
  • 3
  • 55
  • 60
Srpk
  • 21
  • 1
  • 3

3 Answers3

4

XPath strings do not have escape sequences. This

//ul[@id='xyz-ul']/li[contains(text(),'Épargne en vue d\'un objectif précis')]

is just as invalid as this

//ul[@id='xyz-ul']/li[contains(text(),'Épargne en vue d'un objectif précis')]

would be.

It depends on the circumstances if you can mitigate this issue, or if you are out of luck.

In the context of a testing framework you might very well be out of luck, but you need to show more of your code to get a better answer.


There are two ways to get single quotes into a string in XPath.

  1. Use double quotes for the string. Then you can use single quotes inside (and vice versa).

     //ul[@id='xyz-ul']/li[contains(text(),"Épargne en vue d'un objectif précis")]
    
  2. When this is not feasible, like in your situation, because you never know whether a user-supplied value contains single- or double quotes, you must use the concat() XPath function like this:

     //ul[@id='xyz-ul']/li[contains(text(), concat('Épargne en vue d', "'", 'un objectif précis'))]
    

The second version looks difficult, but it can in fact be built reliably by splitting and joining the input string, like this

  1. Input string is Épargne en vue d'un objectif précis
  2. Splitting at ' gives Épargne en vue d and un objectif précis
  3. Joining with ', "'", ' gives Épargne en vue d', "'", 'un objectif précis
  4. Wrapping with concat(' and ') gives concat('Épargne en vue d', "'", 'un objectif précis'),

which is exactly the string you can put into an XPath expression with a placeholder, like this one:

${dropdown_selection_first_part_xpath} =  //ul[@id='xyz-ul']/li[contains(text(), 
${dropdown_selection_second_part_xpath} =  )]
${dropdown_xpath} =  //div[@id='xyz-widget']/div/div[2]/div[1]

${escaped_input_purpose_of_account} = (somehow to the 4 steps above)

${dropdown_selection_xpath} =  catenate  SEPARATOR=            ${dropdown_selection_first_part_xpath}    ${escaped_input_purpose_of_account}    ${dropdown_selection_second_part_xpath}

This approach works for all inputs (empty string, strings with no quotes, strings with any mix of single- and double quotes) and always creates syntactically valid XPath expression that does what you expect.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • I already tried your 2nd option and it didn't work. I have posted the rest of the code as an answer. – Srpk May 17 '17 at 19:28
  • See updated answer. If you can figure out how to do the four steps described in your framework then you are in luck. If your framework can't do this for some reason, then you can't use test strings with single quotes. – Tomalak May 17 '17 at 19:56
1

As rightfully said in another answer, there's no quotes escaping in xpath. What you can do to workaround it, is to use the opposite quote type to surround the text string.

So for the sample in the question, this - double quotes - should do it:

//ul[@id='xyz-ul']/li[contains(text(),"Épargne en vue d'un objectif précis")]

Now, some of your test strings might have single quotes, others - double. Here'she a sample code how to construct the right one locator, per case:

${dropdown_selection_first_part_xpath} =  //ul[@id='xyz-ul']/li[contains(text(),  # no quote symbol
${dropdown_selection_second_part_xpath} =  )]  # no symbol here also
${dropdown_xpath} =  //div[@id='xyz-widget']/div/div[2]/div[1]

# "Get Value" omitted for brevity, no change in it

Enter Value
    click element  xpath=${dropdown_xpath} 
    ${input}=  Get Value  ${language}

    # put the surrounding quotes based on the string's content - double quotes if it has a single one, single otherwise
    ${input}=  Set Variable If    "'" in """${input}"""    "${input}"    '${input}'

    ${dropdown_selection_xpath}=  catenate  SEPARATOR=        ${dropdown_selection_first_part_xpath}  ${input}  ${dropdown_selection_second_part_xpath}
    click element  xpath=${dropdown_selection_xpath}

Finally, if you have test strings with both single and double quotes - it'll be much trickier, but still doable.

Todor Minakov
  • 19,097
  • 3
  • 55
  • 60
  • This will break apart as soon as there are single and double quotes in the same string. The method described in my answer pretty much is the only way to do it without creating code that breaks at some inputs. – Tomalak May 17 '17 at 19:58
  • To self-quote - "Finally, if you have test strings with both single and double quotes - it'll be much trickier, but still doable." It is implied - though yes, not explicitly said, that this will not work for mixed-quote source strings. – Todor Minakov May 17 '17 at 20:02
  • It's not "much trickier", please read my answer, the method is there. You seem to be proficient in the robot framework, I think you will get a simple split-and-join operation to work. I've not worked with it before, so I can't do it as easily. – Tomalak May 17 '17 at 20:05
  • It is "much trickier", because a) I would go for a different approach, not concatenating in the xpath but rather extending the condition set with `contains(text(), 'split_part')`, b) the logic should accommodate for cases where there are no quotes, thus 1-member result arrays on py's `'"'.join(result_set)` would not return surrounded strings, c) which requires writing more lines of code, d) which I'm not willing to do on a phone's keyboard unless e) @op has the real need for, f) and needs help in achieving. So the "much" is the appropriate adjective IMO ;) – Todor Minakov May 17 '17 at 20:20
  • And in what is that better than unconditionally doing that split/join, which always yields the correct result? (phone keyboard reason notwithstanding, I can relate to that, even though you seem to have put up quite a bit of code on that for your anwer) – Tomalak May 17 '17 at 20:22
  • I don't know, it was what I thought of while writing my answer, while yours was still in v1 with "can't be done be done in xpath", without the v2's solution with the concat approach. Having decided on it, it seems suitable and viable solution, and the difference b/n the two would be just what's programatically constructed to be passed in the xpath. (Have to agree though, yours would be computationaly faster, for what it matters in the GHz cpus and the selenium/browser monstrosity evaluating it ;) – Todor Minakov May 17 '17 at 20:31
  • It can't be done in XPath. You can use an external tool to construct XPath that can do what you want. But that *still* means that it can't be done in XPath. :) And whether it can be done in the external tool depends on the tool. I had/have no idea what robot framework can do. I figured, if all you have is config files with XPath templates that are filled for you, then there's not a lot you can do. But apparently much more than that is possible in the robot framework. – Tomalak May 17 '17 at 20:54
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/144500/discussion-between-todor-and-tomalak). – Todor Minakov May 17 '17 at 20:56
  • 1
    @Todor: Thanks! The double quotes worked in my case. – Srpk May 17 '17 at 21:05
0

I've not tested this, but you could try:

//ul[@id='xyz-ul']/li[contains(text(),'Épargne en vue d'un objectif précis')]
Bill Hileman
  • 2,798
  • 2
  • 17
  • 24
  • No, there are no escape sequences in XPath, especially not XML escape sequences. Also, this would still be invalid, even in XML/XSLT. – Tomalak May 17 '17 at 19:40
  • Then this would work (at least in this instance): //ul[@id='xyz-ul']/li[starts-with(text(),'Épargne en vue d') and ends-with(text(),'un objectif précis')] – Bill Hileman May 17 '17 at 19:44
  • This would work, but it is completely unfeasible for a testing framework where you don't know the input strings beforehand. And it would match false positives because it allows anything in-between, so the approach is flawed. – Tomalak May 17 '17 at 19:49
  • I expected that response. In order to use this method in all scenarios, the OP would need to write a function to count the single apostrophe's and manually build the xpath string as correspondingly. I don't mean to imply that it would be simple, but it's doable. Sometimes you're left with two choices: a tough solution or no solution. – Bill Hileman May 17 '17 at 19:56
  • @Tomalak I have to correct you for these statements - "unfeasible for a testing framework where you don't know the input strings beforehand". The robotframework has very good string processing capabilities, in fact - everything possible in python is applicable here. – Todor Minakov May 17 '17 at 20:00
  • @Todor I did not mean to say that it's impossible to solve (in fact, my answer says quite the opposite), I meant to say *"making the kinds of fixed assumptions like "starts-with" and "ends-with" is unfeasible"*, because they depend on the input and you must write code that does not depend on the input. – Tomalak May 17 '17 at 20:02
  • I got the message, @Tomalak, and I agree. My first instinct was the correct answer that you (and @Todor) posted. I don't know why I didn't go with my own instincts. – Bill Hileman May 17 '17 at 20:06
  • Building an XPath string from individual parts and user-supplied bits is not as straight-forward as it should be. It's like building SQL from string parts - not advisable, and hacky. For SQL, luckily, paramterized queries are available pretty much everywhere and solve this problem. For XPath OTOH... not so much. Most tools assume XPath is just a string and when you need to build it dynamically you are pretty much on your own. – Tomalak May 17 '17 at 20:13
  • I don't know the particular framework, but in general, you should not construct XPath expressions by concatenating fixed XPath text with user-supplied strings. Instead, you should use XPath expressions containing variable references, and bind the variables using whatever API your XPath processor supplies. – Michael Kay May 18 '17 at 07:48