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.
Related
In watir we have the ability to find elements by text such as browser.div(text:'some text').
In my project there are a lot of common elements like a menu dropdown that contains divs with text and class "items". In this case I could do browser.div(text:'some text', class: 'items') if I wanted a more generic way to access those elements.
I have this same thing with other html elements like links, spans, buttons etc. There are instances where the same text is in multiple places but just different element types. Is there anyway I could do a method where I pass the element type and still find by text?
Something like
def get_element_by_text(tag, text)
browser.tag(text:'some text')
end
Yes, you can, write the following method
browser.element(tag_name: tag,text:'some text')
In my above code tag is the variable which would receive any tag name via your formal argument of the method.
In addition to Rajagopalan's answer, if you also want to differentiate between finding an array of elements or a single element, you can always user Ruby's send() function:
https://apidock.com/ruby/Object/send
tag = 'input'
browser.send(tag, text: 'some text')
# browser.input(text: 'some text')
tag = 'inputs'
browser.send(tag, text: 'some text')
# browser.inputs(text: 'some text')
So in your case:
def get_element_by_text(tag, text)
browser.send(tag, text: text)
end
Or make it even more dynamic
def get_element(tag, *args)
browser.send(tag, args)
end
Note: Personally I'm against these kind of one-funtion-to-rule-them-all kind of behaviour. Standard Watir is in a good balance of code. When doing this, before you know it you're re-writing watir.
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 ;-)
I'm using selenium-webdriver to get information from web sites.
Currently I get children elements of current element like this.
tr_element.find_elements(:xpath, "./td").each{|td| p td.text }
This tr_element is a Selenium::WebDriver::Element.
Is there a method that enable like tr_element.children.each{|td| p td.text }?
You could make one like so:
class Selenium::WebDriver::Element
def children
self.find_elements(:xpath, "./*")
end
end
This code extends Element class with a method that uses Element.find_elements (which searches for elements using the given element as root node), and then searching for all immediate children of the root node using xpath (see here for an explanation of xpath syntax).
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
This is a follow-up to the Counting the number of HTML elements having same attribute in Watir question.
So, suppose I have a HTML element as follows
<input type="password" class="foo" />
<span class="foo"></span>
Text
So, I can obtain a collection of all the elements having the same class name by using
elements = browser.elements(:class,"foo")
Since, its an collection, I can use the each method to iterate over the collection. While iterating over the collection, I want to determine what type of tag is represented? (Something similar to the nodeName or the tagName method in Javascript). Is there a way we could do this in Watir?
Sample code would be :
elements = browser.elements(:class,"foo")
elements.each { |element|
puts element.<Watir_method_similar_to_nodeName_of_JavaScript>
}
elements.each {|element| puts element.tag_name}
Output:
input
span
a