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.