How do I click_link with Capybara that has a method: :post? - ruby-on-rails

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.

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."

Capybara: click_button with no text or id?

Here is the html code:
<button type="button" class="icl-Button--transparent icl-Button--sm ia-AddCoverLetter-button"><span class="icl-ButtonIcon"><svg aria-label="Add cover letter" class="icl-Icon icl-Icon--blue icl-Icon--sm" role="img"><g><path d="M9.75,5.25H8.25v3h-3v1.5h3v3h1.5v-3h3V8.25h-3v-3ZM9,1.5A7.5,7.5,0,1,0,16.5,9,7.5,7.5,0,0,0,9,1.5ZM9,15a6,6,0,1,1,6-6A6,6,0,0,1,9,15Z"></path></g></svg></span>Add cover letter</button>
How would you get capybara to click on it when it has no name or id. I tried click_link('Add cover letter') but it did not work either.
The accepted answer will work fine, however if you want to stay using click_button to make your code clearer to read (click_button not click_link since it's a button not a link) you could also do
click_button(class: 'ia-AddCoverLetter-button')
or if you want to specify more than one class you can pass an array
click_button(class: ['icl-Button--transparent', 'icl-Button--sm', 'ia-AddCoverLetter-button'])
I came across this question while looking for a way to click on an item based on it's aria-label (as the OP tried to do but couldn't get to work).
In general this can now (since August 2016) be done by enabling an option for Capybara to match ARIA labels. In Rails system tests, this is achieved by adding Capybara.enable_aria_label = true to application_system_test_case.rb:
# test/application_system_test_case.rb
require 'test_helper'
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
Capybara.enable_aria_label = true
end
With that option turned on, to click on an icon link like this one (using HAML):
= link_to edit_reader_path(reader), 'aria-label' => 'Edit' do
%i.icon-pencil.m-auto.text-primary{'aria-hidden' => 'true'}
...I can just write something like this in my system test:
click_on 'Edit', match: :first
I'm assuming this would need to be tweaked to suit the OP's situation where the aria-label is on an SVG nested within a span, within the button, perhaps by finding the SVG and then finding its parent's parent.
You should be able to select it by it's class, have you tried that?
find('button.ia-AddCoverLetter-button').click

How do I confirm a css element attribute with Capybara?

This may seem unusually basic but how do I confirm the presence of a pop up confirmation?
<a data-confirm="delete this video?" rel="nofollow" data-method="delete" href="/videos/21">Delete</a>
<a is the "tag"/"element" and data-confirm is an attribute. I want to test for the existence of the "data-confirm" attribute within the <a> element/tag
I have tried
expect(page).to have_css("a.data-confirm.delete this video?")
from
capybara assert attributes of an element
but no joy.
Edit:
I've tried the expectation from Arup's comment below
expect(page).to have_content "Content"
click_link "Delete"
expect(page).to have_css('a[data-confirm="delete this video?"]')
But it raises the following (same) error
Failures:
1) Visiting the video index page should search and save movies
Failure/Error: expect(page).to have_css('a[data-confirm="delete this video?"]')
expected #has_css?("a[data-confirm=\"delete this video?\"]") to return true, got false
but the page source shows it there and it is clearly working for the user
Any assistance would be very appreciated
You can write this expectation as:
expect(page).to have_css('a[data-confirm="delete this video?"]')
The answer by Arup is correct for the title of the question (and as he stated in the comments it's just valid CSS - https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors), however it's not actually testing the more detailed part of the question "how do I confirm the presence of a pop up confirmation". All it is doing is confirming the correct data attribute is on the link element to trigger the rails provided JS that should show a confirm.
If you wanted to actually test the confirm box is shown you would need to swap to using a JS capable driver - https://github.com/teamcapybara/capybara/tree/2.17_stable#drivers - and then use something like the following in your test
expect(page).to have_content "Content"
accept_confirm "delete this video?" do
click_link "Delete" # The action that will make the system modal confirm box appear
end
See - http://www.rubydoc.info/gems/capybara/Capybara/Session#accept_confirm-instance_method

Rails: How to use Capybara to test a link's href-value without caring about the text?

Capybara provides a useful method to check a link:
have_link
which as far as I can tell can be used like:
have_link("link_text", :href => "actual link")
However, I don't care about the link_text, rather I just want to check href (as linking the test to the text is more brittle). If I only want to check the href this would be a view test.
How do I use Capybara to check the href without needing to check the text? Maybe i can use regex?
[edit] changed wording based on answer below
To find a link based on just its href using capybara you could do
link = page.find(:css, 'a[href="actual link"]')
or if you're looking to assert that the element exists
page.assert_selector(:css, 'a[href="actual link"]')
or - if using RSpec
expect(page).to have_selector(:css, 'a[href="actual link"]')
Since have link by default searches for substrings in the link text you can also do
expect(page).to have_link(nil, href: 'actual link')
or
page.assert_selector(:link, nil, href: 'actual link')
While looking to the same answer I found out that it's possible to pass options to first argument of have_link because it's implementation is:
# RSpec matcher for links
# See {Capybara::Node::Matchers#has_link?}
def have_link(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:link, locator, options, &optional_filter_block)
end
So if you don't care about link text you can safely omit first parameter and do this (RSpec syntax):
expect(page).to have_link(href: 'some url')
# or even
expect(page).to have_link(href: /some url regexp/)

Finding a label with Webrat that contains a link

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

Resources