testa_appium_driver 0.1.11 → 0.1.14

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