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