appium_lib_core 4.1.0 → 9.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +352 -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 +263 -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 +207 -164
  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 +200 -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,9 @@ 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
+
398
+ @custom_url ||= "http://127.0.0.1:#{@port}/wd/hub"
399
+ @custom_url = server_url unless server_url.nil?
354
400
 
355
401
  @http_client = get_http_client http_client: http_client_ops.delete(:http_client),
356
402
  open_timeout: http_client_ops.delete(:open_timeout),
@@ -358,35 +404,35 @@ module Appium
358
404
 
359
405
  if @enable_idempotency_header
360
406
  if @http_client.instance_variable_defined? :@additional_headers
361
- @http_client.additional_headers[Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]] = SecureRandom.uuid
407
+ @http_client.set_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency], SecureRandom.uuid
362
408
  else
363
409
  ::Appium::Logger.warn 'No additional_headers attribute in this http client instance'
364
410
  end
365
411
  end
366
412
 
367
413
  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,
414
+ @driver = ::Appium::Core::Base::Driver.new(listener: @listener,
415
+ http_client: @http_client,
416
+ capabilities: @caps, # ::Appium::Core::Base::Capabilities
371
417
  url: @custom_url,
372
- listener: @listener)
418
+ wait_timeout: @wait_timeout,
419
+ wait_interval: @wait_interval)
373
420
 
374
421
  if @direct_connect
375
422
  d_c = DirectConnections.new(@driver.capabilities)
376
423
  @driver.update_sending_request_to(protocol: d_c.protocol, host: d_c.host, port: d_c.port, path: d_c.path)
377
424
  end
378
-
379
- # export session
380
- write_session_id(@driver.session_id, @export_session_path) if @export_session
381
425
  rescue Errno::ECONNREFUSED
382
426
  raise "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
383
427
  end
384
428
 
385
429
  if @http_client.instance_variable_defined? :@additional_headers
386
430
  # 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]
431
+ @http_client.delete_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
388
432
  end
389
433
 
434
+ # TODO: this method can be removed after releasing Appium 2.0, and after a while
435
+ # since Appium 2.0 reuqires 'automationName'. This method won't help anymore then.
390
436
  # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
391
437
  # Since @automation_name is set only client side before start_driver is called.
392
438
  set_automation_name_if_nil
@@ -396,20 +442,54 @@ module Appium
396
442
  @driver
397
443
  end
398
444
 
399
- private
445
+ # @private
446
+ # Attach to an existing session
447
+ def attach_to(session_id, url: nil, automation_name: nil, platform_name: nil,
448
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
400
449
 
401
- def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
402
- client = http_client || Appium::Core::Base::Http::Default.new
450
+ raise ::Appium::Core::Error::ArgumentError, 'The :url must not be nil' if url.nil?
451
+ raise ::Appium::Core::Error::ArgumentError, 'The :automation_name must not be nil' if automation_name.nil?
452
+ raise ::Appium::Core::Error::ArgumentError, 'The :platform_name must not be nil' if platform_name.nil?
453
+
454
+ @custom_url = url
455
+
456
+ # use lowercase internally
457
+ @automation_name = convert_downcase(automation_name)
458
+ @device = convert_downcase(platform_name)
459
+
460
+ extend_for(device: @device, automation_name: @automation_name)
461
+
462
+ @http_client = get_http_client http_client: http_client_ops.delete(:http_client),
463
+ open_timeout: http_client_ops.delete(:open_timeout),
464
+ read_timeout: http_client_ops.delete(:read_timeout)
465
+
466
+ # Note that 'enable_idempotency_header' works only a new session reqeust. The attach_to method skips
467
+ # the new session request, this it does not needed.
403
468
 
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
469
+ begin
470
+ # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
471
+ @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
472
+ url: @custom_url,
473
+ listener: @listener,
474
+ existing_session_id: session_id,
475
+ automation_name: automation_name,
476
+ platform_name: platform_name)
477
+ rescue Errno::ECONNREFUSED
478
+ raise ::Appium::Core::Error::SessionNotCreatedError,
479
+ "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
480
+ end
407
481
 
408
- client
482
+ @driver
483
+ end
484
+
485
+ def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
486
+ http_client || Appium::Core::Base::Http::Default.new(open_timeout: open_timeout, read_timeout: read_timeout)
409
487
  end
410
488
 
411
489
  # Ignore setting default wait if the target driver has no implementation
412
490
  def set_implicit_wait_by_default(wait)
491
+ return if @default_wait.nil?
492
+
413
493
  @driver.manage.timeouts.implicit_wait = wait
414
494
  rescue ::Selenium::WebDriver::Error::UnknownError => e
415
495
  unless e.message.include?('The operation requested is not yet implemented')
@@ -420,9 +500,7 @@ module Appium
420
500
  {}
421
501
  end
422
502
 
423
- public
424
-
425
- # Quits the driver
503
+ # [Deprecated] Quits the driver. This method is the same as @driver.quit
426
504
  # @return [void]
427
505
  #
428
506
  # @example
@@ -430,12 +508,14 @@ module Appium
430
508
  # @core.quit_driver
431
509
  #
432
510
  def quit_driver
511
+ ::Appium::Logger.warn('[DEPRECATION] quit_driver will be removed. Please use @driver.quit instead.')
433
512
  @driver.quit
434
513
  rescue # rubocop:disable Style/RescueStandardError
435
514
  nil
436
515
  end
437
516
 
438
- # Returns the server's version info
517
+ # Returns the server's version info. This method calls +driver.remote_status+ internally
518
+ #
439
519
  # @return [Hash]
440
520
  #
441
521
  # @example
@@ -449,58 +529,43 @@ module Appium
449
529
  # }
450
530
  # }
451
531
  #
452
- # Returns blank hash for Selenium Grid since 'remote_status' gets 500 error
532
+ # Returns blank hash in a case +driver.remote_status+ got an error
533
+ # such as Selenium Grid. It returns 500 error against 'remote_status'.
453
534
  #
454
535
  # @example
455
536
  #
456
537
  # @core.appium_server_version #=> {}
457
538
  #
458
539
  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')
540
+ return {} if @driver.nil?
462
541
 
463
- # driver.remote_status returns 500 error for using selenium grid
542
+ @driver.remote_status
543
+ rescue StandardError
544
+ # Ignore error case in a case the target appium server
545
+ # does not support `/status` API.
464
546
  {}
465
547
  end
466
548
 
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
549
+ private
478
550
 
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
551
+ def convert_to_symbol(value)
552
+ if value.nil?
553
+ value
554
+ else
555
+ value.to_sym
556
+ end
492
557
  end
493
558
 
494
- private
495
-
496
559
  # @private
497
560
  def extend_for(device:, automation_name:) # rubocop:disable Metrics/CyclomaticComplexity
498
561
  extend Appium::Core
499
562
  extend Appium::Core::Device
500
563
 
501
- case device
564
+ sym_automation_name = convert_to_symbol(automation_name)
565
+
566
+ case convert_to_symbol(device)
502
567
  when :android
503
- case automation_name
568
+ case sym_automation_name
504
569
  when :espresso
505
570
  ::Appium::Core::Android::Espresso::Bridge.for self
506
571
  when :uiautomator2
@@ -511,28 +576,26 @@ module Appium
511
576
  ::Appium::Core::Android::Uiautomator1::Bridge.for self
512
577
  end
513
578
  when :ios, :tvos
514
- case automation_name
579
+ case sym_automation_name
515
580
  when :safari
516
581
  ::Appium::Logger.debug('SafariDriver for iOS')
517
- when :xcuitest
582
+ else # XCUITest
518
583
  ::Appium::Core::Ios::Xcuitest::Bridge.for self
519
- else # default and UIAutomation
520
- ::Appium::Core::Ios::Uiautomation::Bridge.for self
521
584
  end
522
585
  when :mac
523
- case automation_name
586
+ case sym_automation_name
524
587
  when :safari
525
588
  ::Appium::Logger.debug('SafariDriver for macOS')
526
589
  when :gecko
527
590
  ::Appium::Logger.debug('Gecko Driver for macOS')
528
591
  when :mac2
529
- ::Appium::Logger.debug('macOS XCUITest')
592
+ ::Appium::Core::Mac2::Bridge.for self
530
593
  else
531
594
  # no Mac specific extentions
532
595
  ::Appium::Logger.debug('macOS Native')
533
596
  end
534
597
  when :windows
535
- case automation_name
598
+ case sym_automation_name
536
599
  when :gecko
537
600
  ::Appium::Logger.debug('Gecko Driver for Windows')
538
601
  else
@@ -542,7 +605,7 @@ module Appium
542
605
  # https://github.com/Samsung/appium-tizen-driver
543
606
  ::Appium::Logger.debug('tizen')
544
607
  else
545
- case automation_name
608
+ case sym_automation_name
546
609
  when :youiengine
547
610
  # https://github.com/YOU-i-Labs/appium-youiengine-driver
548
611
  ::Appium::Logger.debug('YouiEngine')
@@ -559,36 +622,9 @@ module Appium
559
622
  self
560
623
  end
561
624
 
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
625
  # @private
589
626
  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] || {})
627
+ Core::Base::Capabilities.new(opts[:caps] || opts[:capabilities] || {})
592
628
  end
593
629
 
594
630
  # @private
@@ -596,21 +632,27 @@ module Appium
596
632
  opts[:appium_lib] || {}
597
633
  end
598
634
 
635
+ # @private
636
+ def get_app
637
+ get_cap 'app'
638
+ end
639
+
599
640
  # @private
600
641
  # Path to the .apk, .app or .app.zip.
601
642
  # The path can be local, HTTP/S, Windows Share and other path like 'sauce-storage:'.
602
643
  # Use @caps[:app] without modifications if the path isn't HTTP/S or local path.
603
644
  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
645
+ # FIXME: maybe `:app` should check `app` as well.
646
+ return unless @caps && get_app && !get_app.empty?
647
+ return if get_app =~ URI::DEFAULT_PARSER.make_regexp
648
+
649
+ app_path = File.expand_path(get_app)
650
+ @caps['app'] = if File.exist? app_path
651
+ app_path
652
+ else
653
+ ::Appium::Logger.warn("Use #{get_app} directly since #{app_path} does not exist.")
654
+ get_app
655
+ end
614
656
  end
615
657
 
616
658
  # @private
@@ -623,9 +665,6 @@ module Appium
623
665
 
624
666
  @default_wait = opts.default_wait
625
667
 
626
- @export_session = opts.export_session
627
- @export_session_path = opts.export_session_path
628
-
629
668
  @port = opts.port
630
669
 
631
670
  @wait_timeout = opts.wait_timeout
@@ -639,36 +678,40 @@ module Appium
639
678
  # @private
640
679
  def set_appium_device
641
680
  # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
642
- @device = @caps[:platformName]
681
+ @device = get_cap 'platformName'
643
682
  return @device unless @device
644
683
 
645
- @device = @device.is_a?(Symbol) ? @device.downcase : @device.downcase.strip.intern
684
+ @device = convert_to_symbol(convert_downcase(@device))
646
685
  end
647
686
 
648
687
  # @private
649
688
  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
689
+ candidate = get_cap 'automationName'
690
+ @automation_name = candidate if candidate
691
+ @automation_name = convert_to_symbol(convert_downcase(@automation_name)) if @automation_name
692
+ end
693
+
694
+ # @private
695
+ def convert_downcase(value)
696
+ value.is_a?(Symbol) ? value.downcase : value.downcase.strip.intern
654
697
  end
655
698
 
656
699
  # @private
657
700
  def set_automation_name_if_nil
658
701
  return unless @automation_name.nil?
659
702
 
660
- @automation_name = if @driver.capabilities['automationName']
661
- @driver.capabilities['automationName'].downcase.strip.intern
662
- end
703
+ automation_name = if @driver.capabilities['automationName']
704
+ @driver.capabilities['automationName'].downcase.strip.intern
705
+ end
706
+ @automation_name = convert_to_symbol automation_name
663
707
  end
664
708
 
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
709
+ def get_cap(name)
710
+ name_with_prefix = "appium:#{name}"
711
+ @caps[convert_to_symbol name] ||
712
+ @caps[name] ||
713
+ @caps[convert_to_symbol name_with_prefix] ||
714
+ @caps[name_with_prefix]
672
715
  end
673
716
  end # class Driver
674
717
  end # module Core