testa_appium_driver 0.1.8 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
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 -63
  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 -396
  33. data/lib/testa_appium_driver/common/locator.rb +610 -578
  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 -278
  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