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