I am trying to test feature of editing the question by the user with js: true
, and it fails because user is not signed in during the test (even though there should be user session).
I use Devise for authentication.
feature 'Authenticated User can edit own questions' do
given(:user) { create(:user) }
given(:another_user) { create(:user) }
given!(:question) { create(:question, user:) }
given!(:another_question) { create(:question, user: another_user) }
describe 'Authenticated user', js: true do
background { sign_in user } ### Here I am signing in
scenario 'can edit the question' do
visit question_path(question)
### Here save_and_open_page shows that no active session
within "#question_#{question.id}" do ### Finds turbo frame tag #question_{id}
click_on 'Edit'
fill_in 'Body', with: 'Question is edited.'
click_on 'Save'
expect(page).to_not have_content question.body
expect(page).to have_content 'Question is edited.'
expect(page).to_not have_selector 'textarea'
end
expect(page).to have_content 'Question is edited.'
end
end
end
This is my feature helper for signing in:
def sign_in(user)
visit new_user_session_path
within 'form#new_user' do
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
click_on 'Log in'
end
end
If I am testing without js: true
the test works and user session is not terminated after each redirect during the test.
I use Capybara.javascript_driver = :selenium_chrome_headless
as a js driver.
The accepted answer here may solve the issue but it's a bad solution. The "solution" is dependent on the speed the test hardware runs at, and therefore may be flaky when you move to a cloud test infrastructure, or when your local machine is heavily loaded.
The root cause of the issue is that the tests run asynchronously and require checking for visible items in the page to synchronize. In the test you call 'sign_in' which fills out the login page and clicks the "Log In" button, but there's nothing waiting for the login to actually occur. Therefore the test continues, and calls visit ...
which changes the page, cancelling the browsers submission, before the login has completed and before the session cookie has been returned. Since the browser doesn't yet have the session cookie the user isn't authenticated on the new page. The sleep(1)
"fixes" that by trying to give the login time to finish. In some cases that 1 second will be waiting too long (wasted time) and in other it may not be enough (flaky tests). The better solution is to check for a change on the page which indicates the login has completed
def sign_in(user)
visit new_user_session_path
within 'form#new_user' do
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
click_on 'Log in'
end
expect(page).to have_text('Login Succeeded') # text shown on page that indicates a login has finished - or .have_css('#logged_in_menu'), etc
end
Summary: Don't add sleep
to tests, check for changes in the page which indicate it's safe to move on