testcentricity_mobile 4.0.0

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.
@@ -0,0 +1,692 @@
1
+ require 'test/unit'
2
+
3
+ module TestCentricity
4
+ module AppElements
5
+ class AppUIElement
6
+ include Test::Unit::Assertions
7
+
8
+ attr_reader :parent, :locator, :context, :type, :name
9
+ attr_accessor :mru_object, :mru_locator, :mru_parent, :mru_app_session
10
+
11
+ def initialize(name, parent, locator, context)
12
+ @name = name
13
+ @parent = parent
14
+ @locator = locator
15
+ @context = context
16
+ @type = nil
17
+ reset_mru_cache
18
+ end
19
+
20
+ def reset_mru_cache
21
+ @mru_object = nil
22
+ @mru_locator = nil
23
+ @mru_parent = nil
24
+ @mru_app_session = nil
25
+ end
26
+
27
+ def get_object_type
28
+ @type
29
+ end
30
+
31
+ def get_locator
32
+ @locator
33
+ end
34
+
35
+ def get_name
36
+ @name
37
+ end
38
+
39
+ def set(value)
40
+ obj = element
41
+ object_not_found_exception(obj)
42
+ if value.is_a?(Array)
43
+ obj.send_keys(value[0])
44
+ if value[1].is_a?(Integer)
45
+ press_keycode(value[1])
46
+ else
47
+ obj.send_keys(value[1])
48
+ end
49
+ elsif value.is_a?(String)
50
+ obj.send_keys(value)
51
+ end
52
+ end
53
+
54
+ # Send keystrokes to this UI element.
55
+ #
56
+ # @param value [String] keys
57
+ # @example
58
+ # color_picker_wheel.send_keys('Lime green')
59
+ #
60
+ def send_keys(value)
61
+ obj = element
62
+ object_not_found_exception(obj)
63
+ obj.send_keys(value)
64
+ end
65
+
66
+ def clear
67
+ obj = element
68
+ object_not_found_exception(obj)
69
+ obj.clear
70
+ end
71
+
72
+ def get_value
73
+ obj = element
74
+ object_not_found_exception(obj)
75
+ if AppiumConnect.is_webview?
76
+ case obj.tag_name.downcase
77
+ when 'input', 'select', 'textarea'
78
+ obj.value
79
+ else
80
+ obj.text
81
+ end
82
+ else
83
+ obj.text
84
+ end
85
+ end
86
+
87
+ alias value get_value
88
+
89
+ def get_caption
90
+ obj = element
91
+ object_not_found_exception(obj)
92
+ if AppiumConnect.is_webview?
93
+ case obj.tag_name.downcase
94
+ when 'input', 'select', 'textarea'
95
+ obj.value
96
+ else
97
+ obj.text
98
+ end
99
+ elsif Environ.is_ios?
100
+ caption = case obj.tag_name
101
+ when 'XCUIElementTypeNavigationBar'
102
+ obj.attribute(:name)
103
+ else
104
+ obj.attribute(:label)
105
+ end
106
+ caption = '' if caption.nil?
107
+ else
108
+ caption = obj.text
109
+ if caption.blank?
110
+ case obj.attribute(:class)
111
+ when 'android.view.ViewGroup'
112
+ caption_obj = obj.find_element(:xpath, '//android.widget.TextView')
113
+ caption = caption_obj.text
114
+ when 'android.widget.Button'
115
+ caption_obj = obj.find_element(:xpath, '//android.widget.TextView')
116
+ caption = caption_obj.text
117
+ if caption.blank?
118
+ caption_obj = obj.find_element(:xpath, '//android.widget.ViewGroup/android.widget.TextView')
119
+ caption = caption_obj.text
120
+ end
121
+ end
122
+ end
123
+ end
124
+ caption
125
+ end
126
+
127
+ alias caption get_caption
128
+
129
+ # Does UI object exists?
130
+ #
131
+ # @return [Boolean]
132
+ # @example
133
+ # empty_cart_image.exists?
134
+ #
135
+ def exists?
136
+ obj = element
137
+ !obj.nil?
138
+ end
139
+
140
+ # Is UI object visible?
141
+ #
142
+ # @return [Boolean]
143
+ # @example
144
+ # remember_me_checkbox.visible?
145
+ #
146
+ def visible?
147
+ obj = element
148
+ return false if obj.nil?
149
+ begin
150
+ obj.displayed?
151
+ rescue
152
+ reset_mru_cache
153
+ obj = element
154
+ return false if obj.nil?
155
+ obj.displayed?
156
+ end
157
+ end
158
+
159
+ # Is UI object hidden (not visible)?
160
+ #
161
+ # @return [Boolean]
162
+ # @example
163
+ # remember_me_checkbox.hidden?
164
+ #
165
+ def hidden?
166
+ !visible?
167
+ end
168
+
169
+ # Is UI object enabled?
170
+ #
171
+ # @return [Boolean]
172
+ # @example
173
+ # login_button.enabled?
174
+ #
175
+ def enabled?
176
+ obj = element
177
+ object_not_found_exception(obj)
178
+ obj.enabled?
179
+ end
180
+
181
+ # Is UI object disabled (not enabled)?
182
+ #
183
+ # @return [Boolean]
184
+ # @example
185
+ # refresh_button.disabled?
186
+ #
187
+ def disabled?
188
+ !enabled?
189
+ end
190
+
191
+ def selected?
192
+ obj = element
193
+ object_not_found_exception(obj)
194
+ obj.selected?
195
+ end
196
+
197
+ def get_attribute(attrib)
198
+ obj = element
199
+ object_not_found_exception(obj)
200
+ obj.attribute(attrib)
201
+ end
202
+
203
+ # Wait until the object exists, or until the specified wait time has expired. If the wait time is nil, then the wait
204
+ # time will be Environ.default_max_wait_time.
205
+ #
206
+ # @param seconds [Integer or Float] wait time in seconds
207
+ # @example
208
+ # run_button.wait_until_exists(0.5)
209
+ #
210
+ def wait_until_exists(seconds = nil, post_exception = true)
211
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
212
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
213
+ wait.until do
214
+ reset_mru_cache
215
+ exists?
216
+ end
217
+ rescue
218
+ if post_exception
219
+ raise "Could not find UI #{object_ref_message} after #{timeout} seconds" unless exists?
220
+ else
221
+ exists?
222
+ end
223
+ end
224
+
225
+ # Wait until the object no longer exists, or until the specified wait time has expired. If the wait time is nil, then
226
+ # the wait time will be Environ.default_max_wait_time.
227
+ #
228
+ # @param seconds [Integer or Float] wait time in seconds
229
+ # @example
230
+ # logout_button.wait_until_gone(5)
231
+ #
232
+ def wait_until_gone(seconds = nil, post_exception = true)
233
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
234
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
235
+ wait.until do
236
+ reset_mru_cache
237
+ !exists?
238
+ end
239
+ rescue
240
+ if post_exception
241
+ raise "UI #{object_ref_message} remained visible after #{timeout} seconds" if exists?
242
+ else
243
+ exists?
244
+ end
245
+ end
246
+
247
+ # Wait until the object is visible, or until the specified wait time has expired. If the wait time is nil, then the
248
+ # wait time will be Environ.default_max_wait_time.
249
+ #
250
+ # @param seconds [Integer or Float] wait time in seconds
251
+ # @example
252
+ # run_button.wait_until_visible(0.5)
253
+ #
254
+ def wait_until_visible(seconds = nil, post_exception = true)
255
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
256
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
257
+ wait.until do
258
+ reset_mru_cache
259
+ visible?
260
+ end
261
+ rescue
262
+ if post_exception
263
+ raise "Could not find UI #{object_ref_message} after #{timeout} seconds" unless visible?
264
+ else
265
+ visible?
266
+ end
267
+ end
268
+
269
+ # Wait until the object is hidden, or until the specified wait time has expired. If the wait time is nil, then the
270
+ # wait time will be Environ.default_max_wait_time.
271
+ #
272
+ # @param seconds [Integer or Float] wait time in seconds
273
+ # @example
274
+ # run_button.wait_until_hidden(10)
275
+ #
276
+ def wait_until_hidden(seconds = nil, post_exception = true)
277
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
278
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
279
+ wait.until do
280
+ reset_mru_cache
281
+ hidden?
282
+ end
283
+ rescue
284
+ if post_exception
285
+ raise "UI #{object_ref_message} remained visible after #{timeout} seconds" if visible?
286
+ else
287
+ hidden?
288
+ end
289
+ end
290
+
291
+ # Wait until the object is enabled, or until the specified wait time has expired. If the wait time is nil, then the
292
+ # wait time will be Environ.default_max_wait_time.
293
+ #
294
+ # @param seconds [Integer or Float] wait time in seconds
295
+ # @example
296
+ # run_button.wait_until_enabled(10)
297
+ #
298
+ def wait_until_enabled(seconds = nil, post_exception = true)
299
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
300
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
301
+ wait.until do
302
+ reset_mru_cache
303
+ enabled?
304
+ end
305
+ rescue
306
+ if post_exception
307
+ raise "UI #{object_ref_message} remained disabled after #{timeout} seconds" unless enabled?
308
+ else
309
+ enabled?
310
+ end
311
+ end
312
+
313
+ # Wait until the object's value equals the specified value, or until the specified wait time has expired. If the wait
314
+ # time is nil, then the wait time will be Environ.default_max_wait_time.
315
+ #
316
+ # @param value [String or Hash] value expected or comparison hash
317
+ # @param seconds [Integer or Float] wait time in seconds
318
+ # @example
319
+ # card_authorized_label.wait_until_value_is('Card authorized', 5)
320
+ # or
321
+ # total_weight_field.wait_until_value_is({ :greater_than => '250' }, 5)
322
+ #
323
+ def wait_until_value_is(value, seconds = nil, post_exception = true)
324
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
325
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
326
+ wait.until do
327
+ reset_mru_cache
328
+ compare(value, get_value)
329
+ end
330
+ rescue
331
+ if post_exception
332
+ raise "Value of UI #{object_ref_message} failed to equal '#{value}' after #{timeout} seconds" unless get_value == value
333
+ else
334
+ get_value == value
335
+ end
336
+ end
337
+
338
+ # Wait until the object's value changes to a different value, or until the specified wait time has expired. If the
339
+ # wait time is nil, then the wait time will be Environ.default_max_wait_time.
340
+ #
341
+ # @param seconds [Integer or Float] wait time in seconds
342
+ # @example
343
+ # basket_grand_total_label.wait_until_value_changes(5)
344
+ #
345
+ def wait_until_value_changes(seconds = nil, post_exception = true)
346
+ value = get_value
347
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
348
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
349
+ wait.until do
350
+ reset_mru_cache
351
+ get_value != value
352
+ end
353
+ rescue
354
+ if post_exception
355
+ raise "Value of UI #{object_ref_message} failed to change from '#{value}' after #{timeout} seconds" if get_value == value
356
+ else
357
+ get_value == value
358
+ end
359
+ end
360
+
361
+ # Return width of object.
362
+ #
363
+ # @return [Integer]
364
+ # @example
365
+ # button_width = my_button.width
366
+ #
367
+ def width
368
+ obj = element
369
+ object_not_found_exception(obj)
370
+ obj.size.width
371
+ end
372
+
373
+ # Return height of object.
374
+ #
375
+ # @return [Integer]
376
+ # @example
377
+ # button_height = my_button.height
378
+ #
379
+ def height
380
+ obj = element
381
+ object_not_found_exception(obj)
382
+ obj.size.height
383
+ end
384
+
385
+ # Return x coordinate of object's location.
386
+ #
387
+ # @return [Integer]
388
+ # @example
389
+ # button_x = my_button.x_loc
390
+ #
391
+ def x_loc
392
+ obj = element
393
+ object_not_found_exception(obj)
394
+ obj.location.x
395
+ end
396
+
397
+ # Return y coordinate of object's location.
398
+ #
399
+ # @return [Integer]
400
+ # @example
401
+ # button_y = my_button.y_loc
402
+ #
403
+ def y_loc
404
+ obj = element
405
+ object_not_found_exception(obj)
406
+ obj.location.y
407
+ end
408
+
409
+ # Return the number of occurrences of an object with an ambiguous locator that evaluates to multiple UI elements.
410
+ #
411
+ # @return [Integer]
412
+ # @example
413
+ # num_items = store_item.count
414
+ #
415
+ def count
416
+ objs = find_elements(@locator.keys[0], @locator.values[0])
417
+ objs.count
418
+ end
419
+
420
+ # Click on a UI element
421
+ #
422
+ # @example
423
+ # login_button.click
424
+ #
425
+ def click
426
+ obj = element
427
+ object_not_found_exception(obj)
428
+ obj.click
429
+ end
430
+
431
+ # Tap on a UI element
432
+ #
433
+ # @example
434
+ # bar_chart_close.tap
435
+ #
436
+ def tap
437
+ obj = element
438
+ object_not_found_exception(obj)
439
+ driver.action
440
+ .click_and_hold(obj)
441
+ .release
442
+ .perform
443
+ end
444
+
445
+ # Double-tap on a UI element
446
+ #
447
+ # @example
448
+ # refresh_chart_button.double_tap
449
+ #
450
+ def double_tap
451
+ obj = element
452
+ object_not_found_exception(obj)
453
+ driver.action
454
+ .click_and_hold(obj)
455
+ .release
456
+ .pause(duration: 0.2)
457
+ .click_and_hold(obj)
458
+ .release
459
+ .perform
460
+ end
461
+
462
+ # Long press on a UI element
463
+ #
464
+ # @param duration [Float] duration of long press in seconds
465
+ # @example
466
+ # header_image.long_press(1.5)
467
+ #
468
+ def long_press(duration = 1)
469
+ obj = element
470
+ object_not_found_exception(obj)
471
+ if Environ.is_ios?
472
+ begin
473
+ Environ.appium_driver.execute_script('mobile: touchAndHold', { elementId: obj.id, duration: duration })
474
+ rescue => err
475
+ puts "Retrying longpress due to error: #{err}"
476
+ else
477
+ return
478
+ end
479
+ end
480
+ driver.action
481
+ .click_and_hold(obj)
482
+ .pause(duration: duration)
483
+ .release
484
+ .perform
485
+ end
486
+
487
+ # Drag the UI object by the specified offset. If the optional duration parameter is not specified, the duration
488
+ # defaults to 0.3 seconds (300 milliseconds).
489
+ #
490
+ # @param right_offset [Integer] x coordinate offset
491
+ # @param down_offset [Integer] y coordinate offset
492
+ # @param duration [Float] OPTIONAL duration of drag in seconds
493
+ # @example
494
+ # puzzle_21_piece.drag_by(-100, -300)
495
+ #
496
+ def drag_by(right_offset, down_offset, duration = 0.3)
497
+ obj = element
498
+ object_not_found_exception(obj)
499
+ driver.action
500
+ .click_and_hold(obj)
501
+ .move_by(right_offset, down_offset, duration: duration)
502
+ .release
503
+ .perform
504
+ end
505
+
506
+ # Drag the UI object to the specified target object. If the optional duration parameter is not specified, the
507
+ # duration defaults to 0.3 seconds (300 milliseconds).
508
+ #
509
+ # @param target [String] target object to drag to
510
+ # @param duration [Float] OPTIONAL duration of drag in seconds
511
+ # @example
512
+ # puzzle_21_piece.drag_and_drop(puzzle_21_slot)
513
+ #
514
+ def drag_and_drop(target, duration = 0.3)
515
+ drag = element
516
+ object_not_found_exception(drag)
517
+ drop = target.element
518
+ drag_x = drag.location.x
519
+ drag_y = drag.location.y
520
+ drop_x = drop.location.x
521
+ drop_y = drop.location.y
522
+ driver.action
523
+ .click_and_hold(drag)
524
+ .move_by(drop_x - drag_x, drop_y - drag_y, duration: duration)
525
+ .release
526
+ .perform
527
+ end
528
+
529
+ # Scroll the UI object until it is visible. If scroll_mode is not specified, then vertical scrolling will be used.
530
+ #
531
+ # @param scroll_mode [Symbol] :vertical (default) or :horizontal
532
+ # @example
533
+ # place_order_button.scroll_into_view(scroll_mode = :horizontal)
534
+ #
535
+ def scroll_into_view(scroll_mode = :vertical)
536
+ return if visible?
537
+ case scroll_mode
538
+ when :vertical
539
+ start_direction = :down
540
+ end_direction = :up
541
+ when :horizontal
542
+ start_direction = :right
543
+ end_direction = :left
544
+ else
545
+ raise "#{scroll_mode} is not a valid selector"
546
+ end
547
+ try_count = 8
548
+ direction = start_direction
549
+ while hidden?
550
+ ScreenManager.current_screen.swipe_gesture(direction, distance = 0.1)
551
+ try_count -= 1
552
+ if try_count.zero?
553
+ if direction == end_direction
554
+ break
555
+ else
556
+ direction = end_direction
557
+ try_count = 8
558
+ end
559
+ end
560
+ end
561
+ end
562
+
563
+ # Perform a swipe gesture on the UI object in the specified direction. The swipe start point is the center of the
564
+ # UI object, and the swipe end point is the distance specified.
565
+ #
566
+ # A distance of 1 specifies a swipe gesture with a distance that is the full screen height (vertical swipe), or full
567
+ # screen width (horizontal swipe). A distance of 0.5 specifies a swipe gesture with a distance that is half the screen
568
+ # width or height.
569
+ #
570
+ # If distance is a value less than zero, then the distance of the swipe gesture will be half the height (vertical)
571
+ # or width (horizontal) of the UI element being swiped. This is useful for preforming swipes/scrolls in vertical
572
+ # or horizontal list objects.
573
+ #
574
+ # @param direction [Symbol] :up, :down, :left, or :right
575
+ # @param distance [Float] scroll distance relative to the screen height or width
576
+ # @example
577
+ # carousel_list.swipe_gesture(direction = :right, distance = 1)
578
+ #
579
+ def swipe_gesture(direction, distance = 0.5)
580
+ raise 'Scroll distance must be less than 1' if distance > 1
581
+ obj = element
582
+ object_not_found_exception(obj)
583
+ start_pt = [(obj.location.x + (obj.size.width * 0.5)).to_i, (obj.location.y + (obj.size.height * 0.5)).to_i]
584
+
585
+ if distance < 0
586
+ top = (start_pt[1] - obj.size.height).to_i
587
+ bottom = (start_pt[1] + obj.size.height).to_i
588
+ left = (start_pt[0] - obj.size.width).to_i
589
+ right = (start_pt[0] + obj.size.width).to_i
590
+ else
591
+ screen_size = window_size
592
+ top = (start_pt[1] - ((screen_size.height * distance) * 0.5)).to_i
593
+ bottom = (start_pt[1] + ((screen_size.height * distance) * 0.5)).to_i
594
+ left = (start_pt[0] - ((screen_size.width * distance) * 0.5)).to_i
595
+ right = (start_pt[0] + ((screen_size.width * distance) * 0.5)).to_i
596
+ end
597
+
598
+ end_pt = case direction
599
+ when :up
600
+ [start_pt[0], bottom]
601
+ when :down
602
+ [start_pt[0], top]
603
+ when :left
604
+ [right, start_pt[1]]
605
+ when :right
606
+ [left, start_pt[1]]
607
+ end
608
+
609
+ puts "Swipe start_pt = #{start_pt} / end_pt = #{end_pt}" if ENV['DEBUG']
610
+ driver.action
611
+ .click_and_hold(obj)
612
+ .move_to_location(end_pt[0], end_pt[1], duration: 0.25)
613
+ .pointer_up
614
+ .perform
615
+ end
616
+
617
+ def element
618
+ reset_mru_cache if @mru_app_session != Environ.app_session_id
619
+ obj = if @context == :section
620
+ parent_obj = nil
621
+ parent_locator = @parent.get_locator
622
+
623
+ if @mru_locator == @locator && @mru_parent == parent_locator && !@mru_object.nil?
624
+ return @mru_object
625
+ end
626
+
627
+ parent_locator.each do |locators|
628
+
629
+ if locators.keys[0] == :object
630
+ parent_obj = locators.values[0]
631
+ break
632
+ end
633
+
634
+ parent_obj = if parent_obj.nil?
635
+ find_element(locators.keys[0], locators.values[0])
636
+ else
637
+ parent_obj.find_element(locators.keys[0], locators.values[0])
638
+ end
639
+ end
640
+ puts "Found parent object '#{@parent.get_name}' - #{@parent.get_locator}" if ENV['DEBUG']
641
+ parent_obj.find_element(@locator.keys[0], @locator.values[0])
642
+ else
643
+ return @mru_object if @mru_locator == @locator && !@mru_object.nil?
644
+ find_element(@locator.keys[0], @locator.values[0])
645
+ end
646
+ puts "Found object '#{@name}' - #{@locator}" if ENV['DEBUG']
647
+ @mru_object = obj
648
+ @mru_locator = @locator
649
+ @mru_parent = parent_locator
650
+ @mru_app_session = Environ.app_session_id
651
+ obj
652
+ rescue
653
+ puts "Did not find object '#{@name}' - #{@locator}" if ENV['DEBUG']
654
+ nil
655
+ end
656
+
657
+ private
658
+
659
+ def object_not_found_exception(obj)
660
+ @type.nil? ? object_type = 'Object' : object_type = @type
661
+ raise ObjectNotFoundError.new("#{object_type} named '#{@name}' (#{get_locator}) not found") unless obj
662
+ end
663
+
664
+ def object_ref_message
665
+ "object '#{@name}' (#{get_locator})"
666
+ end
667
+
668
+ def compare(expected, actual)
669
+ if expected.is_a?(Hash)
670
+ result = false
671
+ expected.each do |key, value|
672
+ case key
673
+ when :lt, :less_than
674
+ result = actual < value
675
+ when :lt_eq, :less_than_or_equal
676
+ result = actual <= value
677
+ when :gt, :greater_than
678
+ result = actual > value
679
+ when :gt_eq, :greater_than_or_equal
680
+ result = actual >= value
681
+ when :not_equal
682
+ result = actual != value
683
+ end
684
+ end
685
+ else
686
+ result = expected == actual
687
+ end
688
+ result
689
+ end
690
+ end
691
+ end
692
+ end
@@ -0,0 +1,10 @@
1
+ module TestCentricity
2
+ module AppElements
3
+ class AppButton < AppUIElement
4
+ def initialize(name, parent, locator, context)
5
+ super
6
+ @type = :button
7
+ end
8
+ end
9
+ end
10
+ end