appium_lib 16.4.0 → 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: 737cac2a7befc3590331dca750b538fa83574b34a1e3120d13b16abc46fdbbba
4
- data.tar.gz: 6e98a1456c2ce0eadf278a27472a78ce7975a28b962fdf8fe152c75721563789
3
+ metadata.gz: 06a3340368cd4c4f7018982593d00ae926d13b3d7ed7208f40dadfed018c6804
4
+ data.tar.gz: 032e623a9d4993338857eda7400f5de42b3aa93ddabe0529611a12b626d605b6
5
5
  SHA512:
6
- metadata.gz: 32bb9c6dbfd35f25836f194e07b1f93314027747035754a8fe39fe06a717e1f76bb7d84ef4a24e7ea3a5534a05f892d9f8c15e0513c771f015eceadabe36cc3e
7
- data.tar.gz: bb57e7f98300da2da9bd53a26e83439a4df63abc1d7d80f1dea66942b14d428d7f30c40ae400aa533decc4962232c62e32764a6230be9f216f576cf1d2c782d4
6
+ metadata.gz: ff709d53c045b29ff31fdddbad7ee3a888e7e85dadcae4c1c7e1eb25b49748ce172c7e71428301331333fe4d8fe152a90b051230addc623417aaa5f7e6570253
7
+ data.tar.gz: 67563e9a99af3741b58cb719a40ebc550b8a8f3534d5460cf2aeb798f4832a619532241afc85029e0c4001713ba2e4f52a083c488ee488aea200f19682376446
data/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ 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
+
23
+ ## [16.4.1](https://github.com/appium/ruby_lib/compare/v16.4.0...v16.4.1) (2026-10-28)
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * require error definitions, use next in HTMLElements#result, and fix Driver#log_event= error raising ([#1140](https://github.com/appium/ruby_lib/issues/1140)) ([1b28f90](https://github.com/appium/ruby_lib/commit/1b28f9008c488b8945cfbc41458bb430b2f7a546))
29
+
7
30
  ## [16.4.0](https://github.com/appium/ruby_lib/compare/v16.3.2...v16.4.0) (2026-10-26)
8
31
 
9
32
 
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
@@ -19,6 +19,7 @@ require 'nokogiri'
19
19
  require 'appium_lib_core'
20
20
 
21
21
  # base
22
+ require_relative 'error'
22
23
  require_relative 'driver'
23
24
  require_relative 'sauce_labs'
24
25
 
@@ -151,7 +152,7 @@ module Appium
151
152
  # @param [Array<Module>] modules An array of modules
152
153
  # @param [Driver] driver A driver to extend for
153
154
  def promote_singleton_appium_methods(modules, driver = $driver)
154
- raise ArgumentError, 'Global $driver is nil' if driver.nil?
155
+ raise ArgumentError, 'Driver is nil' if driver.nil?
155
156
 
156
157
  target_modules = []
157
158
 
@@ -166,16 +167,24 @@ module Appium
166
167
  end
167
168
 
168
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)
169
172
  # noinspection RubyResolve
170
173
  driver.public_methods(false).each do |m|
171
- # override unless there's an existing method with matching arity
172
- 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
173
181
 
174
182
  const.send(:define_singleton_method, m) do |*args, **kwargs, &block|
175
183
  super(*args, **kwargs, &block) # promote.rb
176
- rescue NoMethodError, ArgumentError
184
+ rescue NoMethodError, ::ArgumentError
177
185
  driver.send m, *args, **kwargs, &block if driver.respond_to?(m)
178
186
  end
187
+ promoted_methods[m] = const.method(m)
179
188
  end
180
189
  end
181
190
  end
@@ -229,7 +238,7 @@ module Appium
229
238
  # minitest also defines a name method,
230
239
  # so rescue argument error
231
240
  # and call the name method on $driver
232
- rescue NoMethodError, ArgumentError
241
+ rescue NoMethodError, ::ArgumentError
233
242
  if args.size == 1 && args.first.is_a?(Hash)
234
243
  # To prevent warnings by keyword arguments (for Ruby 2.7 and 3)
235
244
  driver.send method, **args.first, &block if driver.respond_to?(method)
@@ -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
@@ -233,7 +233,7 @@ module Appium
233
233
  "#{string} #{attr[0]}: #{attr1}\n"
234
234
  end
235
235
 
236
- return r if attr_string.nil? || attr_string.empty?
236
+ next r if attr_string.nil? || attr_string.empty?
237
237
 
238
238
  "#{r}\n#{name}\n#{attr_string}"
239
239
  end
@@ -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.
@@ -888,9 +890,9 @@ module Appium
888
890
  end
889
891
 
890
892
  def log_event=(log_event)
891
- raise if @driver.nil?
893
+ raise NoDriverInstanceError if @driver.nil?
892
894
  unless log_event.is_a?(Hash)
893
- raise ::Appium::Core::Error::ArgumentError('log_event should be Hash like { vendor: "appium", event: "funEvent"}')
895
+ raise ::Appium::Core::Error::ArgumentError, 'log_event should be Hash like { vendor: "appium", event: "funEvent"}'
894
896
  end
895
897
 
896
898
  @driver.logs.event vendor: log_event[:vendor], event: log_event[:event]
@@ -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.0' unless defined? ::Appium::VERSION
18
- DATE = '2026-10-26' 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.0
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