I am using Machinist to create my test users which I want to log in before each test which I am running with Capybara:
include Capybara::DSL
include ActionController::UrlWriter
before do
SslRequirement.disable_ssl_check = true
user = User.make
visit new_user_session_path
fill_in('username', with: user.email)
fill_in('password', with: '12345')
click_button('submit')
end
it "responds as expected" do
# ...
end
Now when the user is created with User.make I can access it through User.all when it fact it's not persisted in the database yet, this is because
ActiveRecord::Base.connection.open_transactions == 1
I can fix/hack this by doing a
ActiveRecord::Base.connection.commit_db_transaction
after User.make, but I'd rather fix this properly. Is this a Capybara configuration which I am missing? Disclaimer: Using Rails 2.3 and RSpec 1.3
You don't show which driver you're using with Capybara, but generally you shouldn't be using transactions for testing when using Capybara (yes there are potential workarounds to allow it but they all have side-effects). See transactions and database setup and then look into the database_cleaner gem with truncation - here's a blog post about it that should be good with the age of gems you're using - http://devblog.avdi.org/2012/08/31/configuring-database_cleaner-with-rails-rspec-capybara-and-selenium/
Related
I'm attempting to set up system tests with Capybara and Selenium on an existing Rails 5.1 (Upgraded from Rails 4) app that already had capybara based feature tests. Here's what I've done so far.
In the gem file under group :development, :test:
gem 'chromedriver-helper'
gem 'selenium-webdriver'
gem 'rack_session_access'
In the environments/development.rb and environments/test.rb:
config.action_mailer.default_url_options = { host: 'localhost:3000' }
In the spec\rails_helper.rb:
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome)
end
Capybara.configure do |config|
config.default_max_wait_time = 10 # seconds
config.default_driver = :selenium
config.app_host = 'http://localhost:3000'
config.server_host = 'localhost'
end
The issues I'm having are both with new systems tests and old feature tests.
With the system tests it appears that Capybara isn't creating a page object as I get undefined local variable or method 'page' Additionally when I duplicate the same test under the feature test directory I don't have this issue.
With the old Capybara feature tests, working with the rackTest driver, a Chrome window opens but I get No route matches [GET] "/rack_session/edit"
config.middleware.use RackSessionAccess::Middleware is already present in the environments/test.rb
Example system test:
require 'rails_helper'
describe User do
let(:user) { create :user }
let(:membership) { create :membership, admin: true}
let(:admin) { create :user, memberships: [membership] }
context 'viewing the index' do
it 'directs you to the appropriate page' do
set_current_user(admin)
visit root_url
click_button 'Manage Users'
expect(page.current_url).to end_with users_path
expect(page).to have_selector 'h1', text: 'Users'
end
end
end
Example feature test:
require 'rails_helper'
describe 'edit an assignment' do
let(:roster) { create :roster }
let(:user) { create :user, rosters: [roster] }
before :each do
Timecop.freeze Date.new(2018, 1, 10)
set_current_user(user)
end
after :each do
Timecop.return
end
context 'returns the user to the appropriate index page' do
let(:date_today) { Date.new(2017, 4, 4) }
let(:start_date) { Date.new(2017, 3, 31) }
let(:month_date) { date_today.beginning_of_month }
it 'redirects to the correct URL' do
visit roster_assignments_url(roster, date: date_today)
visit new_roster_assignment_url(roster, date: start_date)
click_button 'Create'
expect(current_url)
.to eq roster_assignments_url(roster,
date: month_date)
end
In the spec_helper:
def set_current_user(user)
page.set_rack_session user_id: user.id
end
You need to have the gem puma installed. New rails 5 projects have it installed by default, but your app was made in Rails 4, which is why it didn't.
Why is this? Well, if you were to do a bundle update (and I'll admit I can't explain why) you'd get this error when trying to run the specs, which is a lot more explanatory:
System test integration requires Rails >= 5.1 and has a hard dependency on a webserver and `capybara`, please add capybara to your Gemfile and configure a webserver (e.g. `Capybara.server = :webrick`) before attempting to use system tests.
Googling this error led me to this page, which explains Capybara needs a server.
After adding puma, I'm able to run system tests on your application.
A number of issues here.
Why are you setting - config.app_host = 'http://localhost:3000' ??? That would run the tests against your dev instance rather than the test instance Capybara starts. app_host really should only ever need to be set if you are doing subdomain based testing. This could be the reason for the no route error (normally rack_session_access would only be included in the test environment), or that could be caused by having not actually included the middleware as specified in the rack_session_access gem readme.
NEVER do expectations against current_path/current_url directly, instead use the provided matchers or you'll have flaky tests
expect(page).to have_current_path(users_path)
page is just an alias for Capybara.current_session and is a member of the module Capybara::DSL. If it's not available in the scope of your tests that it most likely means Capybara::DSL isn't included. That would normally be done by rspec-rails - https://github.com/rspec/rspec-rails/blob/master/lib/rspec/rails/vendor/capybara.rb#L21 - so it's possible you haven't actually set the test type to 'system'. If it's that it's not available in your spec_helper methods, just using Capybara.current_session instead is usually easier.
I have the following Cucumber feature testing an input form using typeahead.js:
#javascript
Scenario: Creating a battery using typeahead
When I create a new battery using typeahead
Then I should be on the show battery page
And I should see the battery created message
The test fails on the second step with the following error message:
ActiveRecord::RecordNotFound (ActiveRecord::RecordNotFound)
./features/step_definitions/admin/car_part_steps/battery_steps.rb:37:in `/^I should be on the show battery page$/'
features/admin/creating_car_parts/creating_batteries.feature:20:in `Then I should be on the show battery page'
The relevant step definitions are as follows:
When /^I create a new battery using typeahead$/ do
select_from_typeahead :field => 'battery_manufacturer_typeahead',
:select => #manufacturer.name
fill_in 'Type', :with => '700W'
click_button 'Create Battery'
end
Then /^I should be on the show battery page$/ do
battery = Battery.find_by_type_and_manufacturer_id!('700W', #manufacturer.id)
current_path.should == admin_battery_path(battery)
page.should have_content(battery.type)
end
The select_from_typeahead function is as follows:
def select_from_typeahead(params)
params[:js_field] ||= params[:field]
params[:select_typeahead] ||= params[:select]
fill_in params[:field], :with => params[:select][0, 2]
page.execute_script "$('##{params[:js_field]}').trigger('focus')"
page.execute_script "$('##{params[:js_field]}').trigger('keydown')"
sleep 0.5
page.execute_script "$('.tt-suggestion:contains(\"#{params[:select_typeahead]}\")').trigger('mouseenter').trigger('click')"
end
The problem appears not to have anything to do with the typeahead itself however, as the code works in the browser, and if I add some debug output, I notice that the battery gets saved to the database in the first step when running the test as well, it just mysteriously disappears before the second step runs.
I think it's an issue with database_cleaner, as I know that doesn't play nice with Javascript when set to use transactions, but I've already tried setting it to use truncation instead and disabled transactional fixtures and it still doesn't work.
My features/support/env.rb currently looks like this:
require 'simplecov'
SimpleCov.start 'rails'
require 'cucumber/rails'
Capybara.default_selector = :css
Capybara.javascript_driver = :webkit
ActionController::Base.allow_rescue = false
Cucumber::Rails::World.use_transactional_fixtures = false
DatabaseCleaner.strategy = :truncation
Cucumber::Rails::Database.javascript_strategy = :truncation
My environment is as follows:
rails 4.0.2
cucumber 1.3.10
cucumber-rails 1.4.0
capybara 2.2.0
capybara-webkit 1.1.0
database_cleaner 1.2.0
Am I missing something, is there some other way database_cleaner might still interfere with my test, or is it something else entirely that I haven't thought of?
Any ideas would be very welcome!
I don't think it has to do with Database Cleaner. That wouldn't do cleanup until the end of your scenario. To debug, I highly recommend installing Capybara Screenshot so that you can see exactly what is going on in your page. It provides you the method screenshot_and_open_image, which you can use to pop open an image of what the browser looks like at that exact moment. My guesses are that:
Your jQuery isn't doing what you expect because of timing issues - have you tried longer pauses?
Your transaction isn't committed. Have you looked in test.log to see if the transaction was committed?
Is your form failing validation? Try putting a screenshot_and_open_image at the beginning of your I should be on the show battery page step to see. Another thing I use is a shared step like this, so that I can add screenshots in my scenarios for debugging:
And /^I take a screenshot$/ do
Capybara::Screenshot.screenshot_and_open_image
end
I'm writing a typical test in my application where I create a model through a form and check that the model count equals 1.
The test fails because there are already multiple records in the test DB, and this count increases each time I run my tests. It looks like each example isn't happening inside a transaction (being rolled back) like it's supposed to, and I don't know why.
I have this line in my spec_helper.rb file, which is supposed to run each example in a transaction:
config.use_transactional_fixtures = true
Here is my spec that keeps generating model objects:
require 'spec_helper'
describe "Admin artwork pages" do
subject { page }
let(:gallery) { FactoryGirl.create(:gallery) }
describe "artwork creation" do
context "with valid attributes" do
it "creates new artwork" do
visit admin_gallery_artworks_path(gallery_id: gallery.id)
click_link 'Add new artwork'
fill_in 'artwork_title', with: 'Still Life'
click_button 'Create Artwork'
page.should have_text 'Successfully created'
Artwork.count.should eq 1
end
end
end
end
Here's the error message from Rspec:
Failures:
1) Admin artwork pages artwork creation with valid attributes creates new artwork
Failure/Error: Artwork.count.should eq 1
expected: 1
got: 153
(compared using ==)
Edit: Contents of my spec_helper.rb file:
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rails'
require 'capybara/rspec'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
# Include route helpers
config.include Rails.application.routes.url_helpers
#
# Take the FactoryGirl out of FactoryGirl.create
config.include FactoryGirl::Syntax::Methods
end
I'm using Rails 4.0.0.rc1, Ruby 1.9.3, FactoryGirl and rspec-rails 2.13.0 Thanks for any help.
It turns out that Rails 4 is supported starting in rspec-rails 2.13.1 - I was using 2.13.0. After upgrading, the specs took place within a transaction like they were supposed to.
Thanks to everyone who took the time to post help.
I believe the problem is the way you have your test written and less to due with config.use_transactional_fixtures = true. Focus on the bottom of the error that says (compared using ==)
Try to use the expecting change rspec syntax instead
Change this:
click_button 'Create Artwork'
page.should have_text 'Successfully created'
Artwork.count.should eq 1
To this:
expect { click_button 'Create Artwork' }.to change { Artwork, :count }.by(1)
page.should have_text 'Successfully created'
Let me know if this helps
You're running a request spec: when you call visit the code under test is run in a server instance (in the same process). In particular this means that it's using a different thread.
As a result the application code ends up using a different database connection, and since transactions are a per connection thing there is no transaction used when your controller inserts records into the database.
There are several ways to address this. One is to abandon rspec's transactional fixtures and use the database_cleaner gem. You can set it up so that controller and model specs use transactions but request specs use truncate to forcibly clear out tables.
Another approach is to try and force both the spec code and the server code to use the same database connection, this eliminating the problem. You can see this approach in this answer. In my experience this works pretty well until you start using a capybara driver such as poltergeist which will run any javascript on the page and your page fires ajax requests.
The approach I've been using is to set the active record connection pool size to 1: there is only 1 connection allowed so everyone will use the same one. You do then have to do some work to ensure that connections are returned to the pool or your spec just hangs.
I wrote up the details a while ago as a blog post, but in a nutshell you need to
call ActiveRecord::Base.clear_active_connections! before calling methods like visit, click and so on
hack config.middleware.insert_before ActiveRecord::ConnectionAdapters::ConnectionManagement so that it clears the connection after each request (by default it doesn't do this in tests).
I have an application using rails 3.2 and Devise. I have a request test suite using rspec and Capybara.
I have attempted to shift to using the Warden test helpers in my sign in helper rather than having Capybara fill in the sign in form and submit it. Due to the size and complexity of my test suite, this results in over a minute and a half of savings in my test runtimes.
In my configuration is:
RSpec.configure do |config|
config.include Warden::Test::Helpers, :type => :request
config.after :each do
Warden.test_reset!
end
end
And in a Context:
let!(:current_user) { FactoryGirl.create(:user) }
background do
login_as(current_user, :scope => :user)
end
However, when running the test suite using these configurations nearly every test run has a different randomly failing request spec due to the page presenting as though the user were not logged in. (Specifically, links controlled by a user_signed_in? conditional)
My question: Has anyone else encountered such unreliability and how can it be mitigated?
The absolute easiest thing to do is just use the sign_in form in your before :each block. You need the test helpers in Controller specs, because it's isolated from devise. You don't have that problem in an integration test.
Though an old post I also have this problem with Rails 4.1 and Warden 1.2.3. I noticed that there is a newer version of Warden with work on the test helpers. Anyone success with the newer version?
Btw the behavior that I observe is that Warden does not always successfully login via the login_as helper. This results in my app finding itself on the login page instead of the place it thought it ought to be.
I have all my capybara tests working with my authlogic members area using the default driver, but when i change one test to use selenium driver as it has ajax in it, it gives my theis error :
You must activate the Authlogic::Session::Base.controller with a controller object before creating objects
Things are working with default driver for authlogic so must be something to do with selenium ??
I have include Authlogic::TestCase in my spec_helper and
activate_authlogic
domain.user_sessions.create(user)
in a before each.
Any one help me with this please ?
thanks rick
I posted a cucumber solution here: Log-in through authlogic without having to fill in form every time
For RSpec integration tests it's similar.
In your spec_helper.rb:
require "authlogic/test_case"
RSpec.configure do |config|
...
config.include Authlogic::TestCase
ApplicationController.skip_before_filter :activate_authlogic
config.before(:each, :type => :request) do
activate_authlogic
UserSession.create(User.find_by_email!(email))
end
...
end
Obviously, if your site is not login only you may want to move the two lines in config.before into a before block in your specific test for logged in specs. If you leave as is you can delete the session with UserSession.find.destroy or obviously follow the logout link (if this makes more sense in your spec).
I think the following code will work to activate authlogic:
Authlogic::Session::Base.controller = Authlogic::ControllerAdapters::RailsAdapter.new(self)
Having said that, I prefer defining a step that actually goes to the login form, fills it out, and logs in. It's slower, but I rarely run my entire integration test suite manually, usually the continuous integration server takes care of that.
This work for me (Rails 3.2.1) :
In spec_helper.rb
require 'authlogic/test_case'
include Authlogic::TestCase
In In my controller_specs :
def valid_session
activate_authlogic # run before tests are executed
user = Factory(:user)
UserSession.create(user, true) #create an authlogic session
#user = #controller.current_user
{}
end
# exemple of valid_session utilization in your test:
# valid_session
# user_id = #user.id
#
# or
#
# get :index, {}, valid_session
Enjoy!