testa_appium_driver 0.1.8 → 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 -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,278 +1,312 @@
1
- # frozen_string_literal: true
2
-
3
- require 'em/pure_ruby'
4
- require 'appium_lib_core'
5
-
6
- require_relative 'common/bounds'
7
- require_relative 'common/exceptions/strategy_mix_exception'
8
- require_relative 'common/helpers'
9
- require_relative 'common/locator'
10
- require_relative 'common/scroll_actions'
11
- require_relative 'common/selenium_element'
12
-
13
- module TestaAppiumDriver
14
- class Driver
15
- include Helpers
16
-
17
- # @return [::Appium::Core::Base::Driver] the ruby_lib_core appium driver
18
- attr_accessor :driver
19
-
20
- # @return [String] iOS or Android
21
- attr_reader :device
22
-
23
- # @return [String] driver automation name (uiautomator2 or xcuitest)
24
- attr_reader :automation_name
25
-
26
- # custom options
27
- # - default_find_strategy: default strategy to be used for finding elements. Available strategies :uiautomator or :xpath
28
- # - default_scroll_strategy: default strategy to be used for scrolling. Available strategies: :uiautomator(android only), :w3c
29
- def initialize(opts = {})
30
- @testa_opts = opts[:testa_appium_driver] || {}
31
-
32
- core = Appium::Core.for(opts)
33
- extend_for(core.device, core.automation_name)
34
- @device = core.device
35
- @automation_name = core.automation_name
36
-
37
- handle_testa_opts
38
-
39
- @driver = core.start_driver
40
- invalidate_cache
41
-
42
-
43
- disable_wait_for_idle
44
- disable_implicit_wait
45
-
46
- Selenium::WebDriver::Element.set_driver(self, opts[:caps][:udid])
47
- end
48
-
49
-
50
- # invalidates current find_element cache
51
- def invalidate_cache
52
- @cache = {
53
- strategy: nil,
54
- selector: nil,
55
- element: nil,
56
- from_element: nil,
57
- time: Time.at(0)
58
- }
59
- end
60
-
61
-
62
-
63
-
64
- # Executes the find_element with the resolved locator strategy and selector. Find_element might be skipped if cache is hit.
65
- # Cache stores last executed find_element with given selector, strategy and from_element. If given values are the same within
66
- # last 5 seconds element is retrieved from cache.
67
- # @param [TestaAppiumDriver::Locator, TestaAppiumDriver::Driver] from_element element from which start the search
68
- # @param [Boolean] single fetch single or multiple results
69
- # @param [Array<Hash>] strategies_and_selectors array of usable strategies and selectors
70
- # @param [Boolean] skip_cache to skip checking and storing cache
71
- # @return [Selenium::WebDriver::Element, Array] element is returned if single is true, array otherwise
72
- def execute(from_element, single, strategies_and_selectors, skip_cache: false, ignore_implicit_wait: false)
73
-
74
- # if user wants to wait for element to exist, he can use wait_until_present
75
- start_time = Time.now.to_f
76
- ss_index = 0
77
-
78
-
79
-
80
-
81
- # resolve from_element unique id, so that we can cache it properly
82
- from_element_id = from_element.instance_of?(TestaAppiumDriver::Locator) ? from_element.strategies_and_selectors : nil
83
-
84
- begin
85
- ss = strategies_and_selectors[ss_index % strategies_and_selectors.count]
86
- ss_index +=1
87
-
88
- puts "Executing #{from_element_id ? "from #{from_element.strategy}: #{from_element.strategies_and_selectors} => " : ""}#{ss.keys[0]}: #{ss.values[0]}"
89
-
90
- if @cache[:selector] != ss.values[0] || # cache miss, selector is different
91
- @cache[:time] + 5 <= Time.now || # cache miss, older than 5 seconds
92
- @cache[:strategy] != ss.keys[0] || # cache miss, different find strategy
93
- @cache[:from_element_id] != from_element_id || # cache miss, search is started from different element
94
- skip_cache # cache is skipped
95
-
96
- if ss.keys[0] == FIND_STRATEGY_IMAGE
97
- set_find_by_image_settings(ss.values[0].dup)
98
- if single
99
- execute_result = from_element.find_element_by_image(ss.values[0][:image])
100
- else
101
- execute_result = from_element.find_elements_by_image(ss.values[0][:image])
102
- end
103
- restore_set_by_image_settings
104
- else
105
- if single
106
- execute_result = from_element.find_element(ss)
107
- else
108
- execute_result = from_element.find_elements(ss)
109
- end
110
- end
111
-
112
-
113
-
114
- unless skip_cache
115
- @cache[:selector] = ss.values[0]
116
- @cache[:strategy] = ss.keys[0]
117
- @cache[:time] = Time.now
118
- @cache[:from_element_id] = from_element_id
119
- @cache[:element] = execute_result
120
- end
121
- else
122
- # this is a cache hit, use the element from cache
123
- execute_result = @cache[:element]
124
- puts "Using cache from #{@cache[:time].strftime("%H:%M:%S.%L")}, strategy: #{@cache[:strategy]}"
125
- end
126
- rescue => e
127
- if (start_time + @implicit_wait_ms/1000 < Time.now.to_f && !ignore_implicit_wait) || ss_index < strategies_and_selectors.count
128
- sleep EXISTS_WAIT if ss_index >= strategies_and_selectors.count
129
- retry
130
- else
131
- raise e
132
- end
133
- end
134
-
135
- execute_result
136
- end
137
-
138
-
139
- # method missing is used to forward methods to the actual appium driver
140
- # after the method is executed, find element cache is invalidated
141
- def method_missing(method, *args, &block)
142
- r = @driver.send(method, *args, &block)
143
- invalidate_cache
144
- r
145
- end
146
-
147
- # disables implicit wait
148
- def disable_implicit_wait
149
- @implicit_wait_ms = @driver.get_timeouts["implicit"].to_i
150
- @implicit_wait_ms = @implicit_wait_ms/1000 if @implicit_wait_ms > 100000
151
- @implicit_wait_uiautomator_ms = @driver.get_settings["waitForSelectorTimeout"]
152
- @driver.manage.timeouts.implicit_wait = 0
153
- @driver.update_settings({waitForSelectorTimeout: 1})
154
- end
155
-
156
-
157
- # disables wait for idle, only executed for android devices
158
- def disable_wait_for_idle
159
- if @device == :android
160
- @wait_for_idle_timeout = @driver.settings.get["waitForIdleTimeout"]
161
- @driver.update_settings({waitForIdleTimeout: 1})
162
- end
163
- end
164
-
165
-
166
- def set_find_by_image_settings(settings)
167
- settings.delete(:image)
168
- @default_find_image_settings = {}
169
- old_settings = @driver.get_settings
170
- @default_find_image_settings[:imageMatchThreshold] = old_settings["imageMatchThreshold"]
171
- @default_find_image_settings[:fixImageFindScreenshotDims] = old_settings["fixImageFindScreenshotDims"]
172
- @default_find_image_settings[:fixImageTemplateSize] = old_settings["fixImageTemplateSize"]
173
- @default_find_image_settings[:fixImageTemplateScale] = old_settings["fixImageTemplateScale"]
174
- @default_find_image_settings[:defaultImageTemplateScale] = old_settings["defaultImageTemplateScale"]
175
- @default_find_image_settings[:checkForImageElementStaleness] = old_settings["checkForImageElementStaleness"]
176
- @default_find_image_settings[:autoUpdateImageElementPosition] = old_settings["autoUpdateImageElementPosition"]
177
- @default_find_image_settings[:imageElementTapStrategy] = old_settings["imageElementTapStrategy"]
178
- @default_find_image_settings[:getMatchedImageResult] = old_settings["getMatchedImageResult"]
179
-
180
- @driver.update_settings(settings)
181
- end
182
-
183
- def restore_set_by_image_settings
184
- @driver.update_settings(@default_find_image_settings) if @default_find_image_settings
185
- end
186
-
187
-
188
- # @@return [String] current package under test
189
- def current_package
190
- @driver.current_package
191
- end
192
-
193
-
194
- def window_size
195
- @driver.window_size
196
- end
197
-
198
- def back
199
- @driver.back
200
- end
201
-
202
-
203
- def is_keyboard_shown?
204
- @driver.is_keyboard_shown
205
- end
206
-
207
- def hide_keyboard
208
- @driver.hide_keyboard
209
- end
210
-
211
- def tab_key
212
- @driver.press_keycode(61)
213
- end
214
-
215
- def dpad_up_key
216
- @driver.press_keycode(19)
217
- end
218
-
219
- def dpad_down_key
220
- @driver.press_keycode(20)
221
- end
222
-
223
- def dpad_right_key
224
- @driver.press_keycode(22)
225
- end
226
-
227
- def dpad_left_key
228
- @driver.press_keycode(23)
229
- end
230
-
231
- def enter_key
232
- @driver.press_keycode(66)
233
- end
234
-
235
- def press_keycode(code)
236
- @driver.press_keycode(code)
237
- end
238
-
239
- def long_press_keycode(code)
240
- @driver.long_press_keycode(code)
241
- end
242
-
243
-
244
-
245
-
246
- # @return [Array<Selenium::WebDriver::Element] array of 2 elements, the first element without children and the last element without children in the current page
247
- def first_and_last_leaf(from_element = @driver)
248
- elements = from_element.find_elements(xpath: "//*[not(*)]")
249
- return nil if elements.count == 0
250
- [elements[0], elements[-1]]
251
- end
252
-
253
- private
254
- def extend_for(device, automation_name)
255
- case device
256
- when :android
257
- case automation_name
258
- when :uiautomator2
259
- require_relative 'android/driver'
260
- else
261
- raise "Testa appium driver not supported for #{automation_name} automation"
262
- end
263
- when :ios, :tvos
264
- case automation_name
265
- when :xcuitest
266
- require_relative 'ios/driver'
267
- else
268
- raise "Testa appium driver not supported for #{automation_name} automation"
269
- end
270
- else
271
- raise "Unknown device #{device}, should be either android, ios or tvos"
272
- end
273
- end
274
-
275
-
276
- end
277
-
278
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'em/pure_ruby'
4
+ require 'appium_lib_core'
5
+
6
+ require_relative 'common/bounds'
7
+ require_relative 'common/exceptions/strategy_mix_exception'
8
+ require_relative 'common/helpers'
9
+ require_relative 'common/locator'
10
+ require_relative 'common/scroll_actions'
11
+ require_relative 'common/selenium_element'
12
+
13
+ module TestaAppiumDriver
14
+ class Driver
15
+ include Helpers
16
+
17
+ # @return [::Appium::Core::Base::Driver] the ruby_lib_core appium driver
18
+ attr_accessor :driver
19
+
20
+ # @return [String] iOS or Android
21
+ attr_reader :device
22
+
23
+ # @return [String] driver automation name (uiautomator2 or xcuitest)
24
+ attr_reader :automation_name
25
+
26
+ # custom options
27
+ # - default_find_strategy: default strategy to be used for finding elements. Available strategies :uiautomator or :xpath
28
+ # - default_scroll_strategy: default strategy to be used for scrolling. Available strategies: :uiautomator(android only), :w3c
29
+ def initialize(opts = {})
30
+ @testa_opts = opts[:testa_appium_driver] || {}
31
+
32
+ core = Appium::Core.for(opts)
33
+ extend_for(core.device, core.automation_name)
34
+ @device = core.device
35
+ @automation_name = core.automation_name
36
+
37
+ handle_testa_opts
38
+
39
+ @driver = core.start_driver
40
+ invalidate_cache
41
+
42
+
43
+ disable_wait_for_idle
44
+ disable_implicit_wait
45
+
46
+ Selenium::WebDriver::Element.set_driver(self, opts[:caps][:udid])
47
+ end
48
+
49
+
50
+ # invalidates current find_element cache
51
+ def invalidate_cache
52
+ @cache = {
53
+ strategy: nil,
54
+ selector: nil,
55
+ element: nil,
56
+ from_element: nil,
57
+ time: Time.at(0)
58
+ }
59
+ end
60
+
61
+
62
+
63
+
64
+ # Executes the find_element with the resolved locator strategy and selector. Find_element might be skipped if cache is hit.
65
+ # Cache stores last executed find_element with given selector, strategy and from_element. If given values are the same within
66
+ # last 5 seconds element is retrieved from cache.
67
+ # @param [TestaAppiumDriver::Locator, TestaAppiumDriver::Driver] from_element element from which start the search
68
+ # @param [Boolean] single fetch single or multiple results
69
+ # @param [Array<Hash>] strategies_and_selectors array of usable strategies and selectors
70
+ # @param [Boolean] skip_cache to skip checking and storing cache
71
+ # @return [Selenium::WebDriver::Element, Array] element is returned if single is true, array otherwise
72
+ def execute(from_element, single, strategies_and_selectors, skip_cache: false, ignore_implicit_wait: false)
73
+
74
+ # if user wants to wait for element to exist, he can use wait_until_present
75
+ start_time = Time.now.to_f
76
+ ss_index = 0
77
+
78
+
79
+
80
+
81
+ # resolve from_element unique id, so that we can cache it properly
82
+ from_element_id = from_element.instance_of?(TestaAppiumDriver::Locator) ? from_element.strategies_and_selectors : nil
83
+
84
+ begin
85
+ ss = strategies_and_selectors[ss_index % strategies_and_selectors.count]
86
+ ss_index +=1
87
+
88
+ puts "Executing #{from_element_id ? "from #{from_element.strategy}: #{from_element.strategies_and_selectors} => " : ""}#{ss.keys[0]}: #{ss.values[0]}"
89
+
90
+ if @cache[:selector] != ss.values[0] || # cache miss, selector is different
91
+ @cache[:time] + 5 <= Time.now || # cache miss, older than 5 seconds
92
+ @cache[:strategy] != ss.keys[0] || # cache miss, different find strategy
93
+ @cache[:from_element_id] != from_element_id || # cache miss, search is started from different element
94
+ skip_cache # cache is skipped
95
+
96
+ if ss.keys[0] == FIND_STRATEGY_IMAGE
97
+ set_find_by_image_settings(ss.values[0].dup)
98
+ if single
99
+ execute_result = from_element.find_element_by_image(ss.values[0][:image])
100
+ else
101
+ execute_result = from_element.find_elements_by_image(ss.values[0][:image])
102
+ end
103
+ restore_set_by_image_settings
104
+ else
105
+ if single
106
+ execute_result = from_element.find_element(ss)
107
+ else
108
+ execute_result = from_element.find_elements(ss)
109
+ end
110
+ end
111
+
112
+
113
+
114
+ unless skip_cache
115
+ @cache[:selector] = ss.values[0]
116
+ @cache[:strategy] = ss.keys[0]
117
+ @cache[:time] = Time.now
118
+ @cache[:from_element_id] = from_element_id
119
+ @cache[:element] = execute_result
120
+ end
121
+ else
122
+ # this is a cache hit, use the element from cache
123
+ execute_result = @cache[:element]
124
+ puts "Using cache from #{@cache[:time].strftime("%H:%M:%S.%L")}, strategy: #{@cache[:strategy]}"
125
+ end
126
+ rescue => e
127
+ #if (start_time + @implicit_wait_ms/1000 < Time.now.to_f && !ignore_implicit_wait) || ss_index < strategies_and_selectors.count
128
+ if ss_index < strategies_and_selectors.count
129
+ sleep EXISTS_WAIT if ss_index >= strategies_and_selectors.count
130
+ retry
131
+ else
132
+ raise e
133
+ end
134
+ end
135
+
136
+ execute_result
137
+ end
138
+
139
+
140
+ # method missing is used to forward methods to the actual appium driver
141
+ # after the method is executed, find element cache is invalidated
142
+ def method_missing(method, *args, &block)
143
+ r = @driver.send(method, *args, &block)
144
+ invalidate_cache
145
+ r
146
+ end
147
+
148
+ # disables implicit wait
149
+ def disable_implicit_wait
150
+ @implicit_wait_ms = @driver.get_timeouts["implicit"].to_i
151
+ @implicit_wait_ms = @implicit_wait_ms/1000 if @implicit_wait_ms > 100000
152
+ @implicit_wait_uiautomator_ms = @driver.get_settings["waitForSelectorTimeout"]
153
+ @driver.manage.timeouts.implicit_wait = 0
154
+ @driver.update_settings({waitForSelectorTimeout: 1})
155
+ end
156
+
157
+
158
+ # disables wait for idle, only executed for android devices
159
+ def disable_wait_for_idle
160
+ if @device == :android
161
+ @wait_for_idle_timeout = @driver.settings.get["waitForIdleTimeout"]
162
+ @driver.update_settings({waitForIdleTimeout: 1})
163
+ end
164
+ end
165
+
166
+
167
+ def set_find_by_image_settings(settings)
168
+ settings.delete(:image)
169
+ @default_find_image_settings = {}
170
+ old_settings = @driver.get_settings
171
+ @default_find_image_settings[:imageMatchThreshold] = old_settings["imageMatchThreshold"]
172
+ @default_find_image_settings[:fixImageFindScreenshotDims] = old_settings["fixImageFindScreenshotDims"]
173
+ @default_find_image_settings[:fixImageTemplateSize] = old_settings["fixImageTemplateSize"]
174
+ @default_find_image_settings[:fixImageTemplateScale] = old_settings["fixImageTemplateScale"]
175
+ @default_find_image_settings[:defaultImageTemplateScale] = old_settings["defaultImageTemplateScale"]
176
+ @default_find_image_settings[:checkForImageElementStaleness] = old_settings["checkForImageElementStaleness"]
177
+ @default_find_image_settings[:autoUpdateImageElementPosition] = old_settings["autoUpdateImageElementPosition"]
178
+ @default_find_image_settings[:imageElementTapStrategy] = old_settings["imageElementTapStrategy"]
179
+ @default_find_image_settings[:getMatchedImageResult] = old_settings["getMatchedImageResult"]
180
+
181
+ @driver.update_settings(settings)
182
+ end
183
+
184
+ def restore_set_by_image_settings
185
+ @driver.update_settings(@default_find_image_settings) if @default_find_image_settings
186
+ end
187
+
188
+
189
+ # @@return [String] current package under test
190
+ def current_package
191
+ @driver.current_package
192
+ end
193
+
194
+
195
+ def window_size
196
+ @driver.window_size
197
+ end
198
+
199
+ def back
200
+ @driver.back
201
+ end
202
+
203
+
204
+ def is_keyboard_shown?
205
+ @driver.is_keyboard_shown
206
+ end
207
+
208
+ def hide_keyboard
209
+ @driver.hide_keyboard
210
+ end
211
+
212
+ def home_key
213
+ @driver.press_keycode(3)
214
+ end
215
+ def tab_key
216
+ @driver.press_keycode(61)
217
+ end
218
+
219
+ def dpad_up_key
220
+ @driver.press_keycode(19)
221
+ end
222
+
223
+ def dpad_down_key
224
+ @driver.press_keycode(20)
225
+ end
226
+
227
+ def dpad_right_key
228
+ @driver.press_keycode(22)
229
+ end
230
+
231
+ def dpad_left_key
232
+ @driver.press_keycode(23)
233
+ end
234
+
235
+ def enter_key
236
+ @driver.press_keycode(66)
237
+ end
238
+
239
+ def press_keycode(code)
240
+ @driver.press_keycode(code)
241
+ end
242
+
243
+ def long_press_keycode(code)
244
+ @driver.long_press_keycode(code)
245
+ end
246
+
247
+ def click(x, y)
248
+ ws = driver.window_size
249
+ window_width = ws.width.to_i
250
+ window_height = ws.height.to_i
251
+ if x.kind_of? Integer
252
+ if x < 0
253
+ x = window_width + x
254
+ end
255
+ elsif x.kind_of? Float
256
+ x = window_width*x
257
+ else
258
+ raise "x value #{x} not supported"
259
+ end
260
+
261
+ if y.kind_of? Integer
262
+ if y < 0
263
+ y = window_height + y
264
+ end
265
+ elsif y.kind_of? Float
266
+ y = window_height*y
267
+ end
268
+
269
+
270
+ action_builder = @driver.action
271
+ f1 = action_builder.add_pointer_input(:touch, "finger1")
272
+ f1.create_pointer_move(duration: 0, x: x, y: y, origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
273
+ f1.create_pointer_down(:left)
274
+ f1.create_pointer_up(:left)
275
+ @driver.perform_actions [f1]
276
+ end
277
+
278
+
279
+
280
+ # @return [Array<Selenium::WebDriver::Element] array of 2 elements, the first element without children and the last element without children in the current page
281
+ def first_and_last_leaf(from_element = @driver)
282
+ elements = from_element.find_elements(xpath: "//*[not(*)]")
283
+ return nil if elements.count == 0
284
+ [elements[0], elements[-1]]
285
+ end
286
+
287
+ private
288
+ def extend_for(device, automation_name)
289
+ case device
290
+ when :android
291
+ case automation_name
292
+ when :uiautomator2
293
+ require_relative 'android/driver'
294
+ else
295
+ raise "Testa appium driver not supported for #{automation_name} automation"
296
+ end
297
+ when :ios, :tvos
298
+ case automation_name
299
+ when :xcuitest
300
+ require_relative 'ios/driver'
301
+ else
302
+ raise "Testa appium driver not supported for #{automation_name} automation"
303
+ end
304
+ else
305
+ raise "Unknown device #{device}, should be either android, ios or tvos"
306
+ end
307
+ end
308
+
309
+
310
+ end
311
+
312
+ end