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
Related
I have rspec tests in my rails app that run some methods, mainly:
methods_helper.rb
def registered_admin
#admin = FactoryGirl.create(:admin)
visit new_user_session_path
fill_in "user_username", with: #admin.username
fill_in "user_password", with: #admin.password
click_button 'Sign in'
end
factories.rb
factory :admin, class: User do
username "adminuser"
email "admin#example.com"
password "admin246"
admin true
end
and other assortment of capybara things like filling in lesson forms and checking validations/count etc.
However I noticed running my tests that it creates space in the actual User database, I have 1 main user, id: 1. I created a second one and its suddenly id: 34. Running rspec spec/ and in sandbox I created another, id: 36.
Everything from 2 through 33 is nil. What part of my tests are permanently affecting the DB? I kind of thought tests ran in their own section and didnt "actually" touch the database but just simulate it. Am I wrong? What part of my tests can be changed so it doesn't keep extending the total id's?
(another model is run in the same way, it does not have the same problem, only user id's are actual created entries)
Also, is there any command/way to "free up" the 30 or so nil user.ids?
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
Let's say I'm testing how to create a Widget:
feature "widget management" do
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
end
Okay, great, but let's say I want to create another widget, and test how those two interact with each other. In a unit test I would have no trouble stubbing or using factory girl's create method to set up the hashes I need, but with a feature integration test, I want to test the whole application realistically, just to really, really make absolutely sure there are no bugs. I don't want to stub or use a create method, I want to literally create two different widgets using the form found at the root_url!
But if I do this:
feature "widget management" do
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget"
expect(page).to have_text("Widget_2 was successfully created.")
end
end
Awesome Widget_2 is created in the database, but Awesome_Widget, from the last scenario, is no longer in the database. This is because my config.use_transactional_fixtures is set to true. I want the test database to be cleaned between expectations, though, at least in all of my unit specs, as they use contexts to set up the database in a certain way before each expectation.
But I don't want the database to be cleaned between each scenario in my extensive integration spec! I want to build on what's happened before. Is this the correct way to do it? Or should I keep transitional_fixtures and be stubbing/creating in a before block for all of my feature specs?
Maybe then create one long expectation that creates multiple widgets and makes them interact with each other in one huge it block?
I just want to get to emulate true behaviour! I want to go through the forms, make thousands of widgets (using a loop and a factory girl sequence) and watch it all work for peace of mind, (maybe using a headed server such as Selenium for extra certainty) before it goes live! Surely that's a sensible thing to want to do? It's been surprisingly tricky to do this!
I can understand stubbing in a request spec, because though you're testing a faculty of your app that uses controllers, models, views and active record, you're testing that feature of your app in isolation.
With a feature spec though, you're meant to be telling a story. A user (or whatever) does this, and then he does that, meanwhile, another user is created, he "friends" the first user, the first user "accepts" and so on. Don't really know how I can do this if the database is wiping itself between each expectation!
Basically, how can I turn transactional_fixtures off for certain specs, but have them on for other specs, and is this advisable?
Up for using database cleaner instead of transactional_fixtures!
Update
Okay, this seems to be a good setup for telling a 'story' with a feature spec:
(Note, I've only included the code relevant to setting this up, your spec_helper needs a few more things in it to get rspec, factory girl, guard, whatever working)
Gemfile
(adding the database_cleaner gem for better control when dropping tables)
gem 'database_cleaner'
spec/spec_helper.rb
(configuring the database_cleaner to drop all tables in the test database, also setting transitional fixtures to true so that all tables are dropped between expectations(this is overwritten in the feature spec itself using an instance method, which you'll see in a bit))
RSpec.configure do |config|
config.after(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.use_transactional_fixtures = true
I18n.enforce_available_locales = true
config.order = "random"
end
spec/features/integration.rb
And finally, the feature spec that builds on old expectations to 'tell a story'. Note the instance method that overrides the spec_helper's configuration regarding transactional fixtures:
feature "widget management" do
self.use_transactional_fixtures = false
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget" # Both exist in the database and so they can take part in the story!
expect(page).to have_text("Widget_2 was successfully created.")
end
end
I suppose the question has changed a little!
What are your thoughts on the above as a way to write feature specs?
By the way, if you're attempting this, or anything that uses database cleaner to manually remove information from your database, be aware you can get in a little mess if you're using active record (which you should be in an integration spec) and model validations! Basically, if data from the last spec is lingering in your database for some reason (if you've just turned transactional_fixtures off, for example), your specs could fail any uniqueness validations you have set up, because identical data is already exists in the database.
If you have database cleaner setup like above, it's set to clean the database when the suite finishes. Because your spec is hitting a validation error, it's never finishing, so database cleaner never cleans the database. And because the database hasn't been cleaned, when you run the spec again, your spec hits the validation error again, it still doesn't finish and data base cleaner still doesn't clean the database and so on into infinitum.
To cut a long story short, if you're getting validation errors, manually clean your tables.
You can do this with the sqlite3 shell (A little easier than rails console I think, but you can use that as well if you want. It's similar commands with any shell, postgres, mysql, whatever):
In the command line:
$ cd db
$ sqlite3 test.sqlite3
sqlite > DELETE FROM widgets;
You may need to run that command a few times to empty different tables depending on your spec. The syntax of the command: DELETE FROM [table name you wish to delete from];
sqlite > .exit
What are your thoughts on the above as a way to write feature specs?
I don't think it's a good idea to share data between scenarios. A scenario shouldn't rely on data from another scenario. This will cause problems if you randomize the execution order in your RSpec configuration.
Another problem that I see is that you are not really testing different scenarios.
feature "widget management" do
self.use_transactional_fixtures = false
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget" # Both exist in the database and so they can take part in the story!
expect(page).to have_text("Widget_2 was successfully created.")
end
end
The only difference between the two scenarios is that you are varying the name but not in a relevant way.
You could improve this suite by varying the type of input (e.g. valid then invalid) in order to test more paths in your code. I would refactor that suite to look more like this:
describe "widget management" do
before do
visit root_url
click_link "New Widget"
fill_in "Name", with: name
click_button "Create Widget"
end
context "when creating a widget with a valid name" do
let(:name) { "Awesome Widget" }
it "returns a success message" do
expect(page).to have_text("Widget was successfully created.")
end
end
context "when trying to create a widget with an invalid name" do
let(:name) { "" }
it "returns an error message" do
expect(page).to have_text("Widget_2 must have a valid name.")
end
end
end
I wanna test my functionals in my rails application, but the tests require a user/admin to be logged in. I have looked at many websites, but I cannot grasp what I really should be doing to make it work. I'm sure there there is an easy thing to do, but I cannot seem to find it.
I have seen the stubs command used, but I only get that it is an undefined method and I don't really know what it is supposed to do.
Any ideas on how I can run my tests as if it was an user/admin requesting them?
Thanks for any help I might get.
acceptance/integration testing:
If you're using cucumber, just login as the user. Something like:
fill_in 'users[username]', :with => "my username"
fill_in 'users[password]', :with => "my password"
click_button "Log In"
If you're using rspec+capybara, you can do something like this (assuming you're using Devise for authentication)
# spec/spec_helper.rb
include Warden::Test::Helpers
# in spec/acceptance/some_spec.rb
...
login_as FactoryGirl.create(:user)
unit testing
see https://github.com/plataformatec/devise#test-helpers if you're using Devise
otherwise, you can implement similar functionality by writing to the session object, or by stubbing out current_user (assuming you call it that)
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.