Use `send` to check the existence of an element - ruby

This code is supposed to take a string as input to check if an element of page_object is present. The script has to raise an exception in case it discovers the element, and do nothing if it doesn't.
Example Page Object:
span(:partner_flag, class: 'content-partner-flag')
The script:
def check_element_not_exist(page_object)
page_object = page_object.downcase.gsub(' ', '_')
option = send("#{page_object}")
if option.exists?
raise "#{page_object} was not found!"
end
end
In this case, I use the string partner_flag to feed the function and check the element. Watir fails in the line:
option = send("#{page_object}")
because it needs to find that element in the webpage in order to define option. Is there an alternate way of defining option, or a different way of making this non-existence check with the send functionality?

The accessor methods create a method for checking if an element exists.
For example, when you include:
span(:partner_flag, class: 'content-partner-flag')
Then the method:
partner_flag?
Is created that returns true if the element exists and false if the element does not.
You could call this method in the check_element_not_exist method:
def check_element_not_exist(page_object)
page_object = page_object.downcase.gsub(' ', '_')
exists = send("#{page_object}?")
if exists
raise "#{page_object} was found!"
end
end

Related

How do I check that the value of a param is a number in a certain range

I am trying to write a test suite for a method that sends a POST request with a parameter 'target' that has to be between 0 and 10
My Ruby class:
class ClassName
before_action :must_have_valid_target
def create
target = params[:target]
. . .
end
def must_have_valid_target
return if params.key?(:target)
error_response(422, 'error message')
end
end
My Rspec
it 'cannot create request with negative target' do
post(:create, {target: -1})
assert_response(422) # actual result is: Expected 422, Actual 200
end
I tried:
def must_have_valid_target
valid = params[:target].between?(0,10)
end
but this does not work. How do I check that the symbol has a value between the range so I can give the correct response afterwards?
This is not homework, I am trying to add additional tests to the codebase at my workplace but I am still very new to RSpec and Ruby.
params[:target] is a string, cast to integer prior to the comparison,
def must_have_valid_target
params[:target].present? && params[:target].to_i.between?(0,10)
end

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.

Multiple Parameters

I have an object that allows users to connect to HDFS. One method allows them to download data, and the other upload data. The initialize method looks something like this:
def initialize(file=nil,set=nil,action)
end
How can I change the way arguments are passed to be more efficient? The action param is required every time, but file and file_set are only required depending on the action. I.e., if they want to upload data they need to pass set, if they want to download data, they just pass file.
Since Ruby 2.0 you can use keyword parameters:
def initialize(action, file: nil,set: nil)
unless file.nil?
# do stuff with file
end
end
So you can call it:
MyClass.new(some_action)
MyClass.new(some_action, set: some_set)
MyClass.new(some_action, file: some_file)
MyClass.new(some_action, set: some_set, file: some_file)
...
First of all you should pass requred parameter first e.g.:
def initialize(action, file = nil, set = nil)
end
Then you may want to use hash to pass optional params:
def initialize(action, options = {})
end
The passing hash is a common way when you need to pass more than one optional parameter.
When you need to pass file, set or both you may call initialize method as follow (assume that this method is defened in the class MyModel):
MyModel.new(action, {file: ''})
MyModel.new(action, {set: ''})
MyModel.new(action, {file: '', set: ''})
Or when you don't want to pass any optional params, simply call:
MyModel.new(action)
In this case you will have empty options hash passed in you initialize method.
Quite common is to use positional parameters when they are mandatory, and an options hash
for the rest. You just need to check action to verify given params are then present:
def initialize(action, opts={})
if action == 'foo'
raise ArgumentError, "requires either :file or :set" unless
([:file, :set] & opts.keys).size == 1
end
...
end

Test nested File.open

I need to test a file open operation. I am able to test the first operation but not the second.
File.open("#{TemplateFile.fixture_path}/#{#template_file}") do |input_file|
template = ERB.new(input_file.read)
File.open("#{#project_name}/#{#destination_file}", 'w') do |output_file|
output_file.puts template.result binding
end
end
end
I am using this code:
module Pod
describe TemplateFile do
it "opens the template" do
dict = {"README.md.erb" => "README.md"}
File.expects(:open).with("#{TemplateFile.fixture_path}/README.md.erb")
File.expects(:open).with("Sample/README.md.erb", 'w')
TemplateFile.new(dict, "Sample")
end
end
end
But I am getting an error:
unsatisfied expectations:
- expected exactly once, not yet invoked: File.open('/README.md.erb', 'w')
satisfied expectations:
- expected exactly once, invoked once: File.open('/lib/pod/command/../../../fixtures/README.md.erb')
It seems that Mocha is not geeting the second File.open.
The reason is because expects verifies the call would happen but doesn't actually let it go through. So what's in the block doesn't get run.
However, beyond just telling you why it's not working, I also wanted to point out what you are doing is probably not what you want to do.
What you likely want to do do is:
template = ERB.new(File.read("#{TemplateFile.fixture_path}/#{#template_file}"))
File.open("#{#project_name}/#{#destination_file}", 'w') do |output_file|
output_file.puts template.result binding
end
You don't need that nesting.
Then when testing what you want to do to verify your the correct file is read is:
File.expects(:read).with("#{TemplateFile.fixture_path}/README.md.erb").returns(some_known_fixture)
The returns part says when it does get this read method with the specified argument I want you to return this known thing so that template will have a good value for the rest of the code.

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

Resources