selecting elements using variables in ruby /watir - ruby

The real issue I am encountering is the delay on population of drop down lists in a web page.
The call i am using in the script is :-
clickOndropDown(":id","dropDownAutocomplete","ABC",#b)
where
1. clickOndropDown is the method invoked
2. id is the element selector (which can be :id,xpath, or :css [which are different selectors in my html code for different dropdown boxes] to handle all cases within the code)
3. ABC is the element to be selected from the drop down list
4. #b is the browser object
the function definition is as :
def clickOndropDown(identifier,selector,targetVal,br)
begin
puts "starting off" # gets printed
br.element("#{identifier}" => "#{selector}").fire_event :click #--- does not work---
# br.element(:id => "#{selector}").fire_event :click #--- works
puts "inside selector pass 1"
br.element(:link, targetVal).fire_event :click
...
...
# rest of the code
It does not throw any exception..just does the push statement before the selection statement.
is there any wild card handler for # (--"#{identifier}" ) so that I can write a single code for handling id,xpath,or css

I am surprised that there is no exception. I would expect a MissingWayOfFindingObjectException.
The problem is that the identifier is a String instead of a Symbol. The line that is not working is equivalent to:
br.element(":id" => "dropDownAutocomplete")
Which is not the same as:
br.element(:id => "dropDownAutocomplete")
Notice the difference between the String, ":id", and the Symbol, :id.
You should change the method call to send a Symbol and change the method to no longer change it to a String:
def clickOndropDown(identifier,selector,targetVal,br)
br.element(identifier => selector).fire_event :click
end
clickOndropDown(:id,"dropDownAutocomplete","ABC",#b)
If you want to have a more versatile method, you would be better off to have the clickOndropDown accept a Hash for the locator. This would allow you to accept multiple locator criteria.
def click_on_dropdown(br, target_val, locator)
br.element(locator).fire_event :click
end
# Call for the original example
click_on_dropdown(#b, "ABC", id: "dropDownAutocomplete")
# Call with multiple selectors
click_on_dropdown(#b, "ABC", id: "dropDownAutocomplete", name: "name_value")

Related

Can you pass a block of code that returns an error to a method?

I often find myself dealing with these kind of scenarios:
require 'nokogiri'
require "open-uri"
url = "https://www.random_website.com/contains_info_I_want_to_parse"
nokodoc = Nokogiri::HTML(open(url))
# Let's say one of the following line breaks the ruby script
# because the element I'm searching doesn't contain an attribute.
a = nokodoc.search('#element-1').attribute('href').text
b = nokodoc.search('#element-2').attribute('href').text.gsub("a", "A")
c = nokodoc.search('#element-3 h1').attribute('style').text.strip
What happens is that I'll be creating about 30 variables all searching for different elements in a page, and I'll be looping that code over multiple pages. However, a few of these pages may have an ever-so-slightly different layout and won't have one of those div. This will break my code (because you can't call .attribute or .gsub on nil for example). But I can never guess which line before-hand.
My go-to solution is usually surround each line with:
begin
line #n
rescue
puts "line #n caused an error"
end
I'd like to be able to do something like:
url = "https://www.random_website.com/contains_info_I_want_to_parse"
nokodoc = Nokogiri::HTML(open(url))
catch_error(a, nokodoc.search('#element-1').attribute('href').text)
catch_error(b, nokodoc.search('#element-2').attribute('href').text.gsub("a", "A"))
catch_error(c, nokodoc.search('#element-3 h1').attribute('style').text.strip)
def catch_error(variable_name, code)
begin
variable_name = code
rescue
puts "Code in #{variable_name} caused an error"
end
variable_name
end
I know that putting & before each new method works:
nokodoc.search('#element-1')&.attribute('href')&.text
But I want to be able to display the error with a 'puts' in my terminal to see when my code gives an error.
Is it possible?
You can't pass your code as a regular argument to a method because it'll be evaluated (and raise an exception) before it gets passed to your catch_error method. You could pass it as a block--something like
a = catch_error('element_1 href text') do
nokodoc.search('#element-1').attribute('href').text
end
def catch_error(error_description)
yield
rescue
puts "#{error_description} caused an error"
end
Note that you can't pass a to the method as variable_name: it hasn't been defined anywhere before calling that method, so you'll get an undefined local variable or method error. Even if you define a earlier, it won't work correctly. If your code works without raising an exception, the method will return the right value but the value won't get stored anywhere outside the method scope. If there is an exception, variable_name will have whatever value a had before the method (nil if you defined it without setting it), so your error message would output something like Code in caused an error. That's why I added an error_description parameter.
You could also try logging the message and backtrace if you didn't want to have to specify an error description every time.
a = catch_error(nokodoc) do |doc|
doc.search('#element-1').attribute('href').text
end
def catch_error(doc)
yield doc
rescue => ex
puts doc.title # Or something else that identifies the document
puts ex.message
puts ex.backtrace.join("\n")
end
I made one additional change here: passing the document in as a parameter so that rescue could easily log something that identifies the document, in case that's important.

Ruby Conditional argument to method

I have some 'generic' methods that extract data based on css selectors that usually are the same in many websites. However I have another method that accept as argument the css selector for a given website.
I need to call the get_title method if title_selector argument is nos passed. How can I do that?
Scrape that accept css selectors as arguments
def scrape(urls, item_selector, title_selector, price_selector, image_selector)
collection = []
urls.each do |url|
doc = Nokogiri::HTML(open(url).read) # Opens URL
#items = doc.css(item_selector)[0..1].map {|item| item['href']} # Sets items
#items.each do |item| # Donwload each link and parse
page = Nokogiri::HTML(open(item).read)
collection << {
:title => page.css(title_selector).text, # I guess I need conditional here
:price => page.css(price_selector).text
}
end
#collection = collection
end
end
Generic title extractor
def get_title(doc)
if doc.at_css("meta[property='og:title']")
title = doc.css("meta[property='og:title']")
else doc.css('title')
title = doc.at_css('title').text
end
end
Use an or operator inside your page.css call. It will call get_title if title_selector is falsey (nil).
:title => page.css(title_selector || get_title(doc)).text,
I'm not sure what doc should actually be in this context, though.
EDIT
Given your comment below, I think you can just refactor get_title to handle all of the logic. Allow get_title to take an optional title_selector parameter and add this line to the top of your method:
return doc.css(title_selector).text if title_selector
Then, my original line becomes:
:title => get_title(page, title_selector)

How to access parameters of method being watched in ActiveSupport::Notification instrument?

I have been playing around with ActiveSupport::Notifications recently and I can't figure out how to get to the parameters of a method run in a block. Let me use an example.
You set up the following instrument.
ActiveSupport::Notifications.instrument('render', extra: :information) do
render text: 'Foo'
end
You can access the information by subscribing to the event.
ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
name # => String, name of the event (such as 'render' from above)
start # => Time, when the instrumented block started execution
finish # => Time, when the instrumented block ended execution
id # => String, unique ID for this notification
payload # => Hash, the payload
end
Looking at the documentation, I can't find anything that would give me the parameters of the render method - {text: 'foo'}
What am I missing? Is this possible? Thanks.
render text: 'Foo' is not a parameter; that's a Rails method that renders content back to the client.
ActiveSupport::Notificaitons.instrument("render", text: "foo")
is how you would "submit" the values to the notifier.
ActiveSupport::Notifications.subscribe("render") do |name, start, finish, id, payload|
text = payload[:text]
end
is how you would reference it in the subscriber.

pageobject - when_visible for all elements

I am using a combination of cucumber and pageobject to test my web application. Sometimes, the script tries to click an element even before the page that contains the element starts loading. (I confirmed this by capturing the screenshots of failing scenarios)
This inconsistency is not wide-spread and it happens repeatedly only for a few elements. Instead of directly accessing those elements, if I do example_element.when_visible.click, the test suite always passes.
As of now, I click a link using link_name (generated by pageobject module on calling link(:name, identifier: {index: 0}, &block)
I would like to not edit the above mentioned snippet, but act as if i called link_name_element.when_visible.click. The reason is, the test suite is pretty large and it would be tedious to change all the occurences and I also believe that the functionality is already present and somehow I don't see it anywhere. Can anybody help me out?!
This seems solution seems quite hacky and may not be considering some edge cases. However, I will share it since there are no other answers yet.
You can add the following monkey patch assuming that you are using watir-webdriver. This would be added after you require page-object.
require 'watir-webdriver'
require 'page-object'
module PageObject
module Platforms
module WatirWebDriver
class PageObject
def find_watir_element(the_call, type, identifier, tag_name=nil)
identifier, frame_identifiers, wait = parse_identifiers(identifier, type, tag_name)
the_call, identifier = move_element_to_css_selector(the_call, identifier)
if wait
element = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{the_call}.when_present"
else
element = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{the_call}"
end
switch_to_default_content(frame_identifiers)
type.new(element, :platform => :watir_webdriver)
end
def process_watir_call(the_call, type, identifier, value=nil, tag_name=nil)
identifier, frame_identifiers, wait = parse_identifiers(identifier, type, tag_name)
the_call, identifier = move_element_to_css_selector(the_call, identifier)
if wait
modified_call = the_call.dup.insert(the_call.rindex('.'), '.when_present')
value = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{modified_call}"
else
value = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{the_call}"
end
switch_to_default_content(frame_identifiers)
value
end
def parse_identifiers(identifier, element, tag_name=nil)
wait = identifier.has_key?(:wait) ? false : true
identifier.delete(:wait)
frame_identifiers = identifier.delete(:frame)
identifier = add_tagname_if_needed identifier, tag_name if tag_name
identifier = element.watir_identifier_for identifier
return identifier, frame_identifiers, wait
end
end
end
end
end
Basically, the intent of this patch is that the Watir when_present method is always called. For example, your page object call will get translated to Watir as browser.link.when_present.click. In theory, it should get called for any method called on a page object element.
Unfortunately, there is a catch. There are some situations where you probably do not want to wait for the element to become present. For example, when doing page.link_element.when_not_visible, you would not want to wait for the element to appear before checking that it does not appear. In these cases, you can force the standard behaviour of not waiting by including :wait => false in the element locator:
page.link_element(:wait => false).when_not_visible

Skip HTML escape in custom label_tag helper in Rail 3

I have this nice class ErrorFormBuilder that allows me to add the error description near the corresponding field in the form view :
class ErrorFormBuilder < ActionView::Helpers::FormBuilder
#Adds error message directly inline to a form label
#Accepts all the options normall passed to form.label as well as:
# :hide_errors - true if you don't want errors displayed on this label
# :additional_text - Will add additional text after the error message or after the label if no errors
def label(method, text = nil, options = {})
#Check to see if text for this label has been supplied and humanize the field name if not.
text = text || method.to_s.humanize
#Get a reference to the model object
object = #template.instance_variable_get("##{#object_name}")
#Make sure we have an object and we're not told to hide errors for this label
unless object.nil? || options[:hide_errors]
#Check if there are any errors for this field in the model
errors = object.errors.on(method.to_sym)
if errors
#Generate the label using the text as well as the error message wrapped in a span with error class
text += " <br/><span class=\"error\">#{errors.is_a?(Array) ? errors.first : errors}</span>"
end
end
#Add any additional text that might be needed on the label
text += " #{options[:additional_text]}" if options[:additional_text]
#Finally hand off to super to deal with the display of the label
super(method, text, options)
end
end
But the HTML :
text += " <br/><span class=\"error\">#{errors.is_a?(Array) ? errors.first : errors}</span>"
is escaped by default in the view...
I tried to add the {:escape => false} option :
super(method, text, options.merge({:escape => false}))
without success
Is there any way to bypass this behavior ?
Thanks
Have you tried making your string html_safe?
irb(main):010:0> a = "A string"
=> "A string"
irb(main):011:0> a.html_safe?
=> false
irb(main):012:0> b = a.html_safe
=> "A string"
irb(main):013:0> b.html_safe?
=> true
See http://www.railsdispatch.com/posts/security and scroll down to "What you need to know" near the bottom:
In general, you can build your Rails app exactly as before. Rails will automatically escape any Strings that it doesn’t create. In almost all cases, this is the right behavior, with no further modifications required.
If Rails is escaping a String that you want to pass through without escaping, simply mark it safe. If you create a String in a helper, you may want to mark parts of it as safe.
I can't test whether this will work in your sub-classed helper, but I'd think so.
Just use <%= raw your_variable_here %>

Resources