appium_lib 16.4.1 → 16.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5206e8a735fef077d8eae00bd61c72be9d9845bd5e552ed5cc117b712d118ccf
4
- data.tar.gz: 1e4b652e6c3a190612a46a514d56f040fe2cfc8a5caf30e64c6273b38608192e
3
+ metadata.gz: 06a3340368cd4c4f7018982593d00ae926d13b3d7ed7208f40dadfed018c6804
4
+ data.tar.gz: 032e623a9d4993338857eda7400f5de42b3aa93ddabe0529611a12b626d605b6
5
5
  SHA512:
6
- metadata.gz: b9288fcfbc87a7ef1de6fbecf09591eb6961b25f779c50a6a8a29d7cbe0a8f37e66c94fef47233f3fab5cf6df26b6624580ccbd83e0662d79d896b342d11f3f1
7
- data.tar.gz: 0ffba8aae6f9bdcedc9da7c3aebb2db10cac1e067e38d89b373068555d242a8df18c4b94070c0f4851856f1e9478e136d1db07b2adc203f77c95a870e1a4bbd5
6
+ metadata.gz: ff709d53c045b29ff31fdddbad7ee3a888e7e85dadcae4c1c7e1eb25b49748ce172c7e71428301331333fe4d8fe152a90b051230addc623417aaa5f7e6570253
7
+ data.tar.gz: 67563e9a99af3741b58cb719a40ebc550b8a8f3534d5460cf2aeb798f4832a619532241afc85029e0c4001713ba2e4f52a083c488ee488aea200f19682376446
data/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ New entries are maintained by Release Please.
4
4
 
5
5
  Release tags are https://github.com/appium/ruby_lib/releases .
6
6
 
7
+ ## [16.5.0](https://github.com/appium/ruby_lib/compare/v16.4.1...v16.5.0) (2026-10-30)
8
+
9
+
10
+ ### Features
11
+
12
+ * support independent non-global drivers while preserving explicit global-driver usage and the existing non-global default ([203da2d](https://github.com/appium/ruby_lib/commit/203da2dfe9a2299f24acb52a95161584d5c1d285))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * clear session and app-string cache on quit and reload app strings for replacement sessions ([203da2d](https://github.com/appium/ruby_lib/commit/203da2dfe9a2299f24acb52a95161584d5c1d285))
18
+ * honor symbol and string global_driver hash options when the constructor's second argument is omitted or nil; true now replaces the global driver and quits its previous session, while an explicit boolean argument takes precedence ([203da2d](https://github.com/appium/ruby_lib/commit/203da2dfe9a2299f24acb52a95161584d5c1d285))
19
+ * make start_driver quit only its own previous session; callers relying on cleanup of another global driver must quit that driver explicitly ([203da2d](https://github.com/appium/ruby_lib/commit/203da2dfe9a2299f24acb52a95161584d5c1d285))
20
+ * refresh generated singleton methods on re-promotion so they use the newly supplied driver rather than the previous driver ([203da2d](https://github.com/appium/ruby_lib/commit/203da2dfe9a2299f24acb52a95161584d5c1d285))
21
+ * use the receiving driver's session for coordinate conversion and Android inspection instead of an unrelated global driver ([203da2d](https://github.com/appium/ruby_lib/commit/203da2dfe9a2299f24acb52a95161584d5c1d285))
22
+
7
23
  ## [16.4.1](https://github.com/appium/ruby_lib/compare/v16.4.0...v16.4.1) (2026-10-28)
8
24
 
9
25
 
data/appium_lib.gemspec CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
 
19
19
  s.add_dependency 'appium_lib_core', '>= 13.0.0', '< 14.0'
20
20
  s.add_dependency 'nokogiri', '~> 1.8', '>= 1.8.1'
21
+ s.add_dependency 'ostruct', '~> 0.6'
21
22
  s.add_dependency 'tomlrb', '>= 1.1', '< 4.0'
22
23
 
23
24
  s.files = Dir.glob('lib/**/*.rb', base: __dir__) + %w[appium_lib.gemspec readme.md LICENSE-2.0.txt CHANGELOG.md]
@@ -27,9 +27,10 @@ module Appium
27
27
  @filter = value.to_s.downcase
28
28
  end
29
29
 
30
- def initialize # rubocop:disable Lint/MissingSuper
30
+ def initialize(driver = $driver) # rubocop:disable Lint/MissingSuper
31
31
  reset
32
32
  @filter = false
33
+ @driver = driver
33
34
  end
34
35
 
35
36
  def reset
@@ -38,7 +39,7 @@ module Appium
38
39
  end
39
40
 
40
41
  # http://nokogiri.org/Nokogiri/XML/SAX/Document.html
41
- def start_element(name, attrs = [], driver = $driver)
42
+ def start_element(name, attrs = [], driver = @driver)
42
43
  return if filter && !name.downcase.include?(filter)
43
44
 
44
45
  attributes = {}
@@ -49,7 +50,7 @@ module Appium
49
50
 
50
51
  # scoped to: text resource-id content-desc view-tag
51
52
  attributes_values = attributes.values
52
- strings = driver.lazy_load_strings
53
+ strings = driver ? driver.lazy_load_strings : {}
53
54
  id_matches = strings.empty? ? [] : strings.select { |_key, value| attributes_values.include? value }
54
55
 
55
56
  string_ids = nil
@@ -104,7 +105,7 @@ module Appium
104
105
  parser = if source_is_html # parse html from webview
105
106
  @android_html_parser ||= Nokogiri::HTML::SAX::Parser.new(Appium::Common::HTMLElements.new)
106
107
  else
107
- @android_native_parser ||= Nokogiri::XML::SAX::Parser.new(AndroidElements.new)
108
+ @android_native_parser ||= Nokogiri::XML::SAX::Parser.new(AndroidElements.new(self))
108
109
  end
109
110
  parser.document.reset # ensure document is reset before parsing
110
111
  parser.document.filter = class_name
@@ -152,7 +152,7 @@ module Appium
152
152
  # @param [Array<Module>] modules An array of modules
153
153
  # @param [Driver] driver A driver to extend for
154
154
  def promote_singleton_appium_methods(modules, driver = $driver)
155
- raise ArgumentError, 'Global $driver is nil' if driver.nil?
155
+ raise ArgumentError, 'Driver is nil' if driver.nil?
156
156
 
157
157
  target_modules = []
158
158
 
@@ -167,16 +167,24 @@ module Appium
167
167
  end
168
168
 
169
169
  target_modules.each do |const|
170
+ promoted_methods = const.instance_variable_get(:@_appium_promoted_singleton_methods) || {}
171
+ const.instance_variable_set(:@_appium_promoted_singleton_methods, promoted_methods)
170
172
  # noinspection RubyResolve
171
173
  driver.public_methods(false).each do |m|
172
- # override unless there's an existing method with matching arity
173
- next if const.respond_to?(m) && const.method(m).arity == driver.method(m).arity
174
+ # Preserve user methods, but refresh wrappers that captured an earlier driver.
175
+ if const.respond_to?(m)
176
+ existing = const.method(m)
177
+ next if existing != promoted_methods[m] && existing.arity == driver.method(m).arity
178
+
179
+ const.singleton_class.send(:remove_method, m) if existing == promoted_methods[m]
180
+ end
174
181
 
175
182
  const.send(:define_singleton_method, m) do |*args, **kwargs, &block|
176
183
  super(*args, **kwargs, &block) # promote.rb
177
184
  rescue NoMethodError, ::ArgumentError
178
185
  driver.send m, *args, **kwargs, &block if driver.respond_to?(m)
179
186
  end
187
+ promoted_methods[m] = const.method(m)
180
188
  end
181
189
  end
182
190
  end
@@ -68,6 +68,7 @@ module Appium
68
68
 
69
69
  # json and ap are required for the source method.
70
70
  require 'json'
71
+ require 'ostruct'
71
72
 
72
73
  # @private
73
74
  # http://nokogiri.org/Nokogiri/XML/SAX.html
@@ -162,7 +163,7 @@ module Appium
162
163
  #
163
164
  # px_to_window_rel x: 50, y: 150 #=> #<OpenStruct x="50.0 / 375.0", y="150.0 / 667.0">
164
165
  #
165
- def px_to_window_rel(opts = {}, driver = $driver)
166
+ def px_to_window_rel(opts = {}, driver = self)
166
167
  w = driver.window_size
167
168
  x = opts.fetch :x, 0
168
169
  y = opts.fetch :y, 0
@@ -173,6 +174,8 @@ module Appium
173
174
 
174
175
  # @private
175
176
  def lazy_load_strings
177
+ return {} if @driver.nil?
178
+
176
179
  # app strings only works on local apps.
177
180
  # on disk apps (ex: com.android.settings) will error
178
181
  @lazy_load_strings ||= ignore { app_strings } || {}
@@ -182,24 +185,21 @@ module Appium
182
185
  # @param target [String] the target to search for in strings.xml values
183
186
  # @return [Array]
184
187
  def xml_keys(target)
185
- lazy_load_strings
186
- @lazy_load_strings.select { |key, _value| key.downcase.include? target.downcase }
188
+ lazy_load_strings.select { |key, _value| key.downcase.include? target.downcase }
187
189
  end
188
190
 
189
191
  # Search strings.xml's keys for target.
190
192
  # @param target [String] the target to search for in strings.xml keys
191
193
  # @return [Array]
192
194
  def xml_values(target)
193
- lazy_load_strings
194
- @lazy_load_strings.select { |_key, value| value.downcase.include? target.downcase }
195
+ lazy_load_strings.select { |_key, value| value.downcase.include? target.downcase }
195
196
  end
196
197
 
197
198
  # Resolve id in strings.xml and return the value.
198
199
  # @param id [String] the id to resolve
199
200
  # @return [String]
200
201
  def resolve_id(id)
201
- lazy_load_strings
202
- @lazy_load_strings[id]
202
+ lazy_load_strings[id]
203
203
  end
204
204
 
205
205
  # @private
@@ -130,7 +130,7 @@ module Appium
130
130
  # Instance of Appium::Core::Driver
131
131
  attr_reader :core
132
132
 
133
- # Creates a new driver. The driver is defined as global scope by default.
133
+ # Creates a new driver without a global reference by default.
134
134
  # We can avoid defining global driver.
135
135
  #
136
136
  # @example
@@ -195,15 +195,18 @@ module Appium
195
195
  # appium_driver.start_driver
196
196
  #
197
197
  # @param opts [Object] A hash containing various options.
198
- # @param global_driver [Bool] A bool require global driver before initialize.
198
+ # @param global_driver [Boolean, nil] Register as $driver when true. nil uses opts[:global_driver], default false.
199
199
  # @return [Driver]
200
- def initialize(opts = {}, global_driver = false)
201
- # Capybara can't put `global_driver` as the 2nd argument.
202
- global_driver = opts.delete :global_driver if global_driver.nil?
200
+ def initialize(opts = {}, global_driver = nil)
201
+ raise ArgumentError, 'opts must be a hash' unless opts.is_a? Hash
203
202
 
204
- $driver&.driver_quit if global_driver
203
+ # Support options-only callers without changing their options hash.
204
+ # An explicit second argument takes precedence over the hash option.
205
+ opts = opts.dup
206
+ configured_global_driver = opts.delete(:global_driver) { opts.delete('global_driver') { false } }
207
+ global_driver = configured_global_driver if global_driver.nil?
205
208
 
206
- raise ArgumentError, 'opts must be a hash' unless opts.is_a? Hash
209
+ $driver&.driver_quit if global_driver
207
210
 
208
211
  @core = ::Appium::Core.for(opts)
209
212
  extend ::Appium::Core::Device
@@ -495,9 +498,12 @@ module Appium
495
498
  # @return [void]
496
499
  def driver_quit
497
500
  @driver&.quit
498
- @driver = nil
501
+ nil
499
502
  rescue Selenium::WebDriver::Error::WebDriverError
500
503
  nil
504
+ ensure
505
+ @driver = nil
506
+ @lazy_load_strings = nil
501
507
  end
502
508
  alias quit_driver driver_quit
503
509
 
@@ -534,7 +540,7 @@ module Appium
534
540
  @driver.window_rect
535
541
  end
536
542
 
537
- # Creates a new global driver and quits the old one if it exists.
543
+ # Creates a new session and quits this instance's previous session if it exists.
538
544
  # You can customise http_client as the following
539
545
  #
540
546
  # Read http://www.rubydoc.info/github/appium/ruby_lib_core/Appium/Core/Device to understand more what the driver
@@ -562,7 +568,7 @@ module Appium
562
568
  # @option http_client_ops [Hash] :http_client Custom HTTP Client
563
569
  # @option http_client_ops [Hash] :open_timeout Custom open timeout for http client.
564
570
  # @option http_client_ops [Hash] :read_timeout Custom read timeout for http client.
565
- # @return [Selenium::WebDriver] the new global driver
571
+ # @return [Selenium::WebDriver] the underlying driver for the new session
566
572
  def start_driver(http_client_ops = { http_client: nil, open_timeout: 999_999, read_timeout: 999_999 })
567
573
  http_client = http_client_ops[:http_client]
568
574
  if http_client.nil?
@@ -570,12 +576,8 @@ module Appium
570
576
  read_timeout: http_client_ops[:read_timeout])
571
577
  end
572
578
 
573
- # TODO: do not kill the previous session in the future version.
574
- if $driver.nil?
575
- driver_quit
576
- else
577
- $driver.driver_quit
578
- end
579
+ # Each wrapper owns its session, even when another wrapper is global.
580
+ driver_quit
579
581
 
580
582
  # If automationName is set only in server side, then the following automation_name should be nil before
581
583
  # starting driver.
@@ -14,6 +14,6 @@
14
14
 
15
15
  module Appium
16
16
  # Version and Date are defined on the 'Appium' module, not 'Appium::Common'
17
- VERSION = '16.4.1' unless defined? ::Appium::VERSION
18
- DATE = '2026-10-28' unless defined? ::Appium::DATE # x-release-please-date
17
+ VERSION = '16.5.0' unless defined? ::Appium::VERSION
18
+ DATE = '2026-10-30' unless defined? ::Appium::DATE # x-release-please-date
19
19
  end
data/readme.md CHANGED
@@ -15,6 +15,9 @@ Helper methods for writing cross platform (iOS, Android) tests in Ruby using App
15
15
  Ordinary, `ruby_lib` worked with class driver, `$driver`, mainly.
16
16
  We can avoid the class driver with current `ruby_lib`, but if you'd like to implement your test cases based on instance driver, `@driver`, you can consider using `ruby_lib_core` first.
17
17
 
18
+ See [non-global drivers and migration](docs/parallel.md) for instance-based usage,
19
+ Page Objects, and coexistence with legacy global drivers.
20
+
18
21
  # Setup
19
22
  ## Requirement
20
23
  - [Appium](https://github.com/appium/appium#requirements)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appium_lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.4.1
4
+ version: 16.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - code@bootstraponline.com
@@ -50,6 +50,20 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 1.8.1
53
+ - !ruby/object:Gem::Dependency
54
+ name: ostruct
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.6'
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '0.6'
53
67
  - !ruby/object:Gem::Dependency
54
68
  name: tomlrb
55
69
  requirement: !ruby/object:Gem::Requirement