appium_lib_core 4.1.0 → 7.1.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +250 -271
  3. data/README.md +65 -15
  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 +45 -4
  9. data/lib/appium_lib_core/common/base/bridge.rb +320 -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 +232 -191
  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 +15 -38
  17. data/lib/appium_lib_core/{ios/uiautomation/bridge.rb → common/base/remote_status.rb} +9 -8
  18. data/lib/appium_lib_core/common/base/rotable.rb +62 -0
  19. data/lib/appium_lib_core/common/base/screenshot.rb +8 -8
  20. data/lib/appium_lib_core/common/base/search_context.rb +20 -6
  21. data/lib/appium_lib_core/common/base.rb +1 -3
  22. data/lib/appium_lib_core/common/command.rb +259 -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 -8
  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 +194 -108
  36. data/lib/appium_lib_core/{patch.rb → element.rb} +66 -9
  37. data/lib/appium_lib_core/{common/base/command.rb → mac2/bridge.rb} +9 -8
  38. data/lib/appium_lib_core/mac2/device/screen.rb +48 -0
  39. data/lib/appium_lib_core/mac2/device.rb +92 -0
  40. data/lib/appium_lib_core/{ios.rb → mac2.rb} +2 -5
  41. data/lib/appium_lib_core/support/event_firing_bridge.rb +57 -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 +21 -10
  46. metadata +31 -86
  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/lib/appium_lib_core/ios/uiautomation/device.rb +0 -44
  66. data/lib/appium_lib_core/ios/uiautomation/patch.rb +0 -34
  67. data/release_notes.md +0 -816
  68. 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
@@ -15,6 +15,9 @@
15
15
  require 'uri'
16
16
 
17
17
  module Appium
18
+ # The struct for 'location'
19
+ Location = Struct.new(:latitude, :longitude, :altitude)
20
+
18
21
  module Core
19
22
  module Android
20
23
  autoload :Uiautomator1, 'appium_lib_core/android'
@@ -23,10 +26,11 @@ module Appium
23
26
  end
24
27
 
25
28
  module Ios
26
- autoload :Uiautomation, 'appium_lib_core/ios'
27
29
  autoload :Xcuitest, 'appium_lib_core/ios_xcuitest'
28
30
  end
29
31
 
32
+ autoload :Mac2, 'appium_lib_core/mac2'
33
+
30
34
  autoload :Windows, 'appium_lib_core/windows'
31
35
 
32
36
  # This options affects only client side as <code>:appium_lib</code> key.<br>
@@ -38,18 +42,18 @@ module Appium
38
42
 
39
43
  def initialize(appium_lib_opts)
40
44
  @custom_url = appium_lib_opts.fetch :server_url, nil
41
- @default_wait = appium_lib_opts.fetch :wait, Driver::DEFAULT_IMPLICIT_WAIT
45
+ @default_wait = appium_lib_opts.fetch :wait, nil
42
46
  @enable_idempotency_header = appium_lib_opts.fetch :enable_idempotency_header, true
43
47
 
44
48
  # bump current session id into a particular file
45
49
  @export_session = appium_lib_opts.fetch :export_session, false
46
50
  @export_session_path = appium_lib_opts.fetch :export_session_path, default_tmp_appium_lib_session
47
51
 
48
- @direct_connect = appium_lib_opts.fetch :direct_connect, false
52
+ @direct_connect = appium_lib_opts.fetch :direct_connect, true
49
53
 
50
54
  @port = appium_lib_opts.fetch :port, Driver::DEFAULT_APPIUM_PORT
51
55
 
52
- # timeout and interval used in ::Appium::Comm.wait/wait_true
56
+ # timeout and interval used in ::Appium::Commn.wait/wait_true
53
57
  @wait_timeout = appium_lib_opts.fetch :wait_timeout, ::Appium::Core::Wait::DEFAULT_TIMEOUT
54
58
  @wait_interval = appium_lib_opts.fetch :wait_interval, ::Appium::Core::Wait::DEFAULT_INTERVAL
55
59
 
@@ -74,6 +78,13 @@ module Appium
74
78
  path: 'directConnectPath'
75
79
  }.freeze
76
80
 
81
+ W3C_KEYS = {
82
+ protocol: 'appium:directConnectProtocol',
83
+ host: 'appium:directConnectHost',
84
+ port: 'appium:directConnectPort',
85
+ path: 'appium:directConnectPath'
86
+ }.freeze
87
+
77
88
  # @return [string] Returns a protocol such as http/https
78
89
  attr_reader :protocol
79
90
 
@@ -87,10 +98,10 @@ module Appium
87
98
  attr_reader :path
88
99
 
89
100
  def initialize(capabilities)
90
- @protocol = capabilities[KEYS[:protocol]]
91
- @host = capabilities[KEYS[:host]]
92
- @port = capabilities[KEYS[:port]]
93
- @path = capabilities[KEYS[:path]]
101
+ @protocol = capabilities[W3C_KEYS[:protocol]] || capabilities[KEYS[:protocol]]
102
+ @host = capabilities[W3C_KEYS[:host]] || capabilities[KEYS[:host]]
103
+ @port = capabilities[W3C_KEYS[:port]] || capabilities[KEYS[:port]]
104
+ @path = capabilities[W3C_KEYS[:path]] || capabilities[KEYS[:path]]
94
105
  end
95
106
  end
96
107
 
@@ -133,11 +144,9 @@ module Appium
133
144
  attr_reader :export_session_path
134
145
 
135
146
  # Default wait time for elements to appear in Appium server side.
136
- # Defaults to {::Appium::Core::Driver::DEFAULT_IMPLICIT_WAIT}.<br>
137
147
  # Provide <code>{ appium_lib: { wait: 30 } }</code> to {::Appium::Core.for}
138
148
  # @return [Integer]
139
149
  attr_reader :default_wait
140
- DEFAULT_IMPLICIT_WAIT = 0
141
150
 
142
151
  # Appium's server port. 4723 is by default. Defaults to {::Appium::Core::Driver::DEFAULT_APPIUM_PORT}.<br>
143
152
  # Provide <code>{ appium_lib: { port: 8080 } }</code> to {::Appium::Core.for}.
@@ -175,7 +184,8 @@ module Appium
175
184
  # - <code>directConnectPort</code>
176
185
  # - <code>directConnectPath</code>
177
186
  #
178
- # Ignore them if this parameter is <code>false</code>. Defaults to false.
187
+ # ignore them if this parameter is <code>false</code>. Defaults to true.
188
+ # These keys can have <code>appium:</code> prefix.
179
189
  #
180
190
  # @return [Bool]
181
191
  attr_reader :direct_connect
@@ -185,8 +195,6 @@ module Appium
185
195
  # @option opts [Hash] :caps Appium capabilities.
186
196
  # @option opts [Hash] :capabilities The same as :caps.
187
197
  # 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
198
  # @option opts [Appium::Core::Options] :appium_lib Capabilities affect only ruby client
191
199
  # @option opts [String] :url The same as :custom_url in :appium_lib.
192
200
  # This param is for compatibility with Selenium WebDriver format
@@ -197,10 +205,8 @@ module Appium
197
205
  #
198
206
  # # format 1
199
207
  # @core = Appium::Core.for caps: {...}, appium_lib: {...}
200
- # # format 2. 'capabilities:' or 'desired_capabilities:' is also available instead of 'caps:'.
208
+ # # format 2. 'capabilities:' is also available instead of 'caps:'.
201
209
  # @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
210
  #
205
211
  #
206
212
  # require 'rubygems'
@@ -228,7 +234,7 @@ module Appium
228
234
  # @core.start_driver # Connect to 'http://127.0.0.1:8080/wd/hub' because of 'port: 8080'
229
235
  #
230
236
  # # Start iOS driver with .zip file over HTTP
231
- # # 'desired_capabilities:' or 'capabilities:' is also available instead of 'caps:'. Either is fine.
237
+ # # 'capabilities:' is also available instead of 'caps:'. Either is fine.
232
238
  # opts = {
233
239
  # capabilities: {
234
240
  # platformName: :ios,
@@ -252,7 +258,7 @@ module Appium
252
258
  # # Start iOS driver as another format. 'url' is available like below
253
259
  # opts = {
254
260
  # url: "http://custom-host:8080/wd/hub.com",
255
- # desired_capabilities: {
261
+ # capabilities: {
256
262
  # platformName: :ios,
257
263
  # platformVersion: '11.0',
258
264
  # deviceName: 'iPhone Simulator',
@@ -270,8 +276,58 @@ module Appium
270
276
  # @core = Appium::Core.for(opts) # create a core driver with 'opts' and extend methods into 'self'
271
277
  # @core.start_driver # start driver with 'url'. Connect to 'http://custom-host:8080/wd/hub.com'
272
278
  #
279
+ # # With a custom listener
280
+ # class CustomListener < ::Selenium::WebDriver::Support::AbstractEventListener
281
+ # // something
282
+ # end
283
+ # capabilities: {
284
+ # platformName: :ios,
285
+ # platformVersion: '11.0',
286
+ # deviceName: 'iPhone Simulator',
287
+ # automationName: 'XCUITest',
288
+ # app: '/path/to/MyiOS.app'
289
+ # },
290
+ # appium_lib: {
291
+ # listener: CustomListener.new,
292
+ # }
293
+ # @core = Appium::Core.for capabilities: capabilities, appium_lib: appium_lib
294
+ # @core.start_driver
295
+ #
273
296
  def self.for(opts = {})
274
- new(opts)
297
+ new.setup_for_new_session(opts)
298
+ end
299
+
300
+ # Attach to an existing session. The main usage of this method is to attach to
301
+ # an existing session for debugging. The generated driver instance has the capabilities which
302
+ # has the given automationName and platformName only since the W3C WebDriver spec does not provide
303
+ # an endpoint to get running session's capabilities.
304
+ #
305
+ #
306
+ # @param [String] The session id to attach to.
307
+ # @param [String] url The WebDriver URL to attach to with the session_id.
308
+ # @param [String] automation_name The platform name to keep in the dummy capabilities
309
+ # @param [String] platform_name The automation name to keep in the dummy capabilities
310
+ # @return [Selenium::WebDriver] A new driver instance with the given session id.
311
+ #
312
+ # @example
313
+ #
314
+ # new_driver = ::Appium::Core::Driver.attach_to(
315
+ # driver.session_id, # The 'driver' has an existing session id
316
+ # url: 'http://127.0.0.1:4723/wd/hub', automation_name: 'UiAutomator2', platform_name: 'Android'
317
+ # )
318
+ # new_driver.page_source # for example
319
+ #
320
+ def self.attach_to(
321
+ session_id, url: nil, automation_name: nil, platform_name: nil,
322
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 }
323
+ )
324
+ new.attach_to(
325
+ session_id,
326
+ automation_name: automation_name,
327
+ platform_name: platform_name,
328
+ url: url,
329
+ http_client_ops: http_client_ops
330
+ )
275
331
  end
276
332
 
277
333
  private
@@ -282,17 +338,25 @@ module Appium
282
338
  @delegate_target
283
339
  end
284
340
 
285
- public
286
-
287
341
  # @private
288
- def initialize(opts = {})
342
+ def initialize
289
343
  @delegate_target = self # for testing purpose
290
344
  @automation_name = nil # initialise before 'set_automation_name'
345
+ end
291
346
 
292
- opts = Appium.symbolize_keys opts
293
- validate_keys(opts)
347
+ public
348
+
349
+ # @private
350
+ # Set up for a new session
351
+ def setup_for_new_session(opts = {})
352
+ @custom_url = opts.delete :url # to set the custom url as :url
353
+
354
+ # TODO: Remove when we implement Options
355
+ # The symbolize_keys is to keep compatiility for the legacy code, which allows capabilities to give 'string' as the key.
356
+ # The toplevel `caps`, `capabilities` and `appium_lib` are expected to be symbol.
357
+ # FIXME: First, please try to remove `nested: true` to `nested: false`.
358
+ opts = Appium.symbolize_keys(opts, nested: true)
294
359
 
295
- @custom_url = opts.delete :url
296
360
  @caps = get_caps(opts)
297
361
 
298
362
  set_appium_lib_specific_values(get_appium_lib_opts(opts))
@@ -301,8 +365,7 @@ module Appium
301
365
  set_automation_name
302
366
 
303
367
  extend_for(device: @device, automation_name: @automation_name)
304
-
305
- self # rubocop:disable Lint/Void
368
+ self
306
369
  end
307
370
 
308
371
  # Creates a new global driver and quits the old one if it exists.
@@ -313,7 +376,7 @@ module Appium
313
376
  # @option http_client_ops [Hash] :http_client Custom HTTP Client
314
377
  # @option http_client_ops [Hash] :open_timeout Custom open timeout for http client.
315
378
  # @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
316
- # @return [Selenium::WebDriver] the new global driver
379
+ # @return [Selenium::WebDriver] A new driver instance
317
380
  #
318
381
  # @example
319
382
  #
@@ -324,7 +387,7 @@ module Appium
324
387
  #
325
388
  # # Start iOS driver
326
389
  # opts = {
327
- # caps: {
390
+ # capabilities: {
328
391
  # platformName: :ios,
329
392
  # platformVersion: '11.0',
330
393
  # deviceName: 'iPhone Simulator',
@@ -365,11 +428,12 @@ module Appium
365
428
  end
366
429
 
367
430
  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,
431
+ @driver = ::Appium::Core::Base::Driver.new(listener: @listener,
432
+ http_client: @http_client,
433
+ capabilities: @caps, # ::Appium::Core::Base::Capabilities
371
434
  url: @custom_url,
372
- listener: @listener)
435
+ wait_timeout: @wait_timeout,
436
+ wait_interval: @wait_interval)
373
437
 
374
438
  if @direct_connect
375
439
  d_c = DirectConnections.new(@driver.capabilities)
@@ -387,6 +451,8 @@ module Appium
387
451
  @http_client.additional_headers.delete Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
388
452
  end
389
453
 
454
+ # TODO: this method can be removed after releasing Appium 2.0, and after a while
455
+ # since Appium 2.0 reuqires 'automationName'. This method won't help anymore then.
390
456
  # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
391
457
  # Since @automation_name is set only client side before start_driver is called.
392
458
  set_automation_name_if_nil
@@ -396,20 +462,56 @@ module Appium
396
462
  @driver
397
463
  end
398
464
 
399
- private
465
+ # @private
466
+ # Attach to an existing session
467
+ def attach_to(session_id, url: nil, automation_name: nil, platform_name: nil,
468
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
400
469
 
401
- def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
402
- client = http_client || Appium::Core::Base::Http::Default.new
470
+ raise ::Appium::Core::Error::ArgumentError, 'The :url must not be nil' if url.nil?
471
+ raise ::Appium::Core::Error::ArgumentError, 'The :automation_name must not be nil' if automation_name.nil?
472
+ raise ::Appium::Core::Error::ArgumentError, 'The :platform_name must not be nil' if platform_name.nil?
473
+
474
+ @custom_url = url
475
+
476
+ # use lowercase internally
477
+ @automation_name = convert_downcase(automation_name)
478
+ @device = convert_downcase(platform_name)
479
+
480
+ extend_for(device: @device, automation_name: @automation_name)
403
481
 
404
- # open_timeout and read_timeout are explicit wait.
405
- client.open_timeout = open_timeout if open_timeout
406
- client.read_timeout = read_timeout if read_timeout
482
+ @http_client = get_http_client http_client: http_client_ops.delete(:http_client),
483
+ open_timeout: http_client_ops.delete(:open_timeout),
484
+ read_timeout: http_client_ops.delete(:read_timeout)
407
485
 
408
- client
486
+ # Note that 'enable_idempotency_header' works only a new session reqeust. The attach_to method skips
487
+ # the new session request, this it does not needed.
488
+
489
+ begin
490
+ # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
491
+ @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
492
+ url: @custom_url,
493
+ listener: @listener,
494
+ existing_session_id: session_id,
495
+ automation_name: automation_name,
496
+ platform_name: platform_name)
497
+
498
+ # export session
499
+ write_session_id(@driver.session_id, @export_session_path) if @export_session
500
+ rescue Errno::ECONNREFUSED
501
+ raise "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
502
+ end
503
+
504
+ @driver
505
+ end
506
+
507
+ def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
508
+ http_client || Appium::Core::Base::Http::Default.new(open_timeout: open_timeout, read_timeout: read_timeout)
409
509
  end
410
510
 
411
511
  # Ignore setting default wait if the target driver has no implementation
412
512
  def set_implicit_wait_by_default(wait)
513
+ return if @default_wait.nil?
514
+
413
515
  @driver.manage.timeouts.implicit_wait = wait
414
516
  rescue ::Selenium::WebDriver::Error::UnknownError => e
415
517
  unless e.message.include?('The operation requested is not yet implemented')
@@ -420,9 +522,7 @@ module Appium
420
522
  {}
421
523
  end
422
524
 
423
- public
424
-
425
- # Quits the driver
525
+ # [Deprecated] Quits the driver. This method is the same as @driver.quit
426
526
  # @return [void]
427
527
  #
428
528
  # @example
@@ -430,12 +530,14 @@ module Appium
430
530
  # @core.quit_driver
431
531
  #
432
532
  def quit_driver
533
+ ::Appium::Logger.warn('[DEPRECATION] quit_driver will be removed. Please use @driver.quit instead.')
433
534
  @driver.quit
434
535
  rescue # rubocop:disable Style/RescueStandardError
435
536
  nil
436
537
  end
437
538
 
438
- # Returns the server's version info
539
+ # Returns the server's version info. This method calls +driver.remote_status+ internally
540
+ #
439
541
  # @return [Hash]
440
542
  #
441
543
  # @example
@@ -449,18 +551,20 @@ module Appium
449
551
  # }
450
552
  # }
451
553
  #
452
- # Returns blank hash for Selenium Grid since 'remote_status' gets 500 error
554
+ # Returns blank hash in a case +driver.remote_status+ got an error
555
+ # such as Selenium Grid. It returns 500 error against 'remote_status'.
453
556
  #
454
557
  # @example
455
558
  #
456
559
  # @core.appium_server_version #=> {}
457
560
  #
458
561
  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')
562
+ return {} if @driver.nil?
462
563
 
463
- # driver.remote_status returns 500 error for using selenium grid
564
+ @driver.remote_status
565
+ rescue StandardError
566
+ # Ignore error case in a case the target appium server
567
+ # does not support `/status` API.
464
568
  {}
465
569
  end
466
570
 
@@ -472,35 +576,35 @@ module Appium
472
576
  # @core.platform_version #=> [10,1,1]
473
577
  #
474
578
  def platform_version
579
+ ::Appium::Logger.warn(
580
+ '[DEPRECATION] platform_version method will be. ' \
581
+ 'Please check the platformVersion via @driver.capabilities["platformVersion"] instead.'
582
+ )
583
+
475
584
  p_version = @driver.capabilities['platformVersion'] || @driver.session_capabilities['platformVersion']
476
585
  p_version.split('.').map(&:to_i)
477
586
  end
478
587
 
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
588
  private
495
589
 
590
+ def convert_to_symbol(value)
591
+ if value.nil?
592
+ value
593
+ else
594
+ value.to_sym
595
+ end
596
+ end
597
+
496
598
  # @private
497
599
  def extend_for(device:, automation_name:) # rubocop:disable Metrics/CyclomaticComplexity
498
600
  extend Appium::Core
499
601
  extend Appium::Core::Device
500
602
 
501
- case device
603
+ sym_automation_name = convert_to_symbol(automation_name)
604
+
605
+ case convert_to_symbol(device)
502
606
  when :android
503
- case automation_name
607
+ case sym_automation_name
504
608
  when :espresso
505
609
  ::Appium::Core::Android::Espresso::Bridge.for self
506
610
  when :uiautomator2
@@ -511,28 +615,26 @@ module Appium
511
615
  ::Appium::Core::Android::Uiautomator1::Bridge.for self
512
616
  end
513
617
  when :ios, :tvos
514
- case automation_name
618
+ case sym_automation_name
515
619
  when :safari
516
620
  ::Appium::Logger.debug('SafariDriver for iOS')
517
- when :xcuitest
621
+ else # XCUITest
518
622
  ::Appium::Core::Ios::Xcuitest::Bridge.for self
519
- else # default and UIAutomation
520
- ::Appium::Core::Ios::Uiautomation::Bridge.for self
521
623
  end
522
624
  when :mac
523
- case automation_name
625
+ case sym_automation_name
524
626
  when :safari
525
627
  ::Appium::Logger.debug('SafariDriver for macOS')
526
628
  when :gecko
527
629
  ::Appium::Logger.debug('Gecko Driver for macOS')
528
630
  when :mac2
529
- ::Appium::Logger.debug('macOS XCUITest')
631
+ ::Appium::Core::Mac2::Bridge.for self
530
632
  else
531
633
  # no Mac specific extentions
532
634
  ::Appium::Logger.debug('macOS Native')
533
635
  end
534
636
  when :windows
535
- case automation_name
637
+ case sym_automation_name
536
638
  when :gecko
537
639
  ::Appium::Logger.debug('Gecko Driver for Windows')
538
640
  else
@@ -542,7 +644,7 @@ module Appium
542
644
  # https://github.com/Samsung/appium-tizen-driver
543
645
  ::Appium::Logger.debug('tizen')
544
646
  else
545
- case automation_name
647
+ case sym_automation_name
546
648
  when :youiengine
547
649
  # https://github.com/YOU-i-Labs/appium-youiengine-driver
548
650
  ::Appium::Logger.debug('YouiEngine')
@@ -559,36 +661,9 @@ module Appium
559
661
  self
560
662
  end
561
663
 
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
664
  # @private
589
665
  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] || {})
666
+ Core::Base::Capabilities.new(opts[:caps] || opts[:capabilities] || {})
592
667
  end
593
668
 
594
669
  # @private
@@ -601,6 +676,7 @@ module Appium
601
676
  # The path can be local, HTTP/S, Windows Share and other path like 'sauce-storage:'.
602
677
  # Use @caps[:app] without modifications if the path isn't HTTP/S or local path.
603
678
  def set_app_path
679
+ # FIXME: maybe `:app` should check `app` as well.
604
680
  return unless @caps && @caps[:app] && !@caps[:app].empty?
605
681
  return if @caps[:app] =~ URI::DEFAULT_PARSER.make_regexp
606
682
 
@@ -639,18 +715,24 @@ module Appium
639
715
  # @private
640
716
  def set_appium_device
641
717
  # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
642
- @device = @caps[:platformName]
718
+ # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
719
+ @device = @caps[:platformName] || @caps['platformName']
643
720
  return @device unless @device
644
721
 
645
- @device = @device.is_a?(Symbol) ? @device.downcase : @device.downcase.strip.intern
722
+ @device = convert_downcase @device
646
723
  end
647
724
 
648
725
  # @private
649
726
  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
727
+ # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
728
+ candidate = @caps[:automationName] || @caps['automationName']
729
+ @automation_name = candidate if candidate
730
+ @automation_name = convert_downcase @automation_name if @automation_name
731
+ end
732
+
733
+ # @private
734
+ def convert_downcase(value)
735
+ value.is_a?(Symbol) ? value.downcase : value.downcase.strip.intern
654
736
  end
655
737
 
656
738
  # @private
@@ -664,6 +746,10 @@ module Appium
664
746
 
665
747
  # @private
666
748
  def write_session_id(session_id, export_path = '/tmp/appium_lib_session')
749
+ ::Appium::Logger.warn(
750
+ '[DEPRECATION] export_session option will be removed. ' \
751
+ 'Please save the session id by yourself with #session_id method like @driver.session_id.'
752
+ )
667
753
  export_path = export_path.tr('/', '\\') if ::Appium::Core::Base.platform.windows?
668
754
  File.write(export_path, session_id)
669
755
  rescue IOError => e