Finding a label with Webrat that contains a link - ruby-on-rails

So I'm doing BDD with Cucumber and have a form with checkboxes populated from a database. The labels for the checkboxes contain hyperlinks. So far, not too exotic (note, this is HAML and not Erb, but it should be readable enough for any Rails person):
I would like my donation to support:
%br
- for podcast in #podcasts
= check_box_tag "donation[podcast_ids][]", podcast.id, true
= donation.label "donation[podcast_ids][]", link_to(podcast.name, podcast.url), :value => podcast.id
%br
The problem is that in my Cucumber features, I can't figure out how to find that checkbox to check it. The relevant part of the story is this:
Scenario: Happy path
Given I am on the home page
When I fill in "My email address" with "john#example.org"
# Skipped for brevity...
And I check the "Escape Pod" podcast
And I check the "PodCastle" podcast
And I press "I'm ready!"
Then I should see "Thank you!"
And there should be 2 podcast donation records
If I'm using the bare webrat_steps.rb file I get the following error:
Could not find field: "Escape Pod" (Webrat::NotFoundError)
I'm quite certain it's because of that link_to() method, which I'm using to make "Escape Pod" a hyperlink to the actual Web site. But I can't easily access link_to from my Cucumber step, and I can't figure out any reasonable way of pointing Webrat at the right checkbox short of kludging up a whole bunch of hyperlink code in my step (which makes it very brittle).
My BDD is stalled at this point. I don't want to take out the link just because it's hard to test. And it feels like it shouldn't be hard to test. Webrat is just limiting what I can pass into the checks() method. Can anyone suggest an elegant answer for this?

The short answer is the to use field_by_xpath or one of the other Webrat::Locators methods to select what element to manipulate in your step:
When(/^I check the "(.+?)" podcast$/) do |name|
check(field_by_xpath("//label/a[.=#{name}]")
end
You might need to play with that xpath a little, or use field_by_id instead. Remember it is looking got the html id of the tag not the id from the database.

Can you post what your HTML looks like in the rendered page near the problematic checkbox(es)? Sometimes you have to play with naming the field... I had all sorts of trouble with a login form... I ended up doing this:
<%= submit_tag 'Enter', {:id => "login_button"} %>
So that the following worked:
Given /^I am logged in as admin$/ do
visit login_path
fill_in "login", :with => "admin"
fill_in "password", :with => "password"
# click_button "login_button"
click_button
end
I know it's not a checkbox example, but maybe fiddling with your name/id/etc will work

Related

Capybara not finding content on page when it should?

I'm using Capybara, and I'm selecting an item from a dropdown using this command:
select "Some Option", from: "client_id", visible: :all
This is the view code I'm using to generate the select:
<%= form_with(url: '#', method: 'GET') do |f| %>
<%= f.select :client_id,
options_for_select(
Client.accessible_by(current_ability)
.map{|c| [c.name, c.id]}, selected_client_id
),
{include_blank: defined?(include_blank) ? include_blank : false},
{
class: "form-control selectpicker border",
id: "client_id",
onchange: "this.form.submit()",
data: {'live-search': true, style: "btn-white"}
}
%>
<% end %>
Which renders fine and works as expected when I myself am interacting with it on the page.
I believe the problem stems from this: onchange: "this.form.submit()"
When Capybara chooses an option from the select, it's supposed to submit the form, and then the page is supposed to reload with a query string /?client_id=X, but that is not what's happening and I don't know why.
When I look at the screenshot provided from the error, it appears as though Capybara hasn't even selected anything from the dropdown, it just says "Nothing selected."
Here's the Capybara test:
def check_grand_total(expected_value)
visit current_path
click_link "Balance"
# it seems as though Capybara is not waiting for this to finish... but I don't know, just my theory
# this is supposed to trigger: this.form.submit() on the dropdown
select "Some Client", from: "client_id", visible: :all
actual_value = page.first('.grand-total').text
assert_equal(expected_value, actual_value)
end
The error:
E
Error:
OrdersTest#test_deposit_orders:
Capybara::ExpectationNotMet: expected to find css ".grand-total" at least 1 time but there were no matches
test/system/helpers/balance_test_helper.rb:10:in `check_grand_total'
test/system/orders_test.rb:113:in `block in <class:OrdersTest>'
I'm thinking maybe Capybara isn't actually triggering the change event for some reason.
The class names you're specifying, and the fact that you're specifying (visible: :all) imply you may be using some type of JS widget to replace the standard html select element. Is that true? Overriding visibility checking in Capybara actions doesn't make any sense since you shouldn't be interacting with non-visible elements, and depending on exactly which action and driver will either error, or just not do anything. If you are using a replacement widget then you need to interact with the elements created by the widget rather than using select
If you're not using a JS widget then it could just be a case of a badly written test. There are a couple of things right away - first is that you are visiting current_path, second is that you're asserting against text rather than using one of the Capybara provided assertions. The thing to remember is that browser actions occur asynchronously, so when you tell Capybara to choose an option from a select element there is no guarantee the actions triggered by doing that have completed when the select call returns. Because of that you should be using the capybara provided assertions/expectations which include waiting/retrying behavior in order to sync the browser with the tests.
def check_grand_total(expected_value)
# This visit looks dangerous because it's not guaranteed to have changed
# the page when the `visit` returns, which means it may try and click
# the link on the previous page. You should really be checking for something
# that wouldn't be on the previous page -- It's also strange that there is a current path since tests should be independent
visit current_path
click_link "Balance" # see above comment
select "Some Client", from: "client_id"
# This will wait up to Capybara.default_max_wait_time seconds for
# the grand total element to contain the expected text
assert_selector '.grand-total', exact_text: expected_value
# If using RSpec it would be
# expect(page).to have_selector '.grand-total', exact_text: expected_value
end
So I think the issue is that the onchange even does not actually fire This "issue" suggests causing focus loss via
fill_in("foo", with: "hello world").send_keys(:tab)
so it may be possible to adapt that to
select("Some Client", from: "client_id", visible: :all).send_keys(:tab)
That being said it also appears that a method exists to force an event on an Element Capybara::Node::Element#trigger so I would start with trying:
select("Some Client", from: "client_id", visible: :all).trigger(:onchange)
Disclaimer: I am not very familiar with capybara so this answer is based on source review and googling. According to Thomas Walpole, who clearly has extensive experience, " trigger isn't supported by most drivers because it doesn't make a lot of sense to use during testing due to it not replicating what a user would actually do."

How do I click_link with Capybara that has a method: :post?

In my application, I have this link:
link_to 'Start', some_path, method: :post
and, in a feature test, I need to click this link and then assert that the page does not contain this link (instead, it should contain a 'Stop' link)
I tried to:
click_link 'Start'
but it does not work - either the link Start is still on the page or I am getting a page with a 'getting redirected' text (I use save_and_open_page)
How do I solve this issue?
EDIT: I am using the poltergeist driver.
To avoid js/browser overhead you can also do a fake click with pure ruby.
For example click_ujs_link "Send" with this code.
def click_ujs_link(name)
link = find(:link, name, {})
visit_with_method link['data-method'], link['href']
end
def visit_with_method(method, url)
page.driver.send method, url
visit page.driver.response.location
end
To answer my own question, the solution is trivial: just set js: true on the scenarios that contain the link, like for example this:
scenario 'some description', js: true do
The explanation: for links with methods other than GET, Rails.js dynamically generates a form, something like this
...
so you need to tell Capybara that it should run the scenario using a javascript engine (poltergeist, in our case), and you do so via the js: true option.

How to click the form commit button in Capybara

With Rails, rspec and capybara, I'm trying to test your typical ERB generated form:
<form action="/pages/1" id="edit_page_1" method="post">
<input id="page_title" name="page[title]" type="text">
<input name="commit" type="submit" value="Update Page">
</form>
I run two kinds of feature specs, those that are the same no matter what the language, and those that are I18N specific (for internationalization testing).
The problem is there is no clear way to click that submit button with capybara, unless I'm missing the obvious. I would expect simply click('commit') to do the trick.
Using click_button('Update Page') works but is obviously language specific and can't be used with both the New and Edit templates even though they render the same form template.
Adding an id to the submit button works, but I strongly dislike changing the code exclusively because the test requires it.
Using a css or xml matcher both looks ugly (a user would never know/care about accessing an element that way) and it is overkill.
In the end a macro was the answer I needed as apparently there is nothing native in capybara.
# spec/support/form_helpers.rb
module FormHelpers
def submit_form
find('input[name="commit"]').click
end
end
This gets included in spec_helper.rb
RSpec.configure do |config|
config.include FormHelpers, :type => :feature
...etc...
I used :type => :feature so it gets included only in the integration tests.
In the integration tests you can use it like this:
scenario 'pages can be created' do
visit new_page_path
fill_in 'page_title', with: 'A Tale of Two Cities'
submit_form # Clicks the commit button regardless of id or text
expect(page).to have_content 'The page was created'
...etc..
end
Of course submit_form can also be used inside within blocks and with :js => true.
I usually do:
within 'form#edit_page_1' do
find('input[name="page[title]"]').set "Some Value"
find('input[name="commit"]').click
end
Its tied to the html but to its semantic attributes, so I feel like its fine.
Actually I never use the magical finders.
Btw I dont understand your comment: (a user would never know/care about accessing an element that way).
Integration specs are for you, it mimics a user for sure but its just a matter of giving proper instructions.
Try to use:
find('input[name="commit"]').click
It helps

Check the content of a field populated by javascript with Capybara before submit

I have a field whcih I am auto populating based on a dropdown on the page.
i.e. I select task_type and the task_name has task_type populated.
I can't work out how to test this. I basically just want to test what the current content of the field is.
Everything I try seems to be able to find the field but not check the content of it.
Then(/^"(.*?)" should contain "(.*?)"$/) do |field, text|
page.should have_field(field, :text => value)
end
I'm assuming I need to do some js trickery to get the info from the browser, but can't seem to work it out.
And I should see "Annual Accounts" in "task[name]"
Have tried task_name aswell and both fail with
find(field).should have_text(text)
With message
Unable to find css "task[name]"
if I use
page.should have_field(field, :text => text)
Then they fail with:
expected to find field "task_name" with text "Annual Accounts" but there were no matches. Also found "",
You can use the have_text helper
find(field).should have_text(text)
Right, both of these work:
Not sure what the difference between them is.
I also think this does an exact match. If anyone could let me know how to do a "contains" type match that would be really handy.
Then(/^I should see "(.*?)" in "(.*?)"$/) do |text, field|
# page.has_field?(field, :with => text)
page.should have_field(field, :with => text)
end

Capybara and Rspec: correct way to use within() and have_selector() together?

I use rspec 2.6.0 and Capybara 1.1.1 for acceptance testing.
With a view like the following:
<tr >
<td>Team 3 Name</td>
<td>true</td>
<td>Show</td>
<td>Edit</td>
<td>Deactivate</td>
</tr>
<tr >
<td>Team 4 Name</td>
<td>true</td>
<td>Show</td>
<td>Edit</td>
<td>Deactivate</td>
</tr>
I want to write an acceptance test that states: "Team 3 does NOT have the 'Deactivate' link." I expect the following to fail:
within('tr', :text => 'Team 3 Name') do |ref|
page.should_not have_selector('a', :text => 'Deactivate')
end
But it passes. To further test what is going on, I wrote the absurd:
lock = false
within('tr', :text => 'Team 3 Name') do |ref|
page.should have_selector('a', :text => 'Deactivate')
page.should_not have_selector('a', :text => 'Deactivate')
lock = true
end
lock.should be_true
Which passes as well.
I am assuming from this that the scope the have_selector() call is using is not limited by the within() block, but I am not sure why this is. The capybara documentation uses this pattern and does not seem to mention any gotchas.
What is the correct way to use within to limit the scope of my select?
Thank you.
/Salernost
Still learning Capybara myself, but have you tried have_link instead of have_selector? Also I don't think you need |ref|. For example:
lock = false
within('tr', :text => 'Team 3 Name') do # omit |ref|
page.should have_link('Deactivate')
page.should_not have_link('Deactivate')
lock = true
end
lock.should be_true
Update October 13, 2012
Having come a little further with Capybara, I see several potential issues here:
within may silently ignore the text field. You'll notice that the examples only show CSS or XPath finders without additional arguments.
If within does use text, it may not work here because you are asking it to look at the <tr>, but the text is in the <td>.
It's quite possible that the page subject still targets the entire page even if you are in a within block. The within examples are mostly about using fill_in or click. The exception is the example under Beware the XPath // trap.
As for creating a within block, you can either give your table rows unique ids and search for them using CSS, or you may be able to write a specific XPath targeting the first matching row.
The problem with the latter is that you want use the within on the <tr>, but the text you are using for your targeting is inside a <td> subelement. So for example, this XPath should find the table cell containing the text Team 3 Name but then you are only working within that first cell, not the whole row.
within(:xpath, "//tr/td[normalize-space(text())='Team 3 Name'") do
There are ways to "back up" to a parent element using XPath but I don't know how to do it and I've read that it's not good practice. I think your best bet here might be to just generate ids so your rows start like this:
<tr id="team_3">
then target them with a simple
within("tr#team_3")
I would also recommend Mark Berry's final approach he mentioned of adding id's to each of your table elements.
<tr id="team_3">
then target with
within("tr#team_3")
Capybara has given me issues when selecting by xpath in that it doesn't seem to work consistently, especially with CI services.
I also want to note on the same answer this section:
It's quite possible that the page subject still targets the entire page even if you are in a within block. The within examples are mostly about using fill_in or click. The exception is the example under Beware the XPath // trap.
This may have been the case in an older version, but in the current version of Capybara, calling page inside of a within block only inspects the part of the page targeted. So, using Mark's above example:
within("tr#team_3") do
expect(page).to have_content 'Team 3 Name'
# => true
expect(page).to have_content 'Team 4 Name'
# => false
end
have_selector seems to ignore :text and :content options. I had to use something like this instead:
within 'a' do
page.should have_content 'Deactivate'
end
The solution is to not use within method:
expect(page).to have_css('tr#team_3') do
without_tag('a', text: 'Deactivate')
end

Resources