Rails/Capybara- Test that form does not submit - ruby-on-rails

In my Rails app, I used the following jQuery to disable the ability to submit a form by pressing 'enter' in an input field:
$('input').on('keydown', function(e) {
if(e.which == '13') {
return false;
}
})
I wanted to test this with Capybara, but can't figure out how. For example:
# the form being tested creates a new Shift object:
test "enter does not submit form" do
shift_count = Shift.count
# fill in form, etc...
page.driver.browser.execute_script "var e = jQuery.Event('keydown'); e.which = 13; $('#shift_employee').trigger( e );"
assert Shift.count == shift_count
end
This test passes even when the jQuery function is commented out, presumably because it calculates Shift.count before ActiveRecord has had time to save the new the record to the database.
I also tried having the jQuery function do a custom $.get() request, to which the controller responds with simple text, thinking that this would build in enough time for all server-side work to be completed. But this still doesn't work, and I think it's because, no matter how I set up the jQuery function, the submission of the form and saving of the new record will always occur after any AJAX stuff I build in.
Any ideas on how I can reliably test that the form was not submitted?

So the form submission is done via AJAX? This is a common problem that testers run in to with Capybara. What you need to do is wait for the AJAX to complete.
There are two ways to do this. One is very specific and is the preferred method, the other is generic and should only be used if the first method can't be done.
The first way is to wait for content to change on the DOM. In your case nothing might change, but if, for example, you had a warning come up saying "You cannot press Enter, you must click the Save button!" then you could wait for that to come up by doing something like:
page.driver.browser ... # your code
page.should have_css(".info", :text => "You cannot ...etc...")
Of course you don't have to use the :text option though.
The other way is to make a helper method, like wait_for_ajax, that does a generic wait until AJAX is complete.
def wait_for_ajax
start = Time.now
while true
break if (page.evaluate_script('$.active') == 0)
if Time.now > start + Capybara.default_wait_time.seconds
fail "AJAX did not register as complete after #{Capybara.default_wait_time} seconds!"
end
sleep 0.1
end
end
This is based off the old way that people used to check for AJAX, but this new script is preferred for Capybara 2.0.
Then your step would do:
page.driver.etc
wait_for_ajax
That wait_for_ajax method can go in to any file Cucumber will load.

I got it to work by having Capybara revisit the page, and then checking Shift.count. The test now fails when the jQuery function is commented out, and passes when the function is uncommented. I'd still like to know if there's a better way, though.

Related

Capybara: Session lost when adding a setTimeout on form.submit()

Rails v4.2.7
I have a rails form (not remote) with a submit button with this event attached for both preventing a double submission, and showing a better feedback for the user (basically what data-disable-with would do for a remote form):
$('#my-submit-button').on "click", ->
this.disabled = "disabled"
this.value = "Loading..."
$(this).removeClass("button-active-class")
$(this).addClass("button-busy-class")
myForm = this.form
setTimeout ->
myForm.submit()
, 500
The reason for the setTimeout is that if I instantly submit the form, for some reason in Safari the style changes are not applied.
This works great.
however, I have a Capybara test (which uses headless_chrome) like this:
sign_in user
visit my_page_path
# fill in form
find("#my-submit-button").click
expect(page).to have_content "some content"
For some reason I noticed the current_user was being set to nil after the form was submitted, so I added a log for cookies as a before_filter in my ApplicationController:
def my_before_filter
p cookies.first
end
And the output was effectively showing me two different cookies between the visit, and the request triggered by the form submission.
So I removed the setTimeout in order to submit the form instantly:
$('#my-submit-button').on "click", ->
# [...]
# myForm = this.form
# setTimeout ->
# myForm.submit()
# , 500
this.form.submit()
And now, even though the styling is not correctly applied after clicking on the button, the test passes and cookies are the same.
Any idea what's going on and how to fix it?
Note: This is a guess based on the the assumption your sign_in method actually visits the page, fills in the login info and submits the form. It your sign_in method is actually a shortcut via something like the devise warden test mode this won't be correct.
It sounds like your $('#my-submit-button') is also being applied to the submit button on the login form. By delaying that half a second (seems like a long time to delay) the driver no longer detects that the actions done in sign_in are triggering a page transition and therefore doesn't wait for it. This means the visit my_page_path gets executed before login has completed and the correct cookies have been returned. Your sign_in method should have a positive expectation at the end of it for something on the page that indicates logging in has completed along the lines of
expect(page).to have_text('You are logged in!')

Capybara has_content? block

Sometime I really need to check something in the database,
But if I immediate will go to database with u = User.last there is a chance that AJAX request wont finished and in the User.last - I'll find old record.
So for this I need some workaround, for example,
assert has_content? 'Success' do
# here we access to database
end
We check for Success message, which server response, and this is the sign, that we can go to our database.
How can I achive this?
Thanks all in advance.
You don't need a block for this just do one then the other. The content assertion will wait for the content to exist which will confirm the AJAX has completed and then you can check for the User
assert_content 'Success' # will wait for 'Success' to appear
u = User.last # won't happen until after 'Success has appeared and therefor the AJAX has completed'
Note also the use of the Capybara provided assert_content (rather than assert has_content? ...). You generally want to prefer using the Capybara provided assertions rather than asserting on the result of a Capybara boolean method since the error messages on failure will be a lot clearer.

How can I test Stripe.js using poltergeist and Capybara?

I've been going nuts trying to write an automated test for my user sign up page. Users will be charged a recurring subscription via Stripe. They input their basic details (email, password, etc) and their credit card details on the same form, then the following flow happens:
(On the client-side) stripe.js makes an AJAX request to Stripe's servers, which (assuming everything is valid) returns a credit card token.
My javascript fills in a hidden input in the HTML form with the credit card token, and submits the form to my Rails server.
(Now on the server-side): I validate the user's basic details. If they're invalid, return (because there's no point charging them via Stripe if e.g. their email address is invalid so they can't create an account anyway.)
If they're valid, attempt to create a Stripe::Customer object, add the right subscription and charge them using Stripe's ruby gem etc.
All of this works perfectly fine... except I can't figure out how to test it. Testing step #4 is easy enough as it takes place on the server-side so I can mock out the Stripe calls with a gem like VCR.
Step #1 is what's giving me trouble. I've tried to test this using both puffing-billy and the stripe-ruby-mock gem, but nothing works. Here's my own javascript (simplified):
var stripeResponseHandler = function (status, response) {
console.log("response handler called");
if (response.error) {
// show the errors on the form
} else {
// insert the token into the form so it gets submitted to the server
$("#credit_card_token").val(response.id);
// Now submit the form.
$form.get(0).submit();
}
}
$form.submit(function (event) {
// Disable the submit button to prevent repeated clicks
$submitBtn.prop("disabled", true);
event.preventDefault();
console.log("creating token...");
Stripe.createToken(
// Get the credit card details from the form
// and input them here.
}, stripeResponseHandler);
// Prevent the form from submitting the normal way.
return false;
});
Just to reiterate, this all works fine when I test it manually. But my automated tests fail:
Failure/Error: expect{submit_form}.to change{User.count}.by(1)
expected result to have changed by 1, but was changed by 0
When I try to use the gem puffing-billy, it seems to be caching stripe.js itself (which is loaded from Stripe's own servers at js.stripe.com, not served from my own app, as Stripe don't support this.), but the call initiated by Stripe.createToken isn't being cached. In fact, when I log into my Stripe server logs, it doesn't seem that the call is even been made (or at least Stripe isn't receiving it.)
Note those console.log statements in my JS above. When I run my test suite, the line "creating token..." gets printed, but "response handler called." doesn't. Looks like the response handler is never being called.
I've left out some details because this question is already very long, but can add more on request. What am I doing wrong here? How can I test my sign up page?
UPDATE See [my comment on this Github issue] on stripe-ruby-mock for more info on what I've tried and failed.
If I understand correctly...
Capybara won't know about your ajax requests. You should be able to stub out AJAX requests with Sinatra. Have it return a fixtures much the same as VCR.
Here's an article on it.
https://robots.thoughtbot.com/using-capybara-to-test-javascript-that-makes-http
You need to boot the Sinatra app in Capybara and then match the URLs in your ajax calls.
Something like:
class FakeContinousIntegration < Sinatra::Base
def self.boot
instance = new
Capybara::Server.new(instance).tap { |server| server.boot }
end
get '/some/ajax'
# send ajax back to capybara
end
end
When you boot the server, it will return the address and port which you can write to a config that your js can use.
#server = App.boot
Then I use the address and port to config the JS app
def write_js_config
config['api'] = "http://#{#server.host}:#{#server.port}"
config.to_json
end
In spec_helper.rb send in the config to the js so your script points to your sinatra app. Mine compiles with gulp. So I just build the config into to is before the tests run:
system('gulp build --env capybara')
I've had tests which worked on manual fail in Capybara/poltergeist due to timeout. In my case, the solution was to wait for all AJAX requests to finish. Reference
Not sure whether Stripe.js uses JQuery internally, try checking for a condition set by stripeResponseHandler.
In addition to the wait_for_ajax trick mentioned, it looks like you are calling expect before your database was updated. One way to check that would be to add a breakpoint in your code(binding.pry), and check if it is a race condition issue or not.
Also, as per Capybara's documentation, introducing an expectation of a UI change makes it 'smartly' wait for ajax calls to finish:
expect(page).not_to have_content('Enter credit card details')

How to wait till an element appears after an AJAX call using capybara-webkit?

The capybara method, wait_until doesn't seems to work for capybara-webkit. Is there any alternate solution for that, or any Javascript implementations?
Intentionally need some replacement for sleep, e.g. sleep 2.
If your AJAX call results in a change to the DOM, Capybara will wait for it if you do
page.should have_selector?("some selector")
It is an intentional Capybara feature that it waits (up to Capybara.default_wait_time) for have_selector? and related methods to be true.
If your AJAX call does not result in a change to the DOM, there's no way to wait for it on the browser side using Capybara. You might be able to detect when the AJAX call is complete in Javascript and somehow communicate that to Capybara, but that would couple your test and implementation rather tightly. A common approach in this case is to wait for the server-side effect of your AJAX call (creation or update or deletion of a model object, sending of an email, etc.) to take place. Since Capybara can't see the server side you have to wait for the server-side change yourself.
In Capybara 1 you can use Capybara's wait_until to wait for the server-side change. wait_until was removed from Capybara 2. I posted an implementation of wait_until in my answer to Why does adding "sleep 1" in an after hook cause this Rspec/Capybara test to pass?
I didn't really need the expectation as it wasn't what I needed to test.
This worked for me:
page.has_css?('.my-selector')
# or
page.has_content?('Some text on the page')
# continue with test
Maybe something like this:
# AJAX BEGIN
expect(page).to have_selector('form#new_user_video .submit > .throbber')
expect(page).to_not have_selector('form#new_user_video .submit > .throbber')
# AJAX END
Manually in your js code set 'ajax:send' (append throbber) and 'ajax:success' (remove throbber). This way you'll be able to know when request is finished.
And you should set enough time for ajax to complete:
Capybara.default_max_wait_time = 5

Rails + Cucumber: How to wait for javascript to finish before moving on to next step?

I have a form that uses jQuery and ajax. One part of the form is dependent on the jquery to load certain inputs into the form, however, sometimes my tests fail because they are so fast that they don't wait for the javascript to finish. Is there a way to wrap the function to wait for the javascript response before continuing? Right now I just use sleep 1:
When(/^I add a product to my collection$/) do
select(Product.first.name, from: "unassociated_product_ids[]")
click_link(">") #<-- uses ajax
sleep 1
end
You shouldn't try to explicitly wait for AJAX or Javascript to finish - instead, you should try to wait for your page to change.
Imagine that JS/AJAX that runs by clicking your link causes the product's name to now be listed in a UL/LI element on the page. So initially there is no LI with the text of the product name, but afterwards there will be. Your step should wait for that:
...
click_link(">")
page.should have_css("li", :text => Product.first.name)
...
That will wait until there is an LI element on your page with the product's name. How long will it wait? Well Capybara has a default wait time, Capybara.default_max_wait_time. You can set a default in env.rb like Capybara.default_max_wait_time = 5 or something. And then of course you can change it temporarily in a step if you know you need more time.
default_max_wait_time = Capybara.default_max_wait_time
Capybara.default_max_wait_time = 10 # Really long request
# Do stuff
Capybara.default_max_wait_time = default_max_wait_time
Or use Capybara.using_wait_time to simplify that.
Capybara.using_wait_time(10) do
# Do stuff
end
Sometimes though you might actually have to wait for AJAX and it might be too hard or impossible for you to wait for the page to be different, and this might be outside of your control. In that case, you can make a custom method to wait for AJAX and call it from any step that needs it.
def wait_for_ajax
# See: https://gist.github.com/10c41024510ee9f235e0
# Linked from: http://techblog.fundinggates.com/blog/2012/08/capybara-2-0-upgrade-guide/
start = Time.now
while true
break if (page.evaluate_script("$.active") == 0)
if Time.now > start + Capybara.default_max_wait_time.seconds
fail "AJAX did not register as complete after #{Capybara.default_max_wait_time} seconds!"
end
sleep 0.1
end
end

Resources