Capybara doesn't follow redirect - ruby-on-rails

I'm trying to run expectations on page.current_url, however, for some reason, page.current_url isn't getting updated.
This happens when I click a link rather than visiting a url. Look:
scenario 'When a user who is not an admin visits the dashboard' do
before do
sign_up user
visit admin_statistics_path
end
its(:current_url){ should have_a_uri_of root_path }
end
All those specs pass. However, when I click a link to admin_statistics_path rather than visiting admin_statistics_path, the current_url expectation fails:
describe 'When a user who is not an admin clicks control panel' do
before do
sign_up user
click_link 'dashboard'
# puts current_url
end
it { should have_css 'div.danger', "You do not have sufficient priviledges to access the admin area. Try logging out and logging in with an account that has admin priviledges." }
it { should have_css 'h1', text: 'Goals'}
its(:current_url){ should have_a_uri_of root_path }
end
All the above expectations pass apart from the current_url one! Even weirder, if I put the current_url, the spec passes:
describe 'When a user who is not an admin clicks control panel' do
before do
sign_up user
click_link 'dashboard'
puts current_url #=> returns the initial url despite the specs passing
end
it { should have_css 'div.danger', "You do not have sufficient priviledges to access the admin area. Try logging out and logging in with an account that has admin priviledges." }
it { should have_css 'h1', text: 'Goals'}
its(:current_url){ should have_a_uri_of root_path }
end
How do I get capybara to wait for a redirect?
Why does putting current_url cause expectations to pass?
Why does visiting a url cause the expectations on the current_url to pass?
Why does clicking a link to a url not cause the expectations on the current_url to pass?

For anyone coming to this question now, Capybara 2.5 added a has_current_path matcher which will use Capybaras waiting behavior when checking the current_path
it { should have_current_path(root_path) }
or
expect(page).to have_current_path(root_path)

Related

Capybara::ElementNotFound: Unable to find visible link

I'm having trouble with my test for a method which lets an admin user promote other users to admin by the click of "Promote to Admin". It lies in my controller, I'm writing a feature test for it. I'm using Rails 5.1.4.
def promote
#user = User.find(params[:user_id])
if #user.toggle!(:admin)
flash[:success] = "User is promoted to admin."
redirect_to root_path
else
flash[:notice] = "Can't promote."
redirect_to root_path
end
end
This is the test:
describe "Promotion" do
before do
login_as(User.create!(name: "lala", email: Faker::Internet.email,
password: "lalala", admin: true))
visit users_path
end
context "to admin" do
it "promotes user to admin" do
click_link("Promote to Admin", :match => :first)
expect(current_path).to eq user_promote_path
end
end
end
It gives me the error: Capybara::ElementNotFound:
Unable to find visible link "Promote to Admin"
which I think is because I'm not accessing the right page, trying to log in as admin is perhaps not working.
Any suggestion would be very appreciated!
The most likely reason for your test failing is that you don't appear to have created any other users beyond the one you're logging in as. If you haven't then there wouldn't be any users to show "Promote to Admin" links for. You can always save the page or just do puts page.html to make sure what you think is on the page actually is.
A second issue with your test is that you should never use the eq matcher with current_path. You should be using the Capybara provided path matcher of you want stable tests
expect(page).to have_current_path(user_promote_path)
If you want to be sure that you're on the right page, you can do some debugging with:
save_and_open_page (opens your browser)
save_and_open_screenshot (takes a screenshot and opens it)
If it's all good, maybe Capybara can't find the link : Is it a screen size/responsive issue ? If yes, you can configure the window size that Capybara uses (see this link)
If the test still does not pass, maybe the link is not visible by default ?
You can add to option visible: false in click_link to precise that.

Why do these tests fail when run simultaneously, yet each passes individually?

I am experiencing strange very test behavior, with logged in state being handled inconsistently.
The spec logs a user in, visits a (nested or un-nested) index page, and checks that the correct content is displayed. Records are fetched asynchronously, though I don't think this should have an impact.
When each spec is run individually, they each pass. When all specs are run together, they fail because the expected content is missing. Using save_and_open_page reveals this is because the login page is being rendered, rather than the expected index page.
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
The tests look something like this
let(:user) {create :user}
let(:team) {create :team}
let(:country) {create :country}
before :each do
login_as( user, scope: :user )
end
describe 'unnested' do
it 'should have the expected content', :js do
visit users_path
is_expected.to have_content "some content on the page"
end
end
describe 'nested by team' do
it 'should have the expected content', :js do
visit team_users_path(team)
is_expected.to have_content "some content on the page"
end
end
describe 'nested by nationality' do
it 'should have the expected content', :js do
visit country_users_path(country)
is_expected.to have_content "some content on the page"
end
end
The specs all require javascript (I don't know whether that is important here).
Authentication is handled by Devise, and my rails_helper.rb includes
config.append_after(:each) do
DatabaseCleaner.clean
Warden.test_reset!
end
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
It took a long time to get to the bottom of this. Posting this hear in case it is of help to anyone else encountering the same issue.
After much searching I eventually found this small mention that login_as may not work with Poltergeist when js is enabled on your test scenarios.
I tried the suggested fix to deal with shared DB connections. Unfortunately this resulted in the following errors:
PG::DuplicatePstatement at /session/users/signin
ERROR: prepared statement "a1" already exists
I tried using the Transactional Capybara gem, but this did not seem to work well with Poltergeist.
Eventually I abandonned login_as completely, and instead wrote a short method that visits the login page, fills in email and password, and logs in that way.
This solution appears to be working. It adds a little overhead, so I'm only using it for tests with JS.
If you are using Capybara gem then there is no need to use :js with test cases
What I did if this helps-
scenario "visit with user signed in" do
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
visit "/"
expect(current_path).to eq('/')
expect(page).to have_title "Some Random Title"
end
The other way you can login user using feature specs like-
feature 'User signs in' do
before :each do
#user = FactoryGirl.create(:user)
end
scenario "Signing in with correct credentials" do
visit "/"
fill_in "Email", with: #user.email
fill_in "Password", with: #user.password
click_button "Log In"
expect(current_path).to eq("/login/useremail/verification")
expect(page).to have_content "Signed in successfully"
end
end
If your pages are ajax then refer to this https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara

What is the difference between visit and current_path and use of .should in rails?

Then /^I should be on the free signup page$/ do
*current_path.should == free_signup_path*
end
Then /^I should be on the free signup page$/ do
*visit free_signup_path*
end
What is the difference between these two ?
#visit tells the browser to go to a new url and will initiate a get request to the app being tested. #current_path lets you read the current location the browser is at.
Note: you should stop using current_path.should == some_path, when verifying the current path, and instead use page.should have_current_path(some_path). The have_current_path matcher includes waiting behavior and will help make tests less flaky with JS capable drivers
You use visit to tell rspec to go to that path to validate if something works or is present. For example:
it 'contains the new sessions form' do
visit 'sessions/new'
current_path.should have_content("Sign In")
end
Granted that my sign in form has a button or whatever with the text 'sign in' this test will pass.
You generally have to declare what path the test hould go to in order to detect content.
Hope this helps!

rspec + webkit is losing session/cookie after first request

using rspec with the default driver is working fine and all tests are passed.
changing driver: :webkit will have a bad side-effect.
step: the user is logged in
step: visiting root_path with a session (current_user)
step: visiting root_path without a session (current_user = nil)
so either after the first visit root_path or before the second, the session is killed or whatever - we can't get the user to stay logged in.
test looks like this
scenario 'something', driver: :webkit do
user = FactoryGirl.create :user
login_as(user)
visit root_path
visit root_path
end
is this a known bug? are there any workarounds or are we missing something ?
as requested:
def login_as(user)
visit root_path
click_on "Login"
fill_in "user[login]", with: user.username
fill_in "user[password]", with: user.password
click_on "Sign in"
end
The default driver runs everything synchronously --- drivers that use real browsers and support javascript do not necessarily do things synchronously - so it's possible in drivers other than rack-test for click_on 'Sign in' to return immediately. Therefore if you're not checking for content that would be seen on success the next visit root_path can get executed immediately and cancel the submission of the login form. To fix that add something like
expect(page).to have_content('You are now logged in') # whatever text is shown on a successful login
as the last line of your login_as method. This is not normally an issue for most people, because after logging in the next step is usually to click on something on the page, which will make Capybara wait for that item to appear thereby waiting for the login to complete.
If that is not what is happening here then the only place (given your sample code) that can be logging the user out is your own app
if you are deleting the session after the first visit, it is expected that the user logout.
Does visiting without session means that visiting the page as anonymous?

How to test devise after sign in path with Capybara

I am trying to test what happens after logging in through Devise gem. For example, I have the controller to go to student_dashboard_path after users successfully login.
How can I test this with Capybara and Rspec?
I currently have this in:
/spec/features/user_signs_in_sees_dashboard_spec.rb
require 'rails_helper'
feature 'User sign in' do
scenario 'successfully from sign in page and sees student dashboard' do
sign_in
visit student_dashboard_path
expect(current_path).to eq(student_dashboard_path)
end
end
and I have this in:
/spec/support/features/sign_in.rb
module Features
def sign_in
visit user_session_path
fill_in 'Email', with: User.first.email
fill_in 'Password', with: User.first.password
click_button 'Log in'
end
end
and I am getting this error message:
1) User signs in successfully from sign in and sees dashboard
Failure/Error: expect(current_path).to eq(student_dashboard_path)
expected: "/student/dashboard"
got: "/student/login"
I am not sure why I am not able to log in and see the student dashboard.
I'm leaving my original answer below for anyone still on Capybara < 2.5.0 but in 2.5.0 you can now do
expect(page).to have_current_path(<expected path>)
and it will use Capybara's waiting behavior while checking for the path
---- Below is only for Capybara version < 2.5.0
expect(current_path).to eq(...) doesn't wait for the path to change, it just compares to the current path at the time it's called. If you put a sleep after the click button I bet it works. A better solution would be to have something like
expect(page).to have_text('You are now logged in')
after the click_button. That would cause capybara to wait until the log in has completed, the page loads (and the logged in notice appears), and therefore until the current_path has changed too.
Use capybara save_and_open_page in middle to figure out if the fields are properly set. If you work on the same machine you can switch to selenium to test out on the real browser.
Also be sure that this code does not need any JS to work because default capybara matchers will not be able to run it.

Resources