# frozen_string_literal: true # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'rubygems' require 'selenium-webdriver' require 'nokogiri' require 'appium_lib_core' # base require_relative 'driver' require_relative 'sauce_labs' # common require_relative 'common/wait' require_relative 'common/log' require_relative 'common/helper' require_relative 'common/http_client' require_relative 'common/device' require_relative 'common/command' # ios require_relative 'ios/ios' # android require_relative 'android/android' module Appium class << self # Load arbitrary text ([toml format](https://github.com/toml-lang/toml)) # The toml is parsed by https://github.com/fbernier/tomlrb . # # ``` # [caps] # app = "path/to/app" # # [appium_lib] # port = 8080 # ``` # # :app is expanded # :require is expanded # 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 load_settings(opts = {}) raise 'opts must be a hash' unless opts.is_a? Hash raise 'opts must not be empty' if opts.empty? toml = opts[:file] raise 'Must pass a capability file which has [caps] and [appium_lib]' 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 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.info data if verbose && !data.empty? # FIXME: Deprecated. Will remove when we remove 'Appium::Driver.absolute_app_path' if data if data['caps'] if data['caps'][:app] && !data['caps'][:app].empty? data['caps'][:app] = Appium::Driver.absolute_app_path data elsif data['caps']['app'] && !data['caps']['app'].empty? data['caps']['app'] = Appium::Driver.absolute_app_path data end elsif data[:caps] if data[:caps][:app] && !data[:caps][:app].empty? data[:caps][:app] = Appium::Driver.absolute_app_path data elsif data[:caps]['app'] && !data[:caps]['app'].empty? data[:caps]['app'] = Appium::Driver.absolute_app_path data end end end if data && data[:appium_lib] && data[:appium_lib][:require] parent_dir = File.dirname toml data[:appium_lib][:require] = expand_required_files(parent_dir, data[:appium_lib][:require]) end data end # @see load_settings alias load_appium_txt load_settings # @param [String] base_dir parent directory of loaded appium.txt (toml) # @param [String] file_paths # @return [Array] list of require files as an array, nil if require doesn't exist def expand_required_files(base_dir, file_paths) # ensure files are absolute Array(file_paths).map! do |f| file = File.exist?(f) ? f : File.join(base_dir, f) file = File.expand_path file File.exist?(file) ? file : nil end file_paths.compact! # remove nils files = [] # now expand dirs file_paths.each do |item| unless File.directory? item # save file files << item next # only look inside folders end Dir.glob(File.expand_path(File.join(item, '**', '*.rb'))) do |f| # do not add folders to the file list files << File.expand_path(f) unless File.directory? f end end files end # This method is intended to work with page objects that share # a common module. For example, Page::HomePage, Page::SignIn # those could be promoted on with Appium.promote_singleton_appium_methods Page # # If you are promoting on an individual class then you should use # Appium.promote_appium_methods instead. The singleton method is intended # only for the shared module use case. # # 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. # # @param [Array] modules An array of modules # @param [Driver] driver A driver to extend for def promote_singleton_appium_methods(modules, driver = $driver) raise 'Global $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 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 driver.public_methods(false).each do |m| # override unless there's an existing method with matching arity next if const.respond_to?(m) && const.method(m).arity == driver.method(m).arity const.send(:define_singleton_method, m) do |*args, &block| super(*args, &block) # promote.rb rescue NoMethodError, ArgumentError driver.send m, *args, &block if driver.respond_to?(m) end end end end ## # Promote appium methods to class instance methods # # @param [Array] class_array An array of classes # @param [Driver] driver A driver to extend for # # To promote methods to all classes: # # @example # # Appium.promote_appium_methods Object # # It's better to promote on specific classes instead of Object # # @example # # # promote on rspec # Appium.promote_appium_methods RSpec::Core::ExampleGroup # # @example # # # promote on minispec # Appium.promote_appium_methods Minitest::Spec # def promote_appium_methods(class_array, driver = $driver) raise 'Driver is nil' if driver.nil? # Wrap single class into an array class_array = [class_array] unless class_array.instance_of? Array # Promote Appium driver methods to class instance methods. class_array.each do |klass| driver.public_methods(false).each do |method| klass.class_eval do # NOTE: Do not skip re-definding methods to not keep old instance information. # Probably the global driver ($driver) stuff needs to override (re-defined) # every time to not keep unexpected state. # https://github.com/appium/ruby_lib/issues/917 # Remove the method before adding it. remove_method method if method_defined? method define_method method do |*args, &block| # Prefer existing method. # super will invoke method missing on driver super(*args, &block) # minitest also defines a name method, # so rescue argument error # and call the name method on $driver rescue NoMethodError, ArgumentError if args.size == 1 && args.first.is_a?(Hash) # To prevent warnings by keyword arguments (for Ruby 2.7 and 3) driver.send method, **args.first, &block if driver.respond_to?(method) else ::Appium::Logger.warn "Should fix this '#{args}' for Ruby 2.7 (and 3)" if args.first.is_a?(Hash) driver.send method, *args, &block if driver.respond_to?(method) end end end end end nil # return nil end end # class << self end # module Appium