Capybara: fill_in action locator - ruby-on-rails

So I have this spec
describe "visit /signup" do
before(:each) do
get signup_path
end
it "has an email input field" do
page.has_field?("#user_email")
end
it "accepts an email address" do
page.fill_in('#user_email', :with=>Faker::Internet.email)
end
end
The first test (has an email input) passes, the second fails with
Failure/Error: page.fill_in('#user_email', :with=>Faker::Internet.email)
Capybara::ElementNotFound:
cannot fill in, no text field, text area or password field with id, name, or label '#user_email' found
The input[type='text'] element exists on the page with that DOM ID, have tried locating with the ID with and without the hash, and using its input:name as a locator too.
What am I doing wrong?

It's because you're using get when you should be using visit inside the before block. This:
before(:each) do
get signup_path
end
Should be this:
before(:each) do
visit signup_path
end
Otherwise you're telling Rack::Test to visit that path, not Capybara! A small distinction that trips quite a few people up frequently!

maybe you should remove the #, e.g.
fill_in('user_email', :with=>Faker::Internet.email)

I think fill_in is not on page. Just use fill_in:
describe "visit /signup" do
before(:each) do
get signup_path
end
it "has an email input field" do
page.has_field?("#user_email")
end
it "accepts an email address" do
fill_in('#user_email', :with=>Faker::Internet.email)
end
end

Related

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

Avoid code duplication in Rspec + Capybara

Let's say I have test on new input and test on new input when input is invalid (first test is on case input is valid).
For example (from my code):
scenario "valid input saving" do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
within("#student_0") do
fill_in "Имя", :with => "Name"
fill_in "Фамилия", :with => "Surname"
fill_in "Электронная почта", :with => "randommail#mail.com"
end
print page.html
click_button "Save"
expect(page).to have_current_path program_stream_path(#program, #stream)
#...other code
end
Obviously, test that checks behavior on invalid input repeats this part:
scenario "invalid input leads to correct input page" do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
#other code
How to avoid this copy-paste way?
You can use before blocks for this kind of thing
feature "..." do
before :each do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
end
scenario "valid input saving"
#unique code for this scenario
end
scenario "invalid input leads to correct input page"
# unique code for this scenario
end
end
The outer feature block could be a describe or a scenario block if wanted/needed since you can nest multiple levels. If you need to use the code across multiple feature files then it makes sense to move it to a method in one of your spec helper files.
You can place it in a method in your spec file like this and reuse it.
scenario "valid input saving" do
your_named_method
...
end
scenario "invalid input leads to correct input page" do
your_named_method
...
end
def your_named_method
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
end
A great way to avoid duplication when testing with Capybara is to use Capybara Test Helpers.
RSpec.feature 'Program Stream', test_helpers: [:programs] do
before { visit program_stream_path(#program, #stream) }
scenario 'valid input saving' do
programs.click_to_add_student
programs.should.be_adding_a_student(#stream)
programs.add_student(name: 'Имя', surname: 'Фамилия', email: 'randommail#mail.com')
programs.should.have_new_student('Имя')
end
scenario 'invalid input leads to correct input page' do
programs.click_to_add_student
programs.should.be_adding_a_student(#stream)
programs.add_student(name: nil, surname: nil, email: nil)
programs.should.have_invalid_form("Name can't be blank")
end
end
Besides reducing code duplication, it has the benefit of being a lot more descriptive, which can help to make tests a lot easier to maintain in the long term.
class ProgramsTestHelper < Capybara::TestHelper
# Actions: Encapsulate complex actions to provide a cleaner interface.
def click_to_add_student
click_link '#link'
fill_in '#fill_in', with: '1')
click_button "Next"
end
def add_student(name:, surname:, email:)
fill_in 'Имя', with: name
fill_in 'Фамилия', with: surname
fill_in 'Электронная почта', with: email
click_button 'Save'
end
# Assertions: Allow to check on element properties while keeping it DRY.
def be_adding_a_student(stream)
have_current_path urls.new_students_list_stream_path(stream)
end
def have_new_student(name)
have_content(name)
end
def have_invalid_form(message)
have('form', text: message)
end
end
Have in mind that you could choose to combine click_to_add_student with add_student, running assertions inside the helper methods. It all boils down to how much granularity you need in tests.
Passing blocks to methods is also a nice way to customize interactions or outcomes.

Dynamic expectations in Rspec

I am trying to test a dynamic url path through an Rspec expectation, as below:
describe 'registering a user' do
context 'with valid data' do
it 'confirms user registration' do
visit '/users/new'
fill_in 'Name here...', with: 'johnny bloggs'
fill_in 'Your email here...', with: 'test1#test.com'
click_button 'Notify me'
expect(current_path).to eq '/users/#user.id'
expect(page).to have_content "johhny"
expect(page).not_to have_content "Sign up"
end
end
end
This is a feature test not a model unit test so I suppose this sort of object attribute , #user.id, expectation shouldn't be in a feature test. But testing the redirection to a certain path i guess is a valid thing for a feature test to be checking, and a key part of the functionality that i'm testing is that the action redirects to an object specific show page?!
So a) how should i correctly be testing this redirect thats new path involves a dynamic attribute and b) whether or not the correct thing to be doing here is testing a 'dynamic attribute' how would it be possible to test dynamic content within an rspec test? .erb perhaps?
Thank you in advance for the enlightenment.
You can use plain string interpolation:
expect(current_path).to eq "/users/#{ User.last.id }"
Or a route helper:
expect(current_path).to eq user_path(User.last)

Rails Factory Girl "Create" not saving record?

I have two capybara tests that are testing the signup process on my rails application, both using factory girl. One is just using Factory Girl build command and saving it with the form:
it 'should create a user and associated customer_info', js: true do
visit signup_path
user = build(:user)
customer = build(:customer_info)
sign_up user, customer
page.should have_content 'Welcome back, ' + customer.firstname
end
Whereas the other is using the create command, and then attempting to sign in with that info.
it 'should be able to sign in', js: true do
user = create(:user)
customer = create(:customer_info, user_id: user.id)
visit new_user_session_path
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
click_button 'Sign in'
page.should have_content 'Welcome back, ' + customer.firstname
end
The first one passes and saves in my test database. The second one fails, saying "invalid email or password," but also when I check my database after each test, the first one saves a record but the second one doesn't (which I'm assuming is why it's saying invalid email/password).
Any ideas why my FactoryGirl create function isn't actually saving my record in the database?
EDIT
I have a sequence in my FactoryGirl definition for the email, and build AND create both increase the sequence, so it shouldn't be creating dups, right?
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "foo#{n}#example.com"}
password "secret"
password_confirmation "secret"
end
end
The problem is you are trying to create duplicate user. Sign up creates user in test database and now when you are trying to create a new user with FactoryGirl it would raise validation error because the same user is already there in test database. You should do something like this:
def create_user
#user ||= create(:user)
end
it 'should create a user and associated customer_info', js: true do
visit signup_path
#user = build(:user)
customer = build(:customer_info)
sign_up #user, customer
page.should have_content 'Welcome, ' + customer.firstname
end
it 'should be able to sign in', js: true do
create_user
customer = create(:customer_info, user_id: #user.id)
visit new_user_session_path
fill_in 'user_email', with: #user.email
fill_in 'user_password', with: #user.password
click_button 'Sign in'
page.should have_content 'Welcome back, ' + customer.firstname
end
May be you can use the different approach to solve it. but main focus is on to use the single user object for sign up and sign in.
Hope this would help you.

Determining Form Submission with RSpec/Capybara

I have a project in which I am using RSpec and Capybara to do unit testing. I have fully flushed out model and controller tests which are passing nicely and handle the heavy lifting for pre-database validation.
I am now testing user experience and front end items and want to know how i could verify that a form did NOT submit. If a user mismatches passwords or some other erred data, I have script to set the error and prevent submission.
I know i could search for error text but where there a way to check that the 'submit' never happened and feel confident that no server trip was made.
I want something like:
it "should not sumbit if user name is less than 3 characters" do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
# HOW DO I DO THIS?
expect( ... ).not_to submit_to_server
end
This is not something that you should be testing in an integration test. In integration tests you are taking the point of view of the end user, and therefore you should only be testing what the user can actually see. If the only evidence the user sees that the form has not been submitted is an error message, then that is what you should test for.
In integration tests we most test what the user would see like if an empty field is given then there must be error message according to validations. But still if you want you check as follow
describe "User Registration" do
before do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
end
it "should not sumbit if user name is less than 3 characters" do
page.should have_content "your error message"
end
it "should create the user and redirect to blah_path" do
current_path.should eq blah_path
end
it "should add user in users table" do
expect { user.create }.to change(User, :count).from(0).to(1)
end
end

Resources