I'm using Rails 3.1.0.rc4 and I'm working on doing integration tests with capybara's new Steak-like DSL and Rspec (using Devise authentication)
The issue I'm having is that when I run an integration test, the rack-test driver from capybara seems to just completely lose the user's logged in session, in fact, the session seems to just clear out altogether.
After days of debugging, I'm at a complete loss as to why. Going line by line through the middleware stack, I believe I've ruled the problem down to something going on in the ActiveRecord::SessionStore
that is causing this. I've read here that Rails will clear out a session if it can't validate the CSRF token, which leaves me to believe that I've got something configured wrong, and for some reason this one test is not authenticating the CSRF token correctly.
This is what is in my session_store.rb in the /initializers directory:
MyApp::Application.config.session_store :active_record_store
Does anyone who knows about CSRF protection in rails have any leads on why this may be happening?
Also, here are some things to note:
- the thing I'm trying to test actually works within the browser itself, only this one test is dropping the session
- the session seems to get dropped after the submission of a form to which the action url is to another server. I'm using the VCR gem for capturing the requests/responses to this external server in the test, and while I believe I've ruled the external request as the problem, this may have something directly to do with the CSRF token not authenticating, thus clearing out the session.
- other tests involving logging in / using sessions are not dropping sessions
Can anyone开发者_运维知识库 give me any leads as to what is going on here exactly, and why the one test just seems to arbitrarily drop its session and fail on me? I've done lots of debugging and have tried everything I can possible think of.
I'm new to capybara too and I was having a similar problem.
I was trying to login a user doing something like this:
post user_session_path, :user => {:email => user.email, :password => 'superpassword'}
And that was working ok until I tried to do something with capybara, such as visiting a page and just testing if the user was logged in. This simple test was not passing:
visit root_path
page.should have_content("logout") #if the user is logged in then the logout link should be present
At first I thought capybara was clearing the sessions but I was wrong. The thing that took me some time to realize is that the driver capybara is using handles its own sessions, so, from the point of view of capybara my user was never logged in. To do so you have to do it like this
page.driver.post user_session_path, :user => {:email => user.email, :password => 'superpassword'}
Not sure if this is your case, but hope that helps.
I was able to fix this error by setting this value to true in config/initializers/test.rb
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = true
Beforehand, the CSRF <meta>
tags were not printing out to the <head>
. After changing this value they finally appear.
The manual way of doing it is very simple:
it "does something after login" do
password = "secretpass"
user = Factory(:user, :password => password)
visit login_path
fill_in "Email", :with => user.email
fill_in "Password", :with => password
click_button "Log in"
visit # somewhere else and do something
end
You can then break this out into a function in your 'spec_helper.rb':
# at the bottom of 'spec_helper.rb'
def make_user_and_login
password = "secretpass"
@user = Factory(:user, :password => password)
visit login_path
fill_in "Email", :with => @user.email
fill_in "Password", :with => password
click_button "Log in"
end
and use it in any of your tests (probably request specs):
it "does something after login" do
make_user_and_login
# now test something that requires a logged in user
# you have access to the @user instance variable
end
This might be a long shot but I believe we end up in a bad state after click_button 'Sign in'
and calling visit elsewhere
immediately after.
My theory is that when we click the button the request hasn't completed yet, and we kill it by visiting another path.
From the Capybara documentation:
When issuing instructions to the DSL such as:
click_link('foo') click_link('bar') expect(page).to have_content('baz')
If clicking on the foo link triggers an asynchronous process, such as an Ajax request, which, when complete will add the bar link to the page, clicking on the bar link would be expected to fail, since that link doesn't exist yet. However Capybara is smart enough to retry finding the link for a brief period of time before giving up and throwing an error.
If this is the case the solution is simple: give Capybara something to look for and let it wait until the request is complete. This can be as simple as adding:
expect(page).to have_text('Signed in as bob@example.com')
精彩评论