testcentricity_mobile 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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