testcentricity_apps 4.0.10

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 (31) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +193 -0
  4. data/LICENSE.md +27 -0
  5. data/README.md +2297 -0
  6. data/lib/testcentricity_apps/app_core/appium_connect_helper.rb +667 -0
  7. data/lib/testcentricity_apps/app_core/screen_object.rb +494 -0
  8. data/lib/testcentricity_apps/app_core/screen_objects_helper.rb +211 -0
  9. data/lib/testcentricity_apps/app_core/screen_section.rb +669 -0
  10. data/lib/testcentricity_apps/app_elements/alert.rb +152 -0
  11. data/lib/testcentricity_apps/app_elements/app_element.rb +728 -0
  12. data/lib/testcentricity_apps/app_elements/button.rb +10 -0
  13. data/lib/testcentricity_apps/app_elements/checkbox.rb +61 -0
  14. data/lib/testcentricity_apps/app_elements/image.rb +10 -0
  15. data/lib/testcentricity_apps/app_elements/label.rb +10 -0
  16. data/lib/testcentricity_apps/app_elements/list.rb +188 -0
  17. data/lib/testcentricity_apps/app_elements/menu.rb +159 -0
  18. data/lib/testcentricity_apps/app_elements/menubar.rb +78 -0
  19. data/lib/testcentricity_apps/app_elements/radio.rb +61 -0
  20. data/lib/testcentricity_apps/app_elements/selectlist.rb +126 -0
  21. data/lib/testcentricity_apps/app_elements/switch.rb +66 -0
  22. data/lib/testcentricity_apps/app_elements/textfield.rb +51 -0
  23. data/lib/testcentricity_apps/appium_server.rb +76 -0
  24. data/lib/testcentricity_apps/data_objects/data_objects_helper.rb +100 -0
  25. data/lib/testcentricity_apps/data_objects/environment.rb +423 -0
  26. data/lib/testcentricity_apps/exception_queue_helper.rb +160 -0
  27. data/lib/testcentricity_apps/utility_helpers.rb +48 -0
  28. data/lib/testcentricity_apps/version.rb +3 -0
  29. data/lib/testcentricity_apps/world_extensions.rb +61 -0
  30. data/lib/testcentricity_apps.rb +103 -0
  31. metadata +322 -0
@@ -0,0 +1,494 @@
1
+ require 'test/unit'
2
+
3
+ module TestCentricity
4
+ class ScreenObject < BaseScreenSectionObject
5
+ include Test::Unit::Assertions
6
+
7
+ attr_reader :locator
8
+
9
+ def initialize
10
+ raise "Screen object #{self.class.name} does not have a screen_name trait defined" unless defined?(screen_name)
11
+ @locator = screen_locator if defined?(screen_locator)
12
+ end
13
+
14
+ # Declare and instantiate a single generic UI Element for this screen object.
15
+ #
16
+ # @param element_name [Symbol] name of UI object (as a symbol)
17
+ # @param locator [Hash] { locator_strategy: locator_identifier }
18
+ # The locator_strategy (a Symbol) specifies the selector strategy that Appium will use to find the UI element. Valid
19
+ # selectors are accessibility_id:, id:, name:, class:, xpath:, predicate: (iOS only), class_chain: (iOS only), and
20
+ # css: (WebViews in hybrid apps only).
21
+ # * The locator_identifier (a String) is the value or attribute that uniquely and unambiguously identifies the UI element.
22
+ #
23
+ # @example
24
+ # element :video_player, { accessibility_id: 'YouTube Video Player' }
25
+ #
26
+ def self.element(element_name, locator)
27
+ define_screen_element(element_name, TestCentricity::AppElements::AppUIElement, locator)
28
+ end
29
+
30
+ # Declare and instantiate a collection of generic UI Elements for this screen object.
31
+ #
32
+ # @param element_hash [Hash] names of UI objects (as a Symbol) and locator Hash
33
+ # @example
34
+ # elements drop_down_field: { accessibility_id: 'drop_trigger' },
35
+ # settings_item: { accessibility_id: 'settings' },
36
+ # video_player: { accessibility_id: 'YouTube Video Player' }
37
+ #
38
+ def self.elements(element_hash)
39
+ element_hash.each do |element_name, locator|
40
+ element(element_name, locator)
41
+ end
42
+ end
43
+
44
+ # Declare and instantiate a single button UI Element for this screen object.
45
+ #
46
+ # @param element_name [Symbol] name of button object (as a symbol)
47
+ # @param locator [Hash] { locator_strategy: locator_identifier }
48
+ # @example
49
+ # button :video_play, { accessibility_id: 'video icon play' }
50
+ #
51
+ def self.button(element_name, locator)
52
+ define_screen_element(element_name, TestCentricity::AppElements::AppButton, locator)
53
+ end
54
+
55
+ # Declare and instantiate a collection of buttons for this screen object.
56
+ #
57
+ # @param element_hash [Hash] names of buttons (as symbol) and locator Hash
58
+ # @example
59
+ # buttons video_back: { accessibility_id: 'video icon backward' },
60
+ # video_play: { accessibility_id: 'video icon play' },
61
+ # video_pause: { accessibility_id: 'video icon stop' },
62
+ # video_forward: { accessibility_id: 'video icon forward' }
63
+ #
64
+ def self.buttons(element_hash)
65
+ element_hash.each do |element_name, locator|
66
+ button(element_name, locator)
67
+ end
68
+ end
69
+
70
+ # Declare and instantiate a single textfield UI Element for this screen object.
71
+ #
72
+ # @param element_name [Symbol] name of textfield object (as a symbol)
73
+ # @param locator [Hash] { locator_strategy: locator_identifier }
74
+ # @example
75
+ # textfield :payee_name_field, { xpath: '//android.widget.EditText[@content-desc="Full Name* input field"]' }
76
+ # textfield :payee_name_field, { xpath: '//XCUIElementTypeTextField[@name="Full Name* input field"]' }
77
+ #
78
+ def self.textfield(element_name, locator)
79
+ define_screen_element(element_name, TestCentricity::AppElements::AppTextField, locator)
80
+ end
81
+
82
+ # Declare and instantiate a collection of textfields for this screen object.
83
+ #
84
+ # @param element_hash [Hash] names of textfields (as symbol) and locator Hash
85
+ # @example
86
+ # textfields username_field: { accessibility_id: 'Username input field' },
87
+ # password_field: { accessibility_id: 'Password input field' }
88
+ #
89
+ def self.textfields(element_hash)
90
+ element_hash.each do |element_name, locator|
91
+ textfield(element_name, locator)
92
+ end
93
+ end
94
+
95
+ # Declare and instantiate a single switch UI Element for this screen object.
96
+ #
97
+ # @param element_name [Symbol] name of switch object (as a symbol)
98
+ # @param locator [Hash] { locator_strategy: locator_identifier }
99
+ # @example
100
+ # switch :debug_mode_switch, { accessibility_id: 'debug mode' }
101
+ #
102
+ def self.switch(element_name, locator)
103
+ define_screen_element(element_name, TestCentricity::AppElements::AppSwitch, locator)
104
+ end
105
+
106
+ # Declare and instantiate a collection of switches for this screen object.
107
+ #
108
+ # @param element_hash [Hash] names of switches (as symbol) and locator Hash
109
+ # @example
110
+ # switches debug_mode_switch: { accessibility_id: 'debug mode' },
111
+ # metrics_switch: { accessibility_id: 'metrics' }
112
+ #
113
+ def self.switches(element_hash)
114
+ element_hash.each do |element_name, locator|
115
+ switch(element_name, locator)
116
+ end
117
+ end
118
+
119
+ # Declare and instantiate a single checkbox UI Element for this screen object.
120
+ #
121
+ # @param element_name [Symbol] name of checkbox object (as a symbol)
122
+ # @param locator [Hash] { locator_strategy: locator_identifier }
123
+ # @example
124
+ # checkbox :bill_address_check, { xpath: '//XCUIElementTypeOther[contains(@name, "billing checkbox")]'}
125
+ #
126
+ def self.checkbox(element_name, locator)
127
+ define_screen_element(element_name, TestCentricity::AppElements::AppCheckBox, locator)
128
+ end
129
+
130
+ # Declare and instantiate a collection of checkboxes for this screen object.
131
+ #
132
+ # @param element_hash [Hash] names of checkboxes (as symbol) and locator Hash
133
+ # @example
134
+ # checkboxes bill_address_check: { xpath: '//XCUIElementTypeOther[contains(@name, "billing checkbox")]'},
135
+ # is_gift_check: { accessibility_id: 'is a gift' }
136
+ #
137
+ def self.checkboxes(element_hash)
138
+ element_hash.each do |element_name, locator|
139
+ checkbox(element_name, locator)
140
+ end
141
+ end
142
+
143
+ # Declare and instantiate a single radio button UI Element for this screen object.
144
+ #
145
+ # @param element_name [Symbol] name of radio button object (as a symbol)
146
+ # @param locator [Hash] { locator_strategy: locator_identifier }
147
+ # @example
148
+ # radio :unicode_radio, { xpath: '//XCUIElementTypeRadioButton[@label="Unicode"]'}
149
+ #
150
+ def self.radio(element_name, locator)
151
+ define_screen_element(element_name, TestCentricity::AppElements::AppRadio, locator)
152
+ end
153
+
154
+ # Declare and instantiate a collection of radio buttons for this screen object.
155
+ #
156
+ # @param element_hash [Hash] names of radio buttons (as symbol) and locator Hash
157
+ # @example
158
+ # radios unicode_radio: { xpath: '//XCUIElementTypeRadioButton[@label="Unicode"]'},
159
+ # ascii_radio: { xpath: '//XCUIElementTypeRadioButton[@label="ASCII"] }
160
+ #
161
+ def self.radios(element_hash)
162
+ element_hash.each do |element_name, locator|
163
+ radio(element_name, locator)
164
+ end
165
+ end
166
+
167
+ # Declare and instantiate a single label UI Element for this screen object.
168
+ #
169
+ # @param element_name [Symbol] name of label object (as a symbol)
170
+ # @param locator [Hash] { locator_strategy: locator_identifier }
171
+ # @example
172
+ # label :header_label, { accessibility_id: 'container header' }
173
+ #
174
+ def self.label(element_name, locator)
175
+ define_screen_element(element_name, TestCentricity::AppElements::AppLabel, locator)
176
+ end
177
+
178
+ # Declare and instantiate a collection of labels for this screen object.
179
+ #
180
+ # @param element_hash [Hash] names of labels (as symbol) and locator Hash
181
+ # @example
182
+ # labels total_qty_value: { accessibility_id: 'total number' },
183
+ # total_price_value: { accessibility_id: 'total price' }
184
+ #
185
+ def self.labels(element_hash)
186
+ element_hash.each do |element_name, locator|
187
+ label(element_name, locator)
188
+ end
189
+ end
190
+
191
+ # Declare and instantiate a single list UI Element for this screen object.
192
+ #
193
+ # @param element_name [Symbol] name of list object (as a symbol)
194
+ # @param locator [Hash] { locator_strategy: locator_identifier }
195
+ # @example
196
+ # list :carousel_list, { accessibility_id: 'Carousel' }
197
+ #
198
+ def self.list(element_name, locator)
199
+ define_screen_element(element_name, TestCentricity::AppElements::AppList, locator)
200
+ end
201
+
202
+ # Declare and instantiate a collection of lists for this screen object.
203
+ #
204
+ # @param element_hash [Hash] names of lists (as symbol) and locator Hash
205
+ # @example
206
+ # lists product_grid: { xpath: '//android.widget.ScrollView/android.view.ViewGroup' },
207
+ # cart_list: { xpath: '//android.widget.ScrollView[@content-desc="cart screen"]' }
208
+ #
209
+ def self.lists(element_hash)
210
+ element_hash.each do |element_name, locator|
211
+ list(element_name, locator)
212
+ end
213
+ end
214
+
215
+ # Declare and instantiate a single selectlist UI Element for this screen object.
216
+ #
217
+ # @param element_name [Symbol] name of selectlist object (as a symbol)
218
+ # @param locator [Hash] { locator_strategy: locator_identifier }
219
+ # @example
220
+ # selectlist :convert_list, { xpath: '//XCUIElementTypePopUpButton[@label="convert"]' }
221
+ #
222
+ def self.selectlist(element_name, locator)
223
+ define_screen_element(element_name, TestCentricity::AppElements::AppSelectList, locator)
224
+ end
225
+
226
+ # Declare and instantiate a collection of selectlists for this screen object.
227
+ #
228
+ # @param element_hash [Hash] names of selectlists (as symbol) and locator Hash
229
+ # @example
230
+ # selectlists convert_list: { xpath: '//XCUIElementTypePopUpButton[@label="convert"]' },
231
+ # from_list: { xpath: '//XCUIElementTypePopUpButton[@label="convert_from"]' }
232
+ #
233
+ def self.selectlists(element_hash)
234
+ element_hash.each do |element_name, locator|
235
+ selectlist(element_name, locator)
236
+ end
237
+ end
238
+
239
+ # Declare and instantiate a single image UI Element for this screen object.
240
+ #
241
+ # @param element_name [Symbol] name of image object (as a symbol)
242
+ # @param locator [Hash] { locator_strategy: locator_identifier }
243
+ # @example
244
+ # image :product_image, { xpath: '//XCUIElementTypeImage' }
245
+ #
246
+ def self.image(element_name, locator)
247
+ define_screen_element(element_name, TestCentricity::AppElements::AppImage, locator)
248
+ end
249
+
250
+ # Declare and instantiate a collection of images for this screen object.
251
+ #
252
+ # @param element_hash [Hash] names of images (as symbol) and locator Hash
253
+ # @example
254
+ # images empty_cart_image: { accessibility_id: 'empty_cart' },
255
+ # logo_image: { accessibility_id: 'WebdriverIO logo' }
256
+ #
257
+ def self.images(element_hash)
258
+ element_hash.each do |element_name, locator|
259
+ image(element_name, locator)
260
+ end
261
+ end
262
+
263
+ # Declare and instantiate a single alert UI Element for this screen object.
264
+ #
265
+ # @param element_name [Symbol] name of alert object (as a symbol)
266
+ # @param locator [Hash] { locator_strategy: locator_identifier }
267
+ # @example
268
+ # alert :generic_alert_modal, { id: 'android:id/parentPanel' }
269
+ # alert :generic_alert_modal, { class: 'XCUIElementTypeAlert' }
270
+ #
271
+ def self.alert(element_name, locator)
272
+ define_screen_element(element_name, TestCentricity::AppElements::AppAlert, locator)
273
+ end
274
+
275
+ # Declare and instantiate a collection of alerts for this screen object.
276
+ #
277
+ # @param element_hash [Hash] names of alerts (as symbol) and locator Hash
278
+ # @example
279
+ # alerts grant_modal: { id: 'com.android.permissioncontroller:id/grant_dialog' },
280
+ # alert_modal: { id: 'android:id/parentPanel' }
281
+ #
282
+ def self.alerts(element_hash)
283
+ element_hash.each do |element_name, locator|
284
+ alert(element_name, locator)
285
+ end
286
+ end
287
+
288
+ # Declare and instantiate a single MenuBar object for this screen object.
289
+ #
290
+ # @param element_name [Symbol] name of MenuBar object (as a symbol)
291
+ # @param class_name [Class] Class name of MenuBar object
292
+ # @example
293
+ # menubar :menu_bar, CalculatorMenuBar
294
+ #
295
+ def self.menubar(section_name, obj, locator = 0)
296
+ define_screen_element(section_name, obj, locator)
297
+ end
298
+
299
+ # Instantiate a single ScreenSection object within this ScreenObject.
300
+ #
301
+ # @param section_name [Symbol] name of ScreenSection object (as a symbol)
302
+ # @param class_name [Class] Class name of ScreenSection object
303
+ # @example
304
+ # section :nav_menu, NavMenu
305
+ #
306
+ def self.section(section_name, obj, locator = 0)
307
+ define_screen_element(section_name, obj, locator)
308
+ end
309
+
310
+ # Declare and instantiate a collection of ScreenSection objects for this screen object.
311
+ #
312
+ # @param element_hash [Hash] names of ScreenSections (as symbol) and class name
313
+ # @example
314
+ # sections nav_bar: NavBar,
315
+ # nav_menu: NavMenu
316
+ #
317
+ def self.sections(section_hash)
318
+ section_hash.each do |section_name, class_name|
319
+ section(section_name, class_name)
320
+ end
321
+ end
322
+
323
+ # Does Screen object exists?
324
+ #
325
+ # @return [Boolean]
326
+ # @example
327
+ # home_screen.exists?
328
+ #
329
+ def exists?
330
+ @locator.is_a?(Array) ? tries ||= 2 : tries ||= 1
331
+ if @locator.is_a?(Array)
332
+ loc = @locator[tries - 1]
333
+ find_element(loc.keys[0], loc.values[0])
334
+ else
335
+ find_element(@locator.keys[0], @locator.values[0])
336
+ end
337
+ true
338
+ rescue
339
+ retry if (tries -= 1) > 0
340
+ false
341
+ end
342
+
343
+ # Return window title. For MacOS app testing only. Raises exception if called while testing iOS or Android
344
+ #
345
+ # @return [String]
346
+ # @example
347
+ # calculator_screen.title
348
+ #
349
+ def title
350
+ if Environ.is_macos?
351
+ find_element(@locator.keys[0], @locator.values[0]).title
352
+ else
353
+ raise 'title is not a supported attribute'
354
+ end
355
+ end
356
+
357
+ def identifier
358
+ if Environ.is_macos?
359
+ find_element(@locator.keys[0], @locator.values[0]).identifier
360
+ else
361
+ raise 'identifier is not a supported attribute'
362
+ end
363
+ end
364
+
365
+ # Wait until the Screen object exists, or until the specified wait time has expired. If the wait time is nil, then
366
+ # the wait time will be Environ.default_max_wait_time.
367
+ #
368
+ # @param seconds [Integer or Float] wait time in seconds
369
+ # @example
370
+ # cart_screen.wait_until_exists(15)
371
+ #
372
+ def wait_until_exists(seconds = nil, post_exception = true)
373
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
374
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
375
+ wait.until { exists? }
376
+ rescue
377
+ if post_exception
378
+ raise "Screen object #{self.class.name} not found after #{timeout} seconds" unless exists?
379
+ else
380
+ exists?
381
+ end
382
+ end
383
+
384
+ # Wait until the Screen object is gone, or until the specified wait time has expired. If the wait time is nil, then
385
+ # the wait time will be Environ.default_max_wait_time.
386
+ #
387
+ # @param seconds [Integer or Float] wait time in seconds
388
+ # @example
389
+ # login_screen.wait_until_gone(15)
390
+ #
391
+ def wait_until_gone(seconds = nil, post_exception = true)
392
+ timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
393
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
394
+ wait.until { !exists? }
395
+ rescue
396
+ if post_exception
397
+ raise "Screen object #{self.class.name} remained visible after #{timeout} seconds" if exists?
398
+ else
399
+ exists?
400
+ end
401
+ end
402
+
403
+ def navigate_to; end
404
+
405
+ def verify_screen_ui; end
406
+
407
+ # Verifies that the target screen is displayed, and sets ScreenManager.current_screen to reference the target screen
408
+ # instance.
409
+ #
410
+ def verify_screen_exists
411
+ wait = Selenium::WebDriver::Wait.new(timeout: Environ.default_max_wait_time)
412
+ wait.until { exists? }
413
+ ScreenManager.current_screen = self
414
+ rescue
415
+ raise "Could not find screen_locator for screen object '#{self.class.name}' (#{@locator}) after #{Environ.default_max_wait_time} seconds"
416
+ end
417
+
418
+ # Load the screen using its defined deep_link trait. When testing on physical iOS devices running iOS/iPadOS versions
419
+ # earlier than version 16.4, deep links can only be opened by sending the deeplink URL to the mobile Safari web browser,
420
+ # and then accepting the confirmation modal that pops up. This method handles invoking deeplinks on Android and iOS/iPadOS
421
+ # simulators and physical devices.
422
+ #
423
+ # This method verifies that the target screen is loaded and displayed, and sets ScreenManager.current_screen to reference
424
+ # the target screen instance.
425
+ #
426
+ # @example
427
+ # cart_screen.load_screen
428
+ #
429
+ def load_screen
430
+ # return if target screen is already loaded
431
+ if exists?
432
+ ScreenManager.current_screen = self
433
+ return
434
+ end
435
+
436
+ url = if deep_link.include?("://")
437
+ deep_link
438
+ elsif !Environ.current.deep_link_prefix.blank?
439
+ "#{Environ.current.deep_link_prefix}://#{deep_link}"
440
+ end
441
+
442
+ if Environ.is_android?
443
+ Environ.appium_driver.execute_script('mobile: deepLink', { url: url, package: Environ.current.android_app_id })
444
+ elsif Environ.is_macos?
445
+ Environ.appium_driver.execute_script('macos: deepLink', { url: url, package: Environ.current.android_app_id })
446
+ elsif Environ.is_ios?
447
+ if Environ.is_device? && Environ.device_os_version.to_f < 16.4
448
+ # launch Safari browser on iOS real device if iOS version is below 16.4
449
+ Environ.appium_driver.execute_script('mobile: launchApp', { bundleId: 'com.apple.mobilesafari' })
450
+ unless Environ.appium_driver.is_keyboard_shown
451
+ begin
452
+ # attempt to find and click URL button on iOS 15 Safari browser
453
+ find_element(:accessibility_id, 'TabBarItemTitle').click
454
+ rescue
455
+ # fall back to URL button on iOS 14 Safari browser
456
+ find_element(:xpath, '//XCUIElementTypeButton[@name="URL"]').click
457
+ end
458
+ end
459
+ # enter deep-link URL
460
+ wait_for_object(:xpath, '//XCUIElementTypeTextField[@name="URL"]', 5).send_keys("#{url}\uE007")
461
+ # wait for and accept the popup modal
462
+ wait_for_object(:xpath, '//XCUIElementTypeButton[@name="Open"]', 10).click
463
+ else
464
+ # iOS version is >= 16.4 so directly load screen via deepLink
465
+ Environ.appium_driver.get(url)
466
+ end
467
+ else
468
+ raise "#{Environ.device_os} is not supported"
469
+ end
470
+ verify_screen_exists
471
+ end
472
+
473
+ private
474
+
475
+ def self.define_screen_element(element_name, obj, locator)
476
+ define_method(element_name) do
477
+ ivar_name = "@#{element_name}"
478
+ ivar = instance_variable_get(ivar_name)
479
+ return ivar if ivar
480
+ instance_variable_set(ivar_name, obj.new(element_name, self, locator, :screen))
481
+ end
482
+ end
483
+
484
+ def wait_for_object(find_method, locator, seconds)
485
+ wait = Selenium::WebDriver::Wait.new(timeout: seconds)
486
+ obj = nil
487
+ wait.until do
488
+ obj = find_element(find_method, locator)
489
+ obj.displayed?
490
+ end
491
+ obj
492
+ end
493
+ end
494
+ end