testa_appium_driver 0.1.11 → 0.1.12

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -15
  3. data/.idea/deployment.xml +21 -21
  4. data/.idea/inspectionProfiles/Project_Default.xml +8 -8
  5. data/.idea/misc.xml +5 -5
  6. data/.idea/modules.xml +7 -7
  7. data/.idea/runConfigurations/Android_Test.xml +41 -41
  8. data/.idea/runConfigurations.xml +9 -9
  9. data/.idea/sshConfigs.xml +12 -12
  10. data/.idea/vcs.xml +5 -5
  11. data/.idea/webServers.xml +20 -20
  12. data/.rspec +3 -3
  13. data/.rubocop.yml +13 -13
  14. data/CHANGELOG.md +5 -5
  15. data/CODE_OF_CONDUCT.md +102 -102
  16. data/Gemfile +12 -12
  17. data/LICENSE.txt +21 -21
  18. data/README.md +378 -378
  19. data/Rakefile +12 -12
  20. data/bin/console +17 -17
  21. data/bin/setup +8 -8
  22. data/lib/testa_appium_driver/android/class_selectors.rb +437 -437
  23. data/lib/testa_appium_driver/android/driver.rb +69 -69
  24. data/lib/testa_appium_driver/android/locator/attributes.rb +113 -113
  25. data/lib/testa_appium_driver/android/locator.rb +141 -141
  26. data/lib/testa_appium_driver/android/scroll_actions/uiautomator_scroll_actions.rb +61 -61
  27. data/lib/testa_appium_driver/android/selenium_element.rb +7 -7
  28. data/lib/testa_appium_driver/common/bounds.rb +149 -149
  29. data/lib/testa_appium_driver/common/constants.rb +36 -36
  30. data/lib/testa_appium_driver/common/exceptions/strategy_mix_exception.rb +11 -11
  31. data/lib/testa_appium_driver/common/helpers.rb +270 -270
  32. data/lib/testa_appium_driver/common/locator/scroll_actions.rb +397 -397
  33. data/lib/testa_appium_driver/common/locator.rb +610 -610
  34. data/lib/testa_appium_driver/common/scroll_actions/json_wire_scroll_actions.rb +3 -3
  35. data/lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb +237 -237
  36. data/lib/testa_appium_driver/common/scroll_actions.rb +246 -246
  37. data/lib/testa_appium_driver/common/selenium_element.rb +19 -19
  38. data/lib/testa_appium_driver/driver.rb +312 -312
  39. data/lib/testa_appium_driver/ios/driver.rb +48 -48
  40. data/lib/testa_appium_driver/ios/locator/attributes.rb +80 -80
  41. data/lib/testa_appium_driver/ios/locator.rb +70 -70
  42. data/lib/testa_appium_driver/ios/selenium_element.rb +6 -6
  43. data/lib/testa_appium_driver/ios/type_selectors.rb +187 -187
  44. data/lib/testa_appium_driver/version.rb +5 -5
  45. data/lib/testa_appium_driver.rb +6 -6
  46. data/testa_appium_driver.gemspec +41 -41
  47. data/testa_appium_driver.iml +27 -78
  48. metadata +3 -3
@@ -1,271 +1,271 @@
1
- module TestaAppiumDriver
2
- module Helpers
3
-
4
- # supported selectors
5
- # id: "com.my.package:id/myId"
6
- # id: "myId" => will be converted to "com.my.package:id/myId"
7
- # id: /my/ will find all elements with ids that contain my
8
- # desc: "element description"
9
- # desc: /ription/ will find all elements that contains ription
10
- # class: "android.widget.Button"
11
- # class: /Button/ will find all elements with classes that contain Button
12
- # text: "Hello world"
13
- # text: /ello/ will find all elements with text that contain ello
14
- # package: "com.my.package"
15
- # package: /my/ will find all elements with package that contains my
16
- # long_clickable: true or false
17
- # checkable: true or false
18
- # checked: true or false
19
- # clickable: true or false
20
- # enabled: true or false
21
- # focusable: true or false
22
- # focused: true or false
23
- # index: child index inside of a parent element, index starts from 0
24
- # selected: true or false
25
- # scrollable: true or false
26
- # @param [Hash] hash selectors for finding elements
27
- # @param [Boolean] single should the command return first instance or all of matched elements
28
- # @return [String] hash selectors converted to uiautomator command
29
- def hash_to_uiautomator(hash, single = true)
30
- command = "new UiSelector()"
31
-
32
- id = resolve_id(hash[:id])
33
- command = "#{ command }.resourceId(\"#{ %(#{ id }) }\")" if id && id.kind_of?(String)
34
- command = "#{ command }.resourceIdMatches(\".*#{ %(#{ id.source }) }.*\")" if id && id.kind_of?(Regexp)
35
- command = "#{ command }.description(\"#{ %(#{ hash[:desc] }) }\")" if hash[:desc] && hash[:desc].kind_of?(String)
36
- command = "#{ command }.descriptionMatches(\".*#{ %(#{ hash[:desc].source }) }.*\")" if hash[:desc] && hash[:desc].kind_of?(Regexp)
37
- command = "#{ command }.className(\"#{ %(#{ hash[:class] }) }\")" if hash[:class] && hash[:class].kind_of?(String)
38
- command = "#{ command }.classNameMatches(\".*#{ %(#{ hash[:class].source }) }.*\")" if hash[:class] && hash[:class].kind_of?(Regexp)
39
- command = "#{ command }.text(\"#{ %(#{ hash[:text] }) }\")" if hash[:text] && hash[:text].kind_of?(String)
40
- command = "#{ command }.textMatches(\".*#{ %(#{ hash[:text].source }) }.*\")" if hash[:text] && hash[:text].kind_of?(Regexp)
41
- command = "#{ command }.packageName(\"#{ %(#{ hash[:package] }) }\")" if hash[:package] && hash[:package].kind_of?(String)
42
- command = "#{ command }.packageNameMatches(\".*#{ %(#{ hash[:package].source }) }.*\")" if hash[:package] && hash[:package].kind_of?(Regexp)
43
-
44
- command = "#{ command }.longClickable(#{ hash[:long_clickable] })" if hash[:long_clickable]
45
- command = "#{ command }.checkable(#{ hash[:checkable] })" unless hash[:checkable].nil?
46
- command = "#{ command }.checked(#{ hash[:checked] })" unless hash[:checked].nil?
47
- command = "#{ command }.clickable(#{ hash[:clickable] })" unless hash[:clickable].nil?
48
- command = "#{ command }.enabled(#{ hash[:enabled] })" unless hash[:enabled].nil?
49
- command = "#{ command }.focusable(#{ hash[:focusable] })" unless hash[:focusable].nil?
50
- command = "#{ command }.focused(#{ hash[:focused] })" unless hash[:focused].nil?
51
- command = "#{ command }.index(#{ hash[:index].to_s })" unless hash[:index].nil?
52
- command = "#{ command }.selected(#{ hash[:selected] })" unless hash[:selected].nil?
53
- command = "#{ command }.scrollable(#{ hash[:scrollable] })" unless hash[:scrollable].nil?
54
-
55
- command += ".instance(0)" if single
56
-
57
- command
58
- end
59
-
60
-
61
- # supported selectors
62
- # id: "com.my.package:id/myId"
63
- # id: "myId" => will be converted to "com.my.package:id/myId"
64
- # id: /my/ will find all elements with ids that contain my
65
- # desc: "element description"
66
- # desc: /ription/ will find all elements that contains ription
67
- # class: "android.widget.Button"
68
- # class: /Button/ will find all elements with classes that contain Button
69
- # text: "Hello world"
70
- # text: /ello/ will find all elements with text that contain ello
71
- # package: "com.my.package"
72
- # package: /my/ will find all elements with package that contains my
73
- # long_clickable: true or false
74
- # checkable: true or false
75
- # checked: true or false
76
- # clickable: true or false
77
- # enabled: true or false
78
- # focusable: true or false
79
- # focused: true or false
80
- # index: child index inside of a parent element, index starts from 0
81
- # selected: true or false
82
- # scrollable: true or false
83
- # @param [Hash] hash selectors for finding elements
84
- # @param [Boolean] single should the command return first instance or all of matched elements
85
- # @return [String] hash selectors converted to xpath command
86
- def hash_to_xpath(device, hash, single = true)
87
- for_android = device == :android
88
-
89
-
90
- command = "//"
91
-
92
-
93
-
94
- if for_android
95
- id = resolve_id(hash[:id])
96
- if hash[:class] && hash[:class].kind_of?(String)
97
- command = "#{ command }#{hash[:class] }"
98
- elsif hash[:class] && hash[:class].kind_of?(Regexp)
99
- command = "#{ command}*[contains(@class, \"#{ %(#{hash[:class].source }) }\")]"
100
- else
101
- command = "#{command}*"
102
- end
103
-
104
-
105
- command = "#{ command }[@resource-id=\"#{ %(#{ id }) }\"]" if id && id.kind_of?(String)
106
- command = "#{ command }[contains(@resource-id, \"#{ %(#{ id.source }) }\")]" if id && id.kind_of?(Regexp)
107
- command = "#{ command }[@content-desc=\"#{ %(#{hash[:desc] }) }\"]" if hash[:desc] && hash[:desc].kind_of?(String)
108
- command = "#{ command }[contains(@content-desc, \"#{ %(#{hash[:desc].source }) }\")]" if hash[:desc] && hash[:desc].kind_of?(Regexp)
109
- command = "#{ command }[contains(@class, \"#{ %(#{hash[:class].source }) }\")]" if hash[:class] && hash[:class].kind_of?(Regexp)
110
- command = "#{ command }[@text=\"#{ %(#{hash[:text] }) }\"]" if hash[:text] && hash[:text].kind_of?(String)
111
- command = "#{ command }[contains(@text, \"#{ %(#{hash[:text].source }) }\")]" if hash[:text] && hash[:text].kind_of?(Regexp)
112
- command = "#{ command }[@package=\"#{ %(#{hash[:package] }) }\"]" if hash[:package] && hash[:package].kind_of?(String)
113
- command = "#{ command }[contains=(@package, \"#{ %(#{hash[:package].source }) }\")]" if hash[:package] && hash[:package].kind_of?(Regexp)
114
- command = "#{ command }[@long-clickable=\"#{ hash[:long_clickable] }\"]" if hash[:long_clickable]
115
- command = "#{ command }[@checkable=\"#{ hash[:checkable] }\"]" unless hash[:checkable].nil?
116
- command = "#{ command }[@checked=\"#{ hash[:checked] }\"]" unless hash[:checked].nil?
117
- command = "#{ command }[@clickable=\"#{ hash[:clickable] }\"]" unless hash[:clickable].nil?
118
- command = "#{ command }[@enabled=\"#{ hash[:enabled] }\"]" unless hash[:enabled].nil?
119
- command = "#{ command }[@focusable=\"#{ hash[:focusable] }\"]" unless hash[:focusable].nil?
120
- command = "#{ command }[@focused=\"#{ hash[:focused] }\"]" unless hash[:focused].nil?
121
- command = "#{ command }[@index=\"#{ hash[:index] }\"]" unless hash[:index].nil?
122
- command = "#{ command }[@selected=\"#{ hash[:selected] }\"]" unless hash[:selected].nil?
123
-
124
- # it seems like you cannot query by scrollable
125
- # command = "#{ command }[@scrollable=\"#{ hash[:scrollable] }\"]" unless hash[:scrollable].nil?
126
- else
127
-
128
- hash[:type] = hash[:class] unless hash[:class].nil?
129
- if hash[:type] && hash[:type].kind_of?(String)
130
- command = "#{ command }#{hash[:type] }"
131
- elsif hash[:type] && hash[:type].kind_of?(Regexp)
132
- command = "#{ command}*[contains(@type, \"#{ %(#{hash[:type].source }) }\")]"
133
- else
134
- command = "#{command}*"
135
- end
136
-
137
-
138
- # # ios specific
139
- hash[:label] = hash[:text] unless hash[:text].nil?
140
- hash[:name] = hash[:id] unless hash[:id].nil?
141
-
142
- command = "#{ command }[@enabled=\"#{ hash[:enabled] }\"]" unless hash[:enabled].nil?
143
- command = "#{ command }[@label=\"#{ %(#{hash[:label] }) }\"]" if hash[:label] && hash[:label].kind_of?(String)
144
- command = "#{ command }[contains(@label, \"#{ %(#{hash[:label].source }) }\")]" if hash[:label] && hash[:label].kind_of?(Regexp)
145
- command = "#{ command }[@name=\"#{ %(#{hash[:name] }) }\"]" if hash[:name] && hash[:name].kind_of?(String)
146
- command = "#{ command }[contains(@name, \"#{ %(#{hash[:name].source }) }\")]" if hash[:name] && hash[:name].kind_of?(Regexp)
147
- command = "#{ command }[@value=\"#{ %(#{hash[:value] }) }\"]" if hash[:value] && hash[:value].kind_of?(String)
148
- command = "#{ command }[contains(@value, \"#{ %(#{hash[:value].source }) }\")]" if hash[:value] && hash[:value].kind_of?(Regexp)
149
- command = "#{ command }[@width=\"#{ hash[:width] }\"]" unless hash[:width].nil?
150
- command = "#{ command }[@height=\"#{ hash[:height] }\"]" unless hash[:height].nil?
151
- command = "#{ command }[@visible=\"#{ hash[:visible] }\"]" unless hash[:visible].nil?
152
- end
153
-
154
-
155
- command += "[1]" if single
156
-
157
- command
158
- end
159
-
160
-
161
- def hash_to_class_chain(hash, single = true)
162
- command = "**/"
163
-
164
- hash[:type] = hash[:class] unless hash[:class].nil?
165
- hash[:label] = hash[:text] unless hash[:text].nil?
166
- hash[:name] = hash[:id] unless hash[:id].nil?
167
- if hash[:type] && hash[:type].kind_of?(String)
168
- command = "#{ command }#{hash[:type] }"
169
- else
170
- command = "#{command}*"
171
- end
172
-
173
- command = "#{ command }[`enabled == #{ hash[:enabled] }`]" unless hash[:enabled].nil?
174
- command = "#{ command }[`label == \"#{ %(#{hash[:label] }) }\"`]" if hash[:label] && hash[:label].kind_of?(String)
175
- command = "#{ command }[`label CONTAINS \"#{ %(#{hash[:label].source }) }\"`]" if hash[:label] && hash[:label].kind_of?(Regexp)
176
- command = "#{ command }[`name == \"#{ %(#{hash[:name] }) }\"`]" if hash[:name] && hash[:name].kind_of?(String)
177
- command = "#{ command }[`name CONTAINS \"#{ %(#{hash[:name].source }) }\"`]" if hash[:name] && hash[:name].kind_of?(Regexp)
178
- command = "#{ command }[`value == \"#{ %(#{hash[:value] }) }\"`]" if hash[:value] && hash[:value].kind_of?(String)
179
- command = "#{ command }[`value CONTAINS \"#{ %(#{hash[:value].source }) }\"`]" if hash[:value] && hash[:value].kind_of?(Regexp)
180
- command = "#{ command }[`visible == #{ hash[:visible] }`]" unless hash[:visible].nil?
181
-
182
- command += "[1]" if single
183
-
184
- command
185
- end
186
-
187
- # check if selectors are for a scrollable element
188
- # @param [Boolean] single should the command return first instance or all of matched elements
189
- # @param [Hash] selectors for fetching elements
190
- # @return [Boolean] true if element has scrollable attribute true or is class one of (RecyclerView, HorizontalScrollView, ScrollView, ListView)
191
- def is_scrollable_selector?(selectors, single)
192
- return false unless single
193
- return true if selectors[:scrollable]
194
- if selectors[:class] == "androidx.recyclerview.widget.RecyclerView" ||
195
- selectors[:class] == "android.widget.HorizontalScrollView" ||
196
- selectors[:class] == "android.widget.ScrollView" ||
197
- selectors[:class] == "android.widget.ListView"
198
- return true
199
- elsif selectors[:type] == "XCUIElementTypeScrollView"
200
- return true
201
- end
202
- false
203
- end
204
-
205
- #noinspection RubyUnnecessaryReturnStatement,RubyUnusedLocalVariable
206
- # separate selectors from given hash parameters
207
- # @param [Hash] params
208
- # @return [Array] first element is params, second are selectors
209
- def extract_selectors_from_params(params = {})
210
- selectors = params.select { |key, value| [
211
- :id,
212
- :long_clickable,
213
- :desc,
214
- :class,
215
- :text,
216
- :package,
217
- :checkable,
218
- :checked,
219
- :clickable,
220
- :enabled,
221
- :focusable,
222
- :focused,
223
- :index,
224
- :selected,
225
- :scrollable,
226
-
227
- # ios specific
228
- :type,
229
- :label,
230
- :x,
231
- :y,
232
- :width,
233
- :height,
234
- :visible,
235
- :name,
236
- :value,
237
-
238
- :image
239
- ].include?(key) }
240
- params = Hash[params.to_a - selectors.to_a]
241
-
242
- # default params
243
- params[:single] = true if params[:single].nil?
244
- params[:scrollable_locator] = nil if params[:scrollable_locator].nil?
245
- if params[:default_find_strategy].nil?
246
- params[:default_find_strategy] = DEFAULT_ANDROID_FIND_STRATEGY if @driver.device == :android
247
- params[:default_find_strategy] = DEFAULT_IOS_FIND_STRATEGY if @driver.device == :ios || @driver.device == :tvos
248
- end
249
- if params[:default_scroll_strategy].nil?
250
- params[:default_scroll_strategy] = DEFAULT_ANDROID_SCROLL_STRATEGY if @driver.device == :android
251
- params[:default_scroll_strategy] = DEFAULT_IOS_SCROLL_STRATEGY if @driver.device == :ios || @driver.device == :tvos
252
- end
253
-
254
- return params, selectors
255
- end
256
-
257
-
258
- def resolve_id(id)
259
- if id && id.kind_of?(String) && !id.match?(/.*:id\//)
260
- # shorthand ids like myId make full ids => my.app.package:id/myId
261
- if id[0] == "="
262
- return id[1..-1]
263
- else
264
- return "#{@driver.current_package}:id/#{id}"
265
- end
266
- else
267
- id
268
- end
269
- end
270
- end
1
+ module TestaAppiumDriver
2
+ module Helpers
3
+
4
+ # supported selectors
5
+ # id: "com.my.package:id/myId"
6
+ # id: "myId" => will be converted to "com.my.package:id/myId"
7
+ # id: /my/ will find all elements with ids that contain my
8
+ # desc: "element description"
9
+ # desc: /ription/ will find all elements that contains ription
10
+ # class: "android.widget.Button"
11
+ # class: /Button/ will find all elements with classes that contain Button
12
+ # text: "Hello world"
13
+ # text: /ello/ will find all elements with text that contain ello
14
+ # package: "com.my.package"
15
+ # package: /my/ will find all elements with package that contains my
16
+ # long_clickable: true or false
17
+ # checkable: true or false
18
+ # checked: true or false
19
+ # clickable: true or false
20
+ # enabled: true or false
21
+ # focusable: true or false
22
+ # focused: true or false
23
+ # index: child index inside of a parent element, index starts from 0
24
+ # selected: true or false
25
+ # scrollable: true or false
26
+ # @param [Hash] hash selectors for finding elements
27
+ # @param [Boolean] single should the command return first instance or all of matched elements
28
+ # @return [String] hash selectors converted to uiautomator command
29
+ def hash_to_uiautomator(hash, single = true)
30
+ command = "new UiSelector()"
31
+
32
+ id = resolve_id(hash[:id])
33
+ command = "#{ command }.resourceId(\"#{ %(#{ id }) }\")" if id && id.kind_of?(String)
34
+ command = "#{ command }.resourceIdMatches(\".*#{ %(#{ id.source }) }.*\")" if id && id.kind_of?(Regexp)
35
+ command = "#{ command }.description(\"#{ %(#{ hash[:desc] }) }\")" if hash[:desc] && hash[:desc].kind_of?(String)
36
+ command = "#{ command }.descriptionMatches(\".*#{ %(#{ hash[:desc].source }) }.*\")" if hash[:desc] && hash[:desc].kind_of?(Regexp)
37
+ command = "#{ command }.className(\"#{ %(#{ hash[:class] }) }\")" if hash[:class] && hash[:class].kind_of?(String)
38
+ command = "#{ command }.classNameMatches(\".*#{ %(#{ hash[:class].source }) }.*\")" if hash[:class] && hash[:class].kind_of?(Regexp)
39
+ command = "#{ command }.text(\"#{ %(#{ hash[:text] }) }\")" if hash[:text] && hash[:text].kind_of?(String)
40
+ command = "#{ command }.textMatches(\".*#{ %(#{ hash[:text].source }) }.*\")" if hash[:text] && hash[:text].kind_of?(Regexp)
41
+ command = "#{ command }.packageName(\"#{ %(#{ hash[:package] }) }\")" if hash[:package] && hash[:package].kind_of?(String)
42
+ command = "#{ command }.packageNameMatches(\".*#{ %(#{ hash[:package].source }) }.*\")" if hash[:package] && hash[:package].kind_of?(Regexp)
43
+
44
+ command = "#{ command }.longClickable(#{ hash[:long_clickable] })" if hash[:long_clickable]
45
+ command = "#{ command }.checkable(#{ hash[:checkable] })" unless hash[:checkable].nil?
46
+ command = "#{ command }.checked(#{ hash[:checked] })" unless hash[:checked].nil?
47
+ command = "#{ command }.clickable(#{ hash[:clickable] })" unless hash[:clickable].nil?
48
+ command = "#{ command }.enabled(#{ hash[:enabled] })" unless hash[:enabled].nil?
49
+ command = "#{ command }.focusable(#{ hash[:focusable] })" unless hash[:focusable].nil?
50
+ command = "#{ command }.focused(#{ hash[:focused] })" unless hash[:focused].nil?
51
+ command = "#{ command }.index(#{ hash[:index].to_s })" unless hash[:index].nil?
52
+ command = "#{ command }.selected(#{ hash[:selected] })" unless hash[:selected].nil?
53
+ command = "#{ command }.scrollable(#{ hash[:scrollable] })" unless hash[:scrollable].nil?
54
+
55
+ command += ".instance(0)" if single
56
+
57
+ command
58
+ end
59
+
60
+
61
+ # supported selectors
62
+ # id: "com.my.package:id/myId"
63
+ # id: "myId" => will be converted to "com.my.package:id/myId"
64
+ # id: /my/ will find all elements with ids that contain my
65
+ # desc: "element description"
66
+ # desc: /ription/ will find all elements that contains ription
67
+ # class: "android.widget.Button"
68
+ # class: /Button/ will find all elements with classes that contain Button
69
+ # text: "Hello world"
70
+ # text: /ello/ will find all elements with text that contain ello
71
+ # package: "com.my.package"
72
+ # package: /my/ will find all elements with package that contains my
73
+ # long_clickable: true or false
74
+ # checkable: true or false
75
+ # checked: true or false
76
+ # clickable: true or false
77
+ # enabled: true or false
78
+ # focusable: true or false
79
+ # focused: true or false
80
+ # index: child index inside of a parent element, index starts from 0
81
+ # selected: true or false
82
+ # scrollable: true or false
83
+ # @param [Hash] hash selectors for finding elements
84
+ # @param [Boolean] single should the command return first instance or all of matched elements
85
+ # @return [String] hash selectors converted to xpath command
86
+ def hash_to_xpath(device, hash, single = true)
87
+ for_android = device == :android
88
+
89
+
90
+ command = "//"
91
+
92
+
93
+
94
+ if for_android
95
+ id = resolve_id(hash[:id])
96
+ if hash[:class] && hash[:class].kind_of?(String)
97
+ command = "#{ command }#{hash[:class] }"
98
+ elsif hash[:class] && hash[:class].kind_of?(Regexp)
99
+ command = "#{ command}*[contains(@class, \"#{ %(#{hash[:class].source }) }\")]"
100
+ else
101
+ command = "#{command}*"
102
+ end
103
+
104
+
105
+ command = "#{ command }[@resource-id=\"#{ %(#{ id }) }\"]" if id && id.kind_of?(String)
106
+ command = "#{ command }[contains(@resource-id, \"#{ %(#{ id.source }) }\")]" if id && id.kind_of?(Regexp)
107
+ command = "#{ command }[@content-desc=\"#{ %(#{hash[:desc] }) }\"]" if hash[:desc] && hash[:desc].kind_of?(String)
108
+ command = "#{ command }[contains(@content-desc, \"#{ %(#{hash[:desc].source }) }\")]" if hash[:desc] && hash[:desc].kind_of?(Regexp)
109
+ command = "#{ command }[contains(@class, \"#{ %(#{hash[:class].source }) }\")]" if hash[:class] && hash[:class].kind_of?(Regexp)
110
+ command = "#{ command }[@text=\"#{ %(#{hash[:text] }) }\"]" if hash[:text] && hash[:text].kind_of?(String)
111
+ command = "#{ command }[contains(@text, \"#{ %(#{hash[:text].source }) }\")]" if hash[:text] && hash[:text].kind_of?(Regexp)
112
+ command = "#{ command }[@package=\"#{ %(#{hash[:package] }) }\"]" if hash[:package] && hash[:package].kind_of?(String)
113
+ command = "#{ command }[contains=(@package, \"#{ %(#{hash[:package].source }) }\")]" if hash[:package] && hash[:package].kind_of?(Regexp)
114
+ command = "#{ command }[@long-clickable=\"#{ hash[:long_clickable] }\"]" if hash[:long_clickable]
115
+ command = "#{ command }[@checkable=\"#{ hash[:checkable] }\"]" unless hash[:checkable].nil?
116
+ command = "#{ command }[@checked=\"#{ hash[:checked] }\"]" unless hash[:checked].nil?
117
+ command = "#{ command }[@clickable=\"#{ hash[:clickable] }\"]" unless hash[:clickable].nil?
118
+ command = "#{ command }[@enabled=\"#{ hash[:enabled] }\"]" unless hash[:enabled].nil?
119
+ command = "#{ command }[@focusable=\"#{ hash[:focusable] }\"]" unless hash[:focusable].nil?
120
+ command = "#{ command }[@focused=\"#{ hash[:focused] }\"]" unless hash[:focused].nil?
121
+ command = "#{ command }[@index=\"#{ hash[:index] }\"]" unless hash[:index].nil?
122
+ command = "#{ command }[@selected=\"#{ hash[:selected] }\"]" unless hash[:selected].nil?
123
+
124
+ # it seems like you cannot query by scrollable
125
+ # command = "#{ command }[@scrollable=\"#{ hash[:scrollable] }\"]" unless hash[:scrollable].nil?
126
+ else
127
+
128
+ hash[:type] = hash[:class] unless hash[:class].nil?
129
+ if hash[:type] && hash[:type].kind_of?(String)
130
+ command = "#{ command }#{hash[:type] }"
131
+ elsif hash[:type] && hash[:type].kind_of?(Regexp)
132
+ command = "#{ command}*[contains(@type, \"#{ %(#{hash[:type].source }) }\")]"
133
+ else
134
+ command = "#{command}*"
135
+ end
136
+
137
+
138
+ # # ios specific
139
+ hash[:label] = hash[:text] unless hash[:text].nil?
140
+ hash[:name] = hash[:id] unless hash[:id].nil?
141
+
142
+ command = "#{ command }[@enabled=\"#{ hash[:enabled] }\"]" unless hash[:enabled].nil?
143
+ command = "#{ command }[@label=\"#{ %(#{hash[:label] }) }\"]" if hash[:label] && hash[:label].kind_of?(String)
144
+ command = "#{ command }[contains(@label, \"#{ %(#{hash[:label].source }) }\")]" if hash[:label] && hash[:label].kind_of?(Regexp)
145
+ command = "#{ command }[@name=\"#{ %(#{hash[:name] }) }\"]" if hash[:name] && hash[:name].kind_of?(String)
146
+ command = "#{ command }[contains(@name, \"#{ %(#{hash[:name].source }) }\")]" if hash[:name] && hash[:name].kind_of?(Regexp)
147
+ command = "#{ command }[@value=\"#{ %(#{hash[:value] }) }\"]" if hash[:value] && hash[:value].kind_of?(String)
148
+ command = "#{ command }[contains(@value, \"#{ %(#{hash[:value].source }) }\")]" if hash[:value] && hash[:value].kind_of?(Regexp)
149
+ command = "#{ command }[@width=\"#{ hash[:width] }\"]" unless hash[:width].nil?
150
+ command = "#{ command }[@height=\"#{ hash[:height] }\"]" unless hash[:height].nil?
151
+ command = "#{ command }[@visible=\"#{ hash[:visible] }\"]" unless hash[:visible].nil?
152
+ end
153
+
154
+
155
+ command += "[1]" if single
156
+
157
+ command
158
+ end
159
+
160
+
161
+ def hash_to_class_chain(hash, single = true)
162
+ command = "**/"
163
+
164
+ hash[:type] = hash[:class] unless hash[:class].nil?
165
+ hash[:label] = hash[:text] unless hash[:text].nil?
166
+ hash[:name] = hash[:id] unless hash[:id].nil?
167
+ if hash[:type] && hash[:type].kind_of?(String)
168
+ command = "#{ command }#{hash[:type] }"
169
+ else
170
+ command = "#{command}*"
171
+ end
172
+
173
+ command = "#{ command }[`enabled == #{ hash[:enabled] }`]" unless hash[:enabled].nil?
174
+ command = "#{ command }[`label == \"#{ %(#{hash[:label] }) }\"`]" if hash[:label] && hash[:label].kind_of?(String)
175
+ command = "#{ command }[`label CONTAINS \"#{ %(#{hash[:label].source }) }\"`]" if hash[:label] && hash[:label].kind_of?(Regexp)
176
+ command = "#{ command }[`name == \"#{ %(#{hash[:name] }) }\"`]" if hash[:name] && hash[:name].kind_of?(String)
177
+ command = "#{ command }[`name CONTAINS \"#{ %(#{hash[:name].source }) }\"`]" if hash[:name] && hash[:name].kind_of?(Regexp)
178
+ command = "#{ command }[`value == \"#{ %(#{hash[:value] }) }\"`]" if hash[:value] && hash[:value].kind_of?(String)
179
+ command = "#{ command }[`value CONTAINS \"#{ %(#{hash[:value].source }) }\"`]" if hash[:value] && hash[:value].kind_of?(Regexp)
180
+ command = "#{ command }[`visible == #{ hash[:visible] }`]" unless hash[:visible].nil?
181
+
182
+ command += "[1]" if single
183
+
184
+ command
185
+ end
186
+
187
+ # check if selectors are for a scrollable element
188
+ # @param [Boolean] single should the command return first instance or all of matched elements
189
+ # @param [Hash] selectors for fetching elements
190
+ # @return [Boolean] true if element has scrollable attribute true or is class one of (RecyclerView, HorizontalScrollView, ScrollView, ListView)
191
+ def is_scrollable_selector?(selectors, single)
192
+ return false unless single
193
+ return true if selectors[:scrollable]
194
+ if selectors[:class] == "androidx.recyclerview.widget.RecyclerView" ||
195
+ selectors[:class] == "android.widget.HorizontalScrollView" ||
196
+ selectors[:class] == "android.widget.ScrollView" ||
197
+ selectors[:class] == "android.widget.ListView"
198
+ return true
199
+ elsif selectors[:type] == "XCUIElementTypeScrollView"
200
+ return true
201
+ end
202
+ false
203
+ end
204
+
205
+ #noinspection RubyUnnecessaryReturnStatement,RubyUnusedLocalVariable
206
+ # separate selectors from given hash parameters
207
+ # @param [Hash] params
208
+ # @return [Array] first element is params, second are selectors
209
+ def extract_selectors_from_params(params = {})
210
+ selectors = params.select { |key, value| [
211
+ :id,
212
+ :long_clickable,
213
+ :desc,
214
+ :class,
215
+ :text,
216
+ :package,
217
+ :checkable,
218
+ :checked,
219
+ :clickable,
220
+ :enabled,
221
+ :focusable,
222
+ :focused,
223
+ :index,
224
+ :selected,
225
+ :scrollable,
226
+
227
+ # ios specific
228
+ :type,
229
+ :label,
230
+ :x,
231
+ :y,
232
+ :width,
233
+ :height,
234
+ :visible,
235
+ :name,
236
+ :value,
237
+
238
+ :image
239
+ ].include?(key) }
240
+ params = Hash[params.to_a - selectors.to_a]
241
+
242
+ # default params
243
+ params[:single] = true if params[:single].nil?
244
+ params[:scrollable_locator] = nil if params[:scrollable_locator].nil?
245
+ if params[:default_find_strategy].nil?
246
+ params[:default_find_strategy] = DEFAULT_ANDROID_FIND_STRATEGY if @driver.device == :android
247
+ params[:default_find_strategy] = DEFAULT_IOS_FIND_STRATEGY if @driver.device == :ios || @driver.device == :tvos
248
+ end
249
+ if params[:default_scroll_strategy].nil?
250
+ params[:default_scroll_strategy] = DEFAULT_ANDROID_SCROLL_STRATEGY if @driver.device == :android
251
+ params[:default_scroll_strategy] = DEFAULT_IOS_SCROLL_STRATEGY if @driver.device == :ios || @driver.device == :tvos
252
+ end
253
+
254
+ return params, selectors
255
+ end
256
+
257
+
258
+ def resolve_id(id)
259
+ if id && id.kind_of?(String) && !id.match?(/.*:id\//)
260
+ # shorthand ids like myId make full ids => my.app.package:id/myId
261
+ if id[0] == "="
262
+ return id[1..-1]
263
+ else
264
+ return "#{@driver.current_package}:id/#{id}"
265
+ end
266
+ else
267
+ id
268
+ end
269
+ end
270
+ end
271
271
  end