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.
- checksums.yaml +4 -4
- data/.gitignore +15 -15
- data/.idea/deployment.xml +21 -21
- data/.idea/inspectionProfiles/Project_Default.xml +8 -8
- data/.idea/misc.xml +5 -5
- data/.idea/modules.xml +7 -7
- data/.idea/runConfigurations/Android_Test.xml +41 -41
- data/.idea/runConfigurations.xml +9 -9
- data/.idea/sshConfigs.xml +12 -12
- data/.idea/vcs.xml +5 -5
- data/.idea/webServers.xml +20 -20
- data/.rspec +3 -3
- data/.rubocop.yml +13 -13
- data/CHANGELOG.md +5 -5
- data/CODE_OF_CONDUCT.md +102 -102
- data/Gemfile +12 -12
- data/LICENSE.txt +21 -21
- data/README.md +378 -378
- data/Rakefile +12 -12
- data/bin/console +17 -17
- data/bin/setup +8 -8
- data/lib/testa_appium_driver/android/class_selectors.rb +437 -437
- data/lib/testa_appium_driver/android/driver.rb +69 -69
- data/lib/testa_appium_driver/android/locator/attributes.rb +113 -113
- data/lib/testa_appium_driver/android/locator.rb +141 -141
- data/lib/testa_appium_driver/android/scroll_actions/uiautomator_scroll_actions.rb +61 -61
- data/lib/testa_appium_driver/android/selenium_element.rb +7 -7
- data/lib/testa_appium_driver/common/bounds.rb +149 -149
- data/lib/testa_appium_driver/common/constants.rb +36 -36
- data/lib/testa_appium_driver/common/exceptions/strategy_mix_exception.rb +11 -11
- data/lib/testa_appium_driver/common/helpers.rb +270 -270
- data/lib/testa_appium_driver/common/locator/scroll_actions.rb +397 -397
- data/lib/testa_appium_driver/common/locator.rb +610 -610
- data/lib/testa_appium_driver/common/scroll_actions/json_wire_scroll_actions.rb +3 -3
- data/lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb +237 -237
- data/lib/testa_appium_driver/common/scroll_actions.rb +246 -246
- data/lib/testa_appium_driver/common/selenium_element.rb +19 -19
- data/lib/testa_appium_driver/driver.rb +312 -312
- data/lib/testa_appium_driver/ios/driver.rb +48 -48
- data/lib/testa_appium_driver/ios/locator/attributes.rb +80 -80
- data/lib/testa_appium_driver/ios/locator.rb +70 -70
- data/lib/testa_appium_driver/ios/selenium_element.rb +6 -6
- data/lib/testa_appium_driver/ios/type_selectors.rb +187 -187
- data/lib/testa_appium_driver/version.rb +5 -5
- data/lib/testa_appium_driver.rb +6 -6
- data/testa_appium_driver.gemspec +41 -41
- data/testa_appium_driver.iml +27 -78
- metadata +3 -3
@@ -1,611 +1,611 @@
|
|
1
|
-
require_relative 'locator/scroll_actions'
|
2
|
-
|
3
|
-
|
4
|
-
module TestaAppiumDriver
|
5
|
-
#noinspection RubyTooManyInstanceVariablesInspection,RubyTooManyMethodsInspection
|
6
|
-
class Locator
|
7
|
-
include Helpers
|
8
|
-
|
9
|
-
attr_accessor :xpath_selector
|
10
|
-
attr_accessor :single
|
11
|
-
|
12
|
-
attr_accessor :driver
|
13
|
-
attr_accessor :strategy
|
14
|
-
attr_accessor :strategy_reason
|
15
|
-
|
16
|
-
# @type [Boolean] used to determine if last selector was one of siblings or children. Only in those selectors we can reliably use xpath array [instance] selector
|
17
|
-
attr_accessor :last_selector_adjacent
|
18
|
-
attr_accessor :can_use_id_strategy
|
19
|
-
|
20
|
-
attr_accessor :image_selector
|
21
|
-
|
22
|
-
attr_accessor :from_element
|
23
|
-
attr_accessor :scroll_orientation
|
24
|
-
attr_accessor :scroll_deadzone
|
25
|
-
attr_accessor :scrollable_locator
|
26
|
-
|
27
|
-
attr_accessor :default_find_strategy
|
28
|
-
attr_accessor :default_scroll_strategy
|
29
|
-
|
30
|
-
attr_accessor :index_for_multiple
|
31
|
-
|
32
|
-
|
33
|
-
# locator parameters are:
|
34
|
-
# single: true or false
|
35
|
-
# scrollable_locator: [TestaAppiumDriver::Locator, nil] for scrolling if needed later
|
36
|
-
# default_find_strategy: default strategy if find element strategy is not enforced
|
37
|
-
# default_scroll_strategy: default strategy for scrolling if not enforced
|
38
|
-
#
|
39
|
-
# @param [TestaAppiumDriver::Driver] driver
|
40
|
-
# @param [TestaAppiumDriver::Driver, TestaAppiumDriver::Locator, Selenium::WebDriver::Element] from_element from which element to execute the find_element
|
41
|
-
# @param [Hash] params selectors and params for locator
|
42
|
-
def initialize(driver, from_element, params = {})
|
43
|
-
# @type [TestaAppiumDriver::Driver]
|
44
|
-
@driver = driver
|
45
|
-
@index_for_multiple = nil
|
46
|
-
@image_selector = nil
|
47
|
-
|
48
|
-
params, selectors = extract_selectors_from_params(params)
|
49
|
-
single = params[:single]
|
50
|
-
|
51
|
-
@single = single
|
52
|
-
|
53
|
-
if selectors[:image].nil?
|
54
|
-
if from_element.instance_of?(TestaAppiumDriver::Locator) && !from_element.image_selector.nil?
|
55
|
-
raise "Cannot chain non-image selectors to image selectors"
|
56
|
-
end
|
57
|
-
else
|
58
|
-
handle_image_selector(selectors, params)
|
59
|
-
end
|
60
|
-
|
61
|
-
selectors[:id] = selectors[:name] unless selectors[:name].nil?
|
62
|
-
if from_element.instance_of?(Selenium::WebDriver::Element)
|
63
|
-
@xpath_selector = "//*" # to select current element
|
64
|
-
@xpath_selector += hash_to_xpath(@driver.device, selectors, single)[1..-1]
|
65
|
-
else
|
66
|
-
@xpath_selector = hash_to_xpath(@driver.device, selectors, single)
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
@from_element = from_element
|
71
|
-
@default_find_strategy = params[:default_find_strategy]
|
72
|
-
@default_scroll_strategy = params[:default_scroll_strategy]
|
73
|
-
|
74
|
-
|
75
|
-
@can_use_id_strategy = is_only_id_selector?(selectors)
|
76
|
-
if @can_use_id_strategy
|
77
|
-
if @driver.device == :android
|
78
|
-
@can_use_id_strategy = resolve_id(selectors[:id])
|
79
|
-
else
|
80
|
-
@can_use_id_strategy = selectors[:id]
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
@strategy = params[:strategy]
|
86
|
-
@strategy_reason = params[:strategy_reason]
|
87
|
-
|
88
|
-
@last_selector_adjacent = false
|
89
|
-
|
90
|
-
init(params, selectors, single)
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
def is_only_id_selector?(selectors)
|
97
|
-
# since, name and id is the same thing for iOS,
|
98
|
-
if @driver.device == :android
|
99
|
-
selectors.keys.count == 1 && !selectors[:id].nil?
|
100
|
-
else
|
101
|
-
# if it iOS we assign the name to id
|
102
|
-
selectors.keys.count == 2 && !selectors[:id].nil? && selectors[:id] == selectors[:name]
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
# method missing is used to fetch the element before executing additional commands like click, send_key, count
|
108
|
-
def method_missing(method, *args, &block)
|
109
|
-
r = execute.send(method, *args, &block)
|
110
|
-
@driver.invalidate_cache
|
111
|
-
r
|
112
|
-
end
|
113
|
-
|
114
|
-
|
115
|
-
# @param [Boolean] skip_cache if true it will skip cache check and store
|
116
|
-
# @param [Selenium::WebDriver::Element] force_cache_element, for internal use where we have already the element, and want to execute custom locator methods on it
|
117
|
-
# @return [Selenium::WebDriver::Element, Array]
|
118
|
-
def execute(skip_cache: false, force_cache_element: nil, ignore_implicit_wait: false)
|
119
|
-
return force_cache_element unless force_cache_element.nil?
|
120
|
-
# if we are looking for current element, then return from_element
|
121
|
-
# for example when we have driver.element.elements[1].click
|
122
|
-
# elements[2] will be resolved with xpath because we are looking for multiple elements from element
|
123
|
-
# and since we are looking for instance 2, [](instance) method will return new "empty locator"
|
124
|
-
# we are executing click on that "empty locator" so we have to return the instance 2 of elements for the click
|
125
|
-
if @xpath_selector == "//*[1]" && !@from_element.nil? && @image_selector.nil?
|
126
|
-
return @from_element if @from_element.instance_of?(Selenium::WebDriver::Element)
|
127
|
-
return @from_element.execute(skip_cache: skip_cache, force_cache_element: force_cache_element, ignore_implicit_wait: ignore_implicit_wait) if @from_element.instance_of?(TestaAppiumDriver::Locator)
|
128
|
-
return @from_element
|
129
|
-
end
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
r = @driver.execute(@from_element, @single, strategies_and_selectors, skip_cache: skip_cache, ignore_implicit_wait: ignore_implicit_wait)
|
137
|
-
r = r[@index_for_multiple] if !@index_for_multiple.nil? && !@single
|
138
|
-
r
|
139
|
-
end
|
140
|
-
|
141
|
-
def when_exists(timeout = nil, &block)
|
142
|
-
found = false
|
143
|
-
begin
|
144
|
-
wait_until_exists(timeout)
|
145
|
-
found = true
|
146
|
-
rescue
|
147
|
-
#ignored
|
148
|
-
end
|
149
|
-
if found
|
150
|
-
if block_given? # block is given
|
151
|
-
block.call(self) # use call to execute the block
|
152
|
-
else # the value of block_argument becomes nil if you didn't give a block
|
153
|
-
# block was not given
|
154
|
-
end
|
155
|
-
end
|
156
|
-
self
|
157
|
-
end
|
158
|
-
|
159
|
-
|
160
|
-
# @param [Integer] timeout in seconds
|
161
|
-
# @return [TestaAppiumDriver::Locator]
|
162
|
-
def wait_until_exists(timeout = nil)
|
163
|
-
args = {timeout: timeout}
|
164
|
-
_wait(:until, args)
|
165
|
-
end
|
166
|
-
|
167
|
-
|
168
|
-
# @param [Integer] timeout in seconds
|
169
|
-
# @return [TestaAppiumDriver::Locator]
|
170
|
-
def wait_while_exists(timeout = nil)
|
171
|
-
args = {timeout: timeout}
|
172
|
-
_wait(:while, args)
|
173
|
-
end
|
174
|
-
|
175
|
-
|
176
|
-
def wait_while(timeout = nil, args = {})
|
177
|
-
args[:timeout] = timeout
|
178
|
-
_wait(:while, args)
|
179
|
-
end
|
180
|
-
|
181
|
-
def wait_until(timeout = nil, args = {})
|
182
|
-
args[:timeout] = timeout
|
183
|
-
_wait(:until, args)
|
184
|
-
end
|
185
|
-
|
186
|
-
|
187
|
-
# all timeouts are disabled before check, and enabled after check
|
188
|
-
# @return [boolean] true if it exists in the page regardless if visible or not
|
189
|
-
def exists?
|
190
|
-
found = true
|
191
|
-
begin
|
192
|
-
execute(skip_cache: true, ignore_implicit_wait: true)
|
193
|
-
rescue StandardError
|
194
|
-
found = false
|
195
|
-
end
|
196
|
-
found
|
197
|
-
end
|
198
|
-
|
199
|
-
# @return [TestaAppiumDriver::Locator]
|
200
|
-
def first
|
201
|
-
self[0]
|
202
|
-
end
|
203
|
-
|
204
|
-
# @return [TestaAppiumDriver::Locator]
|
205
|
-
def second
|
206
|
-
self[1]
|
207
|
-
end
|
208
|
-
|
209
|
-
# @return [TestaAppiumDriver::Locator]
|
210
|
-
def third
|
211
|
-
self[2]
|
212
|
-
end
|
213
|
-
|
214
|
-
# @return [TestaAppiumDriver::Locator]
|
215
|
-
def last
|
216
|
-
self[-1]
|
217
|
-
end
|
218
|
-
|
219
|
-
def [](instance)
|
220
|
-
raise "Cannot add index selector to non-Array" if @single
|
221
|
-
if ((@strategy.nil? && !@last_selector_adjacent && @driver.device == :android) || @strategy == FIND_STRATEGY_UIAUTOMATOR) && instance >= 0
|
222
|
-
locator = self.dup
|
223
|
-
locator.strategy = FIND_STRATEGY_UIAUTOMATOR
|
224
|
-
locator.ui_selector = "#{@ui_selector}.instance(#{instance})"
|
225
|
-
locator.single = true
|
226
|
-
locator.can_use_id_strategy = false
|
227
|
-
locator
|
228
|
-
elsif (@driver.device == :ios && !@last_selector_adjacent && @strategy.nil?) || @strategy == FIND_STRATEGY_CLASS_CHAIN
|
229
|
-
locator = self.dup
|
230
|
-
locator.strategy = FIND_STRATEGY_CLASS_CHAIN
|
231
|
-
locator.class_chain_selector += "[#{instance + 1}]"
|
232
|
-
locator.single = true
|
233
|
-
locator.can_use_id_strategy = false
|
234
|
-
locator
|
235
|
-
else
|
236
|
-
from_element = self.dup
|
237
|
-
from_element.index_for_multiple = instance
|
238
|
-
params = {}.merge({single: true, scrollable_locator: @scrollable_locator})
|
239
|
-
#params[:strategy] = FIND_STRATEGY_XPATH
|
240
|
-
#params[:strategy_reason] = "retrieved instance of a array"
|
241
|
-
params[:default_find_strategy] = @default_find_strategy
|
242
|
-
params[:default_scroll_strategy] = @default_scroll_strategy
|
243
|
-
Locator.new(@driver, from_element, params)
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
|
248
|
-
# @param [TestaAppiumDriver::Locator, Selenium::WebDriver::Element, Array] other
|
249
|
-
#noinspection RubyNilAnalysis,RubyUnnecessaryReturnStatement
|
250
|
-
def ==(other)
|
251
|
-
elements = execute
|
252
|
-
other = other.execute if other.kind_of?(TestaAppiumDriver::Locator)
|
253
|
-
|
254
|
-
if elements.kind_of?(Array)
|
255
|
-
return false unless other.kind_of?(Array)
|
256
|
-
return false if other.count != elements.count
|
257
|
-
return (elements - other).empty?
|
258
|
-
else
|
259
|
-
return false if other.kind_of?(Array)
|
260
|
-
return elements == other
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
def as_json
|
265
|
-
{
|
266
|
-
strategy: @strategy,
|
267
|
-
default_strategy: @default_find_strategy,
|
268
|
-
single: @single,
|
269
|
-
uiautomator: defined?(self.ui_selector) ? ui_selector : nil,
|
270
|
-
xpath: @xpath_selector,
|
271
|
-
scroll_orientation: @scroll_orientation,
|
272
|
-
resolved: strategies_and_selectors,
|
273
|
-
index_for_multiple: @index_for_multiple
|
274
|
-
}
|
275
|
-
end
|
276
|
-
|
277
|
-
def to_s
|
278
|
-
JSON.dump(as_json)
|
279
|
-
end
|
280
|
-
|
281
|
-
def to_ary
|
282
|
-
[self.to_s]
|
283
|
-
end
|
284
|
-
|
285
|
-
|
286
|
-
# @return [TestaAppiumDriver::Locator]
|
287
|
-
def as_scrollable(orientation: :vertical, top: nil, bottom: nil, right: nil, left: nil)
|
288
|
-
@scroll_orientation = orientation
|
289
|
-
if !top.nil? || !bottom.nil? || !right.nil? || !left.nil?
|
290
|
-
@scroll_deadzone = {}
|
291
|
-
@scroll_deadzone[:top] = top.to_f unless top.nil?
|
292
|
-
@scroll_deadzone[:bottom] = bottom.to_f unless bottom.nil?
|
293
|
-
@scroll_deadzone[:right] = right.to_f unless right.nil?
|
294
|
-
@scroll_deadzone[:left] = left.to_f unless left.nil?
|
295
|
-
end
|
296
|
-
@scrollable_locator = self.dup
|
297
|
-
self
|
298
|
-
end
|
299
|
-
|
300
|
-
|
301
|
-
def first_and_last_leaf
|
302
|
-
@driver.first_and_last_leaf(execute)
|
303
|
-
end
|
304
|
-
|
305
|
-
|
306
|
-
def tap(x = nil, y = nil)
|
307
|
-
click(x, y)
|
308
|
-
end
|
309
|
-
|
310
|
-
# if both x or y, or both are not given, will click in the center of the element
|
311
|
-
# @param x If positive integer, will offset the click from the left side, if negative integer, will offset the click from the right. If float value is given, it will threat it as percentage offset, giving it 0.5 will click in the middle
|
312
|
-
# @param y If positive integer, will offset the click from the bottom side, if negative integer, will offset the click from the top. If float value is given, it will threat it as percentage offset, giving it 0.5 will click in the middle
|
313
|
-
def click(x = nil, y = nil)
|
314
|
-
if !x.nil? && !y.nil?
|
315
|
-
|
316
|
-
b = self.bounds
|
317
|
-
if x.kind_of? Integer
|
318
|
-
if x >= 0
|
319
|
-
x = b.top_left.x + x
|
320
|
-
else
|
321
|
-
x = b.bottom_right.x + x
|
322
|
-
end
|
323
|
-
elsif x.kind_of? Float
|
324
|
-
x = b.top_left.x + b.width*x
|
325
|
-
else
|
326
|
-
raise "x value #{x} not supported"
|
327
|
-
end
|
328
|
-
|
329
|
-
if y.kind_of? Integer
|
330
|
-
if y >= 0
|
331
|
-
y = b.bottom_right.y + y
|
332
|
-
else
|
333
|
-
y = b.top_left + y
|
334
|
-
end
|
335
|
-
elsif y.kind_of? Float
|
336
|
-
y = b.bottom_right.y + b.height*y
|
337
|
-
end
|
338
|
-
|
339
|
-
action_builder = @driver.action
|
340
|
-
f1 = action_builder.add_pointer_input(:touch, "finger1")
|
341
|
-
f1.create_pointer_move(duration: 0, x: x, y: y, origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
|
342
|
-
f1.create_pointer_down(:left)
|
343
|
-
f1.create_pointer_up(:left)
|
344
|
-
@driver.perform_actions [f1]
|
345
|
-
else
|
346
|
-
if @driver.device == :android
|
347
|
-
perform_driver_method(:click)
|
348
|
-
else
|
349
|
-
# on ios, if element is not visible, first click will scroll to it
|
350
|
-
# then on second click actually perform the click
|
351
|
-
visible = visible?
|
352
|
-
perform_driver_method(:click)
|
353
|
-
perform_driver_method(:click) unless visible rescue nil
|
354
|
-
end
|
355
|
-
end
|
356
|
-
end
|
357
|
-
|
358
|
-
def send_key(*args)
|
359
|
-
perform_driver_method(:send_keys, *args)
|
360
|
-
end
|
361
|
-
|
362
|
-
def clear
|
363
|
-
perform_driver_method(:clear)
|
364
|
-
end
|
365
|
-
|
366
|
-
|
367
|
-
# Return parent element
|
368
|
-
# @return [TestaAppiumDriver::Locator]
|
369
|
-
def parent
|
370
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "parent") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
371
|
-
raise "Cannot add parent selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
372
|
-
|
373
|
-
locator = self.dup
|
374
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
375
|
-
locator.strategy_reason = "parent"
|
376
|
-
locator.xpath_selector += "/.."
|
377
|
-
locator.can_use_id_strategy = false
|
378
|
-
locator
|
379
|
-
end
|
380
|
-
|
381
|
-
# Return all children elements
|
382
|
-
# @return [TestaAppiumDriver::Locator]
|
383
|
-
def children
|
384
|
-
raise "Cannot add children selector to array" unless @single
|
385
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "children") if @strategy != FIND_STRATEGY_XPATH && @strategy != FIND_STRATEGY_CLASS_CHAIN && !@strategy.nil?
|
386
|
-
|
387
|
-
locator = self.dup
|
388
|
-
locator.strategy_reason = "children"
|
389
|
-
locator.xpath_selector += "/*"
|
390
|
-
locator.single = false
|
391
|
-
locator.last_selector_adjacent = true
|
392
|
-
locator.can_use_id_strategy = false
|
393
|
-
|
394
|
-
if @driver.device == :android
|
395
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
396
|
-
else
|
397
|
-
locator.class_chain_selector += "/*"
|
398
|
-
end
|
399
|
-
locator
|
400
|
-
end
|
401
|
-
|
402
|
-
|
403
|
-
# Return first child element
|
404
|
-
# @return [TestaAppiumDriver::Locator]
|
405
|
-
def child
|
406
|
-
raise "Cannot add children selector to array" unless @single
|
407
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "child") if @strategy != FIND_STRATEGY_XPATH && @strategy != FIND_STRATEGY_CLASS_CHAIN && !@strategy.nil?
|
408
|
-
|
409
|
-
locator = self.dup
|
410
|
-
|
411
|
-
locator.strategy_reason = "child"
|
412
|
-
locator.xpath_selector += "/*[1]"
|
413
|
-
locator.single = true
|
414
|
-
locator.can_use_id_strategy = false
|
415
|
-
|
416
|
-
if @driver.device == :android
|
417
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
418
|
-
else
|
419
|
-
locator.class_chain_selector += "/*[1]"
|
420
|
-
end
|
421
|
-
locator
|
422
|
-
end
|
423
|
-
|
424
|
-
|
425
|
-
# @return [TestaAppiumDriver::Locator]
|
426
|
-
def siblings
|
427
|
-
raise "Cannot add siblings selector to array" unless @single
|
428
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "siblings") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
429
|
-
raise "Cannot add siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
430
|
-
|
431
|
-
locator = self.dup
|
432
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
433
|
-
locator.strategy_reason = "siblings"
|
434
|
-
locator.xpath_selector += "/../*[not(@index=\"#{index}\")]"
|
435
|
-
locator.single = false
|
436
|
-
locator.last_selector_adjacent = true
|
437
|
-
locator.can_use_id_strategy = false
|
438
|
-
locator
|
439
|
-
end
|
440
|
-
|
441
|
-
# @return [TestaAppiumDriver::Locator]
|
442
|
-
def preceding_siblings
|
443
|
-
raise "Cannot add preceding_siblings selector to array" unless @single
|
444
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "preceding_siblings") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
445
|
-
raise "Cannot add preceding_siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
446
|
-
|
447
|
-
locator = self.dup
|
448
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
449
|
-
locator.strategy_reason = "preceding_siblings"
|
450
|
-
locator.xpath_selector += "/../*[position() < #{index + 1}]" # position() starts from 1
|
451
|
-
locator.single = false
|
452
|
-
locator.last_selector_adjacent = true
|
453
|
-
locator.can_use_id_strategy = false
|
454
|
-
locator
|
455
|
-
end
|
456
|
-
|
457
|
-
# @return [TestaAppiumDriver::Locator]
|
458
|
-
def preceding_sibling
|
459
|
-
raise "Cannot add preceding_sibling selector to array" unless @single
|
460
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "preceding_sibling") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
461
|
-
raise "Cannot add preceding siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
462
|
-
|
463
|
-
locator = self.dup
|
464
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
465
|
-
locator.strategy_reason = "preceding_sibling"
|
466
|
-
i = index
|
467
|
-
locator.single = true
|
468
|
-
return nil if i == 0
|
469
|
-
locator.xpath_selector += "/../*[@index=\"#{i - 1}\"]"
|
470
|
-
locator.last_selector_adjacent = true
|
471
|
-
locator.can_use_id_strategy = false
|
472
|
-
locator
|
473
|
-
end
|
474
|
-
|
475
|
-
|
476
|
-
# @return [TestaAppiumDriver::Locator]
|
477
|
-
def following_siblings
|
478
|
-
raise "Cannot add following_siblings selector to array" unless @single
|
479
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "following_siblings") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
480
|
-
raise "Cannot add following_siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
481
|
-
|
482
|
-
locator = self.dup
|
483
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
484
|
-
locator.strategy_reason = "following_siblings"
|
485
|
-
locator.xpath_selector += "/../*[position() > #{index + 1}]" # position() starts from 1
|
486
|
-
locator.single = false
|
487
|
-
locator.last_selector_adjacent = true
|
488
|
-
locator.can_use_id_strategy = false
|
489
|
-
locator
|
490
|
-
end
|
491
|
-
|
492
|
-
# @return [TestaAppiumDriver::Locator]
|
493
|
-
def following_sibling
|
494
|
-
raise "Cannot add following_sibling selector to array" unless @single
|
495
|
-
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "following_sibling") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
496
|
-
raise "Cannot add following_sibling selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
497
|
-
|
498
|
-
locator = self.dup
|
499
|
-
locator.strategy = FIND_STRATEGY_XPATH
|
500
|
-
locator.strategy_reason = "following_sibling"
|
501
|
-
i = index
|
502
|
-
locator.single = true
|
503
|
-
return nil if i == 0
|
504
|
-
locator.xpath_selector += "/../*[@index=\"#{i + 1}\"]"
|
505
|
-
locator.last_selector_adjacent = true
|
506
|
-
locator.can_use_id_strategy = false
|
507
|
-
locator
|
508
|
-
end
|
509
|
-
|
510
|
-
|
511
|
-
private
|
512
|
-
|
513
|
-
def _wait(type, args)
|
514
|
-
interval = EXISTS_WAIT
|
515
|
-
interval = args[:interval] unless args[:interval].nil?
|
516
|
-
|
517
|
-
message = "wait #{type} exists timeout exceeded"
|
518
|
-
message = args[:message] unless args[:message].nil?
|
519
|
-
|
520
|
-
if args[:timeout].nil?
|
521
|
-
#timeout = @driver.get_timeouts["implicit"] / 1000
|
522
|
-
timeout = 10
|
523
|
-
else
|
524
|
-
timeout = args[:timeout]
|
525
|
-
end
|
526
|
-
|
527
|
-
args.delete(:message)
|
528
|
-
args.delete(:interval)
|
529
|
-
args.delete(:timeout)
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
start_time = Time.now.to_f
|
534
|
-
if type == :while
|
535
|
-
while exists? && _attributes_match(args)
|
536
|
-
raise message if start_time + timeout < Time.now.to_f
|
537
|
-
sleep interval
|
538
|
-
end
|
539
|
-
else
|
540
|
-
until exists? && _attributes_match(args)
|
541
|
-
raise message if start_time + timeout < Time.now.to_f
|
542
|
-
sleep interval
|
543
|
-
end
|
544
|
-
end
|
545
|
-
self
|
546
|
-
end
|
547
|
-
|
548
|
-
def _attributes_match(attributes)
|
549
|
-
all_match = true
|
550
|
-
attributes.each do |key, value|
|
551
|
-
unless attribute(key) == value
|
552
|
-
all_match = false
|
553
|
-
break
|
554
|
-
end
|
555
|
-
end
|
556
|
-
all_match
|
557
|
-
end
|
558
|
-
|
559
|
-
#noinspection RubyNilAnalysis
|
560
|
-
def perform_driver_method(name, *args)
|
561
|
-
elements = execute
|
562
|
-
if elements.kind_of?(Array)
|
563
|
-
elements.map { |e| e.send(name, *args) }
|
564
|
-
else
|
565
|
-
elements.send(name, *args)
|
566
|
-
end
|
567
|
-
end
|
568
|
-
|
569
|
-
def add_xpath_child_selectors(locator, selectors, single)
|
570
|
-
locator.single = false unless single # switching from single result to multiple
|
571
|
-
locator.xpath_selector += hash_to_xpath(@driver.device, selectors, single)
|
572
|
-
end
|
573
|
-
|
574
|
-
|
575
|
-
def handle_image_selector(selectors, params)
|
576
|
-
image_match_threshold = 0.4
|
577
|
-
image_match_threshold = params[:imageMatchThreshold] unless params[:imageMatchThreshold].nil?
|
578
|
-
image_match_threshold = params[:threshold] unless params[:threshold].nil?
|
579
|
-
fix_image_find_screenshot_dims = true
|
580
|
-
fix_image_find_screenshot_dims = params[:fixImageFindScreenshotDims] unless params[:fixImageFindScreenshotDims].nil?
|
581
|
-
fix_image_template_size = false
|
582
|
-
fix_image_template_size = params[:fixImageTemplateSize] unless params[:fixImageTemplateSize].nil?
|
583
|
-
fix_image_template_scale = false
|
584
|
-
fix_image_template_scale = params[:fixImageTemplateScale] unless params[:fixImageTemplateScale].nil?
|
585
|
-
default_image_template_scale = 1.0
|
586
|
-
default_image_template_scale = params[:defaultImageTemplateScale] unless params[:defaultImageTemplateScale].nil?
|
587
|
-
check_for_image_element_staleness = true
|
588
|
-
check_for_image_element_staleness = params[:checkForImageElementStaleness] unless params[:checkForImageElementStaleness].nil?
|
589
|
-
auto_update_image_element_position = false
|
590
|
-
auto_update_image_element_position = params[:autoUpdateImageElementPosition] unless params[:autoUpdateImageElementPosition].nil?
|
591
|
-
image_element_tap_strategy = "w3cActions"
|
592
|
-
image_element_tap_strategy = params[:imageElementTapStrategy] unless params[:imageElementTapStrategy].nil?
|
593
|
-
get_matched_image_result = false
|
594
|
-
get_matched_image_result = params[:getMatchedImageResult] unless params[:getMatchedImageResult].nil?
|
595
|
-
|
596
|
-
@image_selector = {
|
597
|
-
image: selectors[:image],
|
598
|
-
imageMatchThreshold: image_match_threshold,
|
599
|
-
fixImageFindScreenshotDims: fix_image_find_screenshot_dims,
|
600
|
-
fixImageTemplateSize: fix_image_template_size,
|
601
|
-
fixImageTemplateScale: fix_image_template_scale,
|
602
|
-
defaultImageTemplateScale: default_image_template_scale,
|
603
|
-
checkForImageElementStaleness: check_for_image_element_staleness,
|
604
|
-
autoUpdateImageElementPosition: auto_update_image_element_position,
|
605
|
-
imageElementTapStrategy: image_element_tap_strategy,
|
606
|
-
getMatchedImageResult: get_matched_image_result,
|
607
|
-
}
|
608
|
-
end
|
609
|
-
end
|
610
|
-
|
1
|
+
require_relative 'locator/scroll_actions'
|
2
|
+
|
3
|
+
|
4
|
+
module TestaAppiumDriver
|
5
|
+
#noinspection RubyTooManyInstanceVariablesInspection,RubyTooManyMethodsInspection
|
6
|
+
class Locator
|
7
|
+
include Helpers
|
8
|
+
|
9
|
+
attr_accessor :xpath_selector
|
10
|
+
attr_accessor :single
|
11
|
+
|
12
|
+
attr_accessor :driver
|
13
|
+
attr_accessor :strategy
|
14
|
+
attr_accessor :strategy_reason
|
15
|
+
|
16
|
+
# @type [Boolean] used to determine if last selector was one of siblings or children. Only in those selectors we can reliably use xpath array [instance] selector
|
17
|
+
attr_accessor :last_selector_adjacent
|
18
|
+
attr_accessor :can_use_id_strategy
|
19
|
+
|
20
|
+
attr_accessor :image_selector
|
21
|
+
|
22
|
+
attr_accessor :from_element
|
23
|
+
attr_accessor :scroll_orientation
|
24
|
+
attr_accessor :scroll_deadzone
|
25
|
+
attr_accessor :scrollable_locator
|
26
|
+
|
27
|
+
attr_accessor :default_find_strategy
|
28
|
+
attr_accessor :default_scroll_strategy
|
29
|
+
|
30
|
+
attr_accessor :index_for_multiple
|
31
|
+
|
32
|
+
|
33
|
+
# locator parameters are:
|
34
|
+
# single: true or false
|
35
|
+
# scrollable_locator: [TestaAppiumDriver::Locator, nil] for scrolling if needed later
|
36
|
+
# default_find_strategy: default strategy if find element strategy is not enforced
|
37
|
+
# default_scroll_strategy: default strategy for scrolling if not enforced
|
38
|
+
#
|
39
|
+
# @param [TestaAppiumDriver::Driver] driver
|
40
|
+
# @param [TestaAppiumDriver::Driver, TestaAppiumDriver::Locator, Selenium::WebDriver::Element] from_element from which element to execute the find_element
|
41
|
+
# @param [Hash] params selectors and params for locator
|
42
|
+
def initialize(driver, from_element, params = {})
|
43
|
+
# @type [TestaAppiumDriver::Driver]
|
44
|
+
@driver = driver
|
45
|
+
@index_for_multiple = nil
|
46
|
+
@image_selector = nil
|
47
|
+
|
48
|
+
params, selectors = extract_selectors_from_params(params)
|
49
|
+
single = params[:single]
|
50
|
+
|
51
|
+
@single = single
|
52
|
+
|
53
|
+
if selectors[:image].nil?
|
54
|
+
if from_element.instance_of?(TestaAppiumDriver::Locator) && !from_element.image_selector.nil?
|
55
|
+
raise "Cannot chain non-image selectors to image selectors"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
handle_image_selector(selectors, params)
|
59
|
+
end
|
60
|
+
|
61
|
+
selectors[:id] = selectors[:name] unless selectors[:name].nil?
|
62
|
+
if from_element.instance_of?(Selenium::WebDriver::Element)
|
63
|
+
@xpath_selector = "//*" # to select current element
|
64
|
+
@xpath_selector += hash_to_xpath(@driver.device, selectors, single)[1..-1]
|
65
|
+
else
|
66
|
+
@xpath_selector = hash_to_xpath(@driver.device, selectors, single)
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
@from_element = from_element
|
71
|
+
@default_find_strategy = params[:default_find_strategy]
|
72
|
+
@default_scroll_strategy = params[:default_scroll_strategy]
|
73
|
+
|
74
|
+
|
75
|
+
@can_use_id_strategy = is_only_id_selector?(selectors)
|
76
|
+
if @can_use_id_strategy
|
77
|
+
if @driver.device == :android
|
78
|
+
@can_use_id_strategy = resolve_id(selectors[:id])
|
79
|
+
else
|
80
|
+
@can_use_id_strategy = selectors[:id]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
@strategy = params[:strategy]
|
86
|
+
@strategy_reason = params[:strategy_reason]
|
87
|
+
|
88
|
+
@last_selector_adjacent = false
|
89
|
+
|
90
|
+
init(params, selectors, single)
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
def is_only_id_selector?(selectors)
|
97
|
+
# since, name and id is the same thing for iOS,
|
98
|
+
if @driver.device == :android
|
99
|
+
selectors.keys.count == 1 && !selectors[:id].nil?
|
100
|
+
else
|
101
|
+
# if it iOS we assign the name to id
|
102
|
+
selectors.keys.count == 2 && !selectors[:id].nil? && selectors[:id] == selectors[:name]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# method missing is used to fetch the element before executing additional commands like click, send_key, count
|
108
|
+
def method_missing(method, *args, &block)
|
109
|
+
r = execute.send(method, *args, &block)
|
110
|
+
@driver.invalidate_cache
|
111
|
+
r
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# @param [Boolean] skip_cache if true it will skip cache check and store
|
116
|
+
# @param [Selenium::WebDriver::Element] force_cache_element, for internal use where we have already the element, and want to execute custom locator methods on it
|
117
|
+
# @return [Selenium::WebDriver::Element, Array]
|
118
|
+
def execute(skip_cache: false, force_cache_element: nil, ignore_implicit_wait: false)
|
119
|
+
return force_cache_element unless force_cache_element.nil?
|
120
|
+
# if we are looking for current element, then return from_element
|
121
|
+
# for example when we have driver.element.elements[1].click
|
122
|
+
# elements[2] will be resolved with xpath because we are looking for multiple elements from element
|
123
|
+
# and since we are looking for instance 2, [](instance) method will return new "empty locator"
|
124
|
+
# we are executing click on that "empty locator" so we have to return the instance 2 of elements for the click
|
125
|
+
if @xpath_selector == "//*[1]" && !@from_element.nil? && @image_selector.nil?
|
126
|
+
return @from_element if @from_element.instance_of?(Selenium::WebDriver::Element)
|
127
|
+
return @from_element.execute(skip_cache: skip_cache, force_cache_element: force_cache_element, ignore_implicit_wait: ignore_implicit_wait) if @from_element.instance_of?(TestaAppiumDriver::Locator)
|
128
|
+
return @from_element
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
r = @driver.execute(@from_element, @single, strategies_and_selectors, skip_cache: skip_cache, ignore_implicit_wait: ignore_implicit_wait)
|
137
|
+
r = r[@index_for_multiple] if !@index_for_multiple.nil? && !@single
|
138
|
+
r
|
139
|
+
end
|
140
|
+
|
141
|
+
def when_exists(timeout = nil, &block)
|
142
|
+
found = false
|
143
|
+
begin
|
144
|
+
wait_until_exists(timeout)
|
145
|
+
found = true
|
146
|
+
rescue
|
147
|
+
#ignored
|
148
|
+
end
|
149
|
+
if found
|
150
|
+
if block_given? # block is given
|
151
|
+
block.call(self) # use call to execute the block
|
152
|
+
else # the value of block_argument becomes nil if you didn't give a block
|
153
|
+
# block was not given
|
154
|
+
end
|
155
|
+
end
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
# @param [Integer] timeout in seconds
|
161
|
+
# @return [TestaAppiumDriver::Locator]
|
162
|
+
def wait_until_exists(timeout = nil)
|
163
|
+
args = {timeout: timeout}
|
164
|
+
_wait(:until, args)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
# @param [Integer] timeout in seconds
|
169
|
+
# @return [TestaAppiumDriver::Locator]
|
170
|
+
def wait_while_exists(timeout = nil)
|
171
|
+
args = {timeout: timeout}
|
172
|
+
_wait(:while, args)
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
def wait_while(timeout = nil, args = {})
|
177
|
+
args[:timeout] = timeout
|
178
|
+
_wait(:while, args)
|
179
|
+
end
|
180
|
+
|
181
|
+
def wait_until(timeout = nil, args = {})
|
182
|
+
args[:timeout] = timeout
|
183
|
+
_wait(:until, args)
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# all timeouts are disabled before check, and enabled after check
|
188
|
+
# @return [boolean] true if it exists in the page regardless if visible or not
|
189
|
+
def exists?
|
190
|
+
found = true
|
191
|
+
begin
|
192
|
+
execute(skip_cache: true, ignore_implicit_wait: true)
|
193
|
+
rescue StandardError
|
194
|
+
found = false
|
195
|
+
end
|
196
|
+
found
|
197
|
+
end
|
198
|
+
|
199
|
+
# @return [TestaAppiumDriver::Locator]
|
200
|
+
def first
|
201
|
+
self[0]
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [TestaAppiumDriver::Locator]
|
205
|
+
def second
|
206
|
+
self[1]
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return [TestaAppiumDriver::Locator]
|
210
|
+
def third
|
211
|
+
self[2]
|
212
|
+
end
|
213
|
+
|
214
|
+
# @return [TestaAppiumDriver::Locator]
|
215
|
+
def last
|
216
|
+
self[-1]
|
217
|
+
end
|
218
|
+
|
219
|
+
def [](instance)
|
220
|
+
raise "Cannot add index selector to non-Array" if @single
|
221
|
+
if ((@strategy.nil? && !@last_selector_adjacent && @driver.device == :android) || @strategy == FIND_STRATEGY_UIAUTOMATOR) && instance >= 0
|
222
|
+
locator = self.dup
|
223
|
+
locator.strategy = FIND_STRATEGY_UIAUTOMATOR
|
224
|
+
locator.ui_selector = "#{@ui_selector}.instance(#{instance})"
|
225
|
+
locator.single = true
|
226
|
+
locator.can_use_id_strategy = false
|
227
|
+
locator
|
228
|
+
elsif (@driver.device == :ios && !@last_selector_adjacent && @strategy.nil?) || @strategy == FIND_STRATEGY_CLASS_CHAIN
|
229
|
+
locator = self.dup
|
230
|
+
locator.strategy = FIND_STRATEGY_CLASS_CHAIN
|
231
|
+
locator.class_chain_selector += "[#{instance + 1}]"
|
232
|
+
locator.single = true
|
233
|
+
locator.can_use_id_strategy = false
|
234
|
+
locator
|
235
|
+
else
|
236
|
+
from_element = self.dup
|
237
|
+
from_element.index_for_multiple = instance
|
238
|
+
params = {}.merge({single: true, scrollable_locator: @scrollable_locator})
|
239
|
+
#params[:strategy] = FIND_STRATEGY_XPATH
|
240
|
+
#params[:strategy_reason] = "retrieved instance of a array"
|
241
|
+
params[:default_find_strategy] = @default_find_strategy
|
242
|
+
params[:default_scroll_strategy] = @default_scroll_strategy
|
243
|
+
Locator.new(@driver, from_element, params)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
# @param [TestaAppiumDriver::Locator, Selenium::WebDriver::Element, Array] other
|
249
|
+
#noinspection RubyNilAnalysis,RubyUnnecessaryReturnStatement
|
250
|
+
def ==(other)
|
251
|
+
elements = execute
|
252
|
+
other = other.execute if other.kind_of?(TestaAppiumDriver::Locator)
|
253
|
+
|
254
|
+
if elements.kind_of?(Array)
|
255
|
+
return false unless other.kind_of?(Array)
|
256
|
+
return false if other.count != elements.count
|
257
|
+
return (elements - other).empty?
|
258
|
+
else
|
259
|
+
return false if other.kind_of?(Array)
|
260
|
+
return elements == other
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def as_json
|
265
|
+
{
|
266
|
+
strategy: @strategy,
|
267
|
+
default_strategy: @default_find_strategy,
|
268
|
+
single: @single,
|
269
|
+
uiautomator: defined?(self.ui_selector) ? ui_selector : nil,
|
270
|
+
xpath: @xpath_selector,
|
271
|
+
scroll_orientation: @scroll_orientation,
|
272
|
+
resolved: strategies_and_selectors,
|
273
|
+
index_for_multiple: @index_for_multiple
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
def to_s
|
278
|
+
JSON.dump(as_json)
|
279
|
+
end
|
280
|
+
|
281
|
+
def to_ary
|
282
|
+
[self.to_s]
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
# @return [TestaAppiumDriver::Locator]
|
287
|
+
def as_scrollable(orientation: :vertical, top: nil, bottom: nil, right: nil, left: nil)
|
288
|
+
@scroll_orientation = orientation
|
289
|
+
if !top.nil? || !bottom.nil? || !right.nil? || !left.nil?
|
290
|
+
@scroll_deadzone = {}
|
291
|
+
@scroll_deadzone[:top] = top.to_f unless top.nil?
|
292
|
+
@scroll_deadzone[:bottom] = bottom.to_f unless bottom.nil?
|
293
|
+
@scroll_deadzone[:right] = right.to_f unless right.nil?
|
294
|
+
@scroll_deadzone[:left] = left.to_f unless left.nil?
|
295
|
+
end
|
296
|
+
@scrollable_locator = self.dup
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
def first_and_last_leaf
|
302
|
+
@driver.first_and_last_leaf(execute)
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
def tap(x = nil, y = nil)
|
307
|
+
click(x, y)
|
308
|
+
end
|
309
|
+
|
310
|
+
# if both x or y, or both are not given, will click in the center of the element
|
311
|
+
# @param x If positive integer, will offset the click from the left side, if negative integer, will offset the click from the right. If float value is given, it will threat it as percentage offset, giving it 0.5 will click in the middle
|
312
|
+
# @param y If positive integer, will offset the click from the bottom side, if negative integer, will offset the click from the top. If float value is given, it will threat it as percentage offset, giving it 0.5 will click in the middle
|
313
|
+
def click(x = nil, y = nil)
|
314
|
+
if !x.nil? && !y.nil?
|
315
|
+
|
316
|
+
b = self.bounds
|
317
|
+
if x.kind_of? Integer
|
318
|
+
if x >= 0
|
319
|
+
x = b.top_left.x + x
|
320
|
+
else
|
321
|
+
x = b.bottom_right.x + x
|
322
|
+
end
|
323
|
+
elsif x.kind_of? Float
|
324
|
+
x = b.top_left.x + b.width*x
|
325
|
+
else
|
326
|
+
raise "x value #{x} not supported"
|
327
|
+
end
|
328
|
+
|
329
|
+
if y.kind_of? Integer
|
330
|
+
if y >= 0
|
331
|
+
y = b.bottom_right.y + y
|
332
|
+
else
|
333
|
+
y = b.top_left + y
|
334
|
+
end
|
335
|
+
elsif y.kind_of? Float
|
336
|
+
y = b.bottom_right.y + b.height*y
|
337
|
+
end
|
338
|
+
|
339
|
+
action_builder = @driver.action
|
340
|
+
f1 = action_builder.add_pointer_input(:touch, "finger1")
|
341
|
+
f1.create_pointer_move(duration: 0, x: x, y: y, origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
|
342
|
+
f1.create_pointer_down(:left)
|
343
|
+
f1.create_pointer_up(:left)
|
344
|
+
@driver.perform_actions [f1]
|
345
|
+
else
|
346
|
+
if @driver.device == :android
|
347
|
+
perform_driver_method(:click)
|
348
|
+
else
|
349
|
+
# on ios, if element is not visible, first click will scroll to it
|
350
|
+
# then on second click actually perform the click
|
351
|
+
visible = visible?
|
352
|
+
perform_driver_method(:click)
|
353
|
+
perform_driver_method(:click) unless visible rescue nil
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def send_key(*args)
|
359
|
+
perform_driver_method(:send_keys, *args)
|
360
|
+
end
|
361
|
+
|
362
|
+
def clear
|
363
|
+
perform_driver_method(:clear)
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
# Return parent element
|
368
|
+
# @return [TestaAppiumDriver::Locator]
|
369
|
+
def parent
|
370
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "parent") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
371
|
+
raise "Cannot add parent selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
372
|
+
|
373
|
+
locator = self.dup
|
374
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
375
|
+
locator.strategy_reason = "parent"
|
376
|
+
locator.xpath_selector += "/.."
|
377
|
+
locator.can_use_id_strategy = false
|
378
|
+
locator
|
379
|
+
end
|
380
|
+
|
381
|
+
# Return all children elements
|
382
|
+
# @return [TestaAppiumDriver::Locator]
|
383
|
+
def children
|
384
|
+
raise "Cannot add children selector to array" unless @single
|
385
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "children") if @strategy != FIND_STRATEGY_XPATH && @strategy != FIND_STRATEGY_CLASS_CHAIN && !@strategy.nil?
|
386
|
+
|
387
|
+
locator = self.dup
|
388
|
+
locator.strategy_reason = "children"
|
389
|
+
locator.xpath_selector += "/*"
|
390
|
+
locator.single = false
|
391
|
+
locator.last_selector_adjacent = true
|
392
|
+
locator.can_use_id_strategy = false
|
393
|
+
|
394
|
+
if @driver.device == :android
|
395
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
396
|
+
else
|
397
|
+
locator.class_chain_selector += "/*"
|
398
|
+
end
|
399
|
+
locator
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
# Return first child element
|
404
|
+
# @return [TestaAppiumDriver::Locator]
|
405
|
+
def child
|
406
|
+
raise "Cannot add children selector to array" unless @single
|
407
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "child") if @strategy != FIND_STRATEGY_XPATH && @strategy != FIND_STRATEGY_CLASS_CHAIN && !@strategy.nil?
|
408
|
+
|
409
|
+
locator = self.dup
|
410
|
+
|
411
|
+
locator.strategy_reason = "child"
|
412
|
+
locator.xpath_selector += "/*[1]"
|
413
|
+
locator.single = true
|
414
|
+
locator.can_use_id_strategy = false
|
415
|
+
|
416
|
+
if @driver.device == :android
|
417
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
418
|
+
else
|
419
|
+
locator.class_chain_selector += "/*[1]"
|
420
|
+
end
|
421
|
+
locator
|
422
|
+
end
|
423
|
+
|
424
|
+
|
425
|
+
# @return [TestaAppiumDriver::Locator]
|
426
|
+
def siblings
|
427
|
+
raise "Cannot add siblings selector to array" unless @single
|
428
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "siblings") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
429
|
+
raise "Cannot add siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
430
|
+
|
431
|
+
locator = self.dup
|
432
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
433
|
+
locator.strategy_reason = "siblings"
|
434
|
+
locator.xpath_selector += "/../*[not(@index=\"#{index}\")]"
|
435
|
+
locator.single = false
|
436
|
+
locator.last_selector_adjacent = true
|
437
|
+
locator.can_use_id_strategy = false
|
438
|
+
locator
|
439
|
+
end
|
440
|
+
|
441
|
+
# @return [TestaAppiumDriver::Locator]
|
442
|
+
def preceding_siblings
|
443
|
+
raise "Cannot add preceding_siblings selector to array" unless @single
|
444
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "preceding_siblings") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
445
|
+
raise "Cannot add preceding_siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
446
|
+
|
447
|
+
locator = self.dup
|
448
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
449
|
+
locator.strategy_reason = "preceding_siblings"
|
450
|
+
locator.xpath_selector += "/../*[position() < #{index + 1}]" # position() starts from 1
|
451
|
+
locator.single = false
|
452
|
+
locator.last_selector_adjacent = true
|
453
|
+
locator.can_use_id_strategy = false
|
454
|
+
locator
|
455
|
+
end
|
456
|
+
|
457
|
+
# @return [TestaAppiumDriver::Locator]
|
458
|
+
def preceding_sibling
|
459
|
+
raise "Cannot add preceding_sibling selector to array" unless @single
|
460
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "preceding_sibling") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
461
|
+
raise "Cannot add preceding siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
462
|
+
|
463
|
+
locator = self.dup
|
464
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
465
|
+
locator.strategy_reason = "preceding_sibling"
|
466
|
+
i = index
|
467
|
+
locator.single = true
|
468
|
+
return nil if i == 0
|
469
|
+
locator.xpath_selector += "/../*[@index=\"#{i - 1}\"]"
|
470
|
+
locator.last_selector_adjacent = true
|
471
|
+
locator.can_use_id_strategy = false
|
472
|
+
locator
|
473
|
+
end
|
474
|
+
|
475
|
+
|
476
|
+
# @return [TestaAppiumDriver::Locator]
|
477
|
+
def following_siblings
|
478
|
+
raise "Cannot add following_siblings selector to array" unless @single
|
479
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "following_siblings") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
480
|
+
raise "Cannot add following_siblings selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
481
|
+
|
482
|
+
locator = self.dup
|
483
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
484
|
+
locator.strategy_reason = "following_siblings"
|
485
|
+
locator.xpath_selector += "/../*[position() > #{index + 1}]" # position() starts from 1
|
486
|
+
locator.single = false
|
487
|
+
locator.last_selector_adjacent = true
|
488
|
+
locator.can_use_id_strategy = false
|
489
|
+
locator
|
490
|
+
end
|
491
|
+
|
492
|
+
# @return [TestaAppiumDriver::Locator]
|
493
|
+
def following_sibling
|
494
|
+
raise "Cannot add following_sibling selector to array" unless @single
|
495
|
+
raise StrategyMixException.new(@strategy, @strategy_reason, FIND_STRATEGY_XPATH, "following_sibling") if @strategy != FIND_STRATEGY_XPATH && !@strategy.nil?
|
496
|
+
raise "Cannot add following_sibling selector to a retrieved instance of a class array" if (@xpath_selector == "//*" || @xpath_selector == "//*[1]") && !@from_element.nil?
|
497
|
+
|
498
|
+
locator = self.dup
|
499
|
+
locator.strategy = FIND_STRATEGY_XPATH
|
500
|
+
locator.strategy_reason = "following_sibling"
|
501
|
+
i = index
|
502
|
+
locator.single = true
|
503
|
+
return nil if i == 0
|
504
|
+
locator.xpath_selector += "/../*[@index=\"#{i + 1}\"]"
|
505
|
+
locator.last_selector_adjacent = true
|
506
|
+
locator.can_use_id_strategy = false
|
507
|
+
locator
|
508
|
+
end
|
509
|
+
|
510
|
+
|
511
|
+
private
|
512
|
+
|
513
|
+
def _wait(type, args)
|
514
|
+
interval = EXISTS_WAIT
|
515
|
+
interval = args[:interval] unless args[:interval].nil?
|
516
|
+
|
517
|
+
message = "wait #{type} exists timeout exceeded"
|
518
|
+
message = args[:message] unless args[:message].nil?
|
519
|
+
|
520
|
+
if args[:timeout].nil?
|
521
|
+
#timeout = @driver.get_timeouts["implicit"] / 1000
|
522
|
+
timeout = 10
|
523
|
+
else
|
524
|
+
timeout = args[:timeout]
|
525
|
+
end
|
526
|
+
|
527
|
+
args.delete(:message)
|
528
|
+
args.delete(:interval)
|
529
|
+
args.delete(:timeout)
|
530
|
+
|
531
|
+
|
532
|
+
|
533
|
+
start_time = Time.now.to_f
|
534
|
+
if type == :while
|
535
|
+
while exists? && _attributes_match(args)
|
536
|
+
raise message if start_time + timeout < Time.now.to_f
|
537
|
+
sleep interval
|
538
|
+
end
|
539
|
+
else
|
540
|
+
until exists? && _attributes_match(args)
|
541
|
+
raise message if start_time + timeout < Time.now.to_f
|
542
|
+
sleep interval
|
543
|
+
end
|
544
|
+
end
|
545
|
+
self
|
546
|
+
end
|
547
|
+
|
548
|
+
def _attributes_match(attributes)
|
549
|
+
all_match = true
|
550
|
+
attributes.each do |key, value|
|
551
|
+
unless attribute(key) == value
|
552
|
+
all_match = false
|
553
|
+
break
|
554
|
+
end
|
555
|
+
end
|
556
|
+
all_match
|
557
|
+
end
|
558
|
+
|
559
|
+
#noinspection RubyNilAnalysis
|
560
|
+
def perform_driver_method(name, *args)
|
561
|
+
elements = execute
|
562
|
+
if elements.kind_of?(Array)
|
563
|
+
elements.map { |e| e.send(name, *args) }
|
564
|
+
else
|
565
|
+
elements.send(name, *args)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
def add_xpath_child_selectors(locator, selectors, single)
|
570
|
+
locator.single = false unless single # switching from single result to multiple
|
571
|
+
locator.xpath_selector += hash_to_xpath(@driver.device, selectors, single)
|
572
|
+
end
|
573
|
+
|
574
|
+
|
575
|
+
def handle_image_selector(selectors, params)
|
576
|
+
image_match_threshold = 0.4
|
577
|
+
image_match_threshold = params[:imageMatchThreshold] unless params[:imageMatchThreshold].nil?
|
578
|
+
image_match_threshold = params[:threshold] unless params[:threshold].nil?
|
579
|
+
fix_image_find_screenshot_dims = true
|
580
|
+
fix_image_find_screenshot_dims = params[:fixImageFindScreenshotDims] unless params[:fixImageFindScreenshotDims].nil?
|
581
|
+
fix_image_template_size = false
|
582
|
+
fix_image_template_size = params[:fixImageTemplateSize] unless params[:fixImageTemplateSize].nil?
|
583
|
+
fix_image_template_scale = false
|
584
|
+
fix_image_template_scale = params[:fixImageTemplateScale] unless params[:fixImageTemplateScale].nil?
|
585
|
+
default_image_template_scale = 1.0
|
586
|
+
default_image_template_scale = params[:defaultImageTemplateScale] unless params[:defaultImageTemplateScale].nil?
|
587
|
+
check_for_image_element_staleness = true
|
588
|
+
check_for_image_element_staleness = params[:checkForImageElementStaleness] unless params[:checkForImageElementStaleness].nil?
|
589
|
+
auto_update_image_element_position = false
|
590
|
+
auto_update_image_element_position = params[:autoUpdateImageElementPosition] unless params[:autoUpdateImageElementPosition].nil?
|
591
|
+
image_element_tap_strategy = "w3cActions"
|
592
|
+
image_element_tap_strategy = params[:imageElementTapStrategy] unless params[:imageElementTapStrategy].nil?
|
593
|
+
get_matched_image_result = false
|
594
|
+
get_matched_image_result = params[:getMatchedImageResult] unless params[:getMatchedImageResult].nil?
|
595
|
+
|
596
|
+
@image_selector = {
|
597
|
+
image: selectors[:image],
|
598
|
+
imageMatchThreshold: image_match_threshold,
|
599
|
+
fixImageFindScreenshotDims: fix_image_find_screenshot_dims,
|
600
|
+
fixImageTemplateSize: fix_image_template_size,
|
601
|
+
fixImageTemplateScale: fix_image_template_scale,
|
602
|
+
defaultImageTemplateScale: default_image_template_scale,
|
603
|
+
checkForImageElementStaleness: check_for_image_element_staleness,
|
604
|
+
autoUpdateImageElementPosition: auto_update_image_element_position,
|
605
|
+
imageElementTapStrategy: image_element_tap_strategy,
|
606
|
+
getMatchedImageResult: get_matched_image_result,
|
607
|
+
}
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
611
|
end
|