Using multiple data sets in populate_page_with in page-object gem - ruby

I have a form that I populate with populate_page_with data_for using the page-object gem. It is defined this way:
def add_fruit(data = {})
populate_page_with data_for(:new_fruit, data)
add_fruit_submit
end
Then I call the method in my step this way:
on(AddFruitPage).add_fruit
My yml file looks like this:
new_fruit:
color: red
size: large
type: apple
price: 0.75
...
another_fruit
color: orange
size: medium
type: orange
price: 0.99
...
I know that I can overwrite each of these fields by doing something like this in my step:
When(^/I add a banana$/) do
on(AddFruitPage).add_fruit('color' => 'yellow', 'size' => 'small', 'type' => 'banana')
end
Since the data for another fruit is already in the yml file, can I use a parameter to tell the method which data to load rather than having to specify each value when I use the method? Something like:
def add_fruit(data = {})
if(data['type'] == 'another')
populate_page_with data_for(:new_fruit, data)
else
populate_page_with data_for(:another_fruit, data)
end
end
And call it this way?
on(AddFruitPage).add_fruit('type' => 'another')
Type is an optional parameter that would only be used to load another set of data. Color, size, type, and price all map to text fields on the page that are defined in the class. Is it possible to do something like this?

If you are using Ruby 2, you could used named parameters - creating a named parameter for the fruit type and the rest as being part of the data array.
Instead of using a 'type', which already exists in the data, I would probably use a parameter that specifies the first parameter of the data_for. The method definition would simply be:
def add_fruit(fruit_type: :new_fruit, **data)
populate_page_with data_for(fruit_type, data)
add_fruit_submit
end
Which could be called in variety of ways:
add_fruit() # Specify nothing
add_fruit(:color => 'red') # Just specify the data
add_fruit(:fruit_type => :another_fruit) # Just specify the fruit type
add_fruit(:fruit_type => :another_fruit, :color => 'red') # Specify fruit type and data
If you are using a version prior to Ruby 2, you can do the same with:
def add_fruit(data = {})
fruit_type = data.delete(:fruit_type) || :new_fruit
populate_page_with data_for(fruit_type, data)
add_fruit_submit
end

Related

finding specific element which exists in multiple places but has same id,class

How to find an element if it exists in multiple places in a page but has same id under same class?
For example:
There are two text fields with the same id and I would like to choose the 2nd one.
It works when I just write the watir/ruby(without using page object)
#b.text_fields(:id => 'color').last.set "red"
But I am unsuccessful so far to make it work using page object.
Thanks in advance
As mentioned in the comments, the best solution is to update the fields to have unique ids.
However, assuming that is not possible, you can solve the problem using an :index locator. The following page object finds the 2nd color field, which is equivalent to Watir's #b.text_field(:id => 'color', :index => 1).set:
class MyPage
include PageObject
text_field(:color_1, id: 'color', index: 0)
text_field(:color_2, id: 'color', index: 1)
end
Which would be called like:
page = MyPage.new(browser)
page.color_1 = 'red'
page.color_2 = 'blue'
If you are actually trying to the last field, ie replicate #b.text_fields(:id => 'color').last.set, then the :index would be "-1":
text_field(:color_2, id: 'color', index: -1)
Note that similar can be done when locating the fields dynamically within a method (as opposed to defined by an accessor):
class MyPage
include PageObject
def set_colors(color_1, color_2)
text_field_element(id: 'color', index: 0).value = color_1
text_field_element(id: 'color', index: 1).value = color_2
end
end
#b.text_fields(:id=>'color',:index=>1).set "red"
This can solve your problem.

Which is the correct way to dynamically use variating static data

Following basic principles what would be the preferred way to structure a program and its data that can variate but is used to accomplish the same "standard" task? It should easily be extendable to include more providers.
For example:
require 'mechanize'
require 'open-uri'
class AccountFetcher
##providers = {}
##providers['provider_a'] = [
'http://url_for_login_form.com/', 'name_of_form',
# the number of fields can vary between different providers
[ ['name_of_field', 'value_for_it'], ['another_field', 'another_value'] ],
['name for username field', 'name for password field']
]
##providers['provider_b'] = [
'http://another_url.com/', 'another_form_name',
[
# array of more fields that are specific to this form
],
['name for username field', 'name for password field']
]
# looks more nice using AccountFetcher.accounts opposed to ##accounts
def self.accounts
##providers
end
# example of a method that uses information that can vary
def self.fetch_form(agent, account_type)
# the first element in the array will always be the url
page = agent.get(AccountFetcher.accounts[account_type][0])
# the second element in the array will always be the name of the form
form = page.form(AccountFetcher.accounts[account_type][1])
end
def self.initialize_form(agent, account_type)
form = AccountFetcher.fetch_form(agent, account_type)
# will cycle through all fields that require 'pre-filling'
AccountFetcher.accounts[account_type][2].each do |f|
# f[0] will always be the name of the field and f[1] will
# always be the value for the field; however, the amount of
# fields may vary
form.field_with(:name => f[0]).value = f[1]
end
form
end
So the general idea is use a class variable that consists of a hash and has arrays containing the appropriate data elements. I would need to do this for every class that has similar functions.
Another idea I had would be to replace the class variables and instead turn each provider into a class database that can be accessed by the other classes. So provider_a would become:
class ProviderA
# all the possible information needed
# methods will have similar names across all the different providers
end
And the appropriate provider can be chosen by
class Providers
##p_providers = {}
##p_providers['provider a'] = ProviderA.new
##p_providers['provider b'] = ProviderB.new
##p_providers['provider c'] = ProviderC.new
##p_providers['provider d'] = ProviderD.new
def self.return_provider(name)
##p_providers[name]
end
end
Is the former or latter solution more appropriate? Or is there a more 'ruby-ish' solution?
I would store this configuration values in an external YAML file. Your configuration file could looke like this:
# config/providers.yaml
provider_a:
url: 'http://url_for_login_form.com/'
name: 'name_of_form'
fields:
- name: 'name_of_field'
value: 'value_for_it'
- name: 'another_field'
value: 'another_value'
provider_b:
url: 'http://...'
...
You could load that file with YAML.file_file that returns nested hash in this example.
require 'yaml'
def AccountFetcher
def self.accounts
#accounts ||= YAML.parse_file("config/providers.yaml")
end
#...
end
You may want to consider using a Gem like has_configuration that makes handling the data structure a bit easier.

watir webdriver using a variable

I am having trouble using a variable in browser reference. I am trying to pass the field type such as ul, ol etc. The code works if I type ol, but I would like to use the pass variable labeled 'field'. I recieve an "undefined method for field" error.
I have tried also using #{field}, but that does not work either. The error is that field is not defined.
def CheckNewPageNoUl(browser, field, text1, output)
browser.a(:id => 'submitbtn').hover
browser.a(:id => 'submitbtn').click
output.puts("text1 #{text1}")
browser.body(:id => 'page').wait_until_present
if browser.table.field.exists?
output.puts(" #{text1} was found in CheckNewPageNoUl")
end
end
field = "ol"
text1 = "<ol>"
CheckText.CheckNewPageNoUl(b, field, text1, outputt)
To translate a string into a method call, use Object#send, which can take two parameters:
The method name (as string or symbol)
Arguments for the method (optional)
Some examples:
field = 'ol'
browser.send(field).exists?
#=> Translates to browser.ol.exists?
specifiers = {:text => 'text', :class => 'class'}
browser.send(field, specifiers).exists?
#=> Translates to browser.ol(:text => 'text', :class => 'class').exists?
For your code, you would want to have:
if browser.table.send(field).exists?
output.puts(" #{text1} was found in CheckNewPageNoUl")
end

Trying to populate gmaps4rails with multiple json strings in one page

I hope I am asking this right, so please let me know if I'm way off.
The problem is trying to build a homepage that draws from multiple controllers, to display the nearest locations from multiple controllers, ie. food, businesses, ect.
Right now the individual listings pages have maps drawn from their respective
#json = Controller.all.to_gmaps4rails
How would I do something like :
#json = Controller1 Controller2 .all.to_gmaps4rails
I hope this isnt a noob question and I'm just having a bad day. Thanks guys!
edit 12.5.2011 #seanhill - this is one of the models, the other sections are very close to this format. First off, I wasn't even sure if my homepage requires it's own model, as it doesn't interact with the db at all, more pulling data from controllers that do the work. Thanks for the response Sean!
class Dining < ActiveRecord::Base
validates_uniqueness_of :name, :message => "already exists"
attr_accessible :name, :address, :cuisine, :latitude, :longitude, :about, :facebook, :twitter, :phone, :website
geocoded_by :address
after_validation :geocode, :if => :address_changed?
acts_as_gmappable :process_geocoding => false
def gmaps4rails_address
"#{self.address}"
end
def gmaps4rails_infowindow
"<h3>#{self.name}</h3><br /><h5>#{self.cuisine}</h5>"
end
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
else
scoped
end
end
end
Try this
holder = Controller1.all
holder << Controller2.all
#json = holder.flatten.map{|h| {lng: h.longitude, lat: h.latitude, class: h.class.to_s}}.to_json
Make sure to change longitude and latitude based on your column names and use js to manipulate the markers based upon class.
As the #Sean Hill said you shouldn't be calling .all on controllers but I think you have a slight misunderstanding of how things are working. Assuming you have a Model called Dining and another called Shop, when you call Dining.all or Shop.all inside class DiningsController < ApplicationController, you are calling .all on either the Dining Model or the Shop Model not on the DiningsController.
The information you display through a controller is only limited by the methods you call in it although it is best practice ensure the main focus of the information displayed is related to the respective controller.
So what you are really trying to do is get the records from multiple models and group them together to display them in a single map.
With that said the answer should read something like this
holder = Dining.all # Takes all Dining records returned as an array and sets them to holder variable
holder << Shop.all # Pushes the Shop records array into the holder with the dining records
holder.flatten!# Next we flatten the array so we only have a single array.
# Then we use the map method to run the given code one time for each instance
# in the holder array to extract the info we need. The results for every instance
# in holder are returned in an array which we then convert to_json.
#json = holder.map{|h| {lng: h.longitude, lat: h.latitude, class: h.class.to_s}}.to_json
#json1 = something.to_gmaps4rails
#json2 = something.to_gmaps4rails
#json = (JSON.parse(#json1) + JSON.parse(#json2)).to_json
I populated the map with my initial data of festivals, and then added the rides to it with javascript with this code,
<% content_for :scripts do %>
<script type="text/javascript">
Gmaps.map.callback = function() {
$.getJSON('/rides_gmap', function(data){
Gmaps.map.addMarkers(data);
});
}
</script>
<%end%>
In the rides controller I had this
def rides_gmap
#rides = Ride.all
#json = #rides.to_gmaps4rails do |ride, marker|
marker.infowindow render_to_string(:partial => "/rides/infowindow", :locals => { :ride => ride})
marker.picture({
'picture' => view_context.image_path("orange-dot.png"),
'width' => 20,
'height' => 20
})
marker.title "#{ride.address}"
marker.json({:ride_id => ride.id, :ride_festivaltype => ride.festival.festivaltype
end
respond_with #json
end
I hope this helps.

JSON object for just an integer

Silly question, but I'm unable to figure out..
I tried the following in Ruby:
irb(main):020:0> JSON.load('[1,2,3]').class
=> Array
This seems to work. While neither
JSON.load('1').class
nor this
JSON.load('{1}').class
works. Any ideas?
I'd ask the guys who programmed the library. AFAIK, 1 isn't a valid JSON object, and neither is {1} but 1 is what the library itself generates for the fixnum 1.
You'd need to do: {"number" : 1} to be valid json. The bug is that
a != JSON.parse(JSON.generate(a))
I'd say it's a bug:
>> JSON.parse(1.to_json)
JSON::ParserError: A JSON text must at least contain two octets!
from /opt/local/lib/ruby/gems/1.8/gems/json-1.1.3/lib/json/common.rb:122:in `initialize'
from /opt/local/lib/ruby/gems/1.8/gems/json-1.1.3/lib/json/common.rb:122:in `new'
from /opt/local/lib/ruby/gems/1.8/gems/json-1.1.3/lib/json/common.rb:122:in `parse'
from (irb):7
I assume you're using this: (http://json.rubyforge.org/)
JSON only supporting objects is simply not true -- json.org also does not suggest this imo. it was derived from javascript and thus especially strings and numbers are also valid JSON:
var json_string = "1";
var p = eval('(' + json_string + ')');
console.log(p);
// => 1
typeof p
// => "number"
ActiveSupport::JSON properly understands raw value JSON:
require 'active_support/json'
p = ActiveSupport::JSON.decode '1'
# => 1
p.class
# => Fixnum
and so does MultiJson:
require 'multi_json'
p = MultiJson.load '1'
# => 1
p.class
# => Fixnum
so, as a2800276 mentioned, this must be a bug.
but as of this writing, ruby 2's JSON has quirks_mode enabled by default when using the load method.
require 'json'
p = JSON.load '1'
# => 1
p.class
# => Fixnum
The first example is valid. The second two are not valid JSON data. go to json.org for details.
As said only arrays and objects are allowed at the top level of JSON.
Maybe wrapping your values in an array will solve your problem.
def set( value ); #data = [value].to_json; end
def get; JSON.parse( #data )[0]; end
From the very basics of what JSON is:
Data types in JSON can be:
Number
String
Json Object ... (and some more)
Reference to see complete list of Json data types
Now any Json data has to be encapsulated in 'Json Object' at the top level.
To understand why is this so, you can see that without a Json Object at the top level, everything would be loose and you could only have only one of the data type in the whole of Json. i.e. Either a number, a string, a array, a null value etc... but only one.
'Json Object' type has a fixed format of 'key' : 'value' pair.
You cannot store just the value. Thus you cannot have something like {1}.
You need to put in the correct format, i.e. 'key' : 'value' pair.

Resources