appium_lib_core 4.1.0 → 5.8.0

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