lib/appium_lib/common/helper.rb in appium_lib-0.24.1 vs lib/appium_lib/common/helper.rb in appium_lib-1.0.0

- old
+ new

@@ -1,271 +1,210 @@ -# encoding: utf-8 # Generic helper methods not specific # to a particular tag name -module Appium::Common - # json and ap are required for the source method. - require 'json' - require 'ap' # awesome print - require 'timeout' # for wait +module Appium + module Common + # json and ap are required for the source method. + require 'json' + require 'ap' # awesome print + require 'timeout' # for wait - # iOS .name returns the accessibility attribute if it's set. if not set, the string value is used. - # Android .name returns the accessibility attribute and nothing if it's not set. - # - # .text should be cross platform so prefer that over name, unless both - # Android and iOS have proper accessibility attributes. - # .text and .value should be the same so use .text over .value. - # - # secure tag_name is iOS only because it can't be implemented using uiautomator for Android. - # - # find_element :text doesn't work so use XPath to find by text. + # iOS .name returns the accessibility attribute if it's set. if not set, the string value is used. + # Android .name returns the accessibility attribute and nothing if it's not set. + # + # .text should be cross platform so prefer that over name, unless both + # Android and iOS have proper accessibility attributes. + # .text and .value should be the same so use .text over .value. + # + # secure class_name is iOS only because it can't be implemented using uiautomator for Android. + # + # find_element :text doesn't work so use XPath to find by text. - # Check every 0.5 seconds to see if block.call doesn't raise an exception. - # if .call raises an exception then it will be tried again. - # if .call doesn't raise an exception then it will stop waiting. - # - # Example: wait { name('back').click } - # - # Give up after 30 seconds. - # @param max_wait [Integer] the maximum time in seconds to wait for. - # Note that max wait 0 means infinity. - # @param interval [Float] the time in seconds to wait after calling the block - # @param block [Block] the block to call - # @return [Object] the result of block.call - def wait max_wait=30, interval=0.5, &block - max_wait = 1 if max_wait <= 0 - result = nil - timeout max_wait do - until (result = begin; block.call || true; rescue; end) - sleep interval + # Check every 0.5 seconds to see if block.call doesn't raise an exception. + # if .call raises an exception then it will be tried again. + # if .call doesn't raise an exception then it will stop waiting. + # + # Example: wait { name('back').click } + # + # Give up after 30 seconds. + # @param max_wait [Integer] the maximum time in seconds to wait for. + # Note that max wait 0 means infinity. + # @param interval [Float] the time in seconds to wait after calling the block + # @param block [Block] the block to call + # @return [Object] the result of block.call + def wait max_wait=30, interval=0.5, &block + max_wait = 1 if max_wait <= 0 + result = nil + timeout max_wait do + until ( + begin + result = block.call || true + rescue Exception + end) + sleep interval + end end + result end - result - end - # Return block.call and ignore any exceptions. - def ignore &block - begin; block.call; rescue; end - end + # Return block.call and ignore any exceptions. + def ignore &block + begin + block.call + rescue Exception + end + end - # Check every 0.5 seconds to see if block.call returns true. nil is considered a failure. - # Give up after 30 seconds. - # @param max_wait [Integer] the maximum time in seconds to wait for - # @param interval [Float] the time in seconds to wait after calling the block - # @param block [Block] the block to call - # @return [Object] the result of block.call - def wait_true max_wait=30, interval=0.5, &block - max_wait = 1 if max_wait <= 0 - result = nil - timeout max_wait do - until (result = begin; block.call; rescue; end) - sleep interval + # Check every 0.5 seconds to see if block.call returns a truthy value. + # Note this isn't a strict boolean true, any truthy value is accepted. + # false and nil are considered failures. + # Give up after 30 seconds. + # @param max_wait [Integer] the maximum time in seconds to wait for + # @param interval [Float] the time in seconds to wait after calling the block + # @param block [Block] the block to call + # @return [Object] the result of block.call + def wait_true max_wait=30, interval=0.5, &block + max_wait = 1 if max_wait <= 0 + result = nil + timeout max_wait do + until ( + begin + result = block.call + rescue Exception + end) + sleep interval + end end + result end - result - end - # Navigate back. - # @return [void] - def back - @driver.navigate.back - end + # Navigate back. + # @return [void] + def back + @driver.navigate.back + end - # For Sauce Labs reporting. Returns the current session id. - def session_id - @driver.session_id - end + # For Sauce Labs reporting. Returns the current session id. + def session_id + @driver.session_id + end - # Returns the first element that matches the provided xpath. - # - # @param xpath_str [String] the XPath string - # @return [Element] - def xpath xpath_str - find_element :xpath, xpath_str - end + # Returns the first element that matches the provided xpath. + # + # @param xpath_str [String] the XPath string + # @return [Element] + def xpath xpath_str + find_element :xpath, xpath_str + end - # Returns all elements that match the provided xpath. - # - # @param xpath_str [String] the XPath string - # @return [Array<Element>] - def xpaths xpath_str - find_elements :xpath, xpath_str - end + # Returns all elements that match the provided xpath. + # + # @param xpath_str [String] the XPath string + # @return [Array<Element>] + def xpaths xpath_str + find_elements :xpath, xpath_str + end - # Get the element of type tag_name at matching index. - # @param tag_name [String] the tag name to find - # @param index [Integer] the index - # @return [Element] the found element of type tag_name - def ele_index tag_name, index - # XPath index starts at 1. - raise "#{index} is not a valid xpath index. Must be >= 1" if index <= 0 - find_element :xpath, "//#{tag_name}[#{index}]" - end + # Prints xml of the current page + # @return [void] + def source + doc = Nokogiri::XML(@driver.page_source) do |config| + config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NONET + end + puts doc.to_xml indent: 2 + end - # Get all elements exactly matching tag name - # @param tag_name [String] the tag name to find - # @return [Array<Element>] the found elements of type tag_name - def find_eles tag_name - @driver.find_elements :tag_name, tag_name - end + # Returns XML string for the current page + # Same as driver.page_source + # @return [String] + def get_source + @driver.page_source + end - # Get the first tag that exactly matches tag and text. - # @param tag [String] the tag name to match - # @param text [String] the text to exactly match - # @return [Element] the element of type tag exactly matching text - def find_ele_by_text tag, text - @driver.find_element :xpath, %Q(#{tag}[@text='#{text}']) - end + # @private + # http://nokogiri.org/Nokogiri/XML/SAX.html + class CountElements < Nokogiri::XML::SAX::Document + attr_reader :result - # Get all tags that exactly match tag and text. - # @param tag [String] the tag name to match - # @param text [String] the text to exactly match - # @return [Array<Element>] the elements of type tag exactly matching text - def find_eles_by_text tag, text - @driver.find_elements :xpath, %Q(#{tag}[@text='#{text}']) - end + def initialize + reset + end - # Get the first tag by attribute that exactly matches value. - # @param tag [String] the tag name to match - # @param attr [String] the attribute to compare - # @param value [String] the value of the attribute that the element must include - # @return [Element] the element of type tag who's attribute includes value - def find_ele_by_attr_include tag, attr, value - @driver.find_element :xpath, %Q(#{tag}[contains(@#{attr}, '#{value}')]) - end + def reset + @result = Hash.new 0 + end - # Get tags by attribute that include value. - # @param tag [String] the tag name to match - # @param attr [String] the attribute to compare - # @param value [String] the value of the attribute that the element must include - # @return [Array<Element>] the elements of type tag who's attribute includes value - def find_eles_by_attr_include tag, attr, value - @driver.find_elements :xpath, %Q(#{tag}[contains(@#{attr}, '#{value}')]) - end + # http://nokogiri.org/Nokogiri/XML/SAX/Document.html + def start_element name, attrs = [] + @result[name] += 1 + end - # Get the first tag that includes text. - # @param tag [String] the tag name to match - # @param text [String] the text the element must include - # @return [Element] the element of type tag that includes text - # element.attribute(:text).include? text - def find_ele_by_text_include tag, text - find_ele_by_attr_include tag, :text, text - end + def formatted_result + message = '' + sorted = @result.sort_by { |element, count| count }.reverse + sorted.each do |element, count| + message += "#{count}x #{element}\n" + end + message.strip + end + end # class CountElements - # Get the tags that include text. - # @param tag [String] the tag name to match - # @param text [String] the text the element must include - # @return [Array<Element>] the elements of type tag that includes text - # element.attribute(:text).include? text - def find_eles_by_text_include tag, text - find_eles_by_attr_include tag, :text, text - end + # Returns a string of class counts. + def get_page_class + parser = @count_elements_parser ||= Nokogiri::XML::SAX::Parser.new(CountElements.new) - # Get the first tag that matches tag_name - # @param tag_name [String] the tag to match - # @return [Element] - def first_ele tag_name - # XPath index starts at 1 - find_element :xpath, "//#{tag_name}[1]" - end + parser.document.reset + parser.parse get_source - # Get the last tag that matches tag_name - # @param tag_name [String] the tag to match - # @return [Element] - def last_ele tag_name - xpath "//#{tag_name}[last()]" - end + parser.document.formatted_result + end - # Prints a JSON view of the current page - # @return [void] - def source - ap get_source - end + # Count all classes on screen and print to stdout. + # Useful for appium_console. + def page_class + puts get_page_class + nil + end - # Gets a JSON view of the current page - # @return [JSON] - def get_source - # must set max nesting. default limit of 20 is too low for selendroid - JSON.parse @driver.page_source, max_nesting: 9999 - end + # Converts pixel values to window relative values + # + # ```ruby + # px_to_window_rel x: 50, y: 150 + # ``` + def px_to_window_rel opts={} + w = $driver.window_size + x = opts.fetch :x, 0 + y = opts.fetch :y, 0 - # Returns the first element that exactly matches name - # - # @param name [String] the name to exactly match - # @return [Element] - def find_name name - find_element :name, name - end + OpenStruct.new(x: "#{x.to_f} / #{w.width.to_f}", + y: "#{y.to_f} / #{w.height.to_f}") + end - # Returns all elements that exactly match name - # - # @param name [String] the name to exactly match - # @return [Array<Element>] - def find_names name - find_elements :name, name - end + # @private + def lazy_load_strings + @strings_xml ||= app_strings + end - # Returns the first element matching tag_name - # - # @param tag_name [String] the tag_name to search for - # @return [Element] - def tag tag_name - find_element :tag_name, tag_name - end + # Search strings.xml's values for target. + # @param target [String] the target to search for in strings.xml values + # @return [Array] + def xml_keys target + lazy_load_strings + @strings_xml.select { |key, value| key.downcase.include? target.downcase } + end - # Returns all elements matching tag_name - # - # @param tag_name [String] the tag_name to search for - # @return [Element] - def tags tag_name - find_elements :tag_name, tag_name - end + # Search strings.xml's keys for target. + # @param target [String] the target to search for in strings.xml keys + # @return [Array] + def xml_values target + lazy_load_strings + @strings_xml.select { |key, value| value.downcase.include? target.downcase } + end - # Converts pixel values to window relative values - # - # ```ruby - # px_to_window_rel x: 50, y: 150 - # ``` - - def px_to_window_rel opts={} - - w = $driver.window_size - x = opts.fetch :x, 0 - y = opts.fetch :y, 0 - - OpenStruct.new( x: "#{x.to_f} / #{w.width.to_f}", - y: "#{y.to_f} / #{w.height.to_f}" ) - end - - def lazy_load_strings - @strings_xml ||= mobile(:getStrings) - end - - # Search strings.xml's values for target. - # @param target [String] the target to search for in strings.xml values - # @return [Array] - def xml_keys target - lazy_load_strings - @strings_xml.select { |key, value| key.downcase.include? target.downcase } - end - - # Search strings.xml's keys for target. - # @param target [String] the target to search for in strings.xml keys - # @return [Array] - def xml_values target - lazy_load_strings - @strings_xml.select { |key, value| value.downcase.include? target.downcase } - end - - # Resolve id in strings.xml and return the value. - # @param id [String] the id to resolve - # @return [String] - def resolve_id id - lazy_load_strings - @strings_xml[id] - end - - # Used to error when finding a single element fails. - def raise_no_element_error - raise Selenium::WebDriver::Error::NoSuchElementError, 'An element could not be located on the page using the given search parameters.' - end - -end # module Appium::Common + # Resolve id in strings.xml and return the value. + # @param id [String] the id to resolve + # @return [String] + def resolve_id id + lazy_load_strings + @strings_xml[id] + end + end # module Common +end # module Appium \ No newline at end of file