appium_lib_core 8.0.2 → 9.1.0

Sign up to get free protection for your applications and to get access to all the features.
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