Capybara: Sometimes finds elements, sometimes doesn't - ruby-on-rails

Hello dear Stackoverflowers.
I'm having some seriously insanely annoying trouble with my capybara test. It seems to have a mind of its own and will sometimes decide to run with zero issue and sometimes decide to not find elements, click anything and generally just suck. I have no idea why this is.
I've been researching for days trying to find sure up my logic, find the 'best' ways of finding and clicking elements or filling in fields and haven't gotten any further. Please help, if I still had hair I would be pulling it out.
Also I'm using the Selenium Web Driver. All gems are up to date.
Thanks in advance.
It will randomly decide to not click on 'Amtrak 1234' and sometimes entirely miss the find('#atedrop4').click or will decide to click elements not even close being specified in my test. I'm very confused, and in desperate need of some help.
it 'can view the itinerary print and export', js: true do
visit '/admin/login'
fill_in 'Email', with: "evan#tripwing.com"
fill_in 'Password', with: "guest"
click_button 'Login'
click_link 'Trips'
expect(page).to have_content 'Trips'
click_link('View Trip Page', match: :first)
new_window=windows.last
page.within_window new_window do
expect(page).to have_content 'A Test To Forget'
find('.showItinerary').click
expect(page).to have_content "DAY 1, AMSTERDAM"
find(:xpath, "//a[#href='#flight1day1']").click
expect(page).to have_content "SINGAPORE AIRLINES FLIGHT 326"
click_link 'Add to Calendar'
find('.ategoogle').click
new_window=page.driver.browser.window_handles.last
page.driver.browser.switch_to.window(new_window) do
fill_in "Email", with: "evan#tripwing.com"
fill_in "Password", with: "boarder1"
find("#signIn").click
expect(page).to have_content "evan#tripwing.com"
page.driver.browser.close
end
find(:xpath, "//a[#href='#flight1day1']").click
find(:xpath, "//a[#href='#train1Day1']").click
click_link('Export to Calendar', match: :first)
find('.ategoogle').click
new_window=page.driver.browser.window_handles.last
page.driver.browser.switch_to.window(new_window) do
expect(page).to have_content "evan#tripwing.com"
page.driver.browser.close
end
find(:xpath, "//a[#href='#train1Day1']").click
find(:xpath, "//a[#href='#carRentalDay1']").click
click_link('Export to Calendar', match: :first)
find('.ategoogle').click
new_window=page.driver.browser.window_handles.last
page.driver.browser.switch_to.window(new_window) do
expect(page).to have_content "evan#tripwing.com"
page.driver.browser.close
end
find(:xpath, "//a[#href='#carRentalDay1']").click
find(:xpath, "//a[#href='#hotelDay1']").click
click_link('Export to Calendar', match: :first)
find('.ategoogle').click
new_window=page.driver.browser.window_handles.last
page.driver.browser.switch_to.window(new_window) do
expect(page).to have_content "evan#tripwing.com"
page.driver.browser.close
end
find(:xpath, "//a[#href='#hotelDay1']").click
find(:xpath, "//a[#href='#carTransfer1Day1']").click
click_link('Export to Calendar', match: :first)
find('.ategoogle').click
new_window=page.driver.browser.window_handles.last
page.driver.browser.switch_to.window(new_window) do
expect(page).to have_content "evan#tripwing.com"
page.driver.browser.close
end
find(:xpath, "//a[#href='#carTransfer1Day1']").click

One thing that helps is to use the save_and_open_page in your capybara tests. This way you can open up the html source of the test page and verify if your html id's and classes actually exist / are rendering the way you think they are. This has especially been the case for me when I'm doing nested forms and adding html to the page dynamically.

Related

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.

How to stub Time.now.hour

I have a helper method which outputs a greeting #{greet(Time.now.hour)} in the view pending the time of day:
users_helper.rb:
def greet(hour_of_clock)
if hour_of_clock >= 1 && hour_of_clock <= 11
"Morning"
elsif hour_of_clock >= 12 && hour_of_clock <= 16
"Afternoon"
else
"Evening"
end
end
And I am trying to test this unsuccessfully as below:
users_feature_spec.rb
describe 'greeting a newly registered user' do
before do
#fake_time = Time.parse("11:00")
Time.stub(:now) { #fake_time }
end
it 'tailors the greeting to the time of day' do
visit '/'
fill_in 'Name here...', with: 'test name'
fill_in 'Your email here...', with: 'test#test.com'
click_button 'Notify me'
expect(page).to have_content 'Morning'
end
end
The test fails as the Time.now.hour is not being stubbed as intended above.
I've now tried all sorts of variations thanks to various advice, the two main reformats which it at least seemed were syntactically correct were:
describe 'greeting a newly registered user' do
before do
#fake_time = Time.parse("11:00")
allow(Time).to receive(:now).and_return(#fake_time)
end
it 'tailors the greeting to the time of day' do
visit '/'
fill_in 'Name here...', with: 'test name'
fill_in 'Your email here...', with: 'test#test.com'
click_button 'Notify me'
expect(page).to have_content 'Morning'
end
end
and using the new ActiveSupport::Testing::TimeHelpers method #travel_to:
describe 'greeting a newly registered user' do
it 'tailors the greeting to the time of day' do
travel_to Time.new(2013, 11, 24, 01, 04, 44) do
visit '/'
fill_in 'Name here...', with: 'test name'
fill_in 'Your email here...', with: 'test#test.com'
click_button 'Notify me'
expect(page).to have_content 'Morning'
end
end
But still I'm doing something wrong which means that #greet is still taking the live output of Time.now.hour and not using my stubbed or travel_to Time value. Any help please?
You can try this :
let!(:fake_hour) { '11' }
before do
allow(Time).to receive_message_chain(:now, :hour).and_return(fake_hour)
end
Another approach would be to use Timecop (or the new Rails replacement travel_to) to stub out your time for you. With Timecop you can have super readable specs with no need for manual stubbing:
# spec setup
Timecop.freeze(Time.now.beginning_of_day + 11.hours) do
visit root_path
do_other_stuff!
end
I gave up trying to either stub out myself or use the ::TimeHelpers method #travel_to :( and used the Timecop gem, worked first time as below:
before do
Timecop.freeze(Time.now.beginning_of_day + 11.hours)
end
it 'tailors the greeting to the time of day' do
visit '/'
fill_in 'Name here...', with: 'test name'
fill_in 'Your email here...', with: 'test#test.com'
click_button 'Notify me'
expect(page).to have_content 'Morning'
end
I would really like to understand what was failing with my original approach though, does anyone see what was going wrong?

login test with rspec + Capybara not redirecting to home page

I'm trying requests tests with Capybara and it doesnt seem to work. This is my test file:
describe "Sessions", :type => :request do
let(:company) { FactoryGirl.create(:company) }
let(:user) { FactoryGirl.create(:admin, company_id: company.id ) }
describe "login page" do
it "signs me in" do
visit '/users/sign_in'
within("#new_user") do
fill_in 'Email', :with => user.email
fill_in 'Password', :with => user.password
end
click_button 'Sign in'
expect(page).to have_content 'Agenda'
end
end
end
Throws the following Error:
Failure/Error: expect(page).to have_content 'Agenda'
expected #has_content?("Agenda") to return true, got false
Im not sure if the problem is at logging in or at redirecting. But if i change the last line in the test for this:
expect(page).to have_content 'Invalid email'
I get the same Error.
Thanks in advance.
UPDATE:
i'm using devise for login
Simplest thing is to use screenshot_and_save_page to take a screenshot of the page just before the failing test, maybe you can determine what the problem is there.

Visiting More than one Page in a Describe Block Via Capybara

I'm writing some super simple integration specs, and while Capybara does simulate the browser/user interaction, I suspect that changing through several pages in one test causes issues. Mainly that the variable page doesn't get changed if you visit a few pages in a row. Here's what I mean:
require "rails_helper"
describe "The Initial Experience", :type => :feature do
it "User visits Narrowcontent" do
visit root_path
expect(page).to have_content "Sign In" && "Register"
click_on "Sign In"
expect(page).to have_content("Login Access")
within(".middle-login") do
fill_in "user_email", :with => ENV["admin_username"]
fill_in "user_password", :with => ENV["admin_pw"]
end
click_button 'Sign in'
save_and_open_page
#expect(page).to have_content("Dashboard")
end
end
I had to comment out the last expectation because it causes the test to fail. The keyword "Dashboard" DOES exist, but the problem is that after running click_button 'Sign In', the variable page still represents the old one, as if click_button 'Sign In'has no impact whatsoever.
Any tips?

Rspec/Capybara test cases are not working for multiple 'it' blocks

I am writing some integration test cases for an existing application. My test works fine if there is only one 'it' block. However, If I add more than one 'it' block it throws an error. Below is my code that works:
require 'spec_helper'
describe 'Group' do
before do
visit 'http://groups.caremonkey.com/users/sign_in'
fill_in "Email", :with => "email#example.com"
fill_in "Password", :with => "password"
click_button "Login"
page.should have_link('Account')
end
it 'Should check all the links and functionality of groups' do
#add new subgroup with valid data should save a new group
find("#group-squares").click_link("Add")
fill_in "Group Name", :with => "Melbourne futsal"
click_on("Save")
page.should_not have_content("can't be blank")
page.execute_script("parent.$.fancybox.close();")
page.should have_link('Account')
#test edit group: should be able to update group info provided valid data are given
first(".actual img").click
page.should have_content("Group")
page.should have_link("Cancel")
fill_in "Group name", :with => "Futsal club"
page.execute_script("$('#sub-group-color-options').find('.color23').click()")
click_button "Save"
click_on("Cancel")
page.should have_link('Account')
end
end
It works perfectly fine when I put all the 'it' block together in a single 'it' block. But when I split them in different 'it' block, it stops working. For example if I split this ("test edit group: should be able to update group info provided valid data are given") test case into separate 'it' block as follows
require 'spec_helper'
describe 'Group' do
before do
visit 'http://groups.caremonkey.com/users/sign_in'
fill_in "Email", :with => "email#example.com"
fill_in "Password", :with => "password"
click_button "Login"
page.should have_link('Account')
end
it 'add new subgroup with valid data should save a new group' do
find("#group-squares").click_link("Add")
fill_in "Group Name", :with => "Melbourne futsal"
click_on("Save")
page.should_not have_content("can't be blank")
page.execute_script("parent.$.fancybox.close();")
page.should have_link('Account')
end
it 'should be able to update group info provided valid data are given' do
first(".actual img").click
page.should have_content("Group")
page.should have_link("Cancel")
fill_in "Group name", :with => "Futsal club"
page.execute_script("$('#sub-group-color-options').find('.color23').click()")
click_button "Save"
click_on("Cancel")
page.should have_link('Account')
end
end
then rspec fails, it passes the first test, however second test gets failed throwing following error.
Failure/Error: visit 'http://groups.caremonkey.com/users/sign_in'
ActionController::RoutingError:
No route matches [GET] "/users/sign_in"
One more thing, I have to test all the features in remote(url: http://groups.caremonkey.com/). Because, I am writing integration tests for an existing application. In addition, I need to login to the system before I test rest of the features of my application. Thanks in advance for your help.
Have you followed the Capybara documentation for calling remote servers? It says you should have the following:
Capybara.current_driver = :selenium # Or anything but rack_test, probably
Capybara.run_server = false # Don't run your app in-process
Capybara.app_host = 'http://groups.caremonkey.com/'
My guess is that when you have visited the site once, future visit calls are trying to use relative routes, which then is routed to the default server. I can't think why you would get a ActionController::RoutingError if you don't have some kind of Rack server running. Are you running these tests in some other Rails application?
I guess something like this:
require 'spec_helper'
describe 'Group' do
before do
visit 'http://groups.caremonkey.com/users/sign_in'
fill_in "Email", :with => "email#example.com"
fill_in "Password", :with => "password"
click_button "Login"
page.should have_link('Account')
find("#group-squares").click_link("Add") #apperently both specs are "scoped" to this page
end
it 'Should check all the links and functionality of groups' do
fill_in "Group Name", :with => "Melbourne futsal"
click_on("Save")
page.should_not have_content("can't be blank")
page.execute_script("parent.$.fancybox.close();")
page.should have_link('Account')
end
it "test edit group: should be able to update group info provided valid data are given"
first(".actual img").click
page.should have_content("Group")
page.should have_link("Cancel")
fill_in "Group name", :with => "Futsal club"
page.execute_script("$('#sub-group-color-options').find('.color23').click()")
click_button "Save"
click_on("Cancel")
page.should have_link('Account')
end
end
My gut feeling tells me both test need the follow this: find("#group-squares").click_link("Add") so I added it to the before block This test however is cryptic, what is first(".actual img")?

Resources