appium_lib_core 8.0.2 → 9.1.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: 22e57b152d3c94c2b86f65520b00899c2f9c5bd0231a8496c9f0726849601e93
4
- data.tar.gz: ec392fd1c14218a5306489c05c3364dcd15a6a5f1191e2a42589698429fb9e8c
3
+ metadata.gz: 8679d6bb72c4130e216ac363eaed0c49f13b6d8818db2cba0b44c8f5b9dcef7b
4
+ data.tar.gz: d226eeb911201d9afd12117bb73871afa95e4b0d7f3844f2df2e4efdb45563ed
5
5
  SHA512:
6
- metadata.gz: d4525eac2deb92a9356842d1fa9d518029d8166df50dbc628661e3637ed3f1c0527bfdb7c302a6f2d3fc32c39eee2ca2ca405fd72b572e7413bb91ee0ec66b2b
7
- data.tar.gz: 645d56159c0a8f444511ef25e6ef7d1ead6a428360928adc3219fc2c3e105d6d1fd6dbf204841a107657eecc64c6cfee0dd4326851771fc9eb73b1bc9f79c80b
6
+ metadata.gz: 5ad279ca22ef8f33ad8634fbfcaffaac1f6b16a8904a90f45a39427ed060c444fec5e1130589cac30fd35cdff74df0626e9768e9f1a819f49db678ab6044d2d3
7
+ data.tar.gz: 2f6c31b0718f6155db25a647832d68d370c4dee839fe962f5b99bc6f4d082bdc0d96fdde807f5e89192f814815e998752ab9f2fb772f0a01d823425c7b90ad09
data/CHANGELOG.md CHANGED
@@ -10,6 +10,21 @@ Read `release_notes.md` for commit level details.
10
10
 
11
11
  ### Deprecations
12
12
 
13
+ ## [9.1.0] - 2024-05-18
14
+
15
+ ### Enhancements
16
+ - Require Selenium 4.21.0+
17
+ - Simplify internal code with Selenium 4.21.0. Now it requires selenium webdriver v4.21.0.
18
+
19
+ ## [9.0.0] - 2024-05-14
20
+
21
+ ### Deprecations
22
+ - Stop converting snake cases to camel case for symbols in capabilities
23
+ - Please define camel/snake cases in capabilities as-is for the WebDriver capabilities
24
+ - Stop implicit symbolizing capabilities
25
+ - Historically ruby_lib/ruby_lib_core symbolized capabilities keys from string internally.
26
+ - `Appium.symbolize_keys` to build symbolized capabilities if issues occurred by this change.
27
+
13
28
  ## [8.0.2] - 2024-04-26
14
29
 
15
30
  ### Enhancements
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_runtime_dependency 'selenium-webdriver', '~> 4.2'
25
+ spec.add_runtime_dependency 'selenium-webdriver', '~> 4.21'
26
26
  spec.add_runtime_dependency 'faye-websocket', '~> 0.11.0'
27
27
 
28
28
  spec.add_development_dependency 'rake', '~> 13.0'
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'minitest', '~> 5.0'
31
31
  spec.add_development_dependency 'minitest-reporters', '~> 1.1'
32
32
  spec.add_development_dependency 'webmock', '~> 3.23.0'
33
- spec.add_development_dependency 'rubocop', '1.63.3'
33
+ spec.add_development_dependency 'rubocop', '1.63.5'
34
34
  spec.add_development_dependency 'appium_thor', '~> 2.0'
35
35
  spec.add_development_dependency 'parallel_tests'
36
36
  spec.add_development_dependency 'simplecov'
@@ -15,6 +15,12 @@
15
15
  module Appium
16
16
  module Core
17
17
  class Base
18
+ class LocatorConverter
19
+ def convert(how, what)
20
+ [how, what]
21
+ end
22
+ end # LocatorConverter
23
+
18
24
  class Bridge < ::Selenium::WebDriver::Remote::Bridge
19
25
  include Device::DeviceLock
20
26
  include Device::Keyboard
@@ -31,6 +37,8 @@ module Appium
31
37
  include Device::ExecuteDriver
32
38
  include Device::Orientation
33
39
 
40
+ Bridge.locator_converter = LocatorConverter.new
41
+
34
42
  # Prefix for extra capability defined by W3C
35
43
  APPIUM_PREFIX = 'appium:'
36
44
 
@@ -153,6 +161,18 @@ module Appium
153
161
  public
154
162
 
155
163
  # command for Appium 2.0.
164
+
165
+ # Example:
166
+ # driver.add_command(name: :available_contexts, method: :get, url: 'session/:session_id/contexts') do
167
+ # execute(:available_contexts, {}) || []
168
+ # end
169
+ # Then,
170
+ # driver.available_contexts #=> ["NATIVE_APP"]
171
+
172
+ # def add_command(method:, url:, name:, &block)
173
+ # Bridge.add_command name, method, url, &block
174
+ # end
175
+
156
176
  def add_command(method:, url:, name:, &block)
157
177
  ::Appium::Logger.info "Overriding the method '#{name}' for '#{url}'" if @available_commands.key? name
158
178
 
@@ -162,7 +182,7 @@ module Appium
162
182
  end
163
183
 
164
184
  def commands(command)
165
- @available_commands[command]
185
+ @available_commands[command] || Bridge.extra_commands[command]
166
186
  end
167
187
 
168
188
  def status
@@ -216,52 +236,8 @@ module Appium
216
236
  end
217
237
 
218
238
  # For Appium
219
- # override
220
- def active_element
221
- ::Appium::Core::Element.new self, element_id_from(execute(:get_active_element))
222
- end
223
239
  alias switch_to_active_element active_element
224
240
 
225
- # For Appium
226
- # override
227
- def find_element_by(how, what, parent_ref = [])
228
- how, what = convert_locator(how, what)
229
-
230
- return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
231
-
232
- parent_type, parent_id = parent_ref
233
- id = case parent_type
234
- when :element
235
- execute :find_child_element, { id: parent_id }, { using: how, value: what.to_s }
236
- when :shadow_root
237
- execute :find_shadow_child_element, { id: parent_id }, { using: how, value: what.to_s }
238
- else
239
- execute :find_element, {}, { using: how, value: what.to_s }
240
- end
241
-
242
- ::Appium::Core::Element.new self, element_id_from(id)
243
- end
244
-
245
- # For Appium
246
- # override
247
- def find_elements_by(how, what, parent_ref = [])
248
- how, what = convert_locator(how, what)
249
-
250
- return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
251
-
252
- parent_type, parent_id = parent_ref
253
- ids = case parent_type
254
- when :element
255
- execute :find_child_elements, { id: parent_id }, { using: how, value: what.to_s }
256
- when :shadow_root
257
- execute :find_shadow_child_elements, { id: parent_id }, { using: how, value: what.to_s }
258
- else
259
- execute :find_elements, {}, { using: how, value: what.to_s }
260
- end
261
-
262
- ids.map { |id| ::Appium::Core::Element.new self, element_id_from(id) }
263
- end
264
-
265
241
  # For Appium
266
242
  # @param [Hash] id The id which can get as a response from server
267
243
  # @return [::Appium::Core::Element]
@@ -370,36 +346,6 @@ module Appium
370
346
  arg
371
347
  end
372
348
  end
373
-
374
- def element_id_from(id)
375
- id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
376
- end
377
-
378
- # Don't convert locators for Appium in native context
379
- def convert_locator(how, what)
380
- # case how
381
- # when 'class name'
382
- # how = 'css selector'
383
- # what = ".#{escape_css(what)}"
384
- # when 'id'
385
- # how = 'css selector'
386
- # what = "##{escape_css(what)}"
387
- # when 'name'
388
- # how = 'css selector'
389
- # what = "*[name='#{escape_css(what)}']"
390
- # when 'tag name'
391
- # how = 'css selector'
392
- # end
393
- #
394
- # if what.is_a?(Hash)
395
- # what = what.each_with_object({}) do |(h, w), hash|
396
- # h, w = convert_locator(h.to_s, w)
397
- # hash[h] = w
398
- # end
399
- # end
400
-
401
- [how, what]
402
- end
403
349
  end # class Bridge
404
350
  end # class Base
405
351
  end # module Core
@@ -23,6 +23,20 @@ module Appium
23
23
  # Appium's capabilities could change by depending on Appium versions. So it does not have
24
24
  # standard options like chrome and firefox etc. So, the implementation should differ from
25
25
  # other browsers. But here should inherit `Options` to follow Selenium.
26
+
27
+ # Method override
28
+ # FIXME: when we drop "symbolize_keys", this can be removed.
29
+ def convert_key(key)
30
+ case key
31
+ when String
32
+ key.to_s
33
+ when Symbol
34
+ # here do not convert to camel case
35
+ key.to_s
36
+ else
37
+ raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
38
+ end
39
+ end
26
40
  end
27
41
  end
28
42
  end
@@ -32,7 +32,6 @@ module Appium
32
32
  include ::Selenium::WebDriver::DriverExtensions::HasWebStorage
33
33
 
34
34
  include ::Appium::Core::Base::Rotatable
35
- include ::Appium::Core::Base::SearchContext
36
35
  include ::Appium::Core::Base::TakesScreenshot
37
36
  include ::Appium::Core::Base::HasRemoteStatus
38
37
  include ::Appium::Core::Base::HasLocation
@@ -40,6 +39,8 @@ module Appium
40
39
 
41
40
  include ::Appium::Core::Waitable
42
41
 
42
+ ::Selenium::WebDriver::SearchContext.extra_finders = APPIUM_EXTRA_FINDERS
43
+
43
44
  # Private API.
44
45
  # Do not use this for general use. Used by flutter driver to get bridge for creating a new element
45
46
  attr_reader :bridge
@@ -57,6 +58,7 @@ module Appium
57
58
  @bidi = nil
58
59
 
59
60
  # in the selenium webdriver as well
61
+ ::Selenium::WebDriver::Remote::Bridge.element_class = ::Appium::Core::Element
60
62
  bridge ||= create_bridge(**opts)
61
63
  add_extensions(bridge.browser)
62
64
  @bridge = listener ? ::Appium::Support::EventFiringBridge.new(bridge, listener, **original_opts) : bridge
@@ -1023,8 +1025,8 @@ module Appium
1023
1025
  # ele = @driver.convert_to_element(response) #=> ::Appium::Core::Element
1024
1026
  # ele.rect #=> Can get the rect of the element
1025
1027
  #
1026
- def convert_to_element(id)
1027
- @bridge.convert_to_element id
1028
+ def convert_to_element(response_id)
1029
+ @bridge.convert_to_element response_id
1028
1030
  end
1029
1031
  end # class Driver
1030
1032
  end # class Base
@@ -31,6 +31,9 @@ module Appium
31
31
  class Default < ::Selenium::WebDriver::Remote::Http::Default
32
32
  attr_reader :additional_headers
33
33
 
34
+ ::Selenium::WebDriver::Remote::Http::Common.user_agent = \
35
+ "appium/ruby_lib_core/#{VERSION} (#{::Selenium::WebDriver::Remote::Http::Common.user_agent})"
36
+
34
37
  # override
35
38
  def initialize(open_timeout: nil, read_timeout: nil)
36
39
  @open_timeout = open_timeout
@@ -39,6 +42,17 @@ module Appium
39
42
  super
40
43
  end
41
44
 
45
+ def set_additional_header(key, value)
46
+ @additional_headers[key] = value
47
+ ::Selenium::WebDriver::Remote::Http::Common.extra_headers = @additional_headers
48
+ end
49
+
50
+ def delete_additional_header(key)
51
+ @additional_headers.delete key
52
+ ::Selenium::WebDriver::Remote::Http::Common.extra_headers = @additional_headers
53
+ @common_headers.delete key if defined? @common_headers
54
+ end
55
+
42
56
  # Update <code>server_url</code> provided when ruby_lib _core created a default http client.
43
57
  # Set <code>@http</code> as nil to re-create http client for the <code>server_url</code>
44
58
  #
@@ -59,13 +73,6 @@ module Appium
59
73
  @server_url = URI.parse "#{scheme}://#{host}:#{port}#{path}"
60
74
  end
61
75
 
62
- def request(verb, url, headers, payload, redirects = 0)
63
- headers['User-Agent'] = "appium/ruby_lib_core/#{VERSION} (#{headers['User-Agent']})"
64
- headers = headers.merge @additional_headers unless @additional_headers.empty?
65
-
66
- super(verb, url, headers, payload, redirects)
67
- end
68
-
69
76
  private
70
77
 
71
78
  def validate_url_param(scheme, host, port, path)
@@ -12,172 +12,102 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- module Appium
16
- module Core
17
- class Base
18
- module SearchContext
19
- # referenced: ::Selenium::WebDriver::SearchContext
20
-
21
- FINDERS = ::Selenium::WebDriver::SearchContext::FINDERS.merge(
22
- accessibility_id: 'accessibility id',
23
- image: '-image',
24
- custom: '-custom',
25
- # Android
26
- uiautomator: '-android uiautomator', # Unavailable in Espresso
27
- viewtag: '-android viewtag', # Available in Espresso
28
- data_matcher: '-android datamatcher', # Available in Espresso
29
- view_matcher: '-android viewmatcher', # Available in Espresso
30
- # iOS
31
- predicate: '-ios predicate string',
32
- class_chain: '-ios class chain'
33
- )
34
-
35
- # rubocop:disable Layout/LineLength
36
- #
37
- # Find the first element matching the given arguments
38
- #
39
- # - Android can find with uiautomator like a {http://developer.android.com/tools/help/uiautomator/UiSelector.html UISelector}.
40
- # - iOS can find with a {https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930 UIAutomation command}.
41
- # - iOS, only for XCUITest(WebDriverAgent), can find with a {https://github.com/facebook/WebDriverAgent/wiki/Queries class chain}
42
- #
43
- # == Find with image
44
- # Return an element if current view has a partial image. The logic depends on template matching by OpenCV.
45
- # {https://github.com/appium/appium/blob/1.x/docs/en/writing-running-appium/image-comparison.md image-comparison}
46
- #
47
- # You can handle settings for the comparision following {https://github.com/appium/appium-base-driver/blob/master/lib/basedriver/device-settings.js#L6 here}
48
- #
49
- # == Espresso viewmatcher and datamatcher
50
- # Espresso has {https://developer.android.com/training/testing/espresso/basics _onView_ matcher}
51
- # and {https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf _onData_ matcher} for more reference
52
- # that allows you to target adapters instead of Views. This method find methods based on reflections
53
- #
54
- # This is a selector strategy that allows users to pass a selector of the form:
55
- #
56
- # <code>{ name: '<name>', args: ['arg1', 'arg2', '...'], class: '<optional class>' }</code>
57
- #
58
- # - _name_: The name of a method to invoke. The method must return
59
- # a Hamcrest {http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html Matcher}
60
- # - _args_: The args provided to the method
61
- # - _class_: The class name that the method is part of (defaults to <code>org.hamcrest.Matchers</code>).
62
- # Can be fully qualified, or simple, and simple defaults to <code>androidx.test.espresso.matcher</code> package
63
- # (e.g.: <code>class=CursorMatchers</code> fully qualified is <code>class=androidx.test.espresso.matcher.CursorMatchers</code>
64
- #
65
- # See example how to send viewmatcher and datamatcher in Ruby client
66
- #
67
- #
68
- # @overload find_element(how, what)
69
- # @param [Symbol, String] how The method to find the element by
70
- # @param [String] what The locator to use
71
- #
72
- # @overload find_element(opts)
73
- # @param [Hash] opts Find options
74
- # @option opts [Symbol] :how Key named after the method to find the element by, containing the locator
75
- # @return [Element]
76
- # @raise [Error::NoSuchElementError] if the element doesn't exist
77
- #
78
- # @example Find element with each keys
79
- #
80
- # # with accessibility id. All platforms.
81
- # @driver.find_elements :accessibility_id, 'Animation'
82
- # @driver.find_elements :accessibility_id, 'Animation'
83
- #
84
- # # with base64 encoded template image. All platforms.
85
- # @driver.find_elements :image, Base64.strict_encode64(File.read(file_path))
86
- #
87
- # # For Android
88
- # ## With uiautomator
89
- # @driver.find_elements :uiautomator, 'new UiSelector().clickable(true)'
90
- # ## With viewtag, but only for Espresso
91
- # ## 'setTag'/'getTag' in https://developer.android.com/reference/android/view/View
92
- # @driver.find_elements :viewtag, 'new UiSelector().clickable(true)'
93
- # # With data_matcher. The argument should be JSON format.
94
- # @driver.find_elements :data_matcher, { name: 'hasEntry', args: %w(title Animation) }.to_json
95
- #
96
- # # For iOS
97
- # ## With :predicate
98
- # @driver.find_elements :predicate, "isWDVisible == 1"
99
- # @driver.find_elements :predicate, 'wdName == "Buttons"'
100
- # @driver.find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1'
101
- #
102
- # ## With Class Chain
103
- # ### select the third child button of the first child window element
104
- # @driver.find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]'
105
- # ### select all the children windows
106
- # @driver.find_elements :class_chain, 'XCUIElementTypeWindow'
107
- # ### select the second last child of the second child window
108
- # @driver.find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]'
109
- # ### matching predicate. <code>'</code> is the mark.
110
- # @driver.find_elements :class_chain, 'XCUIElementTypeWindow['visible = 1]['name = "bla"']'
111
- # ### containing predicate. '$' is the mark.
112
- # ### Require appium-xcuitest-driver 2.54.0+. PR: https://github.com/facebook/WebDriverAgent/pull/707/files
113
- # @driver.find_elements :class_chain, 'XCUIElementTypeWindow[$name = \"bla$$$bla\"$]'
114
- # e = find_element :class_chain, "**/XCUIElementTypeWindow[$name == 'Buttons'$]"
115
- # e.tag_name #=> "XCUIElementTypeWindow"
116
- # e = find_element :class_chain, "**/XCUIElementTypeStaticText[$name == 'Buttons'$]"
117
- # e.tag_name #=> "XCUIElementTypeStaticText"
118
- #
119
- # rubocop:enable Layout/LineLength
120
- def find_element(*args)
121
- how, what = extract_args(args)
122
- by = _set_by_from_finders(how)
123
- begin
124
- bridge.find_element_by by, what.to_s, ref
125
- rescue Selenium::WebDriver::Error::TimeoutError
126
- raise Selenium::WebDriver::Error::NoSuchElementError
127
- end
128
- end
129
-
130
- #
131
- # Find all elements matching the given arguments
132
- #
133
- # @return [Array<Selenium::WebDriver::Element>]
134
- #
135
- # @see SearchContext#find_elements
136
- #
137
- def find_elements(*args)
138
- how, what = extract_args(args)
139
- by = _set_by_from_finders(how)
140
- begin
141
- bridge.find_elements_by by, what.to_s, ref
142
- rescue Selenium::WebDriver::Error::TimeoutError
143
- []
144
- end
145
- end
146
-
147
- private
148
-
149
- def _set_by_from_finders(how)
150
- by = FINDERS[how.to_sym]
151
- unless by
152
- raise ::Appium::Core::Error::ArgumentError,
153
- "cannot find element by #{how.inspect}. Available finders are #{FINDERS.keys}."
154
- end
155
-
156
- by
157
- end
158
-
159
- def extract_args(args)
160
- case args.size
161
- when 2
162
- args
163
- when 1
164
- arg = args.first
165
-
166
- unless arg.respond_to?(:shift)
167
- raise ::Appium::Core::Error::ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift"
168
- end
169
-
170
- # this will be a single-entry hash, so use #shift over #first or #[]
171
- arr = arg.dup.shift
172
-
173
- raise ::Appium::Core::Error::ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2
15
+ # rubocop:disable Layout/LineLength
16
+ #
17
+ # Find the first element matching the given arguments
18
+ #
19
+ # - Android can find with uiautomator like a {http://developer.android.com/tools/help/uiautomator/UiSelector.html UISelector}.
20
+ # - iOS can find with a {https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930 UIAutomation command}.
21
+ # - iOS, only for XCUITest(WebDriverAgent), can find with a {https://github.com/facebook/WebDriverAgent/wiki/Queries class chain}
22
+ #
23
+ # == Find with image
24
+ # Return an element if current view has a partial image. The logic depends on template matching by OpenCV.
25
+ # {https://github.com/appium/appium/blob/1.x/docs/en/writing-running-appium/image-comparison.md image-comparison}
26
+ #
27
+ # You can handle settings for the comparision following {https://github.com/appium/appium-base-driver/blob/master/lib/basedriver/device-settings.js#L6 here}
28
+ #
29
+ # == Espresso viewmatcher and datamatcher
30
+ # Espresso has {https://developer.android.com/training/testing/espresso/basics _onView_ matcher}
31
+ # and {https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf _onData_ matcher} for more reference
32
+ # that allows you to target adapters instead of Views. This method find methods based on reflections
33
+ #
34
+ # This is a selector strategy that allows users to pass a selector of the form:
35
+ #
36
+ # <code>{ name: '<name>', args: ['arg1', 'arg2', '...'], class: '<optional class>' }</code>
37
+ #
38
+ # - _name_: The name of a method to invoke. The method must return
39
+ # a Hamcrest {http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html Matcher}
40
+ # - _args_: The args provided to the method
41
+ # - _class_: The class name that the method is part of (defaults to <code>org.hamcrest.Matchers</code>).
42
+ # Can be fully qualified, or simple, and simple defaults to <code>androidx.test.espresso.matcher</code> package
43
+ # (e.g.: <code>class=CursorMatchers</code> fully qualified is <code>class=androidx.test.espresso.matcher.CursorMatchers</code>
44
+ #
45
+ # See example how to send viewmatcher and datamatcher in Ruby client
46
+ #
47
+ #
48
+ # @overload find_element(how, what)
49
+ # @param [Symbol, String] how The method to find the element by
50
+ # @param [String] what The locator to use
51
+ #
52
+ # @overload find_element(opts)
53
+ # @param [Hash] opts Find options
54
+ # @option opts [Symbol] :how Key named after the method to find the element by, containing the locator
55
+ # @return [Element]
56
+ # @raise [Error::NoSuchElementError] if the element doesn't exist
57
+ #
58
+ # @example Find element with each keys
59
+ #
60
+ # # with accessibility id. All platforms.
61
+ # @driver.find_elements :accessibility_id, 'Animation'
62
+ # @driver.find_elements :accessibility_id, 'Animation'
63
+ #
64
+ # # with base64 encoded template image. All platforms.
65
+ # @driver.find_elements :image, Base64.strict_encode64(File.read(file_path))
66
+ #
67
+ # # For Android
68
+ # ## With uiautomator
69
+ # @driver.find_elements :uiautomator, 'new UiSelector().clickable(true)'
70
+ # ## With viewtag, but only for Espresso
71
+ # ## 'setTag'/'getTag' in https://developer.android.com/reference/android/view/View
72
+ # @driver.find_elements :viewtag, 'new UiSelector().clickable(true)'
73
+ # # With data_matcher. The argument should be JSON format.
74
+ # @driver.find_elements :data_matcher, { name: 'hasEntry', args: %w(title Animation) }.to_json
75
+ #
76
+ # # For iOS
77
+ # ## With :predicate
78
+ # @driver.find_elements :predicate, "isWDVisible == 1"
79
+ # @driver.find_elements :predicate, 'wdName == "Buttons"'
80
+ # @driver.find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1'
81
+ #
82
+ # ## With Class Chain
83
+ # ### select the third child button of the first child window element
84
+ # @driver.find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]'
85
+ # ### select all the children windows
86
+ # @driver.find_elements :class_chain, 'XCUIElementTypeWindow'
87
+ # ### select the second last child of the second child window
88
+ # @driver.find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]'
89
+ # ### matching predicate. <code>'</code> is the mark.
90
+ # @driver.find_elements :class_chain, 'XCUIElementTypeWindow['visible = 1]['name = "bla"']'
91
+ # ### containing predicate. '$' is the mark.
92
+ # ### Require appium-xcuitest-driver 2.54.0+. PR: https://github.com/facebook/WebDriverAgent/pull/707/files
93
+ # @driver.find_elements :class_chain, 'XCUIElementTypeWindow[$name = \"bla$$$bla\"$]'
94
+ # e = find_element :class_chain, "**/XCUIElementTypeWindow[$name == 'Buttons'$]"
95
+ # e.tag_name #=> "XCUIElementTypeWindow"
96
+ # e = find_element :class_chain, "**/XCUIElementTypeStaticText[$name == 'Buttons'$]"
97
+ # e.tag_name #=> "XCUIElementTypeStaticText"
98
+ #
99
+ # rubocop:enable Layout/LineLength
174
100
 
175
- arr
176
- else
177
- raise ::Appium::Core::Error::ArgumentError, "wrong number of arguments (#{args.size} for 2)"
178
- end
179
- end
180
- end # module SearchContext
181
- end # class Base
182
- end # module Core
183
- end # module Appium
101
+ APPIUM_EXTRA_FINDERS = {
102
+ accessibility_id: 'accessibility id',
103
+ image: '-image',
104
+ custom: '-custom',
105
+ # Android
106
+ uiautomator: '-android uiautomator', # Unavailable in Espresso
107
+ viewtag: '-android viewtag', # Available in Espresso
108
+ data_matcher: '-android datamatcher', # Available in Espresso
109
+ view_matcher: '-android viewmatcher', # Available in Espresso
110
+ # iOS
111
+ predicate: '-ios predicate string',
112
+ class_chain: '-ios class chain'
113
+ }.freeze
@@ -338,12 +338,6 @@ module Appium
338
338
  def setup_for_new_session(opts = {})
339
339
  @custom_url = opts.delete :url # to set the custom url as :url
340
340
 
341
- # TODO: Remove when we implement Options
342
- # The symbolize_keys is to keep compatiility for the legacy code, which allows capabilities to give 'string' as the key.
343
- # The toplevel `caps`, `capabilities` and `appium_lib` are expected to be symbol.
344
- # FIXME: First, please try to remove `nested: true` to `nested: false`.
345
- opts = Appium.symbolize_keys(opts, nested: true)
346
-
347
341
  @caps = get_caps(opts)
348
342
 
349
343
  set_appium_lib_specific_values(get_appium_lib_opts(opts))
@@ -408,7 +402,7 @@ module Appium
408
402
 
409
403
  if @enable_idempotency_header
410
404
  if @http_client.instance_variable_defined? :@additional_headers
411
- @http_client.additional_headers[Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]] = SecureRandom.uuid
405
+ @http_client.set_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency], SecureRandom.uuid
412
406
  else
413
407
  ::Appium::Logger.warn 'No additional_headers attribute in this http client instance'
414
408
  end
@@ -432,7 +426,7 @@ module Appium
432
426
 
433
427
  if @http_client.instance_variable_defined? :@additional_headers
434
428
  # We only need the key for a new session request. Should remove it for other following commands.
435
- @http_client.additional_headers.delete Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
429
+ @http_client.delete_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]
436
430
  end
437
431
 
438
432
  # TODO: this method can be removed after releasing Appium 2.0, and after a while
@@ -635,22 +629,27 @@ module Appium
635
629
  opts[:appium_lib] || {}
636
630
  end
637
631
 
632
+ # @private
633
+ def get_app
634
+ @caps[:app] || @caps['app']
635
+ end
636
+
638
637
  # @private
639
638
  # Path to the .apk, .app or .app.zip.
640
639
  # The path can be local, HTTP/S, Windows Share and other path like 'sauce-storage:'.
641
640
  # Use @caps[:app] without modifications if the path isn't HTTP/S or local path.
642
641
  def set_app_path
643
642
  # FIXME: maybe `:app` should check `app` as well.
644
- return unless @caps && @caps[:app] && !@caps[:app].empty?
645
- return if @caps[:app] =~ URI::DEFAULT_PARSER.make_regexp
646
-
647
- app_path = File.expand_path(@caps[:app])
648
- @caps[:app] = if File.exist? app_path
649
- app_path
650
- else
651
- ::Appium::Logger.warn("Use #{@caps[:app]} directly since #{app_path} does not exist.")
652
- @caps[:app]
653
- end
643
+ return unless @caps && get_app && !get_app.empty?
644
+ return if get_app =~ URI::DEFAULT_PARSER.make_regexp
645
+
646
+ app_path = File.expand_path(get_app)
647
+ @caps['app'] = if File.exist? app_path
648
+ app_path
649
+ else
650
+ ::Appium::Logger.warn("Use #{get_app} directly since #{app_path} does not exist.")
651
+ get_app
652
+ end
654
653
  end
655
654
 
656
655
  # @private
@@ -676,7 +675,6 @@ module Appium
676
675
  # @private
677
676
  def set_appium_device
678
677
  # https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile
679
- # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
680
678
  @device = @caps[:platformName] || @caps['platformName']
681
679
  return @device unless @device
682
680
 
@@ -685,7 +683,6 @@ module Appium
685
683
 
686
684
  # @private
687
685
  def set_automation_name
688
- # TODO: check if the Appium.symbolize_keys(opts, nested: false) enoug with this
689
686
  candidate = @caps[:automationName] || @caps['automationName']
690
687
  @automation_name = candidate if candidate
691
688
  @automation_name = convert_downcase @automation_name if @automation_name
@@ -17,9 +17,10 @@ module Appium
17
17
  # Implement useful features for element.
18
18
  # Patch for Selenium Webdriver.
19
19
  class Element < ::Selenium::WebDriver::Element
20
- include ::Appium::Core::Base::SearchContext
21
20
  include ::Appium::Core::Base::TakesScreenshot
22
21
 
22
+ ::Selenium::WebDriver::SearchContext.extra_finders = APPIUM_EXTRA_FINDERS
23
+
23
24
  # Retuns the element id.
24
25
  #
25
26
  # @return [String]
@@ -14,7 +14,7 @@
14
14
 
15
15
  module Appium
16
16
  module Core
17
- VERSION = '8.0.2' unless defined? ::Appium::Core::VERSION
18
- DATE = '2024-04-26' unless defined? ::Appium::Core::DATE
17
+ VERSION = '9.1.0' unless defined? ::Appium::Core::VERSION
18
+ DATE = '2024-05-18' unless defined? ::Appium::Core::DATE
19
19
  end
20
20
  end
@@ -22,15 +22,17 @@ require_relative 'appium_lib_core/element'
22
22
  require_relative 'appium_lib_core/support/event_firing_bridge'
23
23
 
24
24
  module Appium
25
- # @private
26
- #
27
25
  # convert the top level keys to symbols.
28
26
  #
29
27
  # @param [Hash] hash Hash value to make symbolise
28
+ #
29
+ # @example
30
+ # opts = Appium.symbolize_keys(opts)
31
+ #
30
32
  def self.symbolize_keys(hash, nested: false, enable_deprecation_msg: true)
31
33
  # FIXME: As https://github.com/appium/ruby_lib/issues/945, we must remove this implicit string to symbol.
32
34
  # 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.
35
+ # the methods which expect the symbol first.
34
36
  raise ::Appium::Core::Error::ArgumentError, 'symbolize_keys requires a hash' unless hash.is_a? Hash
35
37
 
36
38
  hash.each_with_object({}) do |pair, acc|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appium_lib_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.2
4
+ version: 9.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuaki MATSUO
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-27 00:00:00.000000000 Z
11
+ date: 2024-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: selenium-webdriver
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '4.21'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.2'
26
+ version: '4.21'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: faye-websocket
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 1.63.3
117
+ version: 1.63.5
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - '='
123
123
  - !ruby/object:Gem::Version
124
- version: 1.63.3
124
+ version: 1.63.5
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: appium_thor
127
127
  requirement: !ruby/object:Gem::Requirement