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,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