I'm using Capybara to test a little Web app I'm writing. It refuses to select the right item from a select. Here's the code:
before do
fill_in 'Assignment', with: "blah blah blah"
select student.name, from: "assignment[student_id]"
end
I've tried every supported format in the "from" field. I've used the id, the name, and the label text to try and select the correct element. I've even booted the rails server in test, and manually ran through the steps in my test in the console. No matter what I do, capybara kicks back:
cannot select option, no option with text 'John Doe' in select box 'assignment[student_id]'
As stated before, I have kicked up the rails server in the test environment, and then manually added users to the database the same way I have done in my tests. I am 100% confident of the fact that there is indeed an option with text 'John Doe' in select box 'assignment[student].' This leads me to believe that I must be using the select method incorrectly. Can someone enlighten me?
Does the select from: need the select fields name or the ID?
Try it with the ID instead (assuming standard form style):
select student.name, from: "assignment_student_id"
If you inspect source on the select element you can confirm the fields ID.
Does it use the value of the option element rather than the text? Again, inspect and see what the value of "John Doe" is and see if it works by using student.id.
select student.id, from: "assignment[student_id]"
Lastly, have you tried the syntax in the documentation that includes the page?
page.select student.name, :from => 'assignment[student_id]'
The problem wasn't with the select code. I forgot how FactoryGirl's let works. I called build through a relationship on a User created by Factory Girl, and then tried assigning that result to student via let. Unfortunately, since let is lazy, it wasn't being created when it needed to be, so basically the page was getting called up before the student object was even created. I moved the code in my let statement into a before, and then assigned student using let and User.students.first.
Related
My seeds file populated the countries table with a list of countries. But now it needs to be changed to hard-code the id (instead of rails generating the id column for me).
I added the id column and values as per below:
zmb: {id: 103,code: 'ZMB', name: Country.human_attribute_name(:zambia, default: 'Error!'), display_order: nil, create_user: user, update_user: user, eff_date: Time.now, exp_date: default_exp_date},
skn: {id: 104,code: 'SKN', name: Country.human_attribute_name(:st_kitts_and_nevis, default: 'Error!'), display_order: nil, create_user: user, update_user: user, eff_date: Time.now, exp_date: default_exp_date}
countries.each { |key, value| countries_for_later[key] = Country.find_or_initialize_by(id: value[:id]); countries_for_later[key].assign_attributes(value); countries_for_later[key].save!; }
Above it just a snippet. I have added an id: for every country.
But when I run db:seed I get the following error:
ActiveRecord::RecordInvalid: Validation failed: Code has already been taken
I am new to rails so I'm not sure what is causing this - is it because the ID column already exists in the database?
What I think is happening is you have existing data in your database ... let's say
[{id:1 , code: 'ABC'},
{id:2 , code: 'DEF'}]
Now you run your seed file which has {id: 3, 'DEF'} for example.
Because you are using find_or_initialize_by with id you are running into errors. Since you can potentially insert duplicates.
I recon you should just clear your data, but you can try doing find_or_initialize_by using code instead of id. That way you wont ever have a problem of trying to create a duplicate country code.
Country.find_or_initialize_by(code: value[:code])
I think you might run into problems with your ids, but you will have to test that. It's generally bad practice to do what you are doing. Whether they ids change or now should be irrelevant. Your seed file should reference the objects that are being created not ids.
Also make sure you aren't using any default_scopes ... this would affect how find_or_initialize_by works.
The error is about Code: Code has already been taken. You've a validation which says Code should be uniq. You can delete all Countries and load seeds again.
Run this in the rails console:
Country.delete_all
Then re-run the seed:
rake db:seed
Yes, it is due to duplicate entry. In that case run ModelName.delete_all in your rails console and then run rake db:seed again being in the current project directory. Hope this works.
ActiveRecord::RecordInvalid: Validation failed: Code has already been taken
is the default error message for the uniqueness validator for :code.
Running rake db:reset will definitely clear and reseed your database. Not sure about the hardcoded ids though.
Check this : Overriding id on create in ActiveRecord
you will have to disable protection with
save(false)
or
Country.create(attributes_for_country, without_protection: true)
I haven't tested this though, be careful with your validators.
Add the line for
countries_for_later[key].id = value[:id]
the problem is that you can't set :id => value[:id] to Country.new because id is a special attribute, and is automatically protected from mass-assignment
so it will be:
countries.each { |key, value|
countries_for_later[key] = Country.find_or_initialize_by(id: value[:id])
countries_for_later[key].assign_attributes(value)
countries_for_later[key].id = value[:id] if countries_for_later[key].new_record?
countries_for_later[key].save(false)
}
The ids data that you are using in your seeds file: does that have any meaning outside of Rails? Eg
zmb: {id: 103,code: 'ZMB',
is this some external data for Zambia, where 103 is it's ID in some internationally recognised table of country codes? (in my countries database, Zambia's "numcode" value is 894). If it is, then you should rename it to something else, and let Rails decide what the id field should be.
Generally, mucking about with the value of ID in rails is going to be a pain in the ass for you. I'd recommend not doing it. If you need to do tests on data, then use some other unique field (like 'code') to test whether associations etc have been set up, or whatever you want to do, and let Rails worry about what value to use for ID.
I'm working with a Hobo app using the input_many tag to handle a many-to-many relationship on a form. This puts + and - buttons on the page, and the + adds a new select tag for picking the model on the other side of the relationship. This means that there can be an arbitrary number of select menus with very similar characteristics, distinguished only by an array index, like this:
<select class="input belongs_to data_set_graph" name="graph_pane[data_set_graphs][0][data_set_id]">
[...options...]
</select>
<select class="input belongs_to data_set_graph" name="graph_pane[data_set_graphs][1][data_set_id]">
[...options...]
</select>
N.B. the join of GraphPanes and DataSets is polymorphic (there are many kinds of GraphPanes) so the actual CSS class name varies according to the kind of pane - it could be data_set_a_graph_pane_data_set or data_set_b_graph_pane_data_set.
We've been using Capybara 1.1.2 to test this. As long as we're only associating one DataSet with a GraphPane, we've been able to select them with a step definition like this:
included_defs.each do |data_set_name|
click_button "+"
select_node = find(:css, '.input-many-item select') # There may be more than one of these?
select_node.find(:xpath, XPath::HTML.option(data_set_name), :message => "cannot select option with text '#{data_set_name}'").select_option
end
However, now we need to associate two DataSets with a GraphPane, and the find(:css, '.input-many-item select') fails because there are two matching nodes.
It seems to me that if I could just always pick the last one, this would work, but I can't figure out how to do this with Capybara's selectors. (I think part of the problem is that it's not clear to me, in most of the examples I find, whether they refer to the 1.x DSL or the 2.x series.)
Ideas for sorting this out elegantly are welcome.
The answer turns out to be using the "all" finder in Capybara:
included_defs.each do |data_set_name|
click_button "+"
select_node = all(:css, '.input-many-item select').last # There may be more than one of these
select_node.find(:xpath, XPath::HTML.option(data_set_name), :message => "cannot select option with text '#{data_set_name}'").select_option
end
all is like find, but it returns an array of matching nodes, so I can use .last and always get the last one.
DISCLAIMER: I am still a Rails/Cucumber newbie but I am trying to learn. I am still using the "web_steps" for Cucumber while I work through the RailsInAction book and I have experimented with writing my own steps. I understand why they were taken out and I will get there.
Problem:
In one of my Cucumber Scenarios I am populating some data and trying to use that data to populate a dropdown. The data is definitely being inserted, I know this because I wrote a step to test to see if the data is in fact there and it is.
When then DropDown is suppose to populated and I try "select" from that DropDown I get the error "*cannot select option, no option with text 'Steven' in select box 'result_winner' (Capybara::ElementNotFound)*"
I am sure I am missing something basic but I have lost about an hour to this now and I just need some help.
My Code:
The tests:
When I follow "Record new Result"
And the users "Steven, Joshua" exist
And I select "Steven" from "result_winner"
The test steps:
When /^the users "([^"]*)" exist$/ do |playerNames|
#names = playerNames.scan(/[\w']+/)
for name in #names
#newPlayer = Player.new
#newPlayer.name = name
#newPlayer.save
end
end
Then the view code in the _form.html.erb:
<p>
<%= f.label :winner %>
<%= select("result", "winner", #players.map {|p| [p.name, p.id]}) %>
</p>
The Controller:
def new
#result = Result.new
#players = Player.find(:all)
end
If I manually populate the development database with names and load it up in the browser, the dropdowns have data populated in them and work perfectly. I am pretty sure I am misunderstanding when/where test data is visible, or how to test for and select data from a dropdown.
Any help would be greatly appreciated.
It looks to me like a simple problem with the step sequence. You're visiting the page, and then populating the database, but the page doesn't get reloaded to take account of the new database values. If you swap the order of the first 2 steps around, I expect that would fix it.
To be clear, cucumber will literally step through your scenario one line at a time:
Load up the webpage (which doesn't contain your test data)
Insert your data into the DB
Look for that data in the dropdown (which won't be there as the page was loaded before the data was in the DB)
Your "the users "Steven, Joshua" exist" should be in a Given step.
It describes a state before an action is done.
Given the users "Steven, Joshua" exist
When I follow "Record new Result"
And I select "Steven" from "result_winner"
For the sake of simplicity, I've left out most of my test and only included the offending code. It is:
click_button('Search')
page.select 'Preferred', :from => 'ticket_service_type'
When I run this, I receive the following:
Failure/Error: page.select 'Preferred', :from => 'ticket_service_type'
Capybara::ElementNotFound:
cannot select option, no select box with id, name, or label 'ticket_service_type' found`
The AJAX request this button click event triggers doesn't have anything to do with the select tag, so reversing the order in the test causes the test to pass. I know that Capybara's default wait time is 2 seconds and so I changed it to 10 with:
Capybara.default_wait_time = 10
This does not cause the test to pass. How can I get these two methods to play nice with one another and work in the order in which a user would operate the web page?
(Had I posted the code from my spec, I bet this would have been solved quickly.)
From The Cucumber Book (emphasis mine):
Luckily, Capybara knows how to deal with this situation in a simple way. If we add an explicit call to find, passing a CSS selector for a DOM element on the page that doesn’t yet exist, Capybara will wait a little (50ms) and try again until the element appears. If it doesn’t appear after a while (two seconds by default, though this is configurable), it will raise an exception, causing the step definition to fail.
So have your AJAX write something to the DOM then find() it in your step definition. It's not ideal. In my case I'm introducing a (hidden) DOM element just to facilitate testing but I haven't found another way.
Be sure to add :js => true to integration tests which depend upon JavaScript.
I am using Cucumber, Webrat, and Pickle in conjunction.
When I write a scenario, I can do something like this:
Given a product exists with title: "Bread"
When I go to the edit page for that product
And I fill in "Title" with "Milk"
And I press "Save changes"
Then I should see "Successfully edited product."
And I should be on that car's page
Notice the for that product. This is something pickle provides which is very convenient for referencing the record for a product I'm checking the existence of. That last line, though, is not working.
Basically I am trying to make sure I am the show page for that record, but since I do not have an ID for it, I don't know how to reference it.
Any help?
Thanks!
To have a reference to the created product or anything else you can use naming that's provided by pickle:
Given product: "bread" exists with title: "Bread"
...
Then I should be on the showing page for the product "bread"
To handle this url you will need to add couple lines into /features/support/paths.rb:
when %r{^the showing page for the (.+)$}
polymorphic_path(model($1))
Also it could be useful to handle edit path for the model like this:
Then I should be on the edit page for the product "bread"
paths.rb:
when %r{^the edit page for the (.+)$}
polymorphic_path(model($1), :action => 'edit')