0

What is wrong with my path to select the following:

<label class="form-control-label" for="profile_form_state">State</label>

Xpath:

xpath = '//label[ends-with(@for, "_state")]'

I am using rspec and capybara

 expect(rendered).to have_xpath(xpath)

Error:

   xmlXPathCompOpEval: function ends-with not found
user2012677
  • 5,465
  • 6
  • 51
  • 113

4 Answers4

2

As answered by @har07, XPath 1.0 (which browsers implement) doesn’t have an ends-with but CSS provides an ends-with attribute selector $=

expect(rendered).to have_css(‘label[for$=“_state”]’)

or you can use regex with Capybaras built-in :label selector

expect(rendered).to have_selector(:label, for: /_state$/)

If you really want to stick with XPath over CSS then you can use the xpath gem Capybara uses internally for generating its own XPaths and write

xpath = XPath.descendant(:label).where(XPath.attr(:for).ends_with('_start'))
expect(rendered).to have_xpath(xpath)
Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78
  • Would love to just use rspec to locate a css element and it’s previous sibling label and in another case parent form. What is best way? Otherwise, what is your best recommended approach? – user2012677 May 28 '19 at 13:28
  • @user2012677 Without seeing the HTML it's impossible to give the best approach - depends on whether the label is correctly associated with the element, etc. If the label is correctly associated with the field then if you've already found the field you can just do `find(:label, for: field)` - for a parent form you can do `field.ancestor('form')` – Thomas Walpole May 28 '19 at 14:09
  • Are "find() and field.ancestor(), a rspec, xpath or capybara thing? Would I do expect(rendered).find(:label, for:field) and expect(rendered).field.ancestor('form')? – user2012677 May 28 '19 at 14:19
  • @user2012677 They're capybara methods not rspec matchers. This is moving far beyond your original question and it's tough to give a full answer in comments. Ask a new question with a sample HTML and explain exactly what you're trying to do. – Thomas Walpole May 28 '19 at 16:11
0

Looks like your XPath processor only supports XPath, 1.0 while ends-with is defined in XPath 2.0 and above. But you can simulate ends-with() in XPath 1.0 using substring() and string-length() :

xpath = '//label["_state" = substring(@for, string-length(@for) - string-length("_state") +1)]'

You can shorten the expression a bit by replacing string-length("_state") +1 with pre-calculated value 5 (length of the word _state minus one) :

xpath = '//label["_state" = substring(@for, string-length(@for) - 5)]'
har07
  • 88,338
  • 12
  • 84
  • 137
  • Doesn’t caybara run xpath v3? – user2012677 May 28 '19 at 09:00
  • I mean [XPath specification](https://www.w3.org/TR/xpath20/), not ruby xpath library version – har07 May 28 '19 at 09:15
  • Can I change the specification easily? – user2012677 May 28 '19 at 09:16
  • related discussion regarding XPath `matches` which also not supported in version 1.0: https://stackoverflow.com/a/35796197/2998271 – har07 May 28 '19 at 09:17
  • I'm not experienced with ruby, but based on the linked discussion seems like the XPath processor only implement XPath 1.0 so there is no way for you to use XPath 2.0 expression, hence the alternative expression – har07 May 28 '19 at 09:21
0

Other possible Xpath

//label[contains(@for, '_state')]

or

//label[text()='State']

or

//label[contains(text(), 'State')]
frianH
  • 7,295
  • 6
  • 20
  • 45
Vince Twano
  • 117
  • 1
  • 2
0

Try the following Xpath.

xpath = '//label[contains(., "State")]'
KunduK
  • 32,888
  • 5
  • 17
  • 41