I used grouped_select input to group nested associations. (see images below)
It looks like Capybara can't find this kind of input.
Capybara::ElementNotFound: Unable to find option "Ryan, Reynolds and Effertz" within #<Capybara::Node::Element tag="select"
These are my specs
FactoryBot.define do
factory :project_type do
organization
name { FFaker::Company.unique.name }
end
end
FactoryBot.define do
factory :project_stage do
project_type
name { FFaker::Company.unique.name }
end
end
FactoryBot.define do
factory :project do
user
stage factory: :project_stage
name { FFaker::Company.unique.name }
note { FFaker::Lorem.phrase }
start_date { Date.new(2022, 01, 01) }
end_date { Date.new(2022, 12, 19) }
budget { 10000 }
end
end
test "should create project" do
login_as #user
visit projects_url
find(:css, ".project-application .new-item").click
fill_in "project_name", with: #project.name
fill_in "project_budget", with: #project.budget
fill_in "project_start_date", with: #project.start_date
fill_in "project_end_date", with: #project.end_date
fill_in "project_note", with: #project.note
select(#project.stage.name, from: "Stade ")
# fill_in "project_project_stage_id", with: #project.project_stage_id
find(:css, ".hubflo-modal .save-item").click
assert_text(:all, "Projet créé")
find(:css, ".project-application .back-to-index").click
end
Capybara::ElementNotFound: Unable to find option "Ryan, Reynolds and Effertz" within #<Capybara::Node::Element tag="select"
Capybara is finding the select tag correctly, it's just not finding that option in the list. I think you're right that it's struggling with the grouping.
Try selecting an option by value instead of the text.
But, #Thomas Walpole makes a good point.
I tend to believe Capybara is telling the truth. Which means:
#project.stage.name is not an option in the list.
This could be because the controller or view is not creating the select correctly.
It could also be that your test is not saving #project.stage before the controller or view calls for the collection of stages.
Try this:
test "should create project" do
login_as #user
visit projects_url
find(:css, ".project-application .new-item").click
# debugger to manually verify that #project.stage.name is in the select list
save_and_open_page
# fill_in "project_name", with: #project.name
# fill_in "project_budget", with: #project.budget
# fill_in "project_start_date", with: #project.start_date
# fill_in "project_end_date", with: #project.end_date
# fill_in "project_note", with: #project.note
# select(#project.stage.name, from: "Stade ")
# fill_in "project_project_stage_id", with: #project.project_stage_id
# find(:css, ".hubflo-modal .save-item").click
# assert_text(:all, "Projet créé")
# find(:css, ".project-application .back-to-index").click
end
Related
I have a problem.
I'm trying to use capybara to do integration tests, but I can not get the id on my page for it to make the visit when I test the context "edit new tarefa". I'm using devise, so I create the user at the beginning of the code.
Below is the code:
require 'rails_helper'
describe "Tarefas", :type => :feature do
feature "New Tarefa" do
background do
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
end
context "create new tarefa" do
it "preenchendo os campos" do
visit '/tarefas/new'
within("#new_tarefa") do
fill_in 'tarefa_titulo', with: 'user#example.com'
fill_in 'tarefa_descricao', with: 'password'
fill_in 'tarefa_data', with: '18/06/1990 20:00'
end
click_button 'submit'
expect(page).to have_content 'Mostra a tarefa selecionada'
end
end
context "edit new tarefa" do
it "alterando os campos" do
visit "tarefas/#{Tarefa.last.id}/edit"
within("#new_tarefa") do
fill_in 'tarefa_titulo', with: 'user#exa12mple.com'
fill_in 'tarefa_descricao', with: 'passw213ord'
fill_in 'tarefa_data', with: '18/06/1990 21:00'
end
click_button 'submit'
expect(page).to have_content 'Mostra a tarefa selecionada'
end
end
end
end
You should not rely on the tarefa you have created in "create new tarefa" to be available in you next spec as:
It won't work since the ordering is random.
It would create a coupling where one spec relies on the outcome of another.
Instead you want to use database_cleaner to clean out the DB after each spec and use let and let! to setup the requirements for each spec:
require 'rails_helper'
# You can use `feature` as a top level block.
# No need to nest it in descibe.
RSpec.feature "New Tarefa" do
let(:user) { FactoryGirl.create(:user) }
let(:tarefa) { FactoryGirl.create(:tarefa) }
background do
login_as(user, scope: :user)
end
context "create new tarefa" do
it "preenchendo os campos" do
visit '/tarefas/new'
within("#new_tarefa") do
fill_in 'tarefa_titulo', with: 'user#example.com'
fill_in 'tarefa_descricao', with: 'password'
fill_in 'tarefa_data', with: '18/06/1990 20:00'
end
click_button 'submit'
expect(page).to have_content 'Mostra a tarefa selecionada'
end
end
context "edit new tarefa" do
it "alterando os campos" do
# tarefa is created when you reference it.
visit "tarefas/#{tarefa.to_param}/edit"
within("#new_tarefa") do
fill_in 'tarefa_titulo', with: 'user#exa12mple.com'
fill_in 'tarefa_descricao', with: 'passw213ord'
fill_in 'tarefa_data', with: '18/06/1990 21:00'
end
click_button 'submit'
expect(page).to have_content 'Mostra a tarefa selecionada'
end
end
end
I need to test a system in which everything is available only after a user is signed in using Devise. Every time I use "it" I have to include the signup code.
Is there a way to factor the code below so that the "let's me make a new post" test and similar tests won't have to include the sign up?
describe "new post process" do
before :all do
#user = FactoryGirl.create(:user)
#post = FactoryGirl.create(:post)
end
it "signs me in" do
visit '/users/sign_in'
within(".new_user") do
fill_in 'Email', :with => 'user#example.com'
fill_in 'Password', :with => 'password'
end
click_button 'Log in'
expect(page).to have_content 'Signed in successfully'
end
it "let's me make a new post" do
visit '/users/sign_in'
within(".new_user") do
fill_in 'Email', :with => 'user#example.com'
fill_in 'Password', :with => 'password'
end
click_button 'Log in'
visit '/posts/new'
expect( find(:css, 'select#post_id').value ).to eq('1')
end
end
Your first option is to use the Warden methods provided, as per the documentation on this page:
https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
Your second option is just to login for real in your tests as you have done in your examples. You can streamline this though by creating some helper methods to do the login work rather than duplicating the code in all of your tests.
To do this, I would create a support directory within your spec directory, and then a macros directory within that. Then create a file spec/support/macros/authentication_macros.rb:
module AuthenticationMacros
def login_as(user)
visit '/users/sign_in'
within('.new_user') do
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
end
click_button 'Log in'
end
end
Next, update your RSpec config to load your macros. In either spec_helper.rb or rails_helper.rb if you're using a newer setup:
# Load your support files
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
# Include the functions defined in your modules so RSpec can access them
RSpec.configure do |config|
config.include(AuthenticationMacros)
end
Finally, update your tests to use your login_as function:
describe "new post process" do
before :each do
#user = FactoryGirl.create(:user)
#post = FactoryGirl.create(:post)
login_as #user
end
it "signs me in" do
expect(page).to have_content 'Signed in successfully'
end
it "let's me make a new post" do
expect( find(:css, 'select#post_id').value ).to eq('1')
end
end
Obviously, make sure you have password defined in your user factory.
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.
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")?
Using capybara+rspec how can i compile a form with empty fields?
I'm testing an edit resource page, so i have a compiled form and want to clean its text fields. This is a partial of test:
context "when submitting" do
before { visit edit_post_path(post) }
it {should have_content('Editing')}
it {current_path.should == edit_post_path(post)}
describe "whit invalid information" do
before do
fill_in "post[title]", :with => "" #not working
fill_in "post[body]", :with => "" #not working
click_button "update"
end
it {current_path.should == edit_post_path(post)}
end
describe "whit valid information" do
before do
fill_in "post[title]", with: "some"
fill_in "post[body]", with: "some"
click_button "update"
end
it {should have_content('some')}
it {should have_content('some')}
it {current_path.should == post_path(post)}
end
end
Check manually actual ID/name/label of relevant field in generated HTML of Edit-page via Chrome:InspectElement of Firefox:Firebug. High chances they are different to "post[title]".
UPD. Try to fill in empty strings manually on the page. Does it work fine? I mean error displayed and the route is correct. Firing "Update"-button and getting an error you're not on the edit_post_path anymore. It happens bc in case of unsuccessful #post.update you're rendering Post#edit-view from Post#update action.
Likely problem is that post[title] and post[body] are the names of the fields, and not the IDs.
Also, something you might want to look into to make your tests a little more rigorous: capybara has a builtin within function that yields a block in which you can perform more actions. Check out the documentation on the front page of the gem page: https://github.com/jnicklas/capybara. It would probably look something like:
describe "whit invalid information" do
before do
within("#post") do
fill_in "title", :with => ""
fill_in "body", :with => ""
click_button "update"
end
end
it {current_path.should == edit_post_path(post)}
end