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.
Related
We currently face a problem when a request spec fails (typo or other exception) it affects other request specs which actually should be unaffected. The problem is we use many gems so I tried to create a minimal reproduction app (without success, https://github.com/tak1n/reproduction)
The problem in detail:
We have a request spec where we need to login a user. For user authentication we use devise and therefore we use Warden::Test::Helpers to log in users in request specs. Devise automatically updates some user related attributes when a user logs in (last_sign_in_at, last_sign_in_ip etc..).
The problem here is that when it tries to save the user for doing this changes the end result is a INSERT INTO user instead of a UPDATE user and therefore it crashes.
Here is an example spec from our app:
require 'rails_helper'
RSpec.describe 'Suggestions API' do
let(:user) { FactoryGirl.create(:user, :professional) }
before do
login(user) # same as in https://github.com/tak1n/reproduction/blob/master/spec/support/request_macros.rb#L6
end
describe '/suggestions.json', :vcr do
context 'with non saved filter' do
# some setup stuff here (setup proper objects in db)
# also params are defined here through let(:params) { ... }
it 'returns proper suggestions' do
Suggestion.refresh!
get '/suggestions.json', paramst
expect(json.count).to eq(2)
expect(json.first['id']).to eq(sug2.id)
expect(json.second['id']).to eq(sug1.id)
end
end
context 'with saved filter' do
# some setup stuff here (setup proper objects in db)
# also params are defined here through let(:params) { ... }
it 'returns proper suggestions' do
Suggestion.refresh!
get '/suggestions.json', params
expect(json.count).to eq(2)
expect(json.first['id']).to eq(sug2.id)
expect(json.second['id']).to eq(sug1.id)
end
end
end
end
The result of this should be that the first spec fails because of the typo paramst, which it does but it also affects the second spec:
Here is the spec run: https://gist.github.com/tak1n/102c1aa121b66e0ab56602b76f911ec0
I tried to dig deeper and saw that the logic whether save creates or updates a record is depending on https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L85
Next I tried to output what #new_record was in that case.
def new_record?
sync_with_transaction_state
puts "New record: #{#new_record}" if self.class == User
#new_record
end
I got following: https://gist.github.com/tak1n/330560a3a108abc8fce4d105a48ac444
When the specs run in different order (non failing first then spec with typo or exception in it) I got this: https://gist.github.com/tak1n/6eb24693226d8e6a713c0865ea1bebd5
The difference here is for the "working" spec run New Record: <boolean> is different. When the spec with the exception runs before it New record suddenly becomes true and therefore it generates SQL to create a new record.
Next I assumed some gems are causing this problem, most likely I thought it would be database_cleaner but in the reproduction I do more or less the same and there it works.
The problem is I'm really stuck at debugging and not sure where to proceed. Also I'm not sure which gem or our code itself is causing this.
Feel free to ask any questions if you need further details, thanks in advance.
Seems like the issue is caused by devise-async (https://github.com/mhfs/devise-async).
See https://github.com/rails/rails/issues/26038 and https://github.com/mhfs/devise-async/issues/96 for more information.
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
For example, the user should not see a login page while one has already logged in. How should I implement this functionality in cucumber?
Scenario: Authorizzation
Given I am logged in
Then I should not be asked to authenticate
Given(/^I am logged in$/) do
#user = User.new
end
Then(/^I should not be asked to authenticate$/) do
# what to write???
end
Assume the authentication page is called login.
Something like:
page.body.should_not include_text("Please enter your credentials")
Without seeing your existing code, its hard to assume. But it is along these lines
Given...do
...
arg1.should #=> should
...
end
Given...do
...
arg1.should_not #=> should not
...
end
Update
Then(/^I should not be asked to authenticate$/) do
# what to write???
unless #user.logged_in? #=> assumes there is a method that checks it
routines to login
end
end
From my practical experience, I recommend getting your Ruby basics right to enjoy cucumber. Besides, it is very useful when scenarios say what should happen.
Scenario: Authentication
Given user is not logged in
Then he should be asked to authenticate #=> (OR) Then he should be redirected to login page
I think you are fundamentally approaching it from the wrong direction. You might instead assert that I should see something that you expect like a link, text, or other. You can also follow a link one more level that you can assert you have proper access too.
It's hard to test something that isn't happening and you have no evidence, so test what you expect, not the world of things for which you can't test.
On login page it seems that buttons like "Login" or "Sign In" should be present.
So, after login, you can check whether this button not present.
page.should_not have_xpath(:xpath, '//here your xpath to element', :text => "Login")
Then /^(?:|I )should not be on (.+)$/ do |page_name|
URI.parse(current_url).path.should_not == path_to(page_name)
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 am trying to test my controllers, but some of the actions in my controller expect the user to be logged in. How will I test them? Do I mess with the session variable directly? Also, what if a lot of the actions expect the user to be logged in? Should I set up a before action, and log the user in there?
Another idea I had was that I could test them in an integration test, and do a post on the login form, before I actually test the desired action. Something like:
def setup
# log the user in, this will happen before every test
end
# integration test
test "I should see my posts" do
#setup should have happened before this, and I should be logged in
get posts_path
assert ...
end
Is this the way to test these actions? Am I missing something?
Depending on your authentication framework you use there are several ways. Devise for example has some TestHelpers, that make it easy to login users without having go through the actual webpage in functional tests. If thats not an option, like soundsop said, browser testing. (look at, from high to low: cucumber, capybara, selenium/...)
The Book includes some testing examples in their depot application:
test "should login" do
dave = users(:one)
post :create, :name => dave.name, :password => 'secret'
assert_redirected_to admin_url
assert_equal dave.id, session[:user_id]
end
Full details in the "Authenticating Users" section.
You can either make fake user credentials in the setup, or you can stub out the method which checks the credentials using a mocking library. I've done both and don't have a firm preference.