Iterating over all elements of a page-object - ruby

I have a search filter. I need to be able to check that all fields on the page are there and contain values (if they are select boxes).
I've used the MethodFinder gem to successfully do this, but I was wondering if there is any way with just the PageObject gem.
require 'methodfinder'
class BasicSearchFilter
include PageObject
text_field(:facility_name, :id => "facility-name")
text_field(:queue, :id => "queue")
select_list(:from, :id => "from")
button(:continue, :id => "continue")
def get_search_filter_elements
methods = MethodFinder.find_in_class_or_module('BasicSearchFilter', '.*_element$')
elements = []
methods.each do |method|
elements << send(method)
end
end
end
I've successfully used the above, but now I'm unable to use the page object methods which I would like to do. I would like to be able to somehow hand a list of valid "elements" which is just the PageObject version of the elements.
Edit: So it turns out that something extremely fishy is going on.
I have a RSpec test grabbing the fields from the class above. It looks like this:
it "the basic filter dropdowns should not contain duplicate values"
on_page(BasicSearchFilter).get_search_filter_elements.each do |element|
if element.tag_name == "select"
p element
puts "a select tag #{element}"
end
end
end
Now according to documentation the your_element_element command should return the watir element. Which is happening once. The second puts is somehow changing back to a PageObject object. I now have literally no clue what is happening. Here is some of the output from the above.
#<Watir::Select:0x4c6bdfa4 located=true selector={:id=>"facility-name", :tag_name=>"select"}>
a select tag #<PageObject::Elements::SelectList:0x3101538>

Getting All Elements
While the page-object-gem does not have a method to get all elements, you could do it using plain Ruby. The following would give you the same results as your current method (assuming MethodFinder works as expected).
def get_search_filter_elements
element_methods = self.class.instance_methods(false).find_all{ |m| m =~ /_element$/ }
elements = element_methods.collect{ |m| self.send(m) }
end
Iterating Over Elements
Iterating over the elements returned by get_search_filter_elements is done as you did. However, there are a couple of things regarding your observations.
The your_element_element method returns the PageObject element (not the Watir element). This can be seen by outputting the class of the elements returned:
page.get_search_filter_elements.each{ |e| puts e.class }
#=> PageObject::Elements::TextField
#=> PageObject::Elements::TextField
#=> PageObject::Elements::SelectList
#=> PageObject::Elements::Button
The output of having the Watir element and then the PageObject element is not because the element has changed types. It is always the PageObject element. The output changes because you do p vs puts. When you p an object the .inspect method is outputted, where as when you puts an object the .to_s method is outputted. Given the way that the PageObject elements are written, .to_s gives information about the PageObject element, while the .inspect gives information about the native element (ie Watir or Selenium).
page.queue_element.to_s
#=> <PageObject::Elements::TextField:0x3851ca0>
page.queue_element.inspect
#=> #<Watir::TextField:0x1d58cb4 located=false selector={:id=>\"queue\", :tag_name=>\"input or textarea\", :type=>\"(any text type)\"}>
Converting to Native Element
If you actually want to iterate over the Watir elements (ie you want to call a method not supported by the PageObject), you use the element method. For example, the following iterates over the elements as Watir elements:
page.get_search_filter_elements.each{ |e| puts e.element.class }
#=> Watir::TextField
#=> Watir::TextField
#=> Watir::Select
#=> Watir::Button

Related

Finding a list of elements that end with a string by using Selenium with Ruby

I have a method that checks whether a panel is displayed or not.
def verifyNav(section)
wait = Selenium::WebDriver::Wait.new(:timeout => 10)
wait.until { #driver.find_element(:id => section + '-panel').displayed? == true }
end
Now I want to add some code that says that any other elements that have an id that ends in '-panel' should not be displayed.
I've done some searching and I found that I can use the end_with method and there seems to be a find_elements method that returns a list of matching elements.
I've found out that
a = 'radio-panel'
a.end_with?('-panel')
returns true.. but if I try to call
#driver.find_elements(:id => end_with?('-panel'))
I get an error saying that end_with is an undefined method.
Any ideas on how I can do this?
This would help you
p #driver.find_elements(xpath: "(.//*)[contains(#id, '-panel')]").select{|element|element.attribute("id").end_with?"-panel"}
If you want to iterate and wanted to do perform some action on each element, then you can do
#driver.find_elements(xpath: "(.//*)[contains(#id, '-panel')]").select{|element|element.attribute("id").end_with?"-panel"}.each do |i|
puts i.displayed? # Or whatever operation you want to perform
end
If you want to check whether all the element with id which consist of -panel is displayed
p #driver.find_elements(xpath: "(.//*)[contains(#id, '-panel')]")
.select{|element|element.attribute("id").end_with?"-panel"}
.map{|element| element.displayed?}
.all?

How to find a node given the exact HTML tag as a string (using Nokogiri)?

Question
I need to search a given web page for a particular node when given the exact HTML as a string. For instance, if given:
url = "https://www.wikipedia.org/"
node_to_find = "<title>Wikipedia</title>"
I want to "select" the node on the page (and eventually return its children and sibling nodes). I'm having trouble with the Nokogiri docs, and how to exactly go about this. It seems as though, most of the time, people want to use Xpath syntax or the #css method to find nodes that satisfy a set of conditions. I want to use the HTML syntax and just find the exact match within a webpage.
Possible start of a solution?
If I create two Nokogiri::HTML::DocumentFragment objects, they look similar but do not match due to the memory id being different. I think this might be a precursor to solving it?
irb(main):018:0> n = Nokogiri::HTML::DocumentFragment.parse(<title>Wikipedia</title>").child
=> #<Nokogiri::XML::Element:0x47e7e4 name="title" children=[ <Nokogiri::XML::Text:0x47e08c "Wikipedia">]>
irb(main):019:0> n.class
=> Nokogiri::XML::Element
Then I create a second one using the exact same arguments. Compare them - it returns false:
irb(main):020:0> x = Nokogiri::HTML::DocumentFragment.parse("<title>Wikipedia</title>").child
=> #<Nokogiri::XML::Element:0x472958 name="title" children=[#<Nokogiri::XML::Text:0x4724a8 "Wikipedia">]>
irb(main):021:0> n == x
=> false
So I'm thinking that if I can somehow create a method that can find matches like this, then I can perform operations of that node. In particular - I want to find the descendents (children and next sibling).
EDIT: I should mention that I have a method in my code that creates a Nokogiri::HTML::Document object from a given URL. So - that will be available to compare with.
class Page
attr_accessor :url, :node, :doc, :root
def initialize(params = {})
#url = params.fetch(:url, "").to_s
#node = params.fetch(:node, "").to_s
#doc = parse_html(#url)
end
def parse_html(url)
Nokogiri::HTML(open(url).read)
end
end
As suggested by commenter #August, you could use Node#traverse to see if the string representation of any node matches the string form of your target node.
def find_node(html_document, html_fragment)
matching_node = nil
html_document.traverse do |node|
matching_node = node if node.to_s == html_fragment.to_s
end
matching_node
end
Of course, this approach is fraught with problems that boil down to the canonical representation of the data (do you care about attribute ordering? specific syntax items like quotation marks? whitespace?).
[Edit] Here's a prototype of converting an arbitrary HTML element to an XPath expression. It needs some work but the basic idea (match any element with the node name, specific attributes, and possibly text child) should be a good starting place.
def html_to_xpath(html_string)
node = Nokogiri::HTML::fragment(html_string).children.first
has_more_than_one_child = (node.children.size > 1)
has_non_text_child = node.children.any? { |x| x.type != Nokogiri::XML::Node::TEXT_NODE }
if has_more_than_one_child || has_non_text_child
raise ArgumentError.new('element may only have a single text child')
end
xpath = "//#{node.name}"
node.attributes.each do |_, attr|
xpath += "[#{attr.name}='#{attr.value}']" # TODO: escaping.
end
xpath += "[text()='#{node.children.first.to_s}']" unless node.children.empty?
xpath
end
html_to_xpath('<title>Wikipedia</title>') # => "//title[text()='Wikipedia']"
html_to_xpath('<div id="foo">Foo</div>') # => "//div[id='foo'][text()='Foo']"
html_to_xpath('<div><br/></div>') # => ArgumentError: element may only have a single text child
It seems possible that you could build an XPath from any HTML fragment (e.g. not restricted to those with only a single text child, per my prototype above) but I'll leave that as an exercise for the reader ;-)

Return elements that match as an array

I am new to the Watir world, having used webdriver and geb in a previous company. I want to know if Watir offers any method that is analogous to the get_elements method from webdriver. See below for an example.
Imagine the following html exists within a larger page
<div class="someClass">someText</div>
<div class="someClass">someMoreText</div>
<div class="someClass">evenMoreText</div>
I want make some assertion against each of the divs by locating all elements of the given class and iterating through them. Using webdriver, I could do it like this:
elements = driver.get_elements(:css, ".someClass")
elements.each do |element|
//some assert on element
end
Does Watir provide an equivalent construct? I can't find anything useful in the Watir documentation.
You can do:
elements = driver.elements(:css => '.someClass')
Or if you know they are all divs, you should do:
elements = driver.divs(:css => '.someClass')
Both of these methods would return a collection of elements that match your criteria. In the first case it would match any tag type, where as the second case the results would be limited to divs.
Then, with either of the above, you can iterate the same way:
elements.each do |element|
//some assert on element
end
Instead of using :css locator i'd recommend you to use :class locator instead, since it is usually faster and makes your tests more readable:
elements = driver.divs(:class => 'someClass')
Also, don't forget :id, :name, :text and others.

How does one populate an array in Ruby?

Here is the code I'm working with:
class Trader
def initialize(ticker ="GLD")
#ticker = ticker
end
def yahoo_data(days=12)
require 'yahoofinance'
YahooFinance::get_historical_quotes_days( #ticker, days ) do |row|
puts "#{row.join(',')}" # this is where a solution is required
end
end
end
The yahoo_data method gets data from Yahoo Finance and puts the price history on the console. But instead of a simple puts that evaporates into the ether, how would you use the preceding code to populate an array that can be later manipulated as object.
Something along the lines of :
do |row| populate_an_array_method(row.join(',') end
If you don't give a block to get_historical_quotes_days, you'll get an array back. You can then use map on that to get an array of the results of join.
In general since ruby 1.8.7 most iterator methods will return an enumerable when they're called without a block. So if foo.bar {|x| puts x} would print the values 1,2,3 then enum = foo.bar will return an enumerable containing the values 1,2,3. And if you do arr = foo.bar.to_a, you'll get the array [1,2,3].
If have an iterator method, which does not do this (from some library perhaps, which does not adhere to this convention), you can use foo.enum_for(:bar) to get an enumerable which contains all the values yielded by bar.
So hypothetically, if get_historical_quotes_days did not already return an array, you could use YahooFinance.enum_for(:get_historical_quotes_days).map {|row| row.join(",") } to get what you want.

Fastest/One-liner way to remove duplicates (by key) in Ruby Array?

What's the fastest/one-liner way to remove duplicates in an array of objects, based on a specific key:value, or a result returned from a method?
For instance, I have 20 XML Element nodes that are all the same name, but they have different "text" values, some of which are duplicates. I would like to remove the duplicates by saying "if element.text == previous_element.text, remove it". How do I do that in Ruby in the shortest amount of code?
I've seen how to do it for simple string/integer values, but not for objects.
Here's the standard hashy way. Note the use of ||= operator, which is a more convenient (a ||= b) way to write a = b unless a.
array.inject({}) do |hash,item|
hash[item.text]||=item
hash
end.values.inspect
You can do it in a single line either.
The script needs O(n) equality checks of text strings. That's what's covered under O(n) when you see a hash.
This does it all:
Hash[*a.map{|x| [x.text, x]}].values
short? yep.
(asterisk is optional; seems to be required for 1.8.6).
For example:
a = [Thing.new('a'), Thing.new('b'), Thing.new('c'), Thing.new('c')]
=> [#<Thing a>, #<Thing b>, #<Thing c>, #<Thing c>]
Hash[a.map{|x| [x.text, x]}].values
=> [#<Thing a>, #<Thing b>, #<Thing c>]
Boring part: here's the little test class I used:
class Thing
attr_reader :text
def initialize(text)
#text = text
end
def inspect
"#<Thing #{text}>"
end
end
Use Array#uniq with a block. In your case:
array.uniq(&:text) # => array with duplicated `text` removed
This was introduced in Ruby 1.9.2, so if using an earlier version, you can use backports with require 'backports/1.9.2/array/uniq'

Resources