Watir::ElementCollection click action in loop - ruby

Going off the example from the documentation http://www.rubydoc.info/gems/watir-webdriver/0.6.11/Watir/ElementCollection#each-instance_method, I am trying to click each element on the page that has the same class.
This is a code snippet of what I've come up with so far:
#b.divs(:class => 'portal-thumbnail-card').each do |div|
#b.div(:class => 'portal-thumbnail-card').click
puts 'foo'
# my puts statement outputs 'foo' 6 times (matches the number of elements with that class)
# right now this only clicks on the FIRST element, having issues with the other part :(
end
Even though this doesn't involve any page reloading, are click actions possible?

The problem is that you are locating the div to click during each iteration of the loop. In English, your code actually says, "for each div element with the class 'portal-thumbnail-card', click the first div on the page with class 'portal-thumbnail-card'."
What you actually want to do is click the div element that is the subject of each iteration:
#b.divs(:class => 'portal-thumbnail-card').each do |div|
div.click
puts 'foo'
end

The divs method returns a Watir::DivCollection, which is a collection of Watir::Div objects. For example:
require 'watir-webdriver'
b = Watir::Browser.new
b.goto('http://example.org')
divs = b.divs
puts divs.class
#=> Watir::DivCollection
divs.each { |d| puts d.class}
#=> Watir::Div
So--within your iterator--you want to refer to the block-local variable (i.e. div.click) instead of the browser's instance variable (i.e. #b.div(:class => 'portal-thumbnail-card').click)

use flash method for see element what you try click
require 'watir-webdriver'
browser = Watir::Browser.new
browser.goto "data:text/html,#{DATA.read}"
browser.divs(:class => 'portal-thumbnail-card').each do |div|
# browser.div(:class => 'portal-thumbnail-card').flash #you variant
div.flash #correct variant
puts 'foo'
end
browser.close
__END__
<html>
<div class='portal-thumbnail-card'>
<button id="button1">Button 1</button>
</div>
<div class='portal-thumbnail-card'>
<button id="button2">Button 2</button>
</div>
<div class='portal-thumbnail-card'>
<button id="button3">Button 3</button>
</div>
<div class='portal-thumbnail-card'>
<button id="button4">Button 4</button>
</div>
<div class='portal-thumbnail-card'>
<button id="button5">Button 5</button>
</div>
</html>

Related

Watir scraping sequential elements : so simple, but no

This is so simple...
I want to scrap some web page like that with watir (gem of ruby:)
<div class="Time">time1</div>
<div class="Locus">locus1</div>
<div class="Locus">locus2</div>
<div class="Time">time2</div>
<div class="Locus">locus3</div>
<div class="Time">time3</div>
<div class="Locus">locus4</div>
<div class="Locus">locus5</div>
<div class="Locus">locus6</div>
<div class="Time">time4</div>
etc..
The result should be an array like that :
time1 locus1
time1 locus2
time2 locus3
time3 locus4
time3 locus5
time3 locus6
time4 xxx
All the divs are at the same level (not imbricated).
No way to find the solution using the watir methods...
Thx for your help
For each Locus element, you can retrieve the preceding Time element via the #preceding_sibling method:
result = browser.divs(class: 'Locus').map do |div|
time = div.preceding_sibling(class: 'Time').text
locus = div.text
"#{time} #{locus}"
end
p result
#=> ["time1 locus1", "time1 locus2", "time2 locus3", "time3 locus4", "time3 locus5", "time3 locus6"]
Note that if the list is long, you may want to retrieve the HTML via Watir but then do the parsing in Nokogiri. This would save a lot of execution time, but at the cost of readability.
doc = Nokogiri::HTML.parse(browser.html) # where `browser` is the usual Watir::Browser
result = doc.css('.Locus').map do |div|
time = div.at('./preceding-sibling::div[#class="Time"]').text
locus = div.text
"#{time} #{locus}"
end
p result
#=> ["time1 locus1", "time1 locus2", "time1 locus3", "time1 locus4", "time1 locus5", "time1 locus6"]

How to define custom locating strategy for select

I looking for a proper way to redefine/extend locating strategy for select tag in Gwt app.
From html snippet you can see that select tag is not visible.
So to select option from list I need to click on button tag, and than select needed li tag from dropdown.
<div class="form-group">
<select class="bootstrap-select form-control" style="display: none; locator='gender">
<div class="btn-group">
<button class="dropdown-toggle" type="button" title="Male">
<div class="dropdown-menu open">
<ul class="dropdown-menu inner selectpicker" role="menu">
<li data-original-index="1"> (contains a>span with option text)
.....more options
</ul>
</div>
</div>
</div>
I see dirty solution: to implement method in BasePage class. This approach nice page_object sugar(options,get value, etc):
def set_nationality(country, nationality='Nationality')
select = button_element(xpath: "//button[#title='#{nationality}']")
select.click
option = span_element(xpath: "//span[.='#{country}']")
option.when_visible
option.click
end
Is there any other more clear way to do so? Using `PageObject::Widgets maybe?
UPD: Here what I expect to get:
def bool_list(name, identifier={:index => 0}, &block)
define_method("#{name}_btn_element") do
platform.send('button_for', identifier.clone + "//button")
end
define_method("#{name}?") do
platform.send('button_for', identifier.clone + "//button").exists?
end
define_method(name) do
return platform.select_list_value_for identifier.clone + '/select' unless block_given?
self.send("#{name}_element").value
end
define_method("#{name}=") do |value|
return platform.select_list_value_set(identifier.clone + '/select', value) unless block_given?
self.send("#{name}_element").select(value)
end
define_method("#{name}_options") do
element = self.send("#{name}_element")
(element && element.options) ? element.options.collect(&:text) : []
end
end
The select list appears to have the most identify attributes, therefore I would use it as the base element of the widget. All of the other elements, ie the button and list items, would need to be located with respect to the select list. In this case, they all share the same div.form-group ancestor.
The widget could be defined as:
class BoolList < PageObject::Elements::SelectList
def select(value)
dropdown_toggle_element.click
option = span_element(xpath: "./..//span[.='#{value}']")
option.when_visible
option.click
end
def dropdown_toggle_element
button_element(xpath: './../div/button')
end
def self.accessor_methods(widget, name)
widget.send('define_method', "#{name}_btn_element") do
self.send("#{name}_element").dropdown_toggle_element
end
widget.send('define_method', "#{name}?") do
self.send("#{name}_btn_element").exists?
end
widget.send('define_method', name) do
self.send("#{name}_element").value
end
widget.send('define_method', "#{name}=") do |value|
self.send("#{name}_element").select(value)
end
widget.send('define_method', "#{name}_options") do
# Since the element is not displayed, we need to check the inner HTML
element = self.send("#{name}_element")
(element && element.options) ? element.options.map { |o| o.element.inner_html } : []
end
end
end
PageObject.register_widget :bool_list, BoolList, :select
Notice that all locators are in relation to the select list. As well, notice that we use the accessor_methods to add the extra methods to the page object.
The page object would then use the bool_list accessor method. Note that the identifier is for locating the select element, which we said would be the base element of the widget.
class MyPage
include PageObject
bool_list(:gender, title: 'Gender')
bool_list(:nationality, title: 'Nationality')
end
The page will now be able to call the following methods:
page.gender_btn_element.click
page.gender_btn_element.exists?
page.gender
page.gender = 'Female'
page.gender_options
page.nationality_btn_element.click
page.nationality_btn_element.exists?
page.nationality
page.nationality = 'Barbados'
page.nationality_options

How to use Nokogiri to split content between successive h2 tags and wrap it under a chapter div

I want to split a document into "chapters". A chapter starts at a h2 and includes all siblings up to but not including the next h2 tag.
I.e. given this
<div id="content">
<h2>First</h2>
<p>one</p>
<h2>Second</h2>
<p>two</p>
<h2>Third</h2>
</div>
I want this
<div id="dad">
<div class="chapter">
<h2>First</h2>
<p>one</p>
</div>
<div class="chapter">
<h2>Second</h2>
<p>two</p>
</div>
<div class="chapter">
<h2>Third</h2>
</div>
</div>
Whilst I've used Nokogiri and xml to do some basic manipulation, I'm banging my heading wondering how to first group the nodes into chapter blocks and then wrap them in place with the chapter div.
Can anyone help?
You should group your nodes by headers (include related subling nodes) and then transform them to output format.
Here is an idea of algorithm to group nodes:
array = [
:header,
:text,
:text,
:header,
:text,
:header,
:text,
:text,
]
groupped_array = array.reduce([]) do |res, item|
res.tap do
res << [] if item == :header
res.last << item
end
end
p groupped_array
Result:
➜ ruby group_nodes.rb
[[:header, :text, :text], [:header, :text], [:header, :text, :text]]
I think you can add nokogiri here without big problems and transform result to your output format.

looping through a collection of divs in Watir

We're using watir for testing and wondered how to select a group of divs that meet a particular criteria? In our case the (simplified) html looks like this:
<div class="month_main>
<div class="month_cell">
some divs
</div>
<div class="month_cell">
some_other_divs
</div>
<div class = "month_cell OverridenDay">
<div id = "month-2013-05-04"/>
</div>
</div>
We would like to loop through all divs with an id starting with 'month' that are contained in month_cell parent divs that also have classes of OverridenDay. Is there an Xpath or regular expression we can use in conjunction with the Watir browser class to do this?
General
You can get a collection of elements in a similar way to getting a single element. You basically need to pluralize the element type method. For example:
#Singular method returns first matching div
browser.div
#Pluralized method returns all matching divs
browser.divs
Collections can be used using the same locators as single elements.
Solution
For your problem, you can do:
#Iterate over divs that have the class 'month_cell OverridenDay'
browser.divs(:class => 'month_cell OverridenDay').each do |overridden_div|
#Within each div with class 'month_cell OverridenDay',
# iterate over any children divs where the id starts with month
overridden_div.divs(:id => /^month/).each do |div|
#Do something with the div that has id starting with month
puts div.id
end
end
#=> "month-2013-05-0"
If you need to create a single collection that includes all of the matching divs, you will need to use a css or xpath selector.
Using a css-selector (note that in watir-webdriver, only the elements method supports css-locators):
divs = browser.elements(:css => 'div.month_cell.OverridenDay div[id^=month]')
divs.each do |e|
puts e.id
end
#=> "month-2013-05-0"
Using xpath:
divs = browser.divs(:xpath => '//div[#class="month_cell OverridenDay"]//div[starts-with(#id, "month")]')
divs.each do |e|
puts e.id
end
#=> "month-2013-05-0"

Remove all nodes after a specified node [duplicate]

This question already has answers here:
Nokogiri: Select content between element A and B
(3 answers)
Closed 2 years ago.
I'm grabbing a div of text from a url and would like to remove everything underneath a paragraph which has a backtotop class. I'd seen a traverse snippet of code here on stackoverflow which looks promising, but I can't figure out how to get it incorporated so #el only contains everything up to the first p.backtotop in the div.
my code:
#doc = Nokogiri::HTML(open(url))
#el = #doc.css("div")[0]
end
traverse snippet:
doc = Nokogiri::HTML(code)
stop_node = doc.css("p.backtotop")
doc.traverse do |node|
break if node == stop_node
# else, do whatever, e.g. `puts node.name`
end
Find the div you want.
Find the 'stop' item you want, and then find all the following siblings.
Remove them.
For example:
<body>
<div id="a">
<h2>My Section</h2>
<p class="backtotop">Back to Top</p>
<p>More Content</p>
<p>Even More Content</p>
</div>
</body>
require 'nokogiri'
doc = Nokogiri::HTML(my_html)
div = doc.at('#a')
div.at('.backtotop').xpath('following-sibling::*').remove
puts div
#=> <div id="a">
#=> <h2>My Section</h2>
#=> <p class="backtotop">Back to Top</p>
#=>
#=>
#=> </div>
Here's a more complicated example, where the backtotop item may not be at the root of the div:
<body>
<div id="b">
<h2>Another Section</h2>
<section>
<p class="backtotop">Back to Top</p>
<p>More Content</p>
</section>
<p>Even More Content</p>
</div>
</body>
require 'nokogiri'
doc = Nokogiri::HTML(my_html)
div = doc.at('#b')
n = div.at('.backtotop')
until n==div
n.xpath('following-sibling::*').remove
n = n.parent
end
puts div
#=> <div id="b">
#=> <h2>Another Section</h2>
#=> <section><p class="backtotop">Back to Top</p>
#=>
#=> </section>
#=> </div>
If your HTML is more complicated than the above then please provide an actual sample along with the result you want. This is good advice for any future question you ask.

Resources