lib/appium_lib/driver.rb in appium_lib-9.0.0 vs lib/appium_lib/driver.rb in appium_lib-9.1.0

- old
+ new

@@ -1,22 +1,24 @@ require 'rubygems' require 'ap' require 'selenium-webdriver' -require 'selenium/client/errors' # used in helper.rb for CommandError require 'nokogiri' # common require_relative 'common/helper' require_relative 'common/wait' require_relative 'common/patch' require_relative 'common/version' require_relative 'common/error' +require_relative 'common/search_context' +require_relative 'common/command' require_relative 'common/element/window' # ios require_relative 'ios/helper' require_relative 'ios/patch' +require_relative 'ios/errors' require_relative 'ios/element/alert' require_relative 'ios/element/button' require_relative 'ios/element/generic' require_relative 'ios/element/textfield' @@ -68,28 +70,30 @@ # all keys are converted to symbols # # @param opts [Hash] file: '/path/to/appium.txt', verbose: true # @return [hash] the symbolized hash with updated :app and :require keys def self.load_settings(opts = {}) - fail 'opts must be a hash' unless opts.is_a? Hash - fail 'opts must not be empty' if opts.empty? + raise 'opts must be a hash' unless opts.is_a? Hash + raise 'opts must not be empty' if opts.empty? toml = opts[:file] - fail 'Must pass file' unless toml + raise 'Must pass file' unless toml verbose = opts.fetch :verbose, false Appium::Logger.info "appium settings path: #{toml}" if verbose toml_exists = File.exist? toml Appium::Logger.info "Exists? #{toml_exists}" if verbose - fail "toml doesn't exist #{toml}" unless toml_exists + raise "toml doesn't exist #{toml}" unless toml_exists require 'tomlrb' Appium::Logger.info "Loading #{toml}" if verbose data = Tomlrb.load_file(toml, symbolize_keys: true) - Appium::Logger.ap_info data unless data.empty? if verbose + if verbose + Appium::Logger.ap_info data unless data.empty? + end if data && data[:caps] && data[:caps][:app] && !data[:caps][:app].empty? data[:caps][:app] = Appium::Driver.absolute_app_path data end @@ -100,10 +104,11 @@ data end class << self + # rubocop:disable Style/Alias alias_method :load_appium_txt, :load_settings end # @param [String] base_dir parent directory of loaded appium.txt (toml) # @param [String] file_paths @@ -139,11 +144,11 @@ # convert all keys (including nested) to symbols # # based on deep_symbolize_keys & deep_transform_keys from rails # https://github.com/rails/docrails/blob/a3b1105ada3da64acfa3843b164b14b734456a50/activesupport/lib/active_support/core_ext/hash/keys.rb#L84 def self.symbolize_keys(hash) - fail 'symbolize_keys requires a hash' unless hash.is_a? Hash + raise 'symbolize_keys requires a hash' unless hash.is_a? Hash result = {} hash.each do |key, value| key = key.to_sym rescue key # rubocop:disable Style/RescueModifier result[key] = value.is_a?(Hash) ? symbolize_keys(value) : value end @@ -160,35 +165,37 @@ # # if modules is a module instead of an array, then the constants of # that module are promoted on. # otherwise, the array of modules will be used as the promotion target. def self.promote_singleton_appium_methods(modules) - fail 'Driver is nil' if $driver.nil? + raise 'Driver is nil' if $driver.nil? target_modules = [] if modules.is_a? Module modules.constants.each do |sub_module| target_modules << modules.const_get(sub_module) end else - fail 'modules must be a module or an array' unless modules.is_a? Array + raise 'modules must be a module or an array' unless modules.is_a? Array target_modules = modules end target_modules.each do |const| # noinspection RubyResolve + # rubocop:disable Style/MultilineIfModifier $driver.public_methods(false).each do |m| const.send(:define_singleton_method, m) do |*args, &block| begin super(*args, &block) # promote.rb rescue NoMethodError, ArgumentError $driver.send m, *args, &block if $driver.respond_to?(m) end # override unless there's an existing method with matching arity end unless const.respond_to?(m) && const.method(m).arity == $driver.method(m).arity end + # rubocop:enable Style/MultilineIfModifier end end ## # Promote appium methods to class instance methods @@ -211,11 +218,11 @@ # ```ruby # # promote on minispec # Appium.promote_appium_methods Minitest::Spec # ``` def self.promote_appium_methods(class_array) - fail 'Driver is nil' if $driver.nil? + raise 'Driver is nil' if $driver.nil? # Wrap single class into an array class_array = [class_array] unless class_array.class == Array # Promote Appium driver methods to class instance methods. class_array.each do |klass| $driver.public_methods(false).each do |m| @@ -238,10 +245,26 @@ end nil # return nil end class Driver + module Capabilities + # except for browser_name, default capability is equal to ::Selenium::WebDriver::Remote::Capabilities.firefox + # Because Selenium::WebDriver::Remote::Bridge uses Capabilities.firefox by default + # https://github.com/SeleniumHQ/selenium/blob/selenium-3.0.1/rb/lib/selenium/webdriver/remote/bridge.rb#L67 + def self.init_caps_for_appium(opts_caps = {}) + default_caps_opts_firefox = { + javascript_enabled: true, + takes_screenshot: true, + css_selectors_enabled: true + }.merge(opts_caps) + ::Selenium::WebDriver::Remote::Capabilities.new(default_caps_opts_firefox) + end + end + end + + class Driver # attr readers are promoted to global scope. To avoid clobbering, they're # made available via the driver_attributes method # # attr_accessor is repeated for each one so YARD documents them properly. @@ -300,16 +323,16 @@ # @param opts [Object] A hash containing various options. # @return [Driver] def initialize(opts = {}) # quit last driver $driver.driver_quit if $driver - fail 'opts must be a hash' unless opts.is_a? Hash + raise 'opts must be a hash' unless opts.is_a? Hash opts = Appium.symbolize_keys opts - # default to {} to prevent nil.fetch and other nil errors - @caps = opts[:caps] || {} + @caps = Capabilities.init_caps_for_appium(opts[:caps] || {}) + appium_lib_opts = opts[:appium_lib] || {} # appium_lib specific values @custom_url = appium_lib_opts.fetch :server_url, false @export_session = appium_lib_opts.fetch :export_session, false @@ -325,11 +348,11 @@ # `listener = opts.delete(:listener)` is called in Selenium::Driver.new @listener = appium_lib_opts.fetch :listener, nil # Path to the .apk, .app or .app.zip. # The path can be local or remote for Sauce. - if @caps && @caps[:app] && ! @caps[:app].empty? + if @caps && @caps[:app] && !@caps[:app].empty? @caps[:app] = self.class.absolute_app_path opts end # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile @appium_device = @caps[:platformName] @@ -349,10 +372,13 @@ end # apply os specific patches patch_webdriver_element + # for command + patch_remote_driver_commands + # enable debug patch # !!'constant' == true @appium_debug = appium_lib_opts.fetch :debug, !!defined?(Pry) if @appium_debug @@ -368,21 +394,22 @@ self # return newly created driver end # Returns a hash of the driver attributes def driver_attributes - attributes = { caps: @caps, - custom_url: @custom_url, - export_session: @export_session, - default_wait: @default_wait, - last_waits: @last_waits, - sauce_username: @sauce_username, - sauce_access_key: @sauce_access_key, - port: @appium_port, - device: @appium_device, - debug: @appium_debug, - listener: @listener + attributes = { + caps: @caps, + custom_url: @custom_url, + export_session: @export_session, + default_wait: @default_wait, + last_waits: @last_waits, + sauce_username: @sauce_username, + sauce_access_key: @sauce_access_key, + port: @appium_port, + device: @appium_device, + debug: @appium_debug, + listener: @listener } # Return duplicates so attributes are immutable attributes.each do |key, value| attributes[key] = value.duplicable? ? value.dup : value @@ -395,19 +422,19 @@ end # Return true if automationName is 'XCUITest' # @return [Boolean] def automation_name_is_xcuitest? - !@automation_name.nil? && @automation_name.downcase == 'xcuitest' + !@automation_name.nil? && 'xcuitest'.casecmp(@automation_name).zero? end # Return true if the target Appium server is over REQUIRED_VERSION_XCUITEST. # If the Appium server is under REQUIRED_VERSION_XCUITEST, then error is raised. # @return [Boolean] def check_server_version_xcuitest if automation_name_is_xcuitest? && (@appium_server_version['build']['version'] <= REQUIRED_VERSION_XCUITEST) - fail Appium::Error::NotSupportedAppiumServer, "XCUITest requires over Appium #{REQUIRED_VERSION_XCUITEST}" + raise Appium::Error::NotSupportedAppiumServer, "XCUITest requires over Appium #{REQUIRED_VERSION_XCUITEST}" end true end # Returns the server's version info @@ -433,37 +460,37 @@ # # if app isn't set then an error is raised. # # @return [String] APP_PATH as an absolute path def self.absolute_app_path(opts) - fail 'opts must be a hash' unless opts.is_a? Hash + raise 'opts must be a hash' unless opts.is_a? Hash caps = opts[:caps] || {} appium_lib_opts = opts[:appium_lib] || {} server_url = appium_lib_opts.fetch :server_url, false app_path = caps[:app] - fail 'absolute_app_path invoked and app is not set!' if app_path.nil? || app_path.empty? + raise 'absolute_app_path invoked and app is not set!' if app_path.nil? || app_path.empty? # may be absolute path to file on remote server. # if the file is on the remote server then we can't check if it exists return app_path if server_url # Sauce storage API. http://saucelabs.com/docs/rest#storage return app_path if app_path.start_with? 'sauce-storage:' - return app_path if app_path.match(/^http/) # public URL for Sauce - if app_path.match(/^(\/|[a-zA-Z]:)/) # absolute file path + return app_path if app_path =~ /^http/ # public URL for Sauce + if app_path =~ /^(\/|[a-zA-Z]:)/ # absolute file path app_path = File.expand_path app_path unless File.exist? app_path - fail "App doesn't exist. #{app_path}" unless File.exist? app_path + raise "App doesn't exist. #{app_path}" unless File.exist? app_path return app_path end # if it doesn't contain a slash then it's a bundle id - return app_path unless app_path.match(/[\/\\]/) + return app_path unless app_path =~ /[\/\\]/ # relative path that must be expanded. # absolute_app_path is called from load_settings # and the txt file path is the base of the app path in that case. app_path = File.expand_path app_path - fail "App doesn't exist #{app_path}" unless File.exist? app_path + raise "App doesn't exist #{app_path}" unless File.exist? app_path app_path end # Get the server url # @return [String] the server url @@ -496,12 +523,13 @@ # Quits the driver # @return [void] def driver_quit # rescue NoSuchDriverError or nil driver - # rubocop:disable Style/RescueModifier - @driver.quit rescue nil + @driver.quit + rescue + nil end # Creates a new global driver and quits the old one if it exists. # # @return [Selenium::WebDriver] the new global driver @@ -619,18 +647,18 @@ # Calls @driver.find_elements # # @param args [*args] the args to use # @return [Array<Element>] Array is empty when no elements are found. def find_elements(*args) - @driver.find_elements(*args) + @driver.find_elements_with_appium(*args) end # Calls @driver.find_elements # # @param args [*args] the args to use # @return [Element] def find_element(*args) - @driver.find_element(*args) + @driver.find_element_with_appium(*args) end # Calls @driver.set_location # # @note This method does not work on real devices.