appium_lib_core 4.1.0 → 9.2.1

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 +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