appium_lib_core 5.0.0 → 6.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +140 -0
  3. data/README.md +55 -10
  4. data/appium_lib_core.gemspec +5 -5
  5. data/lib/appium_lib_core/common/base/bridge.rb +54 -40
  6. data/lib/appium_lib_core/common/base/capabilities.rb +8 -16
  7. data/lib/appium_lib_core/common/base/driver.rb +68 -40
  8. data/lib/appium_lib_core/common/base/has_location.rb +10 -10
  9. data/lib/appium_lib_core/common/base/http_default.rb +1 -1
  10. data/lib/appium_lib_core/common/base/rotable.rb +11 -3
  11. data/lib/appium_lib_core/common/base/search_context.rb +1 -2
  12. data/lib/appium_lib_core/common/command.rb +4 -2
  13. data/lib/appium_lib_core/common/device/app_management.rb +8 -14
  14. data/lib/appium_lib_core/common/device/value.rb +1 -3
  15. data/lib/appium_lib_core/common/error.rb +0 -4
  16. data/lib/appium_lib_core/driver.rb +170 -62
  17. data/lib/appium_lib_core/support/event_firing_bridge.rb +57 -0
  18. data/lib/appium_lib_core/version.rb +2 -2
  19. data/lib/appium_lib_core/{ios/uiautomation/bridge.rb → windows/device/app_management.rb} +20 -12
  20. data/lib/appium_lib_core/windows/device.rb +2 -0
  21. data/lib/appium_lib_core.rb +19 -5
  22. metadata +21 -34
  23. data/.github/ISSUE_TEMPLATE/issue-report.md +0 -29
  24. data/.github/contributing.md +0 -26
  25. data/.github/dependabot.yml +0 -8
  26. data/.github/issue_template.md +0 -20
  27. data/.github/workflows/unittest.yml +0 -67
  28. data/.gitignore +0 -18
  29. data/.rubocop.yml +0 -146
  30. data/azure-pipelines.yml +0 -15
  31. data/ci-jobs/functional/android_setup.yml +0 -3
  32. data/ci-jobs/functional/ios_setup.yml +0 -7
  33. data/ci-jobs/functional/publish_test_result.yml +0 -18
  34. data/ci-jobs/functional/run_appium.yml +0 -25
  35. data/ci-jobs/functional/start-emulator.sh +0 -26
  36. data/ci-jobs/functional_test.yml +0 -298
  37. data/docs/mobile_command.md +0 -34
  38. data/lib/appium_lib_core/ios/uiautomation/device.rb +0 -44
  39. data/lib/appium_lib_core/ios/uiautomation/patch.rb +0 -34
  40. data/lib/appium_lib_core/ios.rb +0 -20
  41. data/release_notes.md +0 -932
  42. data/script/commands.rb +0 -166
@@ -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,7 +26,6 @@ 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
 
@@ -76,6 +78,13 @@ module Appium
76
78
  path: 'directConnectPath'
77
79
  }.freeze
78
80
 
81
+ W3C_KEYS = {
82
+ protocol: 'appium:directConnectProtocol',
83
+ host: 'appium:directConnectHost',
84
+ port: 'appium:directConnectPort',
85
+ path: 'appium:directConnectPath'
86
+ }.freeze
87
+
79
88
  # @return [string] Returns a protocol such as http/https
80
89
  attr_reader :protocol
81
90
 
@@ -89,10 +98,10 @@ module Appium
89
98
  attr_reader :path
90
99
 
91
100
  def initialize(capabilities)
92
- @protocol = capabilities[KEYS[:protocol]]
93
- @host = capabilities[KEYS[:host]]
94
- @port = capabilities[KEYS[:port]]
95
- @path = capabilities[KEYS[:path]]
101
+ @protocol = capabilities[W3C_KEYS[:protocol]] || capabilities[KEYS[:protocol]]
102
+ @host = capabilities[W3C_KEYS[:host]] || capabilities[KEYS[:host]]
103
+ @port = capabilities[W3C_KEYS[:port]] || capabilities[KEYS[:port]]
104
+ @path = capabilities[W3C_KEYS[:path]] || capabilities[KEYS[:path]]
96
105
  end
97
106
  end
98
107
 
@@ -175,7 +184,8 @@ module Appium
175
184
  # - <code>directConnectPort</code>
176
185
  # - <code>directConnectPath</code>
177
186
  #
178
- # Ignore them if this parameter is <code>false</code>. Defaults to true.
187
+ # ignore them if this parameter is <code>false</code>. Defaults to true.
188
+ # These keys can have <code>appium:</code> prefix.
179
189
  #
180
190
  # @return [Bool]
181
191
  attr_reader :direct_connect
@@ -266,8 +276,58 @@ module Appium
266
276
  # @core = Appium::Core.for(opts) # create a core driver with 'opts' and extend methods into 'self'
267
277
  # @core.start_driver # start driver with 'url'. Connect to 'http://custom-host:8080/wd/hub.com'
268
278
  #
279
+ # # With a custom listener
280
+ # class CustomListener < ::Selenium::WebDriver::Support::AbstractEventListener
281
+ # // something
282
+ # end
283
+ # capabilities: {
284
+ # platformName: :ios,
285
+ # platformVersion: '11.0',
286
+ # deviceName: 'iPhone Simulator',
287
+ # automationName: 'XCUITest',
288
+ # app: '/path/to/MyiOS.app'
289
+ # },
290
+ # appium_lib: {
291
+ # listener: CustomListener.new,
292
+ # }
293
+ # @core = Appium::Core.for capabilities: capabilities, appium_lib: appium_lib
294
+ # @core.start_driver
295
+ #
269
296
  def self.for(opts = {})
270
- new(opts)
297
+ new.setup_for_new_session(opts)
298
+ end
299
+
300
+ # Attach to an existing session. The main usage of this method is to attach to
301
+ # an existing session for debugging. The generated driver instance has the capabilities which
302
+ # has the given automationName and platformName only since the W3C WebDriver spec does not provide
303
+ # an endpoint to get running session's capabilities.
304
+ #
305
+ #
306
+ # @param [String] The session id to attach to.
307
+ # @param [String] url The WebDriver URL to attach to with the session_id.
308
+ # @param [String] automation_name The platform name to keep in the dummy capabilities
309
+ # @param [String] platform_name The automation name to keep in the dummy capabilities
310
+ # @return [Selenium::WebDriver] A new driver instance with the given session id.
311
+ #
312
+ # @example
313
+ #
314
+ # new_driver = ::Appium::Core::Driver.attach_to(
315
+ # driver.session_id, # The 'driver' has an existing session id
316
+ # url: 'http://127.0.0.1:4723/wd/hub', automation_name: 'UiAutomator2', platform_name: 'Android'
317
+ # )
318
+ # new_driver.page_source # for example
319
+ #
320
+ def self.attach_to(
321
+ session_id, url: nil, automation_name: nil, platform_name: nil,
322
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 }
323
+ )
324
+ new.attach_to(
325
+ session_id,
326
+ automation_name: automation_name,
327
+ platform_name: platform_name,
328
+ url: url,
329
+ http_client_ops: http_client_ops
330
+ )
271
331
  end
272
332
 
273
333
  private
@@ -278,17 +338,25 @@ module Appium
278
338
  @delegate_target
279
339
  end
280
340
 
281
- public
282
-
283
341
  # @private
284
- def initialize(opts = {})
342
+ def initialize
285
343
  @delegate_target = self # for testing purpose
286
344
  @automation_name = nil # initialise before 'set_automation_name'
345
+ end
346
+
347
+ public
287
348
 
288
- opts = Appium.symbolize_keys opts
289
- validate_keys(opts)
349
+ # @private
350
+ # Set up for a new session
351
+ def setup_for_new_session(opts = {})
352
+ @custom_url = opts.delete :url # to set the custom url as :url
353
+
354
+ # TODO: Remove when we implement Options
355
+ # The symbolize_keys is to keep compatiility for the legacy code, which allows capabilities to give 'string' as the key.
356
+ # The toplevel `caps`, `capabilities` and `appium_lib` are expected to be symbol.
357
+ # FIXME: First, please try to remove `nested: true` to `nested: false`.
358
+ opts = Appium.symbolize_keys(opts, nested: true)
290
359
 
291
- @custom_url = opts.delete :url
292
360
  @caps = get_caps(opts)
293
361
 
294
362
  set_appium_lib_specific_values(get_appium_lib_opts(opts))
@@ -297,8 +365,7 @@ module Appium
297
365
  set_automation_name
298
366
 
299
367
  extend_for(device: @device, automation_name: @automation_name)
300
-
301
- self # rubocop:disable Lint/Void
368
+ self
302
369
  end
303
370
 
304
371
  # Creates a new global driver and quits the old one if it exists.
@@ -309,7 +376,7 @@ module Appium
309
376
  # @option http_client_ops [Hash] :http_client Custom HTTP Client
310
377
  # @option http_client_ops [Hash] :open_timeout Custom open timeout for http client.
311
378
  # @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
312
- # @return [Selenium::WebDriver] the new global driver
379
+ # @return [Selenium::WebDriver] A new driver instance
313
380
  #
314
381
  # @example
315
382
  #
@@ -363,11 +430,10 @@ module Appium
363
430
  begin
364
431
  @driver = ::Appium::Core::Base::Driver.new(listener: @listener,
365
432
  http_client: @http_client,
366
- capabilities: @caps, # ::Selenium::WebDriver::Remote::Capabilities
433
+ capabilities: @caps, # ::Appium::Core::Base::Capabilities
367
434
  url: @custom_url,
368
435
  wait_timeout: @wait_timeout,
369
- wait_interval: @wait_interval,
370
- automation_name: @automation_name)
436
+ wait_interval: @wait_interval)
371
437
 
372
438
  if @direct_connect
373
439
  d_c = DirectConnections.new(@driver.capabilities)
@@ -385,6 +451,8 @@ module Appium
385
451
  @http_client.additional_headers.delete Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
386
452
  end
387
453
 
454
+ # TODO: this method can be removed after releasing Appium 2.0, and after a while
455
+ # since Appium 2.0 reuqires 'automationName'. This method won't help anymore then.
388
456
  # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name.
389
457
  # Since @automation_name is set only client side before start_driver is called.
390
458
  set_automation_name_if_nil
@@ -394,7 +462,47 @@ module Appium
394
462
  @driver
395
463
  end
396
464
 
397
- private
465
+ # @private
466
+ # Attach to an existing session
467
+ def attach_to(session_id, url: nil, automation_name: nil, platform_name: nil,
468
+ http_client_ops: { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
469
+
470
+ raise ::Appium::Core::Error::ArgumentError, 'The :url must not be nil' if url.nil?
471
+ raise ::Appium::Core::Error::ArgumentError, 'The :automation_name must not be nil' if automation_name.nil?
472
+ raise ::Appium::Core::Error::ArgumentError, 'The :platform_name must not be nil' if platform_name.nil?
473
+
474
+ @custom_url = url
475
+
476
+ # use lowercase internally
477
+ @automation_name = convert_downcase(automation_name)
478
+ @device = convert_downcase(platform_name)
479
+
480
+ extend_for(device: @device, automation_name: @automation_name)
481
+
482
+ @http_client = get_http_client http_client: http_client_ops.delete(:http_client),
483
+ open_timeout: http_client_ops.delete(:open_timeout),
484
+ read_timeout: http_client_ops.delete(:read_timeout)
485
+
486
+ # Note that 'enable_idempotency_header' works only a new session reqeust. The attach_to method skips
487
+ # the new session request, this it does not needed.
488
+
489
+ begin
490
+ # included https://github.com/SeleniumHQ/selenium/blob/43f8b3f66e7e01124eff6a5805269ee441f65707/rb/lib/selenium/webdriver/remote/driver.rb#L29
491
+ @driver = ::Appium::Core::Base::Driver.new(http_client: @http_client,
492
+ url: @custom_url,
493
+ listener: @listener,
494
+ existing_session_id: session_id,
495
+ automation_name: automation_name,
496
+ platform_name: platform_name)
497
+
498
+ # export session
499
+ write_session_id(@driver.session_id, @export_session_path) if @export_session
500
+ rescue Errno::ECONNREFUSED
501
+ raise "ERROR: Unable to connect to Appium. Is the server running on #{@custom_url}?"
502
+ end
503
+
504
+ @driver
505
+ end
398
506
 
399
507
  def get_http_client(http_client: nil, open_timeout: nil, read_timeout: nil)
400
508
  client = http_client || Appium::Core::Base::Http::Default.new
@@ -420,9 +528,7 @@ module Appium
420
528
  {}
421
529
  end
422
530
 
423
- public
424
-
425
- # Quits the driver
531
+ # [Deprecated] Quits the driver. This method is the same as @driver.quit
426
532
  # @return [void]
427
533
  #
428
534
  # @example
@@ -430,6 +536,7 @@ module Appium
430
536
  # @core.quit_driver
431
537
  #
432
538
  def quit_driver
539
+ ::Appium::Logger.warn('[DEPRECATION] quit_driver will be removed. Please use @driver.quit instead.')
433
540
  @driver.quit
434
541
  rescue # rubocop:disable Style/RescueStandardError
435
542
  nil
@@ -475,20 +582,35 @@ module Appium
475
582
  # @core.platform_version #=> [10,1,1]
476
583
  #
477
584
  def platform_version
585
+ ::Appium::Logger.warn(
586
+ '[DEPRECATION] platform_version method will be. ' \
587
+ 'Please check the platformVersion via @driver.capabilities["platformVersion"] instead.'
588
+ )
589
+
478
590
  p_version = @driver.capabilities['platformVersion'] || @driver.session_capabilities['platformVersion']
479
591
  p_version.split('.').map(&:to_i)
480
592
  end
481
593
 
482
594
  private
483
595
 
596
+ def convert_to_symbol(value)
597
+ if value.nil?
598
+ value
599
+ else
600
+ value.to_sym
601
+ end
602
+ end
603
+
484
604
  # @private
485
605
  def extend_for(device:, automation_name:) # rubocop:disable Metrics/CyclomaticComplexity
486
606
  extend Appium::Core
487
607
  extend Appium::Core::Device
488
608
 
489
- case device
609
+ sym_automation_name = convert_to_symbol(automation_name)
610
+
611
+ case convert_to_symbol(device)
490
612
  when :android
491
- case automation_name
613
+ case sym_automation_name
492
614
  when :espresso
493
615
  ::Appium::Core::Android::Espresso::Bridge.for self
494
616
  when :uiautomator2
@@ -499,16 +621,14 @@ module Appium
499
621
  ::Appium::Core::Android::Uiautomator1::Bridge.for self
500
622
  end
501
623
  when :ios, :tvos
502
- case automation_name
624
+ case sym_automation_name
503
625
  when :safari
504
626
  ::Appium::Logger.debug('SafariDriver for iOS')
505
- when :xcuitest
627
+ else # XCUITest
506
628
  ::Appium::Core::Ios::Xcuitest::Bridge.for self
507
- else # default and UIAutomation
508
- ::Appium::Core::Ios::Uiautomation::Bridge.for self
509
629
  end
510
630
  when :mac
511
- case automation_name
631
+ case sym_automation_name
512
632
  when :safari
513
633
  ::Appium::Logger.debug('SafariDriver for macOS')
514
634
  when :gecko
@@ -520,7 +640,7 @@ module Appium
520
640
  ::Appium::Logger.debug('macOS Native')
521
641
  end
522
642
  when :windows
523
- case automation_name
643
+ case sym_automation_name
524
644
  when :gecko
525
645
  ::Appium::Logger.debug('Gecko Driver for Windows')
526
646
  else
@@ -530,7 +650,7 @@ module Appium
530
650
  # https://github.com/Samsung/appium-tizen-driver
531
651
  ::Appium::Logger.debug('tizen')
532
652
  else
533
- case automation_name
653
+ case sym_automation_name
534
654
  when :youiengine
535
655
  # https://github.com/YOU-i-Labs/appium-youiengine-driver
536
656
  ::Appium::Logger.debug('YouiEngine')
@@ -547,32 +667,9 @@ module Appium
547
667
  self
548
668
  end
549
669
 
550
- # @private
551
- def validate_keys(opts)
552
- flatten_ops = flatten_hash_keys(opts)
553
-
554
- raise Error::NoCapabilityError unless opts.member?(:caps) || opts.member?(:capabilities)
555
-
556
- if !opts.member?(:appium_lib) && flatten_ops.member?(:appium_lib)
557
- raise Error::CapabilityStructureError, 'Please check the value of appium_lib in the capability'
558
- end
559
-
560
- true
561
- end
562
-
563
- # @private
564
- def flatten_hash_keys(hash, flatten_keys_result = [])
565
- hash.each do |key, value|
566
- flatten_keys_result << key
567
- flatten_hash_keys(value, flatten_keys_result) if value.is_a?(Hash)
568
- end
569
-
570
- flatten_keys_result
571
- end
572
-
573
670
  # @private
574
671
  def get_caps(opts)
575
- Core::Base::Capabilities.create_capabilities(opts[:caps] || opts[:capabilities] || {})
672
+ Core::Base::Capabilities.new(opts[:caps] || opts[:capabilities] || {})
576
673
  end
577
674
 
578
675
  # @private
@@ -585,6 +682,7 @@ module Appium
585
682
  # The path can be local, HTTP/S, Windows Share and other path like 'sauce-storage:'.
586
683
  # Use @caps[:app] without modifications if the path isn't HTTP/S or local path.
587
684
  def set_app_path
685
+ # FIXME: maybe `:app` should check `app` as well.
588
686
  return unless @caps && @caps[:app] && !@caps[:app].empty?
589
687
  return if @caps[:app] =~ URI::DEFAULT_PARSER.make_regexp
590
688
 
@@ -623,18 +721,24 @@ module Appium
623
721
  # @private
624
722
  def set_appium_device
625
723
  # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
626
- @device = @caps[:platformName]
724
+ # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
725
+ @device = @caps[:platformName] || @caps['platformName']
627
726
  return @device unless @device
628
727
 
629
- @device = @device.is_a?(Symbol) ? @device.downcase : @device.downcase.strip.intern
728
+ @device = convert_downcase @device
630
729
  end
631
730
 
632
731
  # @private
633
732
  def set_automation_name
634
- @automation_name = @caps[:automationName] if @caps[:automationName]
635
- @automation_name = if @automation_name
636
- @automation_name.is_a?(Symbol) ? @automation_name.downcase : @automation_name.downcase.strip.intern
637
- end
733
+ # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
734
+ candidate = @caps[:automationName] || @caps['automationName']
735
+ @automation_name = candidate if candidate
736
+ @automation_name = convert_downcase @automation_name if @automation_name
737
+ end
738
+
739
+ # @private
740
+ def convert_downcase(value)
741
+ value.is_a?(Symbol) ? value.downcase : value.downcase.strip.intern
638
742
  end
639
743
 
640
744
  # @private
@@ -648,6 +752,10 @@ module Appium
648
752
 
649
753
  # @private
650
754
  def write_session_id(session_id, export_path = '/tmp/appium_lib_session')
755
+ ::Appium::Logger.warn(
756
+ '[DEPRECATION] export_session option will be removed. ' \
757
+ 'Please save the session id by yourself with #session_id method like @driver.session_id.'
758
+ )
651
759
  export_path = export_path.tr('/', '\\') if ::Appium::Core::Base.platform.windows?
652
760
  File.write(export_path, session_id)
653
761
  rescue IOError => e
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Appium
16
+ module Support
17
+ class EventFiringBridge < ::Selenium::WebDriver::Support::EventFiringBridge
18
+ # This module inherits ::Selenium::WebDriver::Support::EventFiringBridge
19
+ # to provide customer listener availability.
20
+ # https://github.com/SeleniumHQ/selenium/blob/trunk/rb/lib/selenium/webdriver/support/event_firing_bridge.rb#L79
21
+
22
+ def initialize(delegate, listener, **opts)
23
+ @appium_options = opts
24
+ super delegate, listener
25
+ end
26
+
27
+ def find_element_by(how, what, parent = nil)
28
+ e = dispatch(:find, how, what, driver) do
29
+ @delegate.find_element_by how, what, parent
30
+ end
31
+
32
+ ::Appium::Core::Element.new self, e.ref.last
33
+ end
34
+
35
+ def find_elements_by(how, what, parent = nil)
36
+ es = dispatch(:find, how, what, driver) do
37
+ @delegate.find_elements_by(how, what, parent)
38
+ end
39
+
40
+ es.map { |e| ::Appium::Core::Element.new self, e.ref.last }
41
+ end
42
+
43
+ private
44
+
45
+ def create_element(ref)
46
+ ::Appium::Core::Element.new @delegate, ref
47
+ end
48
+
49
+ def driver
50
+ # To not gives the listener
51
+ @appium_options.delete(:listener)
52
+
53
+ @driver ||= ::Appium::Core::Base::Driver.new(bridge: self, listener: nil, **@appium_options)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -14,7 +14,7 @@
14
14
 
15
15
  module Appium
16
16
  module Core
17
- VERSION = '5.0.0' unless defined? ::Appium::Core::VERSION
18
- DATE = '2021-11-05' unless defined? ::Appium::Core::DATE
17
+ VERSION = '6.3.0' unless defined? ::Appium::Core::VERSION
18
+ DATE = '2023-03-14' unless defined? ::Appium::Core::DATE
19
19
  end
20
20
  end
@@ -14,17 +14,25 @@
14
14
 
15
15
  module Appium
16
16
  module Core
17
- module Ios
18
- module Uiautomation
19
- module Bridge
20
- def self.for(target)
21
- target.extend Appium::Core::Ios::Device
17
+ module Windows
18
+ module Device
19
+ module AppManagement
20
+ # override
21
+ def self.add_methods
22
+ ::Appium::Core::Device.add_endpoint_method(:launch_app) do
23
+ def launch_app
24
+ execute :launch_app
25
+ end
26
+ end
22
27
 
23
- Core::Ios::Uiautomation.patch_webdriver_element
24
- Core::Ios::Uiautomation::Device.add_methods
28
+ ::Appium::Core::Device.add_endpoint_method(:close_app) do
29
+ def close_app
30
+ execute :close_app
31
+ end
32
+ end
25
33
  end
26
- end
27
- end
28
- end
29
- end
30
- end
34
+ end # module AppManagement
35
+ end # module Device
36
+ end # module Windows
37
+ end # module Core
38
+ end # module Appium
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  require_relative 'device/screen'
16
+ require_relative 'device/app_management'
16
17
 
17
18
  module Appium
18
19
  module Core
@@ -78,6 +79,7 @@ module Appium
78
79
  class << self
79
80
  def extended(_mod)
80
81
  Screen.add_methods
82
+ AppManagement.add_methods
81
83
  end
82
84
  end # class << self
83
85
  end # module Device
@@ -19,18 +19,28 @@ require_relative 'appium_lib_core/common'
19
19
  require_relative 'appium_lib_core/driver'
20
20
  require_relative 'appium_lib_core/device'
21
21
  require_relative 'appium_lib_core/element'
22
+ require_relative 'appium_lib_core/support/event_firing_bridge'
22
23
 
23
24
  module Appium
24
- # convert all keys (including nested) to symbols
25
+ # @private
26
+ #
27
+ # convert the top level keys to symbols.
25
28
  #
26
- # based on deep_symbolize_keys & deep_transform_keys from rails
27
- # https://github.com/rails/docrails/blob/a3b1105ada3da64acfa3843b164b14b734456a50/activesupport/lib/active_support/core_ext/hash/keys.rb#L84
28
29
  # @param [Hash] hash Hash value to make symbolise
29
- def self.symbolize_keys(hash)
30
+ def self.symbolize_keys(hash, nested: false, enable_deprecation_msg: true)
31
+ # FIXME: As https://github.com/appium/ruby_lib/issues/945, we must remove this implicit string to symbol.
32
+ # But appium_lib_core's some capability handling expect to be symbol, so we should test to remove
33
+ # the mehotds which expect the symbol first.
30
34
  raise ::Appium::Core::Error::ArgumentError, 'symbolize_keys requires a hash' unless hash.is_a? Hash
31
35
 
32
36
  hash.each_with_object({}) do |pair, acc|
33
37
  key = begin
38
+ if enable_deprecation_msg && !(pair[0].is_a? Symbol)
39
+ ::Appium::Logger.warn("[Deprecation] The key '#{pair[0]}' must be a symbol while currently it " \
40
+ "is #{pair[0].class.name}. Please define the key as a Symbol. " \
41
+ 'Converting it to Symbol for now.')
42
+ end
43
+
34
44
  pair[0].to_sym
35
45
  rescue StandardError => e
36
46
  ::Appium::Logger.warn(e.message)
@@ -38,7 +48,11 @@ module Appium
38
48
  end
39
49
 
40
50
  value = pair[1]
41
- acc[key] = value.is_a?(Hash) ? symbolize_keys(value) : value
51
+ acc[key] = if nested
52
+ value.is_a?(Hash) ? symbolize_keys(value, nested: false, enable_deprecation_msg: false) : value
53
+ else
54
+ value
55
+ end
42
56
  end
43
57
  end
44
58