I have a form where when a user submits an ajax form with a single name field, a record is created that belongs to a Template object. This all works fine manually, but for some reason when I test this via rspec its telling me that the association was not created. Why isn't the template object updated here?
describe "edit page" do
before(:each) do
visit edit_admin_template_path(template)
end
context "form sections", js: true do
it "allows admin to create one" do
find("#btn-add-section").click
within('#new_form_section') do
fill_in 'Name', with: "Personal Info"
click_button "Create Section"
end
expect(template.form_sections.length).to eq(1)
end
end
end
This is the failure that I am getting
Failure/Error: expect(template.form_sections.length).to eq(1)
expected: 1
got: 0
(compared using ==)
UPDATE: just noticed this in the rspec output
An error occurred in an after hook
ActionView::Template::Error: undefined method `form_sections' for nil:NilClass
so its telling me that the template object does not exist, then its able to compare it afterwards?
UPDATE 2: So it appears that the issue is waiting for the ajax call to complete in capybara, before expecting. I added this to the spec and it now works, obviously I need to refacotr this to something better like a helper
it "allows admin to create one" do
find("#btn-add-section").click
within('#new_form_section') do
fill_in 'Name', with: "Personal Info"
click_button "Create Section"
end
sleep(10) #this waits for the request to complete
expect(template.form_sections.length).to eq(1)
end
The key is telling RSpec to wait for the ajax call to complete before doing any expectations. This is a great post on the solution that I used:
Thoughtbot: Automatically Wait for AJAX with Capybara
Just in case others stumble upon this, rather than using sleep you could test based on the UI itself. Capybara will wait for that element to reload, if you use have_css or find matchers, like this:
expect(page).to have_css("#form-page-two", visible: :visible) #this will force test to wait for ajax call
Capybara.automatic_reload must not be set to false. (defaults to true)
This strategy will reduce the run time of your test suite; if the test only took 2 or 3 seconds to load it will only wait that long (not 10).
However, it can be frustrating to write because of intermittent failures. To avoid this you may need to bump up the wait time setting.
Capybara.default_max_wait_time = 10
https://github.com/teamcapybara/capybara#asynchronous-javascript-ajax-and-friends
Related
I am trying to write a feature test where a user leaves a comment and then a notification is sent out via a rails mailer. Everything is working here if I change my mailer call to use .deliver_now but I don't want that in production. I need to be able to test the asynchronous mail delivery or even just force the mailer to deliver now in the test scenario.
login_as campaign_owner
visit campaign_path campaign
fill_in 'Comment', with: 'Foo'
click_on 'Submit'
expect(page).to have_content I18n.t('comments.create.success')
expect(campaign.comments.count).to eq 1
expect(UserMailer.deliveries.count { |d| d.to == [campaign_watcher.email]}).to eq 1
You could always just change the behaviour of your mail delivery by testing for what environment you are running in.
eg,
if Rails.env.test?
UserMailer.mail_template().deliver_now
else
UserMailer.mail_template().deliver_later
end
Though it's questionable what value your test is actually giving you then
Here is short flow of my form:
You click Submit button
Submit button gets disabled
AJAX request is sent to API.
AJAX response is received.
Button is clickable again.
scenario 'user can not resubmit form before API response' do
expect(find_button('Submit')[:disabled]).to be_falsey
click_button 'Submit'
expect(find_button('Submit')[:disabled]).not_to be_falsey
## Here ajax response is received
expect(find_button('Submit')[:disabled]).to be_falsey
end
This test actually passes but I feel I have no control over it and I'm not concerned if it really test what I intended.
Is there any way to have more control over AJAX responses? Does this test actually test feature I described?
edit:
this test actually randomly fails/passes. I guess it is caused by executing expect(find_button('Submit')[:disabled]).not_to be_falsey after AJAX call
Automatically wait for AJAX with Capybara
# spec/support/wait_for_ajax.rb
module WaitForAjax
def wait_for_ajax
Timeout.timeout(Capybara.default_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
page.evaluate_script('jQuery.active').zero?
end
end
RSpec.configure do |config|
config.include WaitForAjax, type: :feature
end
Usage:
scenario 'user can not resubmit form before API response' do
expect(find_button('Submit')[:disabled]).to be_falsey
click_button 'Submit'
expect(find_button('Submit')[:disabled]).not_to be_falsey
wait_for_ajax
expect(find_button('Submit')[:disabled]).to be_falsey
end
The helper uses the jQuery.active variable, which tracks the number of
active AJAX requests. When it’s 0, there are no active AJAX requests,
meaning all of the requests have completed.
The test is not testing what you want because you're querying for the disabled attribute outside the finder which means Capybaras retrying behavior can't work. Instead you want to use the disabled option to the finder
expect(find_button('Submit', disabled: false)).to be
click...
expect(find_button('Submit', disabled: true)).to be
...
This way Capybara can retry the find until it passes (or timeouts). Note: this test is dependent on the Ajax request taking a bit of time, since if it doesn't it is possible for the button to be re-enabled before the find of the disabled button happens
My model and controller specs are running fine, but after the upgrade to rails 4.2, my feature specs, which use Capybara, no longer work. Example:
#in spec_helper.rb:
def login_admin
create(:user,
first_name: 'John',
last_name: 'Doe',
email: 'doej#test.com',
admin: true)
visit root_path
fill_in 'email', with: 'doej#test.com'
fill_in 'password', with: 'password1'
click_button 'Log in'
puts 'created'
end
#in spec/features/albums_spec.rb
feature "Albums", :type => :feature do
before(:each) do
login_admin
end
scenario 'do something' do
save_and_open_page
end
end
When I run this spec, it never finishes, either pass or fail. No error is thrown; it just sits there, showing Albums with the cursor beneath. 'created' is never put to stdout, and the page is never launched by the save_and_open_page call. Test log shows the erb file is rendered by my login action. This was all working prior to the rails 4.2 upgrade.
This only fails during the spec run - using the app in the browser works fine.
What am I missing here?
UPDATE: possible problems related to capybara/rspec:
Avoid creating models in feature specs, because they're created in a different thread. Instead, create the user using capybara steps (i.e. "sign up" the user every time).
If you really need the database prepared for scenario in a way impossible to create from clicking around the site, implement a simple "admin" area in your app (you'll probably need one anyway) or admin API interface or something like a CSV upload option, etc.
Otherwise, you can search for "capybara rspec database_cleaner append_after" for a setup to support creating models in feature specs, but I've found that none of the solutions are really bullet-proof. You could try: https://github.com/RailsApps/rails_apps_testing
I'm guessing your example is/was stuck on a database operation (waiting for db connection in other threads to end).
PREVIOUS ANSWER:
A few ideas:
catch exceptions in the login_admin method and print them out using STDERR.puts
remove the click_button and see if it fails as expected (shows you the login page)
add a sleep 4 after the password is typed in
separate the click_button into 2 calls (to see which one hangs):
btn = find(:button, locator, options)
STDERR.puts "found: #{btn.inspect}"
btn.click
use bundle show capybara to find out where it is, edit the find (or click) method and put in STDERR.puts methods there to see what's wrong
I'm not very proficient with Capybara and testing tools alike. I'm building a multistep form or a form wizard.
This is how I had a scenario in my head :
User visit signup path
fills in bunch of invalid data and some validation errors should be displayed
fills in valid data, user should be taken to the next step
repeat this step above for n steps with valid data
check if number of users has increased by 1
This is what I've got so far (I managed to get something working) :
describe "signup" do
before { visit signup_path }
let(:submit) { 'Start creating my account' }
let(:next_btn) { 'Next' }
describe "with invalid information" do
it "should not create a user" do
expect { click_button submit }.not_to change(User, :count)
end
describe "should not move to the next step" do
before { click_button submit }
it { should have_content('error') }
it { should have_title('Start here') }
end
end
describe "with valid information wizard step 1" do
before do
fill_in 'First name', with: "Example"
fill_in 'Last name', with: "User"
fill_in 'email', with: "user#example.com"
find("#user_password").set "foobar"
end
it "should move to the next wizard step 2" do
click_button submit
should have_content('We need some more info')
end
it "should have error on the wizard step 2" do
fill_in 'Date of birth', with: "Example"
click_button next_btn
should have_content('error')
end
end
end
This assertion fails should have error on the wizard step 2, it seems to be stuck at step 1 still, I know this from looking at page content and by errors where Dob element can't be found and next button cant be found as well.
Is this even possible with capybara to test sequentially, keeping information step by step?
You should be able to make this work by nesting the describe blocks when moving on to a subsequent step, eg.
describe "signup" do
...
describe "with valid information wizard step 1" do
before do
fill_in 'First name', with: "Example"
fill_in 'Last name', with: "User"
fill_in 'email', with: "user#example.com"
find("#user_password").set "foobar"
click_button submit
end
it "should move to the next wizard step 2" do
should have_content('We need some more info')
end
describe "and filling out the date of birth incorrectly" do
before do
fill_in 'Date of birth', with: "Example"
click_button next_btn
end
it "should have error on the wizard step 2" do
should have_content('error')
end
end
end
end
Now the button clicks are repeated in the nested blocks, so the first example clicks 'submit', and the second example clicks 'submit' and then clicks 'next_btn', etc. Keep in mind that the sequence is repeated for each example so this may slow down your tests. OTOH it more accurately reflects the user interaction, so the tradeoff is probably worth it.
I think this is not a capybara question so much as it is an rspec question. I've used cucumber more than rspec, but so far as I know in my limited experience, each "describe" is an independent test case. I don't think that you can either a. expect these to execute in any particular order or b. share context / state between tests.
Again, my experience has mainly been with cucumber, but I have written tests for this EXACT scenario before- a two page user registration wizard. In cucumber, one writes "scenarios," which are basically short stories that describe a user's interaction with the the system in terms of "given," "when," and "then" (or in other worse, assumptions, actions, and assertions) and these are written in plain English. One then writes Ruby code that maps these English sentences to Ruby code (which might use capybara) to do / test what each scenario describes... What I did was constructed several distinct scenarios.
User visits registration page; fills it out with a whole bunch of errors; check that the system does display the errors
User visits registration page; fills it out correctly; check that the user makes it to the second page
User has already completed the first page; fills out the second page with a whole bunch of errors; check that the system does display the errors
User has already completed the first page; fills out the second page correctly; check that the user makes it to the confirmation page
You can obviously translate those stories into rspec.
The thing that recurs in those scenarios is the filling out (whether with good or bad data) of the two forms. To keep the code DRY, create some helper methods that let you easily populate the form with values that will help you satisfy each of those scenarios.
Does that help any?
I want to keep on using the same session and by that I mean Rails' session between various Test::Unit integration tests that use Capybara. The Capybara::Session object is the same in all the tests as it is re-used, but when I access another page in another test, I'm immediately logged out.
Digging in I found that capybara_session.driver.browser.manage.all_cookies is cleared between one test and the next.
Any ideas how? or why? or how to avoid it?
Trying to work-around that, I saved the cookie in a class variable and re-added later by running:
capybara_session.driver.browser.manage.add_cookie(##cookie)
and it seems to work, the cookie is there, but when there's a request, the cookie gets replaced for another one, so it had no effect.
Is there any other way of achieving this?
Add the following after your capybara code that interacts with the page:
Capybara.current_session.instance_variable_set(:#touched, false)
or
page.instance_variable_set(:#touched, false)
If that doesn't work, these might help:
https://github.com/railsware/rack_session_access
http://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles/
If what you are doing is trying to string together individual examples into a story (cucumber style, but without cucumber), you can use a gem called rspec-steps to accomplish this. For example, normally this won't work:
describe "logging in" do
it "when I visit the sign-in page" do
visit "/login"
end
it "and I fill in my registration info and click submit" do
fill_in :username, :with => 'Foo'
fill_in :password, :with => 'foobar'
click_on "Submit"
end
it "should show a successful login" do
page.should have_content("Successfully logged in")
end
end
Because rspec rolls back all of its instance variables, sessions, cookies, etc.
If you install rspec-steps (note: currently not compatible with rspec newer than 2.9), you can replace 'describe' with 'steps' and Rspec and capybara will preserve state between the examples, allowing you to build a longer story, e.g.:
steps "logging in" do
it "when I visit the sign-in page" #... etc.
it "and I fill in" # ... etc.
it "should show a successful" # ... etc.
end
You can prevent the call to #browser.manage.delete_all_cookies that happens between tests by monkey patching the Capybara::Selenium::Driver#reset! method. It's not a clean way of doing it, but it should work...
Add the following code to your project so that it is executed after you require 'capybara':
class Capybara::Selenium::Driver < Capybara::Driver::Base
def reset!
# Use instance variable directly so we avoid starting the browser just to reset the session
if #browser
begin
##browser.manage.delete_all_cookies <= cookie deletion is commented out!
rescue Selenium::WebDriver::Error::UnhandledError => e
# delete_all_cookies fails when we've previously gone
# to about:blank, so we rescue this error and do nothing
# instead.
end
#browser.navigate.to('about:blank')
end
end
end
For interest's sake, the offending line can be seen in Capybara's codebase here: https://github.com/jnicklas/capybara/blob/master/lib/capybara/selenium/driver.rb#L71
It may be worth posting the reason why you need this kind of behaviour. Usually, having the need to monkey patch Capybara, is an indication that you are attempting to use it for something it was not intended for. It is often possible to restructure the tests, so that you don't need the cookies persisted across integration tests.