2

Ruby 1.9.3, Rails 3.1.10, RSpec 2.13.0, Capybara 2.2.1

I am on a page in the app that uses nested forms. The form adds members to a queue. If a member is already saved, the id of that member (in the HTML) is queue_queue_members_attributes_0_user_id, followed by 1, 2 and so on. When I click the link to Add New Member via JavaScript, the HTML id of the new select box is something like: queue_queue_members_attributes_1421760178806_user_id

As you can see, a random string of numbers is generated for every new select box, in which the nested-attribute has yet to be saved. I need to know the id of this HTML element in order to effectively test with Capybara.

I am on a page that adds members to a queue. If members are already present, the ids of their select boxes are 0, 1, so on. If I click "Add New Member", the id of the new select box is a randomly generated number, in this case 1421760178806.

Original Attempt

Below is a helper method I made for the spec. It worked initially. Recently, however, I needed to test for queue members. When I added a queue member factory, and created one with a new queue factory instance, this problem began. Because now I have 2 select boxes matching the xpath query

def select_member(member)
  find(:xpath, "//select[contains(@id, '_user_id')]/option[@value='#{member.id}']").select_option
end

Failure/Error: select_member(@test_user)
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching xpath "//select[contains(@id, '_user_id')]/option[@value='6647']"

With my changes, a member is added to a queue upon creation via FactoryGirl. This means my xpath expression matches both select boxes -- one with id 0 and the other 1421760178806. I only want it to match the latter number (anything but a single "0".) My tests only add a single member, so I am not worrying about ids >0.

Second Attempt

I found this SO question initially helpful, until I ran into another error.

def select_member(member)
  find(:xpath, "//select[matches(@id, '[0-9]{2,}_user_id')]/option[@value='#{member.id}']").select_option
end

Failure/Error: select_member(@test_user)
Capybara::Webkit::InvalidResponseError:
INVALID_EXPRESSION_ERR: DOM XPath Exception 51
Community
  • 1
  • 1
onebree
  • 1,853
  • 1
  • 17
  • 44
  • 2
    Please add the HTML source to your question. Sure that you're using XPath 2.0? That would be exceptional. There is no XPath 2.0.0, do you mean the Ruby class here: http://ruby-doc.org/stdlib-2.0/libdoc/rexml/rdoc/REXML/XPath.html ? – Mathias Müller Jan 19 '15 at 21:38
  • Thank you, I specified the xpath as a gem. As far as HTML source, do you mean the entire element I am trying to find? – onebree Jan 19 '15 at 22:20
  • What exactly do you expect the [`xpath`](https://github.com/jnicklas/xpath) gem to do for you? – Mark Thomas Jan 19 '15 at 23:08
  • What testing framework are you using? – Mark Thomas Jan 19 '15 at 23:15
  • I'm just listing the xpath gem version as i thought it would be helpful... I'll get back to you tomorrow on the rest... I'm not at my work computer right now – onebree Jan 19 '15 at 23:21
  • @MarkThomas I hope my edits help you, and everyone else, out. – onebree Jan 20 '15 at 13:42
  • 1
    So apparently you are using Capybara. The `xpath` gem is not doing a thing for you. – Mark Thomas Jan 21 '15 at 13:06

1 Answers1

2

Moving to Rails 4 with updated gems, I was able to solve my question about a month after posting. The following should still help with a Rails 3 project.

After much research and digging, I found out that our app had xpath as a default Capybara selector. I am not sure why, but I think it was because our views were complicated with forms-in-forms-in-tables. Now that are views were tidy with Twitter Bootstrap and Rails 4, I decided to change the default Capybara selector to, well, the default option -- :css. This allowed me to select or fill_in based on the (cleaner) id, name, or label of the element.

In regards to the nested forms issue, I solved it by creating a spec helper method. In it, I locate the table that contains the form element. Using xpath, I locate the last row of the table in that instance. The last row selection acts as a within do block in Capybara.

# xpath selector - locate last nested row within a given table id
def last_row(id)
  find(:xpath, "//table[@id='#{id}']//tbody/tr[last()]")
end

within last_row("queues") do
  # css selector - select item based on label
  select "Extension User 2", from: "Queue member"
  select "1", from: "Penalty"
end

I am sure the above can be changed to CSS somehow, but I was still new to both selectors in testing. Feel free to comment with the css selector version.

onebree
  • 1,853
  • 1
  • 17
  • 44