appium_lib 9.6.1 → 9.7.0

Sign up to get free protection for your applications and to get access to all the features.
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