I'm trying to test the devise feature for reseting a password, but I'm having some issues trying to test the link in the mail and visiting the page that is linked to.
I've tried unsuccessfully 2 ways to do it:
1)
scenario "User resets his password" do
user2 = FactoryGirl.create(
:user, reset_password_token: "new_password",
)
visit new_user_password_path
fill_in "Email", with: user2.email
click_on "Send me reset password instructions"
open_email(user2.email)
click_first_link_in_email
expect(page).to have_content("Reset your password")
end
If I do this I get the error:
Failure/Error: click_first_link_in_email
ActionController::RoutingError:
No route matches [GET] "/1999/xhtml'"
2)
scenario "User resets his password" do
user2 = FactoryGirl.create( :user, reset_password_token: "new_password" )
visit edit_user_password_path(reset_password_token:
expect(page).to have_content("Reset your password")
end
If I do this it takes me to the initial page saying:
"You can't access this page without coming from a password reset email"
Solved!
there were more links inside the mail.
The way that I selected the one that I wanted was by using links_in_email(email).
I used pry to check the links inside the array (links_in_email(email) and then select the one that I was interested in.
scenario "User resets his password" do
visit new_user_password_path
fill_in "Email", with: #user.email
click_on "Send me reset password instructions"
mail = ActionMailer::Base.deliveries.last
link = links_in_email(mail)[1]
visit link
expect(page).to have_content("Reset your password")
end
Related
I wrote the following feature spec to test sign up, sign in, password recovery, and account locking behavior in a Rails 4.2.8 application with Devise.
The tests work. However, I'm relatively new to Capybara, so I'd like your suggestions regarding how to improve their readability, maintainability, DRYness, speed, reduce brittleness, etc.
Validations such email uniqueness, password strength and so on are performed at the User model spec. Hence, I did not re-test those behaviors here.
# spec/features/user_registration_spec.rb
require 'rails_helper'
def login_via_form(email, password)
expect(page).to have_current_url("/users/sign_in")
fill_in "Email", with: email
fill_in "Password", with: password
click_button "Sign in"
end
RSpec::Matchers.define :be_logged_in do |user_first_name|
match do |page|
expect(page).to have_current_path "/"
expect(page).to have_content "Hello #{user_first_name}"
end
failure_message do |actual|
"expected the current path to be /, actual path is #{page.current_path}" \
"\nexpected to find a 'Hello #{user_first_name}' in '#{page.text}'."
end
end
describe "User self-management", type: :feature, js: true do
let!(:user) { FactoryGirl.create(:user) }
let(:valid_attributes) { FactoryGirl.attributes_for(:user) }
it "registers a new user" do
visit "/"
click_link "Sign up"
fill_in "Email", with: valid_attributes[:email]
fill_in "Password", with: valid_attributes[:password]
fill_in "Password confirmation", with: valid_attributes[:password]
fill_in "First name", with: valid_attributes[:first_name]
fill_in "Last name", with: valid_attributes[:last_name]
select "United States", from: "Country"
select "New York", from: "State"
select "New York", from: "City"
fill_in "Sangha", with: "My Sangha"
fill_in "Phone number", with: "+1 212-555-0187"
fill_in "Facebook url", with: "http://www.facebook.com/johndoe"
click_button "Sign up"
# The user may only login *after* confirming her e-mail
expect(page).to have_content "A message with a confirmation link has been" \
" sent to your email address. Please follow the link to activate your" \
" account."
expect(page).to have_current_path "/users/sign_in"
# Find email sent to the given recipient and set the current_email variable
# Implemented by https://github.com/DockYard/capybara-email
open_email(valid_attributes[:email])
expect(current_email.subject).to eq "Confirmation instructions"
current_email.click_link "Confirm my account"
expect(page).to have_content "Your email address has been successfully " \
"confirmed."
login_via_form(valid_attributes[:email],
valid_attributes[:password])
expect(page).to have_content "Signed in successfully."
expect(page).to be_logged_in(valid_attributes[:first_name])
end
it "performs password recovery (creates a new password)" do
visit "/users/sign_in"
click_link "Forgot your password?"
fill_in "Email", with: user.email
click_button "Send me reset password instructions"
expect(page).to have_text "You will receive an email with instructions " \
"on how to reset your password in a few minutes."
# Find email sent to the given recipient and set the current_email variable
open_email(user.email)
expect(current_email.subject).to eq "Reset password instructions"
current_email.click_link "Change my password"
fill_in "New password", with: valid_attributes[:password]
fill_in "Confirm new password", with: valid_attributes[:password]
click_button "Change my password"
expect(page).to have_content "Your password has been changed " \
"successfully. You are now signed in."
expect(page).to be_logged_in(user.first_name)
open_email(user.email)
expect(current_email.subject).to eq "Password Changed"
expect(current_email.body). to have_text "We're contacting you to notify " \
"you that your password has been changed."
end
describe "resend confirmation e-mail" do
context "with an already confirmed e-mail address" do
it "warns the user and does not send a new confirmation e-mail" do
# Our factory creates users with confirmed e-mails
visit "/users/sign_in"
click_link "Didn't receive confirmation instructions?"
fill_in "Email", with: user.email
expect {
click_button "Resend confirmation instructions"
}.not_to change(ActionMailer::Base.deliveries, :count)
expect(page).to have_text "Email was already confirmed"
end
end
context "with an unconfirmed e-mail address" do
it "sends a new confirmation e-mail" do
# Unconfirm user (our factory creates users with confirmed e-mails)
user.update(confirmed_at: nil)
visit "/users/sign_in"
click_link "Didn't receive confirmation instructions?"
fill_in "Email", with: user.email
click_button "Resend confirmation instructions"
expect(page).to have_text "You will receive an email with " \
"instructions for how to confirm your email address in a few minutes."
open_email(user.email)
expect(current_email.subject).to eq "Confirmation instructions"
current_email.click_link "Confirm my account"
expect(page).to have_content "Your email address has been " \
"successfully confirmed."
login_via_form(user.email, user.password)
expect(page).to have_content "Signed in successfully."
expect(page).to be_logged_in(user.first_name)
end
end
end
it "locks the account after 5 failed login attempts" do
visit "/users/sign_in"
3.times do
login_via_form(user.email, "bogus")
expect(page).to have_text "Invalid Email or password."
end
login_via_form(user.email, "bogus")
expect(page).to have_text "You have one more attempt before your " \
"account is locked."
login_via_form(user.email, "bogus")
expect(page).to have_text "Your account is locked."
open_email(user.email)
expect(current_email.subject).to eq "Unlock instructions"
current_email.click_link "Unlock my account"
expect(page).to have_content "Your account has been unlocked " \
"successfully. Please sign in to continue."
login_via_form(user.email, user.password)
expect(page).to have_content "Signed in successfully."
expect(page).to be_logged_in(user.first_name)
end
context "account is locked, didn't receive unlocking instructions" do
it "sends a new unlocking instructions e-mail" do
user.update(locked_at: DateTime.now)
visit "/users/sign_in"
click_link "Didn't receive unlock instructions?"
fill_in "Email", with: user.email
click_button "Resend unlock instructions"
expect(page).to have_text "You will receive an email with instructions " \
"for how to unlock your account in a few minutes."
open_email(user.email)
expect(current_email.subject).to eq "Unlock instructions"
current_email.click_link "Unlock my account"
expect(page).to have_content "Your account has been unlocked " \
"successfully. Please sign in to continue."
login_via_form(user.email, user.password)
expect(page).to have_content "Signed in successfully."
expect(page).to be_logged_in(user.first_name)
end
end
context "account is not locked, attempts to re-send unlocking instructions" do
it "warns the user and does not send a new confirmation e-mail" do
# Our factory creates users with confirmed e-mails
visit "/users/sign_in"
click_link "Didn't receive unlock instructions?"
fill_in "Email", with: user.email
expect {
click_button "Resend unlock instructions"
}.not_to change(ActionMailer::Base.deliveries, :count)
expect(page).to have_text "Email was not locked"
end
end
end
Thank you.
This might be better received at https://codereview.stackexchange.com/
That said, I'm not wild about how open_email somehow magically sets current_email. I would prefer that method returned it, and you checked against that return value if possible; this can be a problem with multithreaded apps or test suites.
Your expectations of text on page are pretty brittle. If any of that copy changes, the test will also need to be reworked. Personally, I'm OK with this, but it could potentially save you some effort if you either matched against a semi-lax regular expression, or put those strings into a translation file, and checked against I18n.t('email.confirmation.message').
To improve performance, you may want to use a login method that doesn't use login_via_form, I think Devise test helpers still include login_as that directly sets a logged-in session for you.
Overall, I think this is great work. Keep it up!
I am trying to test a signup flow using rspec and cpaybara within a feature spec. The signup happens via ajax and capybara doesn't wait for the request to complete. I am using the capybara matchers as specified in the documentation. If I add a sleep statement the test succeeds
let(:user) { create(:user, email: 'test#example.com', password: 'password') }
scenario 'visitor can sign up with valid email address and password', js: true do
sign_up_with('Name', 'test#example.com', 'password')
expect(page).to have_content(I18n.t( 'devise.registrations.signed_up'))
end
Helper Method to handle signup:
def sign_up_with(name, email, password)
visit root_path
find(:xpath,"//a[text()='Join']").click
within("#sign_up_choices") do
click_on "Sign up with Email"
end
within("#sign_up") do
fill_in 'user[name]', with: name
fill_in 'user[email]', with: email
fill_in 'user[password]', with: password
click_button 'Sign up'
end
end
When the user clicks on signup the form gets submitted via ajax.
Error I am getting occasionally:
Selenium::WebDriver::Error::StaleElementReferenceError:
stale element reference: element is not attached to the page document
(Session info: chrome=55.0.2883.95)
(Driver info: chromedriver=2.25.426935 (820a95b0b81d33e42712f9198c215f703412e1a1),platform=Mac OS X 10.11.4 x86_64)
Any help would be much appreciated!
I'm having trouble getting Cucumber / Capybara to find a 'success' flash message on a page after a user is logged in. I'm able to see the message if I do it manually in the browser, so I'm thinking I'm doing something wrong in my steps.
user_login.feature
Feature: User login
Scenario: A user successfully logs in from the homepage
Given A user is on the homepage
When the user clicks "Login"
And they fill out the form
Then they should see a success message
step_definition
Given /^A user is on the homepage$/ do
#user = Factory.create(:user)
visit root_path
end
When /^the user clicks "(.*?)"$/ do |arg1|
click_link arg1
end
When /^they fill out the form$/ do
fill_in "email", with: #user.email
fill_in "password", with: "blahblah1"
click_button "Sign in"
end
Then /^they should see a success message$/ do
page.should have_selector ".alert", text: "Logged in!"
end
output
expected css ".alert" with text "Logged in!" to return something (RSpec::Expectations::ExpectationNotMetError)
Basically, I made a DERP. The credentials I was using (password) to fill in the form weren't the same as the user I was creating via FactoryGirl. This was causing an 'invalid email / password' message to appear when I was testing for a 'success' message.
To debug what the page was outputting, I added a page.should have_content "asdfsdfas" in my spec. When the test fails, Cucumber outputs the content it got on the page compared to what you expected it to receive.
I am trying to test the login functionality with Cucumber.
My file users_steps.rb contains
Given /^I am a user named "([^"]*)" with an email "([^"]*)" and password "([^"]*)"$/ do |name, email, password|
u = User.new(:name => name,
:email => email,
:password => password,
:password_confirmation => password)
u.skip_confirmation!
u.save!
end
When /^I sign in as "(.*)\/(.*)"$/ do |email, password|
#Given %{I am not logged in}
When %{I go to the sign in page}
And %{I fill in "user_email" with "#{email}"}
And %{I fill in "user_password" with "#{password}"}
And %{I press "Log Me In"}
end
Then /^I should be signed in$/ do
Then %{I should see "Sign out"}
end
Then /^I should be signed in$/ do
Then %{I should see "Sign out"}
end
Then /^I sign out$/ do
visit('/account/logout')
end
and my cucumber scenario is:
Scenario: User signs in successfully with email
Given I am not logged in
And I am a user named "foo" with an email "user#test.com" and password "please"
When I go to the sign in page
And I sign in as "user#test.com/please"
Then I should be signed in
When I return next time
Then I should be already signed in
However this test fails to sign the user in. I have checked that the user is correctly created but after filling in the form I am redirected to the login page.
I am using capybara. What am I missing?
You probably want to use :
1. In a user_steps.rb file located in step_definitions:
Given /^a valid user$/ do
#user = User.create!({
:email => "minikermit#hotmail.com",
:password => "12345678",
:password_confirmation => "12345678"
})
end
Given /^a logged in user$/ do
Given "a valid user"
visit signin_url
fill_in "Email", :with => "minikermit#hotmail.com"
fill_in "Password", :with => "12345678"
click_button "Sign in"
end
In your feature to test authentication:
Scenario: Login
Given a valid user
When I go to the login page
And I fill in the following:
|Email|minikermit#hotmail.com|
|Password|12345678|
And I press "Sign in"
Then I should see "Signed in successfully."
Do not forget to change the path to your login page in the support/paths.rb
when /the login page/
user_session_path
Here my path is using devise default setting. You can use rake routes to discover your login path.
You might have to change the text in the "Sign in", "Signed in successfully" to match your page. My assumptions here is that your are using the default config for cucumber+capybara+devise.
I'm new to Cucumber and totally lost as to why this integration test is failing. I have the following scenarios:
Scenario: User changes profile
Given I have an account
When I change my profile
Then my profile should be saved
Scenario: User changes login
Given I have an account
When I change my login information
Then my account should be changed
And these step definitions:
Given /^I have an account$/ do
#user = Factory.create(:user)
visit login_path
fill_in "Email", :with => #user.email
fill_in "Password", :with => 'secret'
click_button "Sign in"
end
When /^I change my profile$/ do
visit edit_user_profile_path(#user)
fill_in "First name", :with => "John"
fill_in "Last name", :with => "Doe"
click_button "Update Profile"
end
Then /^my profile should be saved$/ do
#user.profile.first_name.should == "John"
#user.profile.last_name.should == "Doe"
end
When /^I change my login information$/ do
visit edit_user_path(#user)
fill_in "Email", :with => "foo#example.com"
click_button "Update Login Information"
end
Then /^my account should be changed$/ do
#user.email.should == "foo#example.com"
end
And I fail the "Then" condition on both scenarios with this message:
# Scenario 1
expected: "John"
got: "Test1" (using ==) (RSpec::Expectations::ExpectationNotMetError)
# Scenario 1
expected: "foo#example.com"
got: "test2#example.com" (using ==) (RSpec::Expectations::ExpectationNotMetError)
So, in both cases the factory information is still present after submitting the form to update the user login or profile. However, if I test this in a real browser it works perfectly. So, why is this test failing???
Thanks for the help!
#user is just a variable which lives inside your cucumber code block. It will not be changed. What will be changed in the test is the database record. To check that it was changed indeed you have to visit some page where user name is displayed.
(Just as you do in your real life test)