appium_lib_core 4.1.0 → 9.3.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +357 -270
  3. data/README.md +68 -16
  4. data/Rakefile +8 -20
  5. data/Steepfile +11 -0
  6. data/appium_lib_core.gemspec +13 -15
  7. data/bin/console +0 -4
  8. data/lib/appium_lib_core/android/device/auth_finger_print.rb +4 -1
  9. data/lib/appium_lib_core/android/device/clipboard.rb +4 -2
  10. data/lib/appium_lib_core/android/device/emulator.rb +11 -5
  11. data/lib/appium_lib_core/android/device/network.rb +10 -0
  12. data/lib/appium_lib_core/android/device/performance.rb +3 -0
  13. data/lib/appium_lib_core/android/device/screen.rb +5 -1
  14. data/lib/appium_lib_core/android/device.rb +83 -20
  15. data/lib/appium_lib_core/common/base/bridge.rb +238 -95
  16. data/lib/appium_lib_core/common/base/capabilities.rb +21 -8
  17. data/lib/appium_lib_core/common/{command/mjsonwp.rb → base/device_ime.rb} +33 -12
  18. data/lib/appium_lib_core/common/base/driver.rb +260 -334
  19. data/lib/appium_lib_core/common/base/driver_settings.rb +51 -0
  20. data/lib/appium_lib_core/common/base/has_location.rb +80 -0
  21. data/lib/appium_lib_core/common/base/has_network_connection.rb +56 -0
  22. data/lib/appium_lib_core/common/base/http_default.rb +22 -38
  23. data/lib/appium_lib_core/{ios/uiautomation/bridge.rb → common/base/remote_status.rb} +9 -8
  24. data/lib/appium_lib_core/common/base/rotable.rb +62 -0
  25. data/lib/appium_lib_core/common/base/screenshot.rb +10 -10
  26. data/lib/appium_lib_core/common/base/search_context.rb +98 -172
  27. data/lib/appium_lib_core/common/base.rb +1 -5
  28. data/lib/appium_lib_core/common/command.rb +244 -4
  29. data/lib/appium_lib_core/common/device/app_management.rb +2 -26
  30. data/lib/appium_lib_core/common/device/context.rb +1 -5
  31. data/lib/appium_lib_core/common/device/image_comparison.rb +27 -10
  32. data/lib/appium_lib_core/common/device/keyevent.rb +4 -4
  33. data/lib/appium_lib_core/common/device/{touch_actions.rb → orientation.rb} +6 -10
  34. data/lib/appium_lib_core/common/device/screen_record.rb +8 -2
  35. data/lib/appium_lib_core/common/error.rb +5 -5
  36. data/lib/appium_lib_core/common/log.rb +5 -4
  37. data/lib/appium_lib_core/common/wait.rb +38 -6
  38. data/lib/appium_lib_core/device.rb +3 -9
  39. data/lib/appium_lib_core/driver.rb +193 -182
  40. data/lib/appium_lib_core/{patch.rb → element.rb} +64 -26
  41. data/lib/appium_lib_core/ios/device/clipboard.rb +4 -2
  42. data/lib/appium_lib_core/ios/xcuitest/device.rb +2 -0
  43. data/lib/appium_lib_core/{common/base/command.rb → mac2/bridge.rb} +9 -8
  44. data/lib/appium_lib_core/mac2/device/screen.rb +48 -0
  45. data/lib/appium_lib_core/mac2/device.rb +92 -0
  46. data/lib/appium_lib_core/{ios.rb → mac2.rb} +2 -5
  47. data/lib/appium_lib_core/support/event_firing_bridge.rb +57 -0
  48. data/lib/appium_lib_core/version.rb +2 -2
  49. data/lib/appium_lib_core.rb +23 -10
  50. data/rbs_collection.lock.yaml +252 -0
  51. data/rbs_collection.yaml +15 -0
  52. data/sig/gems/selenium/abstract_event_listener.rbs +8 -0
  53. data/sig/gems/selenium/capabilities.rbs +8 -0
  54. data/sig/gems/selenium/common.rbs +10 -0
  55. data/sig/gems/selenium/default.rbs +10 -0
  56. data/sig/gems/selenium/driver.rbs +7 -0
  57. data/sig/gems/selenium/has_session_id.rbs +8 -0
  58. data/sig/gems/selenium/has_web_storage.rbs +8 -0
  59. data/sig/gems/selenium/uploads_files.rbs +8 -0
  60. data/sig/lib/appium_lib_core/common/base/capabilities.rbs +9 -0
  61. data/sig/lib/appium_lib_core/common/base/driver.rbs +167 -0
  62. data/sig/lib/appium_lib_core/common/base/driver_settings.rbs +15 -0
  63. data/sig/lib/appium_lib_core/common/base/has_location.rbs +13 -0
  64. data/sig/lib/appium_lib_core/common/base/has_network_connection.rbs +19 -0
  65. data/sig/lib/appium_lib_core/common/base/http_default.rbs +38 -0
  66. data/sig/lib/appium_lib_core/common/base/platform.rbs +7 -0
  67. data/sig/lib/appium_lib_core/common/base/remote_status.rbs +9 -0
  68. data/sig/lib/appium_lib_core/common/base/rotable.rbs +17 -0
  69. data/sig/lib/appium_lib_core/common/base/screenshot.rbs +19 -0
  70. data/sig/lib/appium_lib_core/common/device/battery_status.rbs +13 -0
  71. data/sig/lib/appium_lib_core/common/wait.rbs +31 -0
  72. data/sig/lib/appium_lib_core/device.rbs +21 -0
  73. data/sig/lib/appium_lib_core/driver.rbs +196 -0
  74. data/sig/lib/appium_lib_core/ios/xcuitest/device/battery.rbs +15 -0
  75. data/sig/lib/appium_lib_core/version.rbs +7 -0
  76. data/sig/lib/appium_lib_core.rbs +8 -0
  77. metadata +88 -111
  78. data/.github/ISSUE_TEMPLATE/issue-report.md +0 -29
  79. data/.github/contributing.md +0 -26
  80. data/.github/issue_template.md +0 -20
  81. data/.github/workflows/unittest.yml +0 -68
  82. data/.gitignore +0 -18
  83. data/.rubocop.yml +0 -58
  84. data/azure-pipelines.yml +0 -15
  85. data/ci-jobs/functional/android_setup.yml +0 -3
  86. data/ci-jobs/functional/ios_setup.yml +0 -7
  87. data/ci-jobs/functional/publish_test_result.yml +0 -18
  88. data/ci-jobs/functional/run_appium.yml +0 -25
  89. data/ci-jobs/functional/start-emulator.sh +0 -26
  90. data/ci-jobs/functional_test.yml +0 -298
  91. data/docs/mobile_command.md +0 -34
  92. data/lib/appium_lib_core/common/base/bridge/mjsonwp.rb +0 -81
  93. data/lib/appium_lib_core/common/base/bridge/w3c.rb +0 -252
  94. data/lib/appium_lib_core/common/command/common.rb +0 -110
  95. data/lib/appium_lib_core/common/command/w3c.rb +0 -56
  96. data/lib/appium_lib_core/common/device/value.rb +0 -52
  97. data/lib/appium_lib_core/common/touch_action/multi_touch.rb +0 -56
  98. data/lib/appium_lib_core/common/touch_action/touch_actions.rb +0 -203
  99. data/lib/appium_lib_core/ios/uiautomation/device.rb +0 -44
  100. data/lib/appium_lib_core/ios/uiautomation/patch.rb +0 -34
  101. data/release_notes.md +0 -816
  102. data/script/commands.rb +0 -200
@@ -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,33 +26,30 @@ 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>
33
37
  # Read {::Appium::Core::Driver} about each attribute
34
38
  class Options
35
- attr_reader :custom_url, :default_wait, :export_session, :export_session_path,
39
+ attr_reader :custom_url, :default_wait,
36
40
  :port, :wait_timeout, :wait_interval, :listener,
37
41
  :direct_connect, :enable_idempotency_header
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
- # bump current session id into a particular file
45
- @export_session = appium_lib_opts.fetch :export_session, false
46
- @export_session_path = appium_lib_opts.fetch :export_session_path, default_tmp_appium_lib_session
47
-
48
- @direct_connect = appium_lib_opts.fetch :direct_connect, false
48
+ @direct_connect = appium_lib_opts.fetch :direct_connect, true
49
49
 
50
50
  @port = appium_lib_opts.fetch :port, Driver::DEFAULT_APPIUM_PORT
51
51
 
52
- # timeout and interval used in ::Appium::Comm.wait/wait_true
52
+ # timeout and interval used in ::Appium::Commn.wait/wait_true
53
53
  @wait_timeout = appium_lib_opts.fetch :wait_timeout, ::Appium::Core::Wait::DEFAULT_TIMEOUT
54
54
  @wait_interval = appium_lib_opts.fetch :wait_interval, ::Appium::Core::Wait::DEFAULT_INTERVAL
55
55
 
@@ -74,6 +74,13 @@ module Appium
74
74
  path: 'directConnectPath'
75
75
  }.freeze
76
76
 
77
+ W3C_KEYS = {
78
+ protocol: 'appium:directConnectProtocol',
79
+ host: 'appium:directConnectHost',
80
+ port: 'appium:directConnectPort',
81
+ path: 'appium:directConnectPath'
82
+ }.freeze
83
+
77
84
  # @return [string] Returns a protocol such as http/https
78
85
  attr_reader :protocol
79
86
 
@@ -87,17 +94,17 @@ module Appium
87
94
  attr_reader :path
88
95
 
89
96
  def initialize(capabilities)
90
- @protocol = capabilities[KEYS[:protocol]]
91
- @host = capabilities[KEYS[:host]]
92
- @port = capabilities[KEYS[:port]]
93
- @path = capabilities[KEYS[:path]]
97
+ @protocol = capabilities[W3C_KEYS[:protocol]] || capabilities[KEYS[:protocol]]
98
+ @host = capabilities[W3C_KEYS[:host]] || capabilities[KEYS[:host]]
99
+ @port = capabilities[W3C_KEYS[:port]] || capabilities[KEYS[:port]]
100
+ @path = capabilities[W3C_KEYS[:path]] || capabilities[KEYS[:path]]
94
101
  end
95
102
  end
96
103
 
97
104
  class Driver
98
105
  include Waitable
99
106
 
100
- # Selenium webdriver capabilities
107
+ # Selenium webdriver capabilities, but the value is provided capabilities basis.
101
108
  # @return [Core::Base::Capabilities]
102
109
  attr_reader :caps
103
110
 
@@ -118,7 +125,7 @@ module Appium
118
125
 
119
126
  # Automation name sent to appium server or received by server.<br>
120
127
  # If automation_name is <code>nil</code>, it is not set both client side and server side.
121
- # @return [Hash]
128
+ # @return [Symbol]
122
129
  attr_reader :automation_name
123
130
 
124
131
  # Custom URL for the selenium server. If set this attribute, ruby_lib_core try to handshake to the custom url.<br>
@@ -126,18 +133,10 @@ module Appium
126
133
  # @return [String]
127
134
  attr_reader :custom_url
128
135
 
129
- # Export session id to textfile in /tmp for 3rd party tools. False by default.
130
- # @return [Boolean]
131
- attr_reader :export_session
132
- # @return [String] By default, session id is exported in '/tmp/appium_lib_session'
133
- attr_reader :export_session_path
134
-
135
136
  # Default wait time for elements to appear in Appium server side.
136
- # Defaults to {::Appium::Core::Driver::DEFAULT_IMPLICIT_WAIT}.<br>
137
137
  # Provide <code>{ appium_lib: { wait: 30 } }</code> to {::Appium::Core.for}
138
138
  # @return [Integer]
139
139
  attr_reader :default_wait
140
- DEFAULT_IMPLICIT_WAIT = 0
141
140
 
142
141
  # Appium's server port. 4723 is by default. Defaults to {::Appium::Core::Driver::DEFAULT_APPIUM_PORT}.<br>
143
142
  # Provide <code>{ appium_lib: { port: 8080 } }</code> to {::Appium::Core.for}.
@@ -175,7 +174,8 @@ module Appium
175
174
  # - <code>directConnectPort</code>
176
175
  # - <code>directConnectPath</code>
177
176
  #
178
- # Ignore them if this parameter is <code>false</code>. Defaults to false.
177
+ # ignore them if this parameter is <code>false</code>. Defaults to true.
178
+ # These keys can have <code>appium:</code> prefix.
179
179
  #
180
180
  # @return [Bool]
181
181
  attr_reader :direct_connect
@@ -185,8 +185,6 @@ module Appium
185
185
  # @option opts [Hash] :caps Appium capabilities.
186
186
  # @option opts [Hash] :capabilities The same as :caps.
187
187
  # 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
188
  # @option opts [Appium::Core::Options] :appium_lib Capabilities affect only ruby client
191
189
  # @option opts [String] :url The same as :custom_url in :appium_lib.
192
190
  # This param is for compatibility with Selenium WebDriver format
@@ -197,10 +195,8 @@ module Appium
197
195
  #
198
196
  # # format 1
199
197
  # @core = Appium::Core.for caps: {...}, appium_lib: {...}
200
- # # format 2. 'capabilities:' or 'desired_capabilities:' is also available instead of 'caps:'.
198
+ # # format 2. 'capabilities:' is also available instead of 'caps:'.
201
199
  # @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
200
  #
205
201
  #
206
202
  # require 'rubygems'
@@ -216,7 +212,6 @@ module Appium
216
212
  # app: '/path/to/MyiOS.app'
217
213
  # },
218
214
  # appium_lib: {
219
- # export_session: false,
220
215
  # port: 8080,
221
216
  # wait: 0,
222
217
  # wait_timeout: 20,
@@ -228,7 +223,7 @@ module Appium
228
223
  # @core.start_driver # Connect to 'http://127.0.0.1:8080/wd/hub' because of 'port: 8080'
229
224
  #
230
225
  # # Start iOS driver with .zip file over HTTP
231
- # # 'desired_capabilities:' or 'capabilities:' is also available instead of 'caps:'. Either is fine.
226
+ # # 'capabilities:' is also available instead of 'caps:'. Either is fine.
232
227
  # opts = {
233
228
  # capabilities: {
234
229
  # platformName: :ios,
@@ -239,7 +234,6 @@ module Appium
239
234
  # },
240
235
  # appium_lib: {
241
236
  # server_url: 'http://custom-host:8080/wd/hub.com',
242
- # export_session: false,
243
237
  # wait: 0,
244
238
  # wait_timeout: 20,
245
239
  # wait_interval: 0.3,
@@ -252,7 +246,7 @@ module Appium
252
246
  # # Start iOS driver as another format. 'url' is available like below
253
247
  # opts = {
254
248
  # url: "http://custom-host:8080/wd/hub.com",
255
- # desired_capabilities: {
249
+ # capabilities: {
256
250
  # platformName: :ios,
257
251
  # platformVersion: '11.0',
258
252
  # deviceName: 'iPhone Simulator',
@@ -260,7 +254,6 @@ module Appium
260
254
  # app: '/path/to/MyiOS.app'
261
255
  # },
262
256
  # appium_lib: {
263
- # export_session: false,
264
257
  # wait: 0,
265
258
  # wait_timeout: 20,
266
259
  # wait_interval: 0.3,
@@ -270,8 +263,58 @@ module Appium
270
263
  # @core = Appium::Core.for(opts) # create a core driver with 'opts' and extend methods into 'self'
271
264
  # @core.start_driver # start driver with 'url'. Connect to 'http://custom-host:8080/wd/hub.com'
272
265
  #
266
+ # # With a custom listener
267
+ # class CustomListener < ::Selenium::WebDriver::Support::AbstractEventListener
268
+ # // something
269
+ # end
270
+ # capabilities: {
271
+ # platformName: :ios,
272
+ # platformVersion: '11.0',
273
+ # deviceName: 'iPhone Simulator',
274
+ # automationName: 'XCUITest',
275
+ # app: '/path/to/MyiOS.app'
276
+ # },
277
+ # appium_lib: {
278
+ # listener: CustomListener.new,
279
+ # }
280
+ # @core = Appium::Core.for capabilities: capabilities, appium_lib: appium_lib
281
+ # @core.start_driver
282
+ #
273
283
  def self.for(opts = {})
274
- new(opts)
284
+ new.setup_for_new_session(opts)
285
+ end
286
+
287
+ # Attach to an existing session. The main usage of this method is to attach to
288
+ # an existing session for debugging. The generated driver instance has the capabilities which
289
+ # has the given automationName and platformName only since the W3C WebDriver spec does not provide
290
+ # an endpoint to get running session's capabilities.
291
+ #
292
+ #
293
+ # @param [String] The session id to attach to.
294
+ # @param [String] url The WebDriver URL to attach to with the session_id.
295
+ # @param [String] automation_name The platform name to keep in the dummy capabilities
296
+ # @param [String] platform_name The automation name to keep in the dummy capabilities
297
+ # @return [Selenium::WebDriver] A new driver instance with the given session id.
298
+ #
299
+ # @example
300
+ #
301
+ # new_driver = ::Appium::Core::Driver.attach_to(
302
+ # driver.session_id, # The 'driver' has an existing session id
303
+ # url: 'http://127.0.0.1:4723/wd/hub', automation_name: 'UiAutomator2', platform_name: 'Android'
304
+ # )
305
+ # new_driver.page_source # for example
306
+ #
307
+ def self.attach_to(
308
+ session_id, url: nil, automation_name: nil, platform_name: nil,
309
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 }
310
+ )
311
+ new.attach_to(
312
+ session_id,
313
+ automation_name: automation_name,
314
+ platform_name: platform_name,
315
+ url: url,
316
+ http_client_ops: http_client_ops
317
+ )
275
318
  end
276
319
 
277
320
  private
@@ -282,17 +325,19 @@ module Appium
282
325
  @delegate_target
283
326
  end
284
327
 
285
- public
286
-
287
328
  # @private
288
- def initialize(opts = {})
329
+ def initialize
289
330
  @delegate_target = self # for testing purpose
290
331
  @automation_name = nil # initialise before 'set_automation_name'
332
+ end
333
+
334
+ public
291
335
 
292
- opts = Appium.symbolize_keys opts
293
- validate_keys(opts)
336
+ # @private
337
+ # Set up for a new session
338
+ def setup_for_new_session(opts = {})
339
+ @custom_url = opts.delete :url # to set the custom url as :url
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',
@@ -350,7 +394,8 @@ module Appium
350
394
 
351
395
  def start_driver(server_url: nil,
352
396
  http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
353
- @custom_url ||= server_url || "http://127.0.0.1:#{@port}/wd/hub"
397
+ @custom_url ||= "http://127.0.0.1:#{@port}/wd/hub"
398
+ @custom_url = server_url unless server_url.nil?
354
399
 
355
400
  @http_client = get_http_client http_client: http_client_ops.delete(:http_client),
356
401
  open_timeout: http_client_ops.delete(:open_timeout),
@@ -358,58 +403,85 @@ module Appium
358
403
 
359
404
  if @enable_idempotency_header
360
405
  if @http_client.instance_variable_defined? :@additional_headers
361
- @http_client.additional_headers[Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]] = SecureRandom.uuid
406
+ @http_client.set_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency], SecureRandom.uuid
362
407
  else
363
408
  ::Appium::Logger.warn 'No additional_headers attribute in this http client instance'
364
409
  end
365
410
  end
366
411
 
367
412
  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,
413
+ @driver = ::Appium::Core::Base::Driver.new(listener: @listener,
414
+ http_client: @http_client,
415
+ capabilities: @caps, # ::Appium::Core::Base::Capabilities
371
416
  url: @custom_url,
372
- listener: @listener)
417
+ wait_timeout: @wait_timeout,
418
+ wait_interval: @wait_interval)
373
419
 
374
420
  if @direct_connect
375
421
  d_c = DirectConnections.new(@driver.capabilities)
376
422
  @driver.update_sending_request_to(protocol: d_c.protocol, host: d_c.host, port: d_c.port, path: d_c.path)
377
423
  end
378
-
379
- # export session
380
- write_session_id(@driver.session_id, @export_session_path) if @export_session
381
424
  rescue Errno::ECONNREFUSED
382
425
  raise "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
383
426
  end
384
427
 
385
428
  if @http_client.instance_variable_defined? :@additional_headers
386
429
  # We only need the key for a new session request. Should remove it for other following commands.
387
- @http_client.additional_headers.delete Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
430
+ @http_client.delete_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
388
431
  end
389
432
 
390
- # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
391
- # Since @automation_name is set only client side before start_driver is called.
392
- set_automation_name_if_nil
393
-
394
433
  set_implicit_wait_by_default(@default_wait)
395
434
 
396
435
  @driver
397
436
  end
398
437
 
399
- private
438
+ # @private
439
+ # Attach to an existing session
440
+ def attach_to(session_id, url: nil, automation_name: nil, platform_name: nil,
441
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
442
+ raise ::Appium::Core::Error::ArgumentError, 'The :url must not be nil' if url.nil?
443
+ raise ::Appium::Core::Error::ArgumentError, 'The :automation_name must not be nil' if automation_name.nil?
444
+ raise ::Appium::Core::Error::ArgumentError, 'The :platform_name must not be nil' if platform_name.nil?
400
445
 
401
- def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
402
- client = http_client || Appium::Core::Base::Http::Default.new
446
+ @custom_url = url
447
+
448
+ # use lowercase internally
449
+ @automation_name = convert_downcase(automation_name)
450
+ @device = convert_downcase(platform_name)
451
+
452
+ extend_for(device: @device, automation_name: @automation_name)
403
453
 
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
454
+ @http_client = get_http_client http_client: http_client_ops.delete(:http_client),
455
+ open_timeout: http_client_ops.delete(:open_timeout),
456
+ read_timeout: http_client_ops.delete(:read_timeout)
457
+
458
+ # Note that 'enable_idempotency_header' works only a new session reqeust. The attach_to method skips
459
+ # the new session request, this it does not needed.
460
+
461
+ begin
462
+ # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
463
+ @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
464
+ url: @custom_url,
465
+ listener: @listener,
466
+ existing_session_id: session_id,
467
+ automation_name: automation_name,
468
+ platform_name: platform_name)
469
+ rescue Errno::ECONNREFUSED
470
+ raise ::Appium::Core::Error::SessionNotCreatedError,
471
+ "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
472
+ end
407
473
 
408
- client
474
+ @driver
475
+ end
476
+
477
+ def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
478
+ http_client || Appium::Core::Base::Http::Default.new(open_timeout: open_timeout, read_timeout: read_timeout)
409
479
  end
410
480
 
411
481
  # Ignore setting default wait if the target driver has no implementation
412
482
  def set_implicit_wait_by_default(wait)
483
+ return if @default_wait.nil?
484
+
413
485
  @driver.manage.timeouts.implicit_wait = wait
414
486
  rescue ::Selenium::WebDriver::Error::UnknownError => e
415
487
  unless e.message.include?('The operation requested is not yet implemented')
@@ -420,22 +492,8 @@ module Appium
420
492
  {}
421
493
  end
422
494
 
423
- public
424
-
425
- # Quits the driver
426
- # @return [void]
495
+ # Returns the server's version info. This method calls +driver.remote_status+ internally
427
496
  #
428
- # @example
429
- #
430
- # @core.quit_driver
431
- #
432
- def quit_driver
433
- @driver.quit
434
- rescue # rubocop:disable Style/RescueStandardError
435
- nil
436
- end
437
-
438
- # Returns the server's version info
439
497
  # @return [Hash]
440
498
  #
441
499
  # @example
@@ -449,58 +507,43 @@ module Appium
449
507
  # }
450
508
  # }
451
509
  #
452
- # Returns blank hash for Selenium Grid since 'remote_status' gets 500 error
510
+ # Returns blank hash in a case +driver.remote_status+ got an error
511
+ # such as Selenium Grid. It returns 500 error against 'remote_status'.
453
512
  #
454
513
  # @example
455
514
  #
456
515
  # @core.appium_server_version #=> {}
457
516
  #
458
517
  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')
518
+ return {} if @driver.nil?
462
519
 
463
- # driver.remote_status returns 500 error for using selenium grid
520
+ @driver.remote_status
521
+ rescue StandardError
522
+ # Ignore error case in a case the target appium server
523
+ # does not support `/status` API.
464
524
  {}
465
525
  end
466
526
 
467
- # Return the platform version as an array of integers
468
- # @return [Array<Integer>]
469
- #
470
- # @example
471
- #
472
- # @core.platform_version #=> [10,1,1]
473
- #
474
- def platform_version
475
- p_version = @driver.capabilities['platformVersion'] || @driver.session_capabilities['platformVersion']
476
- p_version.split('.').map(&:to_i)
477
- end
527
+ private
478
528
 
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
529
+ def convert_to_symbol(value)
530
+ if value.nil?
531
+ value
532
+ else
533
+ value.to_sym
534
+ end
492
535
  end
493
536
 
494
- private
495
-
496
537
  # @private
497
538
  def extend_for(device:, automation_name:) # rubocop:disable Metrics/CyclomaticComplexity
498
539
  extend Appium::Core
499
540
  extend Appium::Core::Device
500
541
 
501
- case device
542
+ sym_automation_name = convert_to_symbol(automation_name)
543
+
544
+ case convert_to_symbol(device)
502
545
  when :android
503
- case automation_name
546
+ case sym_automation_name
504
547
  when :espresso
505
548
  ::Appium::Core::Android::Espresso::Bridge.for self
506
549
  when :uiautomator2
@@ -511,28 +554,26 @@ module Appium
511
554
  ::Appium::Core::Android::Uiautomator1::Bridge.for self
512
555
  end
513
556
  when :ios, :tvos
514
- case automation_name
557
+ case sym_automation_name
515
558
  when :safari
516
559
  ::Appium::Logger.debug('SafariDriver for iOS')
517
- when :xcuitest
560
+ else # XCUITest
518
561
  ::Appium::Core::Ios::Xcuitest::Bridge.for self
519
- else # default and UIAutomation
520
- ::Appium::Core::Ios::Uiautomation::Bridge.for self
521
562
  end
522
563
  when :mac
523
- case automation_name
564
+ case sym_automation_name
524
565
  when :safari
525
566
  ::Appium::Logger.debug('SafariDriver for macOS')
526
567
  when :gecko
527
568
  ::Appium::Logger.debug('Gecko Driver for macOS')
528
569
  when :mac2
529
- ::Appium::Logger.debug('macOS XCUITest')
570
+ ::Appium::Core::Mac2::Bridge.for self
530
571
  else
531
572
  # no Mac specific extentions
532
573
  ::Appium::Logger.debug('macOS Native')
533
574
  end
534
575
  when :windows
535
- case automation_name
576
+ case sym_automation_name
536
577
  when :gecko
537
578
  ::Appium::Logger.debug('Gecko Driver for Windows')
538
579
  else
@@ -542,7 +583,7 @@ module Appium
542
583
  # https://github.com/Samsung/appium-tizen-driver
543
584
  ::Appium::Logger.debug('tizen')
544
585
  else
545
- case automation_name
586
+ case sym_automation_name
546
587
  when :youiengine
547
588
  # https://github.com/YOU-i-Labs/appium-youiengine-driver
548
589
  ::Appium::Logger.debug('YouiEngine')
@@ -559,36 +600,9 @@ module Appium
559
600
  self
560
601
  end
561
602
 
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
603
  # @private
589
604
  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] || {})
605
+ Core::Base::Capabilities.new(opts[:caps] || opts[:capabilities] || {})
592
606
  end
593
607
 
594
608
  # @private
@@ -596,21 +610,27 @@ module Appium
596
610
  opts[:appium_lib] || {}
597
611
  end
598
612
 
613
+ # @private
614
+ def get_app
615
+ get_cap 'app'
616
+ end
617
+
599
618
  # @private
600
619
  # Path to the .apk, .app or .app.zip.
601
620
  # The path can be local, HTTP/S, Windows Share and other path like 'sauce-storage:'.
602
621
  # Use @caps[:app] without modifications if the path isn't HTTP/S or local path.
603
622
  def set_app_path
604
- return unless @caps && @caps[:app] && !@caps[:app].empty?
605
- return if @caps[:app] =~ URI::DEFAULT_PARSER.make_regexp
606
-
607
- app_path = File.expand_path(@caps[:app])
608
- @caps[:app] = if File.exist? app_path
609
- app_path
610
- else
611
- ::Appium::Logger.warn("Use #{@caps[:app]} directly since #{app_path} does not exist.")
612
- @caps[:app]
613
- end
623
+ # FIXME: maybe `:app` should check `app` as well.
624
+ return unless @caps && get_app && !get_app.empty?
625
+ return if get_app =~ URI::DEFAULT_PARSER.make_regexp
626
+
627
+ app_path = File.expand_path(get_app)
628
+ @caps['app'] = if File.exist? app_path
629
+ app_path
630
+ else
631
+ ::Appium::Logger.warn("Use #{get_app} directly since #{app_path} does not exist.")
632
+ get_app
633
+ end
614
634
  end
615
635
 
616
636
  # @private
@@ -623,9 +643,6 @@ module Appium
623
643
 
624
644
  @default_wait = opts.default_wait
625
645
 
626
- @export_session = opts.export_session
627
- @export_session_path = opts.export_session_path
628
-
629
646
  @port = opts.port
630
647
 
631
648
  @wait_timeout = opts.wait_timeout
@@ -639,36 +656,30 @@ module Appium
639
656
  # @private
640
657
  def set_appium_device
641
658
  # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
642
- @device = @caps[:platformName]
659
+ @device = get_cap 'platformName'
643
660
  return @device unless @device
644
661
 
645
- @device = @device.is_a?(Symbol) ? @device.downcase : @device.downcase.strip.intern
662
+ @device = convert_to_symbol(convert_downcase(@device))
646
663
  end
647
664
 
648
665
  # @private
649
666
  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
667
+ candidate = get_cap 'automationName'
668
+ @automation_name = candidate if candidate
669
+ @automation_name = convert_to_symbol(convert_downcase(@automation_name)) if @automation_name
654
670
  end
655
671
 
656
672
  # @private
657
- def set_automation_name_if_nil
658
- return unless @automation_name.nil?
659
-
660
- @automation_name = if @driver.capabilities['automationName']
661
- @driver.capabilities['automationName'].downcase.strip.intern
662
- end
673
+ def convert_downcase(value)
674
+ value.is_a?(Symbol) ? value.downcase : value.downcase.strip.intern
663
675
  end
664
676
 
665
- # @private
666
- def write_session_id(session_id, export_path = '/tmp/appium_lib_session')
667
- export_path = export_path.tr('/', '\\') if ::Appium::Core::Base.platform.windows?
668
- File.write(export_path, session_id)
669
- rescue IOError => e
670
- ::Appium::Logger.warn e
671
- nil
677
+ def get_cap(name)
678
+ name_with_prefix = "#{::Appium::Core::Base::Bridge::APPIUM_PREFIX}#{name}"
679
+ @caps[convert_to_symbol name] ||
680
+ @caps[name] ||
681
+ @caps[convert_to_symbol name_with_prefix] ||
682
+ @caps[name_with_prefix]
672
683
  end
673
684
  end # class Driver
674
685
  end # module Core