appium_lib_core 4.1.0 → 5.8.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +187 -273
  3. data/README.md +32 -12
  4. data/Rakefile +4 -0
  5. data/appium_lib_core.gemspec +5 -8
  6. data/bin/console +0 -4
  7. data/lib/appium_lib_core/android/device/auth_finger_print.rb +2 -1
  8. data/lib/appium_lib_core/android/device.rb +4 -4
  9. data/lib/appium_lib_core/common/base/bridge.rb +315 -90
  10. data/lib/appium_lib_core/common/base/capabilities.rb +8 -9
  11. data/lib/appium_lib_core/common/base/device_ime.rb +49 -0
  12. data/lib/appium_lib_core/common/base/driver.rb +222 -187
  13. data/lib/appium_lib_core/common/base/driver_settings.rb +51 -0
  14. data/lib/appium_lib_core/common/base/has_location.rb +80 -0
  15. data/lib/appium_lib_core/common/base/has_network_connection.rb +56 -0
  16. data/lib/appium_lib_core/common/base/http_default.rb +1 -3
  17. data/lib/appium_lib_core/common/base/remote_status.rb +31 -0
  18. data/lib/appium_lib_core/common/base/rotable.rb +54 -0
  19. data/lib/appium_lib_core/common/base/screenshot.rb +6 -6
  20. data/lib/appium_lib_core/common/base/search_context.rb +20 -5
  21. data/lib/appium_lib_core/common/base.rb +1 -3
  22. data/lib/appium_lib_core/common/command.rb +255 -4
  23. data/lib/appium_lib_core/common/device/app_management.rb +8 -14
  24. data/lib/appium_lib_core/common/device/image_comparison.rb +12 -4
  25. data/lib/appium_lib_core/common/device/keyevent.rb +4 -4
  26. data/lib/appium_lib_core/common/{command/mjsonwp.rb → device/orientation.rb} +14 -11
  27. data/lib/appium_lib_core/common/device/touch_actions.rb +2 -0
  28. data/lib/appium_lib_core/common/device/value.rb +6 -6
  29. data/lib/appium_lib_core/common/error.rb +4 -5
  30. data/lib/appium_lib_core/common/log.rb +4 -1
  31. data/lib/appium_lib_core/common/touch_action/multi_touch.rb +19 -0
  32. data/lib/appium_lib_core/common/touch_action/touch_actions.rb +16 -2
  33. data/lib/appium_lib_core/common/wait.rb +38 -6
  34. data/lib/appium_lib_core/device.rb +1 -5
  35. data/lib/appium_lib_core/driver.rb +166 -97
  36. data/lib/appium_lib_core/{patch.rb → element.rb} +66 -9
  37. data/lib/appium_lib_core/ios/uiautomation/patch.rb +1 -1
  38. data/lib/appium_lib_core/{common/base/command.rb → mac2/bridge.rb} +9 -8
  39. data/lib/appium_lib_core/mac2/device/screen.rb +48 -0
  40. data/lib/appium_lib_core/mac2/device.rb +92 -0
  41. data/lib/appium_lib_core/mac2.rb +17 -0
  42. data/lib/appium_lib_core/version.rb +2 -2
  43. data/lib/appium_lib_core/windows/device/app_management.rb +38 -0
  44. data/lib/appium_lib_core/windows/device.rb +2 -0
  45. data/lib/appium_lib_core.rb +20 -10
  46. metadata +30 -82
  47. data/.github/ISSUE_TEMPLATE/issue-report.md +0 -29
  48. data/.github/contributing.md +0 -26
  49. data/.github/issue_template.md +0 -20
  50. data/.github/workflows/unittest.yml +0 -68
  51. data/.gitignore +0 -18
  52. data/.rubocop.yml +0 -58
  53. data/azure-pipelines.yml +0 -15
  54. data/ci-jobs/functional/android_setup.yml +0 -3
  55. data/ci-jobs/functional/ios_setup.yml +0 -7
  56. data/ci-jobs/functional/publish_test_result.yml +0 -18
  57. data/ci-jobs/functional/run_appium.yml +0 -25
  58. data/ci-jobs/functional/start-emulator.sh +0 -26
  59. data/ci-jobs/functional_test.yml +0 -298
  60. data/docs/mobile_command.md +0 -34
  61. data/lib/appium_lib_core/common/base/bridge/mjsonwp.rb +0 -81
  62. data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -252
  63. data/lib/appium_lib_core/common/command/common.rb +0 -110
  64. data/lib/appium_lib_core/common/command/w3c.rb +0 -56
  65. data/release_notes.md +0 -816
  66. data/script/commands.rb +0 -200
@@ -38,6 +38,8 @@ module Appium
38
38
  #
39
39
  # result = Appium::Core::Wait.until { @driver.find_element(:id, 'something') }
40
40
  #
41
+ # result = Appium::Core::Wait.until(timeout: 30, message: 'timeout') { @driver.find_element(:id, 'something') }
42
+ #
41
43
  # result = Appium::Core::Wait.until(object: 'some object') { |object|
42
44
  # @driver.find_element(:id, object)
43
45
  # }
@@ -82,6 +84,8 @@ module Appium
82
84
  #
83
85
  # Appium::Core::Wait.until_true { @driver.find_element(:id, 'something') }
84
86
  #
87
+ # Appium::Core::Wait.until_true(timeout: 30) { @driver.find_element(:id, 'something') }
88
+ #
85
89
  # Appium::Core::Wait.until_true(object: 'some object') { |object|
86
90
  # @driver.find_element(:id, object)
87
91
  # }
@@ -133,17 +137,31 @@ module Appium
133
137
  # @param [String] message Exception message if timed out.
134
138
  # @param [Array, Exception] ignored Exceptions to ignore while polling (default: Exception)
135
139
  #
136
- # @example
140
+ # @example With core instance
137
141
  #
138
142
  # @core.wait_true { @driver.find_element :accessibility_id, 'something' }
143
+ # @core.wait_true(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
144
+ #
145
+ # @core.wait_until_true { @driver.find_element :accessibility_id, 'something' }
146
+ # @core.wait_until_true(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
147
+ #
148
+ # @example With driver instance
149
+ #
150
+ # @driver.wait_true { |d| d.find_element :accessibility_id, 'something' }
151
+ # @driver.wait_true(timeout: 30, interval: 2) { |d| driver.find_element :accessibility_id, 'something' }
139
152
  #
140
- def wait_true(timeout: nil, interval: nil, message: nil, ignored: nil)
153
+ # @driver.wait_until_true { |d| d.find_element :accessibility_id, 'something' }
154
+ # @driver.wait_until_true(timeout: 30, interval: 2) { |d| driver.find_element :accessibility_id, 'something' }
155
+ #
156
+ def wait_until_true(timeout: nil, interval: nil, message: nil, ignored: nil, &block)
141
157
  Wait.until_true(timeout: timeout || @wait_timeout,
142
158
  interval: interval || @wait_interval,
143
159
  message: message,
144
160
  ignored: ignored,
145
- object: self) { yield }
161
+ object: self,
162
+ &block)
146
163
  end
164
+ alias wait_true wait_until_true
147
165
 
148
166
  # Check every interval seconds to see if yield doesn't raise an exception.
149
167
  # Give up after timeout seconds.
@@ -155,17 +173,31 @@ module Appium
155
173
  # @param [String] message Exception message if timed out.
156
174
  # @param [Array, Exception] ignored Exceptions to ignore while polling (default: Exception)
157
175
  #
158
- # @example
176
+ # @example With core instance
159
177
  #
160
178
  # @core.wait { @driver.find_element :accessibility_id, 'something' }
179
+ # @core.wait(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
180
+ #
181
+ # @core.wait_until { @driver.find_element :accessibility_id, 'something' }
182
+ # @core.wait_until(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
183
+ #
184
+ # @example With driver instance
185
+ #
186
+ # @driver.wait { @driver.find_element :accessibility_id, 'something' }
187
+ # @driver.wait(timeout: 30, interval: 2) { @driver.find_element :accessibility_id, 'something' }
188
+ #
189
+ # @driver.wait_until { |d| d.find_element :accessibility_id, 'something' }
190
+ # @driver.wait_until(timeout: 30, interval: 2) { |d| d.find_element :accessibility_id, 'something' }
161
191
  #
162
- def wait(timeout: nil, interval: nil, message: nil, ignored: nil)
192
+ def wait_until(timeout: nil, interval: nil, message: nil, ignored: nil, &block)
163
193
  Wait.until(timeout: timeout || @wait_timeout,
164
194
  interval: interval || @wait_interval,
165
195
  message: message,
166
196
  ignored: ignored,
167
- object: self) { yield }
197
+ object: self,
198
+ &block)
168
199
  end
200
+ alias wait wait_until
169
201
  end
170
202
  end # module Core
171
203
  end # module Appium
@@ -79,11 +79,7 @@ module Appium
79
79
  end
80
80
 
81
81
  def create_bridge_command(method, &block)
82
- ::Appium::Core::Base::Bridge::MJSONWP.class_eval do
83
- undef_method method if method_defined? method
84
- block_given? ? class_eval(&block) : define_method(method) { execute method }
85
- end
86
- ::Appium::Core::Base::Bridge::W3C.class_eval do
82
+ ::Appium::Core::Base::Bridge.class_eval do
87
83
  undef_method method if method_defined? method
88
84
  block_given? ? class_eval(&block) : define_method(method) { execute method }
89
85
  end
@@ -27,6 +27,8 @@ module Appium
27
27
  autoload :Xcuitest, 'appium_lib_core/ios_xcuitest'
28
28
  end
29
29
 
30
+ autoload :Mac2, 'appium_lib_core/mac2'
31
+
30
32
  autoload :Windows, 'appium_lib_core/windows'
31
33
 
32
34
  # This options affects only client side as <code>:appium_lib</code> key.<br>
@@ -38,18 +40,18 @@ module Appium
38
40
 
39
41
  def initialize(appium_lib_opts)
40
42
  @custom_url = appium_lib_opts.fetch :server_url, nil
41
- @default_wait = appium_lib_opts.fetch :wait, Driver::DEFAULT_IMPLICIT_WAIT
43
+ @default_wait = appium_lib_opts.fetch :wait, nil
42
44
  @enable_idempotency_header = appium_lib_opts.fetch :enable_idempotency_header, true
43
45
 
44
46
  # bump current session id into a particular file
45
47
  @export_session = appium_lib_opts.fetch :export_session, false
46
48
  @export_session_path = appium_lib_opts.fetch :export_session_path, default_tmp_appium_lib_session
47
49
 
48
- @direct_connect = appium_lib_opts.fetch :direct_connect, false
50
+ @direct_connect = appium_lib_opts.fetch :direct_connect, true
49
51
 
50
52
  @port = appium_lib_opts.fetch :port, Driver::DEFAULT_APPIUM_PORT
51
53
 
52
- # timeout and interval used in ::Appium::Comm.wait/wait_true
54
+ # timeout and interval used in ::Appium::Commn.wait/wait_true
53
55
  @wait_timeout = appium_lib_opts.fetch :wait_timeout, ::Appium::Core::Wait::DEFAULT_TIMEOUT
54
56
  @wait_interval = appium_lib_opts.fetch :wait_interval, ::Appium::Core::Wait::DEFAULT_INTERVAL
55
57
 
@@ -74,6 +76,13 @@ module Appium
74
76
  path: 'directConnectPath'
75
77
  }.freeze
76
78
 
79
+ W3C_KEYS = {
80
+ protocol: 'appium:directConnectProtocol',
81
+ host: 'appium:directConnectHost',
82
+ port: 'appium:directConnectPort',
83
+ path: 'appium:directConnectPath'
84
+ }.freeze
85
+
77
86
  # @return [string] Returns a protocol such as http/https
78
87
  attr_reader :protocol
79
88
 
@@ -87,10 +96,10 @@ module Appium
87
96
  attr_reader :path
88
97
 
89
98
  def initialize(capabilities)
90
- @protocol = capabilities[KEYS[:protocol]]
91
- @host = capabilities[KEYS[:host]]
92
- @port = capabilities[KEYS[:port]]
93
- @path = capabilities[KEYS[:path]]
99
+ @protocol = capabilities[W3C_KEYS[:protocol]] || capabilities[KEYS[:protocol]]
100
+ @host = capabilities[W3C_KEYS[:host]] || capabilities[KEYS[:host]]
101
+ @port = capabilities[W3C_KEYS[:port]] || capabilities[KEYS[:port]]
102
+ @path = capabilities[W3C_KEYS[:path]] || capabilities[KEYS[:path]]
94
103
  end
95
104
  end
96
105
 
@@ -133,11 +142,9 @@ module Appium
133
142
  attr_reader :export_session_path
134
143
 
135
144
  # Default wait time for elements to appear in Appium server side.
136
- # Defaults to {::Appium::Core::Driver::DEFAULT_IMPLICIT_WAIT}.<br>
137
145
  # Provide <code>{ appium_lib: { wait: 30 } }</code> to {::Appium::Core.for}
138
146
  # @return [Integer]
139
147
  attr_reader :default_wait
140
- DEFAULT_IMPLICIT_WAIT = 0
141
148
 
142
149
  # Appium's server port. 4723 is by default. Defaults to {::Appium::Core::Driver::DEFAULT_APPIUM_PORT}.<br>
143
150
  # Provide <code>{ appium_lib: { port: 8080 } }</code> to {::Appium::Core.for}.
@@ -175,7 +182,8 @@ module Appium
175
182
  # - <code>directConnectPort</code>
176
183
  # - <code>directConnectPath</code>
177
184
  #
178
- # Ignore them if this parameter is <code>false</code>. Defaults to false.
185
+ # ignore them if this parameter is <code>false</code>. Defaults to true.
186
+ # These keys can have <code>appium:</code> prefix.
179
187
  #
180
188
  # @return [Bool]
181
189
  attr_reader :direct_connect
@@ -185,8 +193,6 @@ module Appium
185
193
  # @option opts [Hash] :caps Appium capabilities.
186
194
  # @option opts [Hash] :capabilities The same as :caps.
187
195
  # This param is for compatibility with Selenium WebDriver format
188
- # @option opts [Hash] :desired_capabilities The same as :caps.
189
- # This param is for compatibility with Selenium WebDriver format
190
196
  # @option opts [Appium::Core::Options] :appium_lib Capabilities affect only ruby client
191
197
  # @option opts [String] :url The same as :custom_url in :appium_lib.
192
198
  # This param is for compatibility with Selenium WebDriver format
@@ -197,10 +203,8 @@ module Appium
197
203
  #
198
204
  # # format 1
199
205
  # @core = Appium::Core.for caps: {...}, appium_lib: {...}
200
- # # format 2. 'capabilities:' or 'desired_capabilities:' is also available instead of 'caps:'.
206
+ # # format 2. 'capabilities:' is also available instead of 'caps:'.
201
207
  # @core = Appium::Core.for url: "http://127.0.0.1:8080/wd/hub", capabilities: {...}, appium_lib: {...}
202
- # # format 3. 'appium_lib: {...}' can be blank
203
- # @core = Appium::Core.for url: "http://127.0.0.1:8080/wd/hub", desired_capabilities: {...}
204
208
  #
205
209
  #
206
210
  # require 'rubygems'
@@ -228,7 +232,7 @@ module Appium
228
232
  # @core.start_driver # Connect to 'http://127.0.0.1:8080/wd/hub' because of 'port: 8080'
229
233
  #
230
234
  # # Start iOS driver with .zip file over HTTP
231
- # # 'desired_capabilities:' or 'capabilities:' is also available instead of 'caps:'. Either is fine.
235
+ # # 'capabilities:' is also available instead of 'caps:'. Either is fine.
232
236
  # opts = {
233
237
  # capabilities: {
234
238
  # platformName: :ios,
@@ -252,7 +256,7 @@ module Appium
252
256
  # # Start iOS driver as another format. 'url' is available like below
253
257
  # opts = {
254
258
  # url: "http://custom-host:8080/wd/hub.com",
255
- # desired_capabilities: {
259
+ # capabilities: {
256
260
  # platformName: :ios,
257
261
  # platformVersion: '11.0',
258
262
  # deviceName: 'iPhone Simulator',
@@ -271,7 +275,40 @@ module Appium
271
275
  # @core.start_driver # start driver with 'url'. Connect to 'http://custom-host:8080/wd/hub.com'
272
276
  #
273
277
  def self.for(opts = {})
274
- new(opts)
278
+ new.setup_for_new_session(opts)
279
+ end
280
+
281
+ # Attach to an existing session. The main usage of this method is to attach to
282
+ # an existing session for debugging. The generated driver instance has the capabilities which
283
+ # has the given automationName and platformName only since the W3C WebDriver spec does not provide
284
+ # an endpoint to get running session's capabilities.
285
+ #
286
+ #
287
+ # @param [String] The session id to attach to.
288
+ # @param [String] url The WebDriver URL to attach to with the session_id.
289
+ # @param [String] automation_name The platform name to keep in the dummy capabilities
290
+ # @param [String] platform_name The automation name to keep in the dummy capabilities
291
+ # @return [Selenium::WebDriver] A new driver instance with the given session id.
292
+ #
293
+ # @example
294
+ #
295
+ # new_driver = ::Appium::Core::Driver.attach_to(
296
+ # driver.session_id, # The 'driver' has an existing session id
297
+ # url: 'http://127.0.0.1:4723/wd/hub', automation_name: 'UiAutomator2', platform_name: 'Android'
298
+ # )
299
+ # new_driver.page_source # for example
300
+ #
301
+ def self.attach_to(
302
+ session_id, url: nil, automation_name: nil, platform_name: nil,
303
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 }
304
+ )
305
+ new.attach_to(
306
+ session_id,
307
+ automation_name: automation_name,
308
+ platform_name: platform_name,
309
+ url: url,
310
+ http_client_ops: http_client_ops
311
+ )
275
312
  end
276
313
 
277
314
  private
@@ -282,17 +319,25 @@ module Appium
282
319
  @delegate_target
283
320
  end
284
321
 
285
- public
286
-
287
322
  # @private
288
- def initialize(opts = {})
323
+ def initialize
289
324
  @delegate_target = self # for testing purpose
290
325
  @automation_name = nil # initialise before 'set_automation_name'
326
+ end
327
+
328
+ public
291
329
 
292
- opts = Appium.symbolize_keys opts
293
- validate_keys(opts)
330
+ # @private
331
+ # Set up for a neww session
332
+ def setup_for_new_session(opts = {})
333
+ @custom_url = opts.delete :url # to set the custom url as :url
334
+
335
+ # TODO: Remove when we implement Options
336
+ # The symbolize_keys is to keep compatiility for the legacy code, which allows capabilities to give 'string' as the key.
337
+ # The toplevel `caps`, `capabilities` and `appium_lib` are expected to be symbol.
338
+ # FIXME: First, please try to remove `nested: true` to `nested: false`.
339
+ opts = Appium.symbolize_keys(opts, nested: true)
294
340
 
295
- @custom_url = opts.delete :url
296
341
  @caps = get_caps(opts)
297
342
 
298
343
  set_appium_lib_specific_values(get_appium_lib_opts(opts))
@@ -301,8 +346,7 @@ module Appium
301
346
  set_automation_name
302
347
 
303
348
  extend_for(device: @device, automation_name: @automation_name)
304
-
305
- self # rubocop:disable Lint/Void
349
+ self
306
350
  end
307
351
 
308
352
  # Creates a new global driver and quits the old one if it exists.
@@ -313,7 +357,7 @@ module Appium
313
357
  # @option http_client_ops [Hash] :http_client Custom HTTP Client
314
358
  # @option http_client_ops [Hash] :open_timeout Custom open timeout for http client.
315
359
  # @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
316
- # @return [Selenium::WebDriver] the new global driver
360
+ # @return [Selenium::WebDriver] A new driver instance
317
361
  #
318
362
  # @example
319
363
  #
@@ -324,7 +368,7 @@ module Appium
324
368
  #
325
369
  # # Start iOS driver
326
370
  # opts = {
327
- # caps: {
371
+ # capabilities: {
328
372
  # platformName: :ios,
329
373
  # platformVersion: '11.0',
330
374
  # deviceName: 'iPhone Simulator',
@@ -365,11 +409,12 @@ module Appium
365
409
  end
366
410
 
367
411
  begin
368
- # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
369
- @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
370
- desired_capabilities: @caps,
412
+ @driver = ::Appium::Core::Base::Driver.new(listener: @listener,
413
+ http_client: @http_client,
414
+ capabilities: @caps, # ::Appium::Core::Base::Capabilities
371
415
  url: @custom_url,
372
- listener: @listener)
416
+ wait_timeout: @wait_timeout,
417
+ wait_interval: @wait_interval)
373
418
 
374
419
  if @direct_connect
375
420
  d_c = DirectConnections.new(@driver.capabilities)
@@ -387,6 +432,8 @@ module Appium
387
432
  @http_client.additional_headers.delete Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
388
433
  end
389
434
 
435
+ # TODO: this method can be removed after releasing Appium 2.0, and after a while
436
+ # since Appium 2.0 reuqires 'automationName'. This method won't help anymore then.
390
437
  # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
391
438
  # Since @automation_name is set only client side before start_driver is called.
392
439
  set_automation_name_if_nil
@@ -396,7 +443,47 @@ module Appium
396
443
  @driver
397
444
  end
398
445
 
399
- private
446
+ # @privvate
447
+ # Attach to an existing session
448
+ def attach_to(session_id, url: nil, automation_name: nil, platform_name: nil,
449
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
450
+
451
+ raise ::Appium::Core::Error::ArgumentError, 'The :url must not be nil' if url.nil?
452
+ raise ::Appium::Core::Error::ArgumentError, 'The :automation_name must not be nil' if automation_name.nil?
453
+ raise ::Appium::Core::Error::ArgumentError, 'The :platform_name must not be nil' if platform_name.nil?
454
+
455
+ @custom_url = url
456
+
457
+ # use lowercase internally
458
+ @automation_name = convert_downcase(automation_name)
459
+ @device = convert_downcase(platform_name)
460
+
461
+ extend_for(device: @device, automation_name: @automation_name)
462
+
463
+ @http_client = get_http_client http_client: http_client_ops.delete(:http_client),
464
+ open_timeout: http_client_ops.delete(:open_timeout),
465
+ read_timeout: http_client_ops.delete(:read_timeout)
466
+
467
+ # Note that 'enable_idempotency_header' works only a new session reqeust. The attach_to method skips
468
+ # the new session request, this it does not needed.
469
+
470
+ begin
471
+ # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
472
+ @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
473
+ url: @custom_url,
474
+ listener: @listener,
475
+ existing_session_id: session_id,
476
+ automation_name: automation_name,
477
+ platform_name: platform_name)
478
+
479
+ # export session
480
+ write_session_id(@driver.session_id, @export_session_path) if @export_session
481
+ rescue Errno::ECONNREFUSED
482
+ raise "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
483
+ end
484
+
485
+ @driver
486
+ end
400
487
 
401
488
  def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
402
489
  client = http_client || Appium::Core::Base::Http::Default.new
@@ -410,6 +497,8 @@ module Appium
410
497
 
411
498
  # Ignore setting default wait if the target driver has no implementation
412
499
  def set_implicit_wait_by_default(wait)
500
+ return if @default_wait.nil?
501
+
413
502
  @driver.manage.timeouts.implicit_wait = wait
414
503
  rescue ::Selenium::WebDriver::Error::UnknownError => e
415
504
  unless e.message.include?('The operation requested is not yet implemented')
@@ -420,8 +509,6 @@ module Appium
420
509
  {}
421
510
  end
422
511
 
423
- public
424
-
425
512
  # Quits the driver
426
513
  # @return [void]
427
514
  #
@@ -435,7 +522,8 @@ module Appium
435
522
  nil
436
523
  end
437
524
 
438
- # Returns the server's version info
525
+ # Returns the server's version info. This method calls +driver.remote_status+ internally
526
+ #
439
527
  # @return [Hash]
440
528
  #
441
529
  # @example
@@ -449,18 +537,20 @@ module Appium
449
537
  # }
450
538
  # }
451
539
  #
452
- # Returns blank hash for Selenium Grid since 'remote_status' gets 500 error
540
+ # Returns blank hash in a case +driver.remote_status+ got an error
541
+ # such as Selenium Grid. It returns 500 error against 'remote_status'.
453
542
  #
454
543
  # @example
455
544
  #
456
545
  # @core.appium_server_version #=> {}
457
546
  #
458
547
  def appium_server_version
459
- @driver.remote_status
460
- rescue Selenium::WebDriver::Error::ServerError => e
461
- raise ::Appium::Core::Error::ServerError unless e.message.include?('status code 500')
548
+ return {} if @driver.nil?
462
549
 
463
- # driver.remote_status returns 500 error for using selenium grid
550
+ @driver.remote_status
551
+ rescue StandardError
552
+ # Ignore error case in a case the target appium server
553
+ # does not support `/status` API.
464
554
  {}
465
555
  end
466
556
 
@@ -476,31 +566,26 @@ module Appium
476
566
  p_version.split('.').map(&:to_i)
477
567
  end
478
568
 
479
- # Takes a png screenshot and saves to the target path.
480
- #
481
- # @param png_save_path [String] the full path to save the png
482
- # @return [File]
483
- #
484
- # @example
485
- #
486
- # @core.screenshot '/tmp/hi.png' #=> nil
487
- # # same as '@driver.save_screenshot png_save_path'
488
- #
489
- def screenshot(png_save_path)
490
- ::Appium::Logger.warn '[DEPRECATION] screenshot will be removed. Please use driver.save_screenshot instead.'
491
- @driver.save_screenshot png_save_path
492
- end
493
-
494
569
  private
495
570
 
571
+ def convert_to_symbol(value)
572
+ if value.nil?
573
+ value
574
+ else
575
+ value.to_sym
576
+ end
577
+ end
578
+
496
579
  # @private
497
580
  def extend_for(device:, automation_name:) # rubocop:disable Metrics/CyclomaticComplexity
498
581
  extend Appium::Core
499
582
  extend Appium::Core::Device
500
583
 
501
- case device
584
+ sym_automation_name = convert_to_symbol(automation_name)
585
+
586
+ case convert_to_symbol(device)
502
587
  when :android
503
- case automation_name
588
+ case sym_automation_name
504
589
  when :espresso
505
590
  ::Appium::Core::Android::Espresso::Bridge.for self
506
591
  when :uiautomator2
@@ -511,7 +596,7 @@ module Appium
511
596
  ::Appium::Core::Android::Uiautomator1::Bridge.for self
512
597
  end
513
598
  when :ios, :tvos
514
- case automation_name
599
+ case sym_automation_name
515
600
  when :safari
516
601
  ::Appium::Logger.debug('SafariDriver for iOS')
517
602
  when :xcuitest
@@ -520,19 +605,19 @@ module Appium
520
605
  ::Appium::Core::Ios::Uiautomation::Bridge.for self
521
606
  end
522
607
  when :mac
523
- case automation_name
608
+ case sym_automation_name
524
609
  when :safari
525
610
  ::Appium::Logger.debug('SafariDriver for macOS')
526
611
  when :gecko
527
612
  ::Appium::Logger.debug('Gecko Driver for macOS')
528
613
  when :mac2
529
- ::Appium::Logger.debug('macOS XCUITest')
614
+ ::Appium::Core::Mac2::Bridge.for self
530
615
  else
531
616
  # no Mac specific extentions
532
617
  ::Appium::Logger.debug('macOS Native')
533
618
  end
534
619
  when :windows
535
- case automation_name
620
+ case sym_automation_name
536
621
  when :gecko
537
622
  ::Appium::Logger.debug('Gecko Driver for Windows')
538
623
  else
@@ -542,7 +627,7 @@ module Appium
542
627
  # https://github.com/Samsung/appium-tizen-driver
543
628
  ::Appium::Logger.debug('tizen')
544
629
  else
545
- case automation_name
630
+ case sym_automation_name
546
631
  when :youiengine
547
632
  # https://github.com/YOU-i-Labs/appium-youiengine-driver
548
633
  ::Appium::Logger.debug('YouiEngine')
@@ -559,36 +644,9 @@ module Appium
559
644
  self
560
645
  end
561
646
 
562
- # @private
563
- def validate_keys(opts)
564
- flatten_ops = flatten_hash_keys(opts)
565
-
566
- # FIXME: Remove 'desired_capabilities' in the next major Selenium update
567
- unless opts.member?(:caps) || opts.member?(:capabilities) || opts.member?(:desired_capabilities)
568
- raise Error::NoCapabilityError
569
- end
570
-
571
- if !opts.member?(:appium_lib) && flatten_ops.member?(:appium_lib)
572
- raise Error::CapabilityStructureError, 'Please check the value of appium_lib in the capability'
573
- end
574
-
575
- true
576
- end
577
-
578
- # @private
579
- def flatten_hash_keys(hash, flatten_keys_result = [])
580
- hash.each do |key, value|
581
- flatten_keys_result << key
582
- flatten_hash_keys(value, flatten_keys_result) if value.is_a?(Hash)
583
- end
584
-
585
- flatten_keys_result
586
- end
587
-
588
647
  # @private
589
648
  def get_caps(opts)
590
- # FIXME: Remove 'desired_capabilities' in the next major Selenium update
591
- Core::Base::Capabilities.create_capabilities(opts[:caps] || opts[:capabilities] || opts[:desired_capabilities] || {})
649
+ Core::Base::Capabilities.new(opts[:caps] || opts[:capabilities] || {})
592
650
  end
593
651
 
594
652
  # @private
@@ -601,6 +659,7 @@ module Appium
601
659
  # The path can be local, HTTP/S, Windows Share and other path like 'sauce-storage:'.
602
660
  # Use @caps[:app] without modifications if the path isn't HTTP/S or local path.
603
661
  def set_app_path
662
+ # FIXME: maybe `:app` should check `app` as well.
604
663
  return unless @caps && @caps[:app] && !@caps[:app].empty?
605
664
  return if @caps[:app] =~ URI::DEFAULT_PARSER.make_regexp
606
665
 
@@ -639,18 +698,24 @@ module Appium
639
698
  # @private
640
699
  def set_appium_device
641
700
  # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
642
- @device = @caps[:platformName]
701
+ # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
702
+ @device = @caps[:platformName] || @caps['platformName']
643
703
  return @device unless @device
644
704
 
645
- @device = @device.is_a?(Symbol) ? @device.downcase : @device.downcase.strip.intern
705
+ @device = convert_downcase @device
646
706
  end
647
707
 
648
708
  # @private
649
709
  def set_automation_name
650
- @automation_name = @caps[:automationName] if @caps[:automationName]
651
- @automation_name = if @automation_name
652
- @automation_name.is_a?(Symbol) ? @automation_name.downcase : @automation_name.downcase.strip.intern
653
- end
710
+ # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
711
+ candidate = @caps[:automationName] || @caps['automationName']
712
+ @automation_name = candidate if candidate
713
+ @automation_name = convert_downcase @automation_name if @automation_name
714
+ end
715
+
716
+ # @private
717
+ def convert_downcase(value)
718
+ value.is_a?(Symbol) ? value.downcase : value.downcase.strip.intern
654
719
  end
655
720
 
656
721
  # @private
@@ -664,6 +729,10 @@ module Appium
664
729
 
665
730
  # @private
666
731
  def write_session_id(session_id, export_path = '/tmp/appium_lib_session')
732
+ ::Appium::Logger.warn(
733
+ '[DEPRECATION] export_session option will be removed. ' \
734
+ 'Please save the session id by yourself with #session_id method like @driver.session_id.'
735
+ )
667
736
  export_path = export_path.tr('/', '\\') if ::Appium::Core::Base.platform.windows?
668
737
  File.write(export_path, session_id)
669
738
  rescue IOError => e