From deaace1060d354c0e0a025647e0d27e639d7a2e6 Mon Sep 17 00:00:00 2001 From: Nacy Dev Date: Wed, 28 Oct 2026 17:08:19 +0700 Subject: [PATCH] fix: support non-global drivers in get_android_inspect and px_to_window_rel and preserve kwargs in promote_appium_methods --- lib/appium_lib/android/common/helper.rb | 9 ++-- lib/appium_lib/appium.rb | 13 ++---- lib/appium_lib/common/helper.rb | 11 +++-- test/regression_test.rb | 58 ++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/lib/appium_lib/android/common/helper.rb b/lib/appium_lib/android/common/helper.rb index 50cb291b..90688bbb 100644 --- a/lib/appium_lib/android/common/helper.rb +++ b/lib/appium_lib/android/common/helper.rb @@ -27,9 +27,10 @@ def filter=(value) @filter = value.to_s.downcase end - def initialize # rubocop:disable Lint/MissingSuper + def initialize(driver = $driver) # rubocop:disable Lint/MissingSuper reset @filter = false + @driver = driver end def reset @@ -38,7 +39,7 @@ def reset end # http://nokogiri.org/Nokogiri/XML/SAX/Document.html - def start_element(name, attrs = [], driver = $driver) + def start_element(name, attrs = [], driver = @driver || $driver) return if filter && !name.downcase.include?(filter) attributes = {} @@ -49,7 +50,7 @@ def start_element(name, attrs = [], driver = $driver) # scoped to: text resource-id content-desc view-tag attributes_values = attributes.values - strings = driver.lazy_load_strings + strings = driver ? driver.lazy_load_strings : {} id_matches = strings.empty? ? [] : strings.select { |_key, value| attributes_values.include? value } string_ids = nil @@ -104,7 +105,7 @@ def get_android_inspect(class_name = false) parser = if source_is_html # parse html from webview @android_html_parser ||= Nokogiri::HTML::SAX::Parser.new(Appium::Common::HTMLElements.new) else - @android_native_parser ||= Nokogiri::XML::SAX::Parser.new(AndroidElements.new) + @android_native_parser ||= Nokogiri::XML::SAX::Parser.new(AndroidElements.new(self)) end parser.document.reset # ensure document is reset before parsing parser.document.filter = class_name diff --git a/lib/appium_lib/appium.rb b/lib/appium_lib/appium.rb index de3d2610..941eb7be 100644 --- a/lib/appium_lib/appium.rb +++ b/lib/appium_lib/appium.rb @@ -222,23 +222,16 @@ def promote_appium_methods(class_array, driver = $driver) # Remove the method before adding it. remove_method method if instance_methods(false).include? method - define_method method do |*args, &block| + define_method method do |*args, **kwargs, &block| # Prefer existing method. # super will invoke method missing on driver - super(*args, &block) + super(*args, **kwargs, &block) # minitest also defines a name method, # so rescue argument error # and call the name method on $driver rescue NoMethodError, ::ArgumentError - if args.size == 1 && args.first.is_a?(Hash) - # To prevent warnings by keyword arguments (for Ruby 2.7 and 3) - driver.send method, **args.first, &block if driver.respond_to?(method) - else - ::Appium::Logger.warn "Should fix this '#{args}' for Ruby 2.7 (and 3)" if args.first.is_a?(Hash) - - driver.send method, *args, &block if driver.respond_to?(method) - end + driver.send method, *args, **kwargs, &block if driver.respond_to?(method) end end end diff --git a/lib/appium_lib/common/helper.rb b/lib/appium_lib/common/helper.rb index a457b7cb..87ae43be 100644 --- a/lib/appium_lib/common/helper.rb +++ b/lib/appium_lib/common/helper.rb @@ -69,6 +69,9 @@ def xpaths(xpath_str) # json and ap are required for the source method. require 'json' + WindowRel = Struct.new(:x, :y) + private_constant :WindowRel + # @private # http://nokogiri.org/Nokogiri/XML/SAX.html class CountElements < Nokogiri::XML::SAX::Document @@ -160,15 +163,15 @@ def get_source # # @example # - # px_to_window_rel x: 50, y: 150 #=> # + # px_to_window_rel x: 50, y: 150 #=> # # - def px_to_window_rel(opts = {}, driver = $driver) + def px_to_window_rel(opts = {}, driver = $driver || self) w = driver.window_size x = opts.fetch :x, 0 y = opts.fetch :y, 0 - OpenStruct.new(x: "#{x.to_f} / #{w.width.to_f}", - y: "#{y.to_f} / #{w.height.to_f}") + WindowRel.new("#{x.to_f} / #{w.width.to_f}", + "#{y.to_f} / #{w.height.to_f}") end # @private diff --git a/test/regression_test.rb b/test/regression_test.rb index 87a94f67..2b4d5c01 100644 --- a/test/regression_test.rb +++ b/test/regression_test.rb @@ -163,7 +163,7 @@ def stub_driver.lazy_load_strings ) doc.start_element( 'androidx.compose.ui.platform.ComposeView', - [['view-tag', 'compose_tag']], + [%w(view-tag compose_tag)], stub_driver ) @@ -187,6 +187,62 @@ def test_log_event_writer_raises_no_driver_and_argument_errors assert_raises(Appium::Core::Error::ArgumentError) { driver.log_event = 'invalid' } end + def test_get_android_inspect_without_global_driver + previous_driver = $driver + $driver = nil + + driver = build_driver + driver.extend(Appium::Common) + driver.extend(Appium::Android) + def driver.get_source + '' + end + + def driver.lazy_load_strings + { 'submit_label' => 'Submit' } + end + + expected = "\nandroid.widget.Button\n text: Submit\n id: com.example:id/submit\n strings.xml: submit_label\n" + assert_equal expected, driver.get_android_inspect + ensure + $driver = previous_driver + end + + def test_px_to_window_rel_without_global_driver + previous_driver = $driver + $driver = nil + + driver = build_driver + driver.extend(Appium::Common) + def driver.window_size + Struct.new(:width, :height).new(375, 667) + end + + rel = driver.px_to_window_rel(x: 50, y: 150) + assert_equal '50.0 / 375.0', rel.x + assert_equal '150.0 / 667.0', rel.y + ensure + $driver = previous_driver + end + + def test_instance_promotion_forwards_keywords_and_string_keyed_hash + driver = Object.new + def driver.update_settings(settings) + settings + end + + def driver.log_event(vendor:, event:) + "#{vendor}:#{event}" + end + + target = Class.new + Appium.promote_appium_methods(target, driver) + instance = target.new + + assert_equal({ 'allowInvisibleElements' => true }, instance.update_settings({ 'allowInvisibleElements' => true })) + assert_equal 'appium:funEvent', instance.log_event(vendor: 'appium', event: 'funEvent') + end + private def build_driver