appium_lib 9.6.1 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -2
  3. data/CHANGELOG.md +43 -0
  4. data/Rakefile +1 -1
  5. data/appium_lib.gemspec +1 -1
  6. data/docs/android_docs.md +440 -1295
  7. data/docs/docs.md +10 -103
  8. data/docs/index_paths.md +2 -0
  9. data/docs/ios_docs.md +725 -1674
  10. data/docs/migration.md +17 -0
  11. data/lib/appium_lib.rb +1 -2
  12. data/lib/appium_lib/android/android.rb +20 -0
  13. data/lib/appium_lib/android/{helper.rb → common/helper.rb} +1 -1
  14. data/lib/appium_lib/android/uiautomator2.rb +5 -4
  15. data/lib/appium_lib/android/uiautomator2/bridge.rb +16 -0
  16. data/lib/appium_lib/appium.rb +201 -0
  17. data/lib/appium_lib/common/helper.rb +18 -20
  18. data/lib/appium_lib/common/log.rb +24 -0
  19. data/lib/appium_lib/common/multi_touch.rb +89 -0
  20. data/lib/appium_lib/common/touch_actions.rb +48 -0
  21. data/lib/appium_lib/common/wait.rb +10 -49
  22. data/lib/appium_lib/core/android.rb +4 -0
  23. data/lib/appium_lib/core/android/device.rb +142 -0
  24. data/lib/appium_lib/core/android/search_context.rb +17 -0
  25. data/lib/appium_lib/core/android/uiautomator1/bridge.rb +16 -0
  26. data/lib/appium_lib/core/android/uiautomator2/bridge.rb +16 -0
  27. data/lib/appium_lib/core/android_uiautomator2.rb +4 -0
  28. data/lib/appium_lib/core/common.rb +6 -0
  29. data/lib/appium_lib/core/common/base.rb +8 -0
  30. data/lib/appium_lib/core/common/base/bridge.rb +47 -0
  31. data/lib/appium_lib/core/common/base/capabilities.rb +16 -0
  32. data/lib/appium_lib/core/common/base/command.rb +10 -0
  33. data/lib/appium_lib/core/common/base/driver.rb +40 -0
  34. data/lib/appium_lib/core/common/base/http_default.rb +12 -0
  35. data/lib/appium_lib/core/common/base/search_context.rb +89 -0
  36. data/lib/appium_lib/core/common/base/wait.rb +56 -0
  37. data/lib/appium_lib/{common → core/common}/command.rb +20 -16
  38. data/lib/appium_lib/core/common/device.rb +470 -0
  39. data/lib/appium_lib/core/common/error.rb +13 -0
  40. data/lib/appium_lib/core/common/log.rb +30 -0
  41. data/lib/appium_lib/{logger.rb → core/common/logger.rb} +2 -0
  42. data/lib/appium_lib/core/core.rb +38 -0
  43. data/lib/appium_lib/core/device/multi_touch.rb +213 -0
  44. data/lib/appium_lib/core/device/touch_actions.rb +206 -0
  45. data/lib/appium_lib/core/driver.rb +274 -0
  46. data/lib/appium_lib/core/ios.rb +6 -0
  47. data/lib/appium_lib/core/ios/device.rb +44 -0
  48. data/lib/appium_lib/core/ios/search_context.rb +27 -0
  49. data/lib/appium_lib/core/ios/uiautomation/bridge.rb +17 -0
  50. data/lib/appium_lib/core/ios/uiautomation/patch.rb +20 -0
  51. data/lib/appium_lib/core/ios/xcuitest/bridge.rb +18 -0
  52. data/lib/appium_lib/{ios → core/ios}/xcuitest/device.rb +5 -5
  53. data/lib/appium_lib/{ios → core/ios}/xcuitest/search_context.rb +13 -9
  54. data/lib/appium_lib/core/ios_xcuitest.rb +7 -0
  55. data/lib/appium_lib/core/patch.rb +56 -0
  56. data/lib/appium_lib/driver.rb +174 -446
  57. data/lib/appium_lib/ios/{errors.rb → common/errors.rb} +0 -0
  58. data/lib/appium_lib/ios/{helper.rb → common/helper.rb} +9 -110
  59. data/lib/appium_lib/ios/ios.rb +20 -0
  60. data/lib/appium_lib/ios/xcuitest.rb +1 -3
  61. data/lib/appium_lib/ios/xcuitest/bridge.rb +19 -0
  62. data/lib/appium_lib/ios/xcuitest/command.rb +4 -1
  63. data/lib/appium_lib/ios/xcuitest/{gestures.rb → command/gestures.rb} +1 -1
  64. data/lib/appium_lib/ios/xcuitest/element.rb +1 -18
  65. data/lib/appium_lib/ios/xcuitest/helper.rb +0 -6
  66. data/lib/appium_lib/sauce_labs.rb +29 -0
  67. data/lib/appium_lib/version.rb +5 -0
  68. data/release_notes.md +8 -0
  69. metadata +50 -25
  70. data/lib/appium_lib/android/client_xpath.rb +0 -51
  71. data/lib/appium_lib/android/device.rb +0 -39
  72. data/lib/appium_lib/android/mobile_methods.rb +0 -15
  73. data/lib/appium_lib/android/patch.rb +0 -16
  74. data/lib/appium_lib/capabilities.rb +0 -13
  75. data/lib/appium_lib/common/element/window.rb +0 -10
  76. data/lib/appium_lib/common/error.rb +0 -8
  77. data/lib/appium_lib/common/patch.rb +0 -190
  78. data/lib/appium_lib/common/search_context.rb +0 -10
  79. data/lib/appium_lib/common/version.rb +0 -5
  80. data/lib/appium_lib/device/device.rb +0 -611
  81. data/lib/appium_lib/device/multi_touch.rb +0 -225
  82. data/lib/appium_lib/device/touch_actions.rb +0 -230
  83. data/lib/appium_lib/ios/mobile_methods.rb +0 -25
  84. data/lib/appium_lib/ios/patch.rb +0 -22
@@ -0,0 +1,7 @@
1
+ # loaded in common/driver.rb
2
+ require_relative 'ios/search_context'
3
+ require_relative 'ios/device'
4
+
5
+ require_relative 'ios/xcuitest/search_context'
6
+ require_relative 'ios/xcuitest/device'
7
+ require_relative 'ios/xcuitest/bridge'
@@ -0,0 +1,56 @@
1
+ require_relative '../version'
2
+
3
+ module Appium
4
+ module Core
5
+ # Implement useful features for element.
6
+ class Selenium::WebDriver::Element # rubocop:disable Style/ClassAndModuleChildren
7
+ # Note: For testing .text should be used over value, and name.
8
+
9
+ # Returns the value attribute
10
+ #
11
+ # Fixes NoMethodError: undefined method `value' for Selenium::WebDriver::Element
12
+ def value
13
+ attribute :value
14
+ end
15
+
16
+ # Returns the name attribute
17
+ #
18
+ # Fixes NoMethodError: undefined method `name' for Selenium::WebDriver::Element
19
+ def name
20
+ attribute :name
21
+ end
22
+
23
+ # Enable access to iOS accessibility label
24
+ # accessibility identifier is supported as 'name'
25
+ def label
26
+ attribute :label
27
+ end
28
+
29
+ # Alias for type
30
+ alias type send_keys
31
+
32
+ # For use with mobile tap.
33
+ #
34
+ # ```ruby
35
+ # execute_script 'mobile: tap', :x => 0.0, :y => 0.98
36
+ # ```
37
+ #
38
+ # @return [OpenStruct] the relative x, y in a struct. ex: { x: 0.50, y: 0.20 }
39
+ def location_rel(driver = $driver)
40
+ rect = self.rect
41
+ location_x = rect.x.to_f
42
+ location_y = rect.y.to_f
43
+
44
+ size_width = rect.width.to_f
45
+ size_height = rect.height.to_f
46
+
47
+ center_x = location_x + (size_width / 2.0)
48
+ center_y = location_y + (size_height / 2.0)
49
+
50
+ w = driver.window_size
51
+ OpenStruct.new(x: "#{center_x} / #{w.width.to_f}",
52
+ y: "#{center_y} / #{w.height.to_f}")
53
+ end
54
+ end
55
+ end # module Core
56
+ end # module Appium
@@ -1,56 +1,3 @@
1
- require 'rubygems'
2
- require 'ap'
3
- require 'selenium-webdriver'
4
- require 'nokogiri'
5
-
6
- # base
7
- require_relative 'capabilities'
8
-
9
- # common
10
- require_relative 'common/helper'
11
- require_relative 'common/wait'
12
- require_relative 'common/patch'
13
- require_relative 'common/version'
14
- require_relative 'common/error'
15
- require_relative 'common/search_context'
16
- require_relative 'common/command'
17
- require_relative 'common/element/window'
18
-
19
- # ios
20
- require_relative 'ios/helper'
21
- require_relative 'ios/patch'
22
- require_relative 'ios/errors'
23
-
24
- require_relative 'ios/element/alert'
25
- require_relative 'ios/element/button'
26
- require_relative 'ios/element/generic'
27
- require_relative 'ios/element/textfield'
28
- require_relative 'ios/element/text'
29
- require_relative 'ios/mobile_methods'
30
-
31
- require_relative 'ios/xcuitest'
32
-
33
- # android
34
- require_relative 'android/helper'
35
- require_relative 'android/patch'
36
- require_relative 'android/client_xpath'
37
- require_relative 'android/element/alert'
38
- require_relative 'android/element/button'
39
- require_relative 'android/element/generic'
40
- require_relative 'android/element/textfield'
41
- require_relative 'android/element/text'
42
- require_relative 'android/mobile_methods'
43
-
44
- require_relative 'android/device'
45
-
46
- # android - uiautomator2
47
- require_relative 'android/uiautomator2'
48
-
49
- # device methods
50
- require_relative 'device/device'
51
- require_relative 'device/touch_actions'
52
- require_relative 'device/multi_touch'
53
-
54
1
  # Fix uninitialized constant Minitest (NameError)
55
2
  module Minitest
56
3
  # Fix superclass mismatch for class Spec
@@ -62,206 +9,11 @@ module Minitest
62
9
  end
63
10
  end
64
11
 
12
+ require_relative 'core/core'
13
+
65
14
  module Appium
66
15
  REQUIRED_VERSION_XCUITEST = '1.6.0'.freeze
67
16
 
68
- # Load arbitrary text ([toml format](https://github.com/toml-lang/toml))
69
- # The toml is parsed by https://github.com/fbernier/tomlrb .
70
- #
71
- # ```
72
- # [caps]
73
- # app = "path/to/app"
74
- #
75
- # [appium_lib]
76
- # port = 8080
77
- # ```
78
- #
79
- # :app is expanded
80
- # :require is expanded
81
- # all keys are converted to symbols
82
- #
83
- # @param opts [Hash] file: '/path/to/appium.txt', verbose: true
84
- # @return [hash] the symbolized hash with updated :app and :require keys
85
- def self.load_settings(opts = {})
86
- raise 'opts must be a hash' unless opts.is_a? Hash
87
- raise 'opts must not be empty' if opts.empty?
88
-
89
- toml = opts[:file]
90
- raise 'Must pass a capability file which has [caps] and [appium_lib]' unless toml
91
- verbose = opts.fetch :verbose, false
92
-
93
- Appium::Logger.info "appium settings path: #{toml}" if verbose
94
-
95
- toml_exists = File.exist? toml
96
- Appium::Logger.info "Exists? #{toml_exists}" if verbose
97
-
98
- raise "toml doesn't exist #{toml}" unless toml_exists
99
- require 'tomlrb'
100
- Appium::Logger.info "Loading #{toml}" if verbose
101
-
102
- data = Tomlrb.load_file(toml, symbolize_keys: true)
103
- if verbose
104
- Appium::Logger.ap_info data unless data.empty?
105
- end
106
-
107
- if data && data[:caps] && data[:caps][:app] && !data[:caps][:app].empty?
108
- data[:caps][:app] = Appium::Driver.absolute_app_path data
109
- end
110
-
111
- if data && data[:appium_lib] && data[:appium_lib][:require]
112
- parent_dir = File.dirname toml
113
- data[:appium_lib][:require] = expand_required_files(parent_dir, data[:appium_lib][:require])
114
- end
115
-
116
- data
117
- end
118
-
119
- class << self
120
- # rubocop:disable Style/Alias
121
- alias_method :load_appium_txt, :load_settings
122
- end
123
-
124
- # @param [String] base_dir parent directory of loaded appium.txt (toml)
125
- # @param [String] file_paths
126
- # @return [Array] list of require files as an array, nil if require doesn't exist
127
- def self.expand_required_files(base_dir, file_paths)
128
- # ensure files are absolute
129
- Array(file_paths).map! do |f|
130
- file = File.exist?(f) ? f : File.join(base_dir, f)
131
- file = File.expand_path file
132
-
133
- File.exist?(file) ? file : nil
134
- end
135
- file_paths.compact! # remove nils
136
-
137
- files = []
138
-
139
- # now expand dirs
140
- file_paths.each do |item|
141
- unless File.directory? item
142
- # save file
143
- files << item
144
- next # only look inside folders
145
- end
146
- Dir.glob(File.expand_path(File.join(item, '**', '*.rb'))) do |f|
147
- # do not add folders to the file list
148
- files << File.expand_path(f) unless File.directory? f
149
- end
150
- end
151
-
152
- files
153
- end
154
-
155
- # convert all keys (including nested) to symbols
156
- #
157
- # based on deep_symbolize_keys & deep_transform_keys from rails
158
- # https://github.com/rails/docrails/blob/a3b1105ada3da64acfa3843b164b14b734456a50/activesupport/lib/active_support/core_ext/hash/keys.rb#L84
159
- def self.symbolize_keys(hash)
160
- raise 'symbolize_keys requires a hash' unless hash.is_a? Hash
161
- result = {}
162
- hash.each do |key, value|
163
- key = key.to_sym rescue key # rubocop:disable Style/RescueModifier
164
- result[key] = value.is_a?(Hash) ? symbolize_keys(value) : value
165
- end
166
- result
167
- end
168
-
169
- # This method is intended to work with page objects that share
170
- # a common module. For example, Page::HomePage, Page::SignIn
171
- # those could be promoted on with Appium.promote_singleton_appium_methods Page
172
- #
173
- # If you are promoting on an individual class then you should use
174
- # Appium.promote_appium_methods instead. The singleton method is intended
175
- # only for the shared module use case.
176
- #
177
- # if modules is a module instead of an array, then the constants of
178
- # that module are promoted on.
179
- # otherwise, the array of modules will be used as the promotion target.
180
- def self.promote_singleton_appium_methods(modules, driver = $driver)
181
- raise 'Global $driver is nil' if driver.nil?
182
-
183
- target_modules = []
184
-
185
- if modules.is_a? Module
186
- modules.constants.each do |sub_module|
187
- target_modules << modules.const_get(sub_module)
188
- end
189
- else
190
- raise 'modules must be a module or an array' unless modules.is_a? Array
191
- target_modules = modules
192
- end
193
-
194
- target_modules.each do |const|
195
- # noinspection RubyResolve
196
- # rubocop:disable Style/MultilineIfModifier
197
- driver.public_methods(false).each do |m|
198
- const.send(:define_singleton_method, m) do |*args, &block|
199
- begin
200
- super(*args, &block) # promote.rb
201
- rescue NoMethodError, ArgumentError
202
- driver.send m, *args, &block if driver.respond_to?(m)
203
- end
204
- # override unless there's an existing method with matching arity
205
- end unless const.respond_to?(m) && const.method(m).arity == driver.method(m).arity
206
- end
207
- # rubocop:enable Style/MultilineIfModifier
208
- end
209
- end
210
-
211
- ##
212
- # Promote appium methods to class instance methods
213
- #
214
- # @param class_array [Array<Class>] An array of classes
215
- #
216
- # To promote methods to all classes:
217
- #
218
- # ```ruby
219
- # Appium.promote_appium_methods Object
220
- # ```
221
- #
222
- # It's better to promote on specific classes instead of Object
223
- #
224
- # ```ruby
225
- # # promote on rspec
226
- # Appium.promote_appium_methods RSpec::Core::ExampleGroup
227
- # ```
228
- #
229
- # ```ruby
230
- # # promote on minispec
231
- # Appium.promote_appium_methods Minitest::Spec
232
- # ```
233
- def self.promote_appium_methods(class_array, driver = $driver)
234
- raise 'Driver is nil' if driver.nil?
235
- # Wrap single class into an array
236
- class_array = [class_array] unless class_array.class == Array
237
- # Promote Appium driver methods to class instance methods.
238
- class_array.each do |klass|
239
- driver.public_methods(false).each do |m|
240
- klass.class_eval do
241
- define_method m do |*args, &block|
242
- begin
243
- # Prefer existing method.
244
- # super will invoke method missing on driver
245
- super(*args, &block)
246
-
247
- # minitest also defines a name method,
248
- # so rescue argument error
249
- # and call the name method on $driver
250
- rescue NoMethodError, ArgumentError
251
- driver.send m, *args, &block if driver.respond_to?(m)
252
- end
253
- end
254
- end
255
- end
256
- end
257
- nil # return nil
258
- end
259
-
260
- def self.selenium_webdriver_version_more?(version)
261
- require 'rubygems'
262
- Gem.loaded_specs['selenium-webdriver'].version >= Gem::Version.new(version)
263
- end
264
-
265
17
  class Driver
266
18
  # attr readers are promoted to global scope. To avoid clobbering, they're
267
19
  # made available via the driver_attributes method
@@ -270,52 +22,41 @@ module Appium
270
22
 
271
23
  # The amount to sleep in seconds before every webdriver http call.
272
24
  attr_accessor :global_webdriver_http_sleep
273
- # Selenium webdriver capabilities
274
- attr_reader :caps
275
- # Custom URL for the selenium server
276
- attr_reader :custom_url
277
- # Export session id to textfile in /tmp for 3rd party tools
278
- attr_reader :export_session
279
- # Default wait time for elements to appear
280
- # Returns the default client side wait.
281
- # This value is independent of what the server is using
282
- # @return [Integer]
283
- attr_reader :default_wait
25
+
26
+ # SauceLab's settings
27
+ attr_reader :sauce
284
28
  # Username for use on Sauce Labs. Set `false` to disable Sauce, even when SAUCE_USERNAME is in ENV.
29
+ # same as @sauce.username
285
30
  attr_reader :sauce_username
286
31
  # Access Key for use on Sauce Labs. Set `false` to disable Sauce, even when SAUCE_ACCESS_KEY is in ENV.
32
+ # same as @sauce.access_key
287
33
  attr_reader :sauce_access_key
288
34
  # Override the Sauce Appium endpoint to allow e.g. TestObject tests
35
+ # same as @sauce.endpoint
289
36
  attr_reader :sauce_endpoint
290
- # Appium's server port
37
+
38
+ # from Core
39
+ attr_reader :caps
40
+ attr_reader :custom_url
41
+ attr_reader :export_session
42
+ attr_reader :default_wait
291
43
  attr_reader :appium_port
292
- # Device type to request from the appium server
293
44
  attr_reader :appium_device
294
- # Automation name sent to appium server or received from server
295
- # If automation_name is nil, it is not set both client side and server side.
296
45
  attr_reader :automation_name
46
+ attr_reader :listener
47
+ attr_reader :http_client
48
+ attr_reader :appium_wait_timeout
49
+ attr_reader :appium_wait_interval
50
+
297
51
  # Appium's server version
298
52
  attr_reader :appium_server_status
299
53
  # Boolean debug mode for the Appium Ruby bindings
300
54
  attr_reader :appium_debug
301
- # instance of AbstractEventListener for logging support
302
- attr_reader :listener
303
55
  # Returns the driver
304
56
  # @return [Driver] the driver
305
57
  attr_reader :driver
306
- # Return http client called in start_driver()
307
- # @return [Selenium::WebDriver::Remote::Http::Default] the http client
308
- attr_reader :http_client
309
- # Return a time wait timeout
310
- # Wait time for ::Appium::Common.wait or ::Appium::Common.wait_true.
311
- # Provide Appium::Drive like { appium_lib: { wait_timeout: 20 } }
312
- # @return [Integer]
313
- attr_reader :appium_wait_timeout
314
- # Return a time wait timeout
315
- # Wait interval time for ::Appium::Common.wait or ::Appium::Common.wait_true.
316
- # Provide Appium::Drive like { appium_lib: { wait_interval: 20 } }
317
- # @return [Integer]
318
- attr_reader :appium_wait_interval
58
+ # Instance of Appium::Core::Driver
59
+ attr_reader :core
319
60
 
320
61
  # Creates a new driver. The driver is defined as global scope by default.
321
62
  # We can avoid defining global driver.
@@ -338,7 +79,7 @@ module Appium
338
79
  # wait_timeout: 30
339
80
  # }
340
81
  # }
341
- # Appium::Driver.new(opts).start_driver
82
+ # Appium::Driver.new(opts, true).start_driver
342
83
  #
343
84
  # # Start Android driver with global scope
344
85
  # opts = {
@@ -351,7 +92,7 @@ module Appium
351
92
  # wait_interval: 1
352
93
  # }
353
94
  # }
354
- # Appium::Driver.new(opts).start_driver
95
+ # Appium::Driver.new(opts, true).start_driver
355
96
  #
356
97
  # # Start iOS driver without global scope
357
98
  # opts = {
@@ -370,6 +111,7 @@ module Appium
370
111
  # @param global_driver [Bool] A bool require global driver before initialize.
371
112
  # @return [Driver]
372
113
  def initialize(opts = {}, global_driver = nil)
114
+ # TODO: set `global_driver = false` by default in the future.
373
115
  if global_driver.nil?
374
116
  warn '[DEPRECATION] Appium::Driver.new(opts) will not generate global driver by default.' \
375
117
  'If you would like to generate the global driver dy default, ' \
@@ -382,68 +124,41 @@ module Appium
382
124
  end
383
125
  raise 'opts must be a hash' unless opts.is_a? Hash
384
126
 
385
- opts = Appium.symbolize_keys opts
386
- @caps = Capabilities.init_caps_for_appium(opts[:caps] || {})
127
+ @core = Appium::Core.for(self, opts)
387
128
 
388
- appium_lib_opts = opts[:appium_lib] || {}
129
+ opts = Appium.symbolize_keys opts
130
+ appium_lib_opts = opts[:appium_lib] || {}
389
131
 
390
- set_appium_lib_specific_values(appium_lib_opts)
132
+ @caps = @core.caps
133
+ @custom_url = @core.custom_url
134
+ @export_session = @core.export_session
135
+ @default_wait = @core.default_wait
136
+ @appium_port = @core.port
137
+ @appium_wait_timeout = @core.wait_timeout
138
+ @appium_wait_interval = @core.wait_interval
139
+ @listener = @core.listener
140
+ @appium_device = @core.device
141
+ @automation_name = @core.automation_name
391
142
 
392
- # Path to the .apk, .app or .app.zip.
393
- # The path can be local or remote for Sauce.
394
- if @caps && @caps[:app] && !@caps[:app].empty?
395
- @caps[:app] = self.class.absolute_app_path opts
396
- end
143
+ # override opts[:app] if sauce labs
144
+ set_app_path(opts)
397
145
 
398
- # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
399
- @appium_device = @caps[:platformName]
400
- @appium_device = @appium_device.is_a?(Symbol) ? @appium_device : @appium_device.downcase.strip.intern if @appium_device
401
-
402
- @automation_name = @caps[:automationName] if @caps[:automationName]
403
- @automation_name = if @automation_name
404
- @automation_name.is_a?(Symbol) ? @automation_name : @automation_name.downcase.strip.intern
405
- end
146
+ # enable debug patch
147
+ @appium_debug = appium_lib_opts.fetch :debug, !!defined?(Pry)
148
+ set_sauce_related_values(appium_lib_opts)
406
149
 
407
- # load common methods
150
+ # Extend Common methods
408
151
  extend Appium::Common
409
- extend Appium::Device
410
-
411
- if device_is_android?
412
- extend Appium::Android
413
- extend Appium::Android::Device
414
- if automation_name_is_uiautomator2?
415
- extend Appium::Android::Uiautomator2
416
- extend Appium::Android::Uiautomator2::Helper
417
- extend Appium::Android::Uiautomator2::Element
418
- end
419
- else
420
- extend Appium::Ios
421
- if automation_name_is_xcuitest?
422
- extend Appium::Ios::Xcuitest
423
- extend Appium::Ios::Xcuitest::SearchContext
424
- extend Appium::Ios::Xcuitest::Command
425
- extend Appium::Ios::Xcuitest::Helper
426
- extend Appium::Ios::Xcuitest::Gesture
427
- extend Appium::Ios::Xcuitest::Device
428
- extend Appium::Ios::Xcuitest::Element
429
- end
430
- end
431
152
 
432
- # apply os specific patches
433
- patch_webdriver_element
153
+ # Extend each driver's methods
154
+ extend_for(device: @core.device, automation_name: @core.automation_name)
434
155
 
435
156
  # for command
436
- patch_remote_driver_commands
437
-
438
- # enable debug patch
439
- # !!'constant' == true
440
- @appium_debug = appium_lib_opts.fetch :debug, !!defined?(Pry)
441
157
 
442
158
  if @appium_debug
443
159
  Appium::Logger.ap_debug opts unless opts.empty?
444
160
  Appium::Logger.debug "Debug is: #{@appium_debug}"
445
- Appium::Logger.debug "Device is: #{@appium_device}"
446
- patch_webdriver_bridge
161
+ Appium::Logger.debug "Device is: #{@core.device}"
447
162
  end
448
163
 
449
164
  # Save global reference to last created Appium driver for top level methods.
@@ -454,27 +169,47 @@ module Appium
454
169
 
455
170
  private
456
171
 
457
- def set_appium_lib_specific_values(appium_lib_opts)
458
- @custom_url = appium_lib_opts.fetch :server_url, false
459
- @export_session = appium_lib_opts.fetch :export_session, false
460
- @default_wait = appium_lib_opts.fetch :wait, 0
172
+ # @private
173
+ def extend_for(device:, automation_name:)
174
+ case device
175
+ when :android
176
+ case automation_name
177
+ when :uiautomator2
178
+ ::Appium::Android::Uiautomator2::Bridge.for(self)
179
+ else # default and UiAutomator
180
+ ::Appium::Android::Bridge.for(self)
181
+ end
182
+ when :ios
183
+ case automation_name
184
+ when :xcuitest
185
+ ::Appium::Ios::Xcuitest::Bridge.for(self)
186
+ else # default and UIAutomation
187
+ ::Appium::Ios::Bridge.for(self)
188
+ end
189
+ when :mac
190
+ # no Mac specific extentions
191
+ Appium::Logger.debug('mac')
192
+ when :windows
193
+ # no windows specific extentions
194
+ Appium::Logger.debug('windows')
195
+ else
196
+ Appium::Logger.warn('no device matched')
197
+ end
198
+ end
461
199
 
462
- @sauce_username = appium_lib_opts.fetch :sauce_username, ENV['SAUCE_USERNAME']
463
- @sauce_username = nil if !@sauce_username || (@sauce_username.is_a?(String) && @sauce_username.empty?)
464
- @sauce_access_key = appium_lib_opts.fetch :sauce_access_key, ENV['SAUCE_ACCESS_KEY']
465
- @sauce_access_key = nil if !@sauce_access_key || (@sauce_access_key.is_a?(String) && @sauce_access_key.empty?)
466
- @sauce_endpoint = appium_lib_opts.fetch :sauce_endpoint, ENV['SAUCE_ENDPOINT']
467
- @sauce_endpoint = 'ondemand.saucelabs.com:443/wd/hub' if
468
- !@sauce_endpoint || (@sauce_endpoint.is_a?(String) && @sauce_endpoint.empty?)
200
+ # @private
201
+ def set_app_path(opts)
202
+ return unless @core.caps && @core.caps[:app] && !@core.caps[:app].empty?
469
203
 
470
- @appium_port = appium_lib_opts.fetch :port, 4723
471
- # timeout and interval used in ::Appium::Comm.wait/wait_true
472
- @appium_wait_timeout = appium_lib_opts.fetch :wait_timeout, 30
473
- @appium_wait_interval = appium_lib_opts.fetch :wait_interval, 0.5
204
+ @core.caps[:app] = self.class.absolute_app_path opts
205
+ end
474
206
 
475
- # to pass it in Selenium.new.
476
- # `listener = opts.delete(:listener)` is called in Selenium::Driver.new
477
- @listener = appium_lib_opts.fetch :listener, nil
207
+ # @private
208
+ def set_sauce_related_values(appium_lib_opts)
209
+ @sauce = Appium::SauceLabs.new(appium_lib_opts)
210
+ @sauce_username = @sauce.username
211
+ @sauce_access_key = @sauce.access_key
212
+ @sauce_endpoint = @sauce.endpoint
478
213
  end
479
214
 
480
215
  public
@@ -482,43 +217,51 @@ module Appium
482
217
  # Returns a hash of the driver attributes
483
218
  def driver_attributes
484
219
  {
485
- caps: @caps,
486
- automation_name: @automation_name,
487
- custom_url: @custom_url,
488
- export_session: @export_session,
489
- default_wait: @default_wait,
490
- sauce_username: @sauce_username,
491
- sauce_access_key: @sauce_access_key,
492
- sauce_endpoint: @sauce_endpoint,
493
- port: @appium_port,
494
- device: @appium_device,
220
+ caps: @core.caps,
221
+ automation_name: @core.automation_name,
222
+ custom_url: @core.custom_url,
223
+ export_session: @core.export_session,
224
+ default_wait: @core.default_wait,
225
+ sauce_username: @sauce.username,
226
+ sauce_access_key: @sauce.access_key,
227
+ sauce_endpoint: @sauce.endpoint,
228
+ port: @core.port,
229
+ device: @core.device,
495
230
  debug: @appium_debug,
496
231
  listener: @listener,
497
- wait_timeout: @appium_wait_timeout,
498
- wait_interval: @appium_wait_interval
232
+ wait_timeout: @core.wait_timeout,
233
+ wait_interval: @core.wait_interval
499
234
  }
500
235
  end
501
236
 
502
237
  def device_is_android?
503
- @appium_device == :android
238
+ @core.device == :android
504
239
  end
505
240
 
506
- # Return true if automationName is 'XCUITest'
507
- # @return [Boolean]
508
- def automation_name_is_xcuitest?
509
- !@automation_name.nil? && @automation_name == :xcuitest
241
+ def device_is_ios?
242
+ @core.device == :ios
243
+ end
244
+
245
+ def device_is_windows?
246
+ @core.device == :windows
510
247
  end
511
248
 
512
249
  # Return true if automationName is 'uiautomator2'
513
250
  # @return [Boolean]
514
251
  def automation_name_is_uiautomator2?
515
- !@automation_name.nil? && @automation_name == :uiautomator2
252
+ !@core.automation_name.nil? && @core.automation_name == :uiautomator2
516
253
  end
517
254
 
518
255
  # Return true if automationName is 'Espresso'
519
256
  # @return [Boolean]
520
257
  def automation_name_is_espresso?
521
- !@automation_name.nil? && @automation_name == :espresso
258
+ !@core.automation_name.nil? && @core.automation_name == :espresso
259
+ end
260
+
261
+ # Return true if automationName is 'XCUITest'
262
+ # @return [Boolean]
263
+ def automation_name_is_xcuitest?
264
+ !@core.automation_name.nil? && @core.automation_name == :xcuitest
522
265
  end
523
266
 
524
267
  # Return true if the target Appium server is over REQUIRED_VERSION_XCUITEST.
@@ -528,7 +271,8 @@ module Appium
528
271
  if automation_name_is_xcuitest? &&
529
272
  !@appium_server_status.empty? &&
530
273
  (@appium_server_status['build']['version'] < REQUIRED_VERSION_XCUITEST)
531
- raise Appium::Error::NotSupportedAppiumServer, "XCUITest requires Appium version >= #{REQUIRED_VERSION_XCUITEST}"
274
+ raise(Appium::Core::Error::NotSupportedAppiumServer,
275
+ "XCUITest requires Appium version >= #{REQUIRED_VERSION_XCUITEST}")
532
276
  end
533
277
  true
534
278
  end
@@ -544,23 +288,25 @@ module Appium
544
288
  # }
545
289
  # ```
546
290
  #
547
- # Returns blank hash for Selenium Grid since `remote_status` gets 500 error
548
- #
549
- # ```ruby
550
- # {}
551
- # ```
552
- #
553
291
  # @return [Hash]
554
292
  def appium_server_version
555
- driver.remote_status
293
+ @core.appium_server_version
556
294
  rescue Selenium::WebDriver::Error::WebDriverError => ex
557
- raise ::Appium::Error::ServerError unless ex.message.include?('content-type=""')
295
+ raise ::Appium::Core::Error::ServerError unless ex.message.include?('content-type=""')
558
296
  # server (TestObject for instance) does not respond to status call
559
297
  {}
560
- rescue Selenium::WebDriver::Error::ServerError => e
561
- raise ::Appium::Error::ServerError unless e.message.include?('status code 500')
562
- # driver.remote_status returns 500 error for using selenium grid
563
- {}
298
+ end
299
+
300
+ # Return the platform version as an array of integers
301
+ # @return [Array<Integer>]
302
+ def platform_version
303
+ @core.platform_version
304
+ end
305
+
306
+ # @private
307
+ def ios_version
308
+ warn '[DEPRECATION] ios_version will be removed. Please use platform_version instead.'
309
+ platform_version
564
310
  end
565
311
 
566
312
  # Returns the client's version info
@@ -618,12 +364,9 @@ module Appium
618
364
  # Get the server url
619
365
  # @return [String] the server url
620
366
  def server_url
621
- return @custom_url if @custom_url
622
- if !@sauce_username.nil? && !@sauce_access_key.nil?
623
- "https://#{@sauce_username}:#{@sauce_access_key}@#{@sauce_endpoint}"
624
- else
625
- "http://127.0.0.1:#{@appium_port}/wd/hub"
626
- end
367
+ return @core.custom_url if @core.custom_url
368
+ return @sauce.server_url if @sauce.sauce_server_url?
369
+ "http://127.0.0.1:#{@core.port}/wd/hub"
627
370
  end
628
371
 
629
372
  # Restarts the driver
@@ -640,21 +383,26 @@ module Appium
640
383
  # @param png_save_path [String] the full path to save the png
641
384
  # @return [nil]
642
385
  def screenshot(png_save_path)
643
- @driver.save_screenshot png_save_path
644
- nil
386
+ @core.screenshot png_save_path
645
387
  end
646
388
 
647
389
  # Quits the driver
648
390
  # @return [void]
649
391
  def driver_quit
650
- # rescue NoSuchDriverError or nil driver
651
- @driver.quit
652
- rescue
653
- nil
392
+ @core.quit_driver
654
393
  end
394
+ alias quit_driver driver_quit
655
395
 
656
- # Alias for driver_quit
657
- alias_method :quit_driver, :driver_quit
396
+ # Get the device window's size.
397
+ # @return [Selenium::WebDriver::Dimension]
398
+ #
399
+ # @example
400
+ # size = @driver.window_size
401
+ # size.width #=> Integer
402
+ # size.height #=> Integer
403
+ def window_size
404
+ @driver.window_size
405
+ end
658
406
 
659
407
  # Creates a new global driver and quits the old one if it exists.
660
408
  # You can customise http_client as the following
@@ -683,40 +431,23 @@ module Appium
683
431
  # @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
684
432
  # @return [Selenium::WebDriver] the new global driver
685
433
  def start_driver(http_client_ops = { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
686
- # open_timeout and read_timeout are explicit wait.
687
- open_timeout = http_client_ops.delete(:open_timeout)
688
- read_timeout = http_client_ops.delete(:read_timeout)
434
+ driver_quit
689
435
 
690
- http_client = http_client_ops.delete(:http_client)
691
- @http_client ||= http_client ? http_client : Selenium::WebDriver::Remote::Http::Default.new
436
+ # If automationName is set only in server side, then the following automation_name should be nil before
437
+ # starting driver.
438
+ automation_name = @core.automation_name
692
439
 
693
- @http_client.open_timeout = open_timeout if open_timeout
694
- @http_client.read_timeout = read_timeout if read_timeout
440
+ @driver = @core.start_driver(server_url: server_url, http_client_ops: http_client_ops)
441
+ @http_client = @core.http_client
695
442
 
696
- begin
697
- driver_quit
698
- @driver = Selenium::WebDriver.for(:remote,
699
- http_client: @http_client,
700
- desired_capabilities: @caps,
701
- url: server_url,
702
- listener: @listener)
703
-
704
- # Load touch methods.
705
- @driver.extend Selenium::WebDriver::DriverExtensions::HasTouchScreen
706
- @driver.extend Selenium::WebDriver::DriverExtensions::HasLocation
707
-
708
- # export session
709
- write_session_id(@driver.session_id) if @export_session
710
- rescue Errno::ECONNREFUSED
711
- raise "ERROR: Unable to connect to Appium. Is the server running on #{server_url}?"
712
- end
443
+ # if automation_name was nil before start_driver, then re-extend driver specific methods
444
+ # to be able to extend correctly.
445
+ extend_for(device: @core.device, automation_name: @core.automation_name) if automation_name.nil?
713
446
 
714
447
  @appium_server_status = appium_server_version
715
-
716
448
  check_server_version_xcuitest
717
- set_automation_name_if_nil
718
449
 
719
- set_implicit_wait(@default_wait)
450
+ set_implicit_wait(@core.default_wait)
720
451
 
721
452
  @driver
722
453
  end
@@ -726,7 +457,7 @@ module Appium
726
457
  @driver.manage.timeouts.implicit_wait = wait
727
458
  rescue Selenium::WebDriver::Error::UnknownError => e
728
459
  unless e.message.include?('The operation requested is not yet implemented by Espresso driver')
729
- raise ::Appium::Error::ServerError
460
+ raise ::Appium::Core::Error::ServerError
730
461
  end
731
462
  {}
732
463
  end
@@ -736,18 +467,18 @@ module Appium
736
467
  @driver.manage.timeouts.implicit_wait = 0
737
468
  end
738
469
 
739
- # Set implicit wait. Default to @default_wait.
470
+ # Set implicit wait. Default to @core.default_wait.
740
471
  #
741
472
  # ```ruby
742
473
  # set_wait 2
743
- # set_wait # @default_wait
474
+ # set_wait # @core.default_wait
744
475
  #
745
476
  # ```
746
477
  #
747
478
  # @param timeout [Integer] the timeout in seconds
748
479
  # @return [void]
749
480
  def set_wait(timeout = nil)
750
- timeout = @default_wait if timeout.nil?
481
+ timeout = @core.default_wait if timeout.nil?
751
482
  @driver.manage.timeouts.implicit_wait = timeout
752
483
  end
753
484
 
@@ -763,7 +494,7 @@ module Appium
763
494
  # wait to after checking existence
764
495
  # @yield The block to call
765
496
  # @return [Boolean]
766
- def exists(pre_check = 0, post_check = @default_wait)
497
+ def exists(pre_check = 0, post_check = @core.default_wait)
767
498
  # do not uset set_wait here.
768
499
  # it will cause problems with other methods reading the default_wait of 0
769
500
  # which then gets converted to a 1 second wait.
@@ -793,17 +524,19 @@ module Appium
793
524
 
794
525
  # Calls @driver.find_elements_with_appium
795
526
  #
796
- # ```
797
- # @driver = Appium::Driver.new()
798
- # @driver.find_elements :predicate, yyy
799
- # ```
527
+ # @example
528
+ # ```ruby
529
+ # @driver = Appium::Driver.new(opts, false)
530
+ # @driver.find_elements :predicate, yyy
531
+ # ```
800
532
  #
801
533
  # If you call `Appium.promote_appium_methods`, you can call `find_elements` directly.
802
534
  #
803
- # ```
804
- # @driver = Appium::Driver.new()
805
- # @driver.find_elements :predicate, yyy
806
- # ```
535
+ # @example
536
+ # ```ruby
537
+ # @driver = Appium::Driver.new(opts, false)
538
+ # @driver.find_elements :predicate, yyy
539
+ # ```
807
540
  #
808
541
  # If you call `Appium.promote_appium_methods`, you can call `find_elements` directly.
809
542
  #
@@ -815,10 +548,11 @@ module Appium
815
548
 
816
549
  # Calls @driver.find_element
817
550
  #
818
- # ```
819
- # @driver = Appium::Driver.new()
820
- # @driver.find_element :accessibility_id, zzz
821
- # ```
551
+ # @example
552
+ # ```ruby
553
+ # @driver = Appium::Driver.new(opts, false)
554
+ # @driver.find_element :accessibility_id, zzz
555
+ # ```
822
556
  #
823
557
  # If you call `Appium.promote_appium_methods`, you can call `find_element` directly.
824
558
  #
@@ -854,19 +588,13 @@ module Appium
854
588
 
855
589
  private
856
590
 
591
+ # @private
857
592
  def write_session_id(session_id)
858
593
  File.open('/tmp/appium_lib_session', 'w') { |f| f.puts session_id }
859
594
  rescue IOError => e
860
595
  ::Appium::Logger.warn e
861
596
  nil
862
597
  end
863
-
864
- # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
865
- # Since @automation_name is set only client side before start_driver is called.
866
- def set_automation_name_if_nil
867
- return unless @automation_name.nil?
868
- @automation_name = @driver.capabilities['automationName']
869
- end
870
598
  end # class Driver
871
599
  end # module Appium
872
600