testcentricity_apps 4.0.15 → 4.1.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.
data/README.md CHANGED
@@ -15,12 +15,12 @@ or locally or cloud hosted iOS and Android real devices or simulators.
15
15
  The TestCentricity™ For Apps gem supports automated testing of MacOS desktop apps and native iOS and Android apps running
16
16
  on the following mobile test targets:
17
17
  * locally hosted MacOS desktop apps (using Appium 2.x, the Mac2 driver, and XCode on macOS)
18
- * locally hosted iOS device simulators or physical iOS devices (using Appium, the XCUItest driver, and XCode on macOS)
19
- * locally hosted Android devices or Android Studio virtual device emulators (using Appium, the UIAutomator2 driver, and Android Studio)
18
+ * locally hosted iOS device simulators or physical iOS devices (using Appium 2.x, the XCUItest driver, and XCode on macOS)
19
+ * locally hosted Android devices or Android Studio virtual device emulators (using Appium 2.x, the UIAutomator2 driver, and Android Studio)
20
20
  * cloud hosted iOS or Android physical devices and simulators from the following service:
21
- * [Browserstack](https://www.browserstack.com/list-of-browsers-and-platforms/app_automate)
22
- * [Sauce Labs](https://saucelabs.com/platform/mobile-testing)
23
- * [TestingBot](https://testingbot.com/mobile/realdevicetesting)
21
+ * [Browserstack](https://www.browserstack.com/list-of-browsers-and-platforms/app_automate)
22
+ * [Sauce Labs](https://saucelabs.com/platform/mobile-testing)
23
+ * [TestingBot](https://testingbot.com/mobile/realdevicetesting)
24
24
 
25
25
 
26
26
  ## What's New
@@ -31,13 +31,12 @@ The RubyDocs for this gem can be found [here](https://www.rubydoc.info/gems/test
31
31
 
32
32
  Three example projects that demonstrates the implementation of a screen object model framework using TestCentricity™ For Apps
33
33
  and Cucumber can be found at the following:
34
- * [tc_mac_calculator_demo](https://github.com/TestCentricity/tc_mac_calculator_demo)
35
- * [tc_mobile_react_native_demo](https://github.com/TestCentricity/tc_mobile_react_native_demo)
36
- * [tc_mobile_wdio_demo](https://github.com/TestCentricity/tc_mobile_wdio_demo)
34
+ * [tc_mac_calculator_demo](https://github.com/TestCentricity/tc_mac_calculator_demo)
35
+ * [tc_mobile_react_native_demo](https://github.com/TestCentricity/tc_mobile_react_native_demo)
36
+ * [tc_mobile_wdio_demo](https://github.com/TestCentricity/tc_mobile_wdio_demo)
37
37
 
38
- Refer to [this wiki page](https://github.com/TestCentricity/testcentricity_apps/wiki/XCUItest-driver-bug-impacts-iOS-dialogs-managed-by-com.apple.springboard) for
39
- information on a bug with the latest versions of the XCUItest driver that affects Appium's ability to interact with and
40
- verify iOS system level modal dialogs.
38
+ Refer to [this wiki page](https://github.com/TestCentricity/testcentricity_apps/wiki/XCUItest-driver-bug-impacts-iOS-dialogs-managed-by-com.apple.springboard) for information on a bug with the latest versions of the XCUItest driver that affects Appium's
39
+ ability to interact with and verify iOS system level modal dialogs.
41
40
 
42
41
 
43
42
  ### Which gem should I use?
@@ -55,7 +54,7 @@ verify iOS system level modal dialogs.
55
54
 
56
55
  ## Installation
57
56
 
58
- TestCentricity For Apps requires Ruby 3.0.0 or later. To install the TestCentricity For Apps gem, add this line to your
57
+ TestCentricity For Apps requires Ruby 3.1.0 or later. To install the TestCentricity For Apps gem, add this line to your
59
58
  automation project's `Gemfile`:
60
59
 
61
60
  gem 'testcentricity_apps'
@@ -110,33 +109,33 @@ Your `ScreenObject` class definitions should be contained within individual `.rb
110
109
  folder of your test automation project, where `<platform>` is typically `mac`, `ios`, or `android`. For each screen in your app,
111
110
  you will typically have to define a `ScreenObject` for each platform version of your app.
112
111
 
113
- my_automation_project
114
- ├── config
115
- ├── features
116
- │ ├── step_definitions
117
- │ ├── support
118
- │ │ ├── android
119
- | | | └── screens
120
- │ │ ├── ios
121
- | | | └── screens
122
- │ │ ├── mac
123
- | | | └── screens
124
- │ │ ├── env.rb
125
- │ │ └── hooks.rb
126
- ├── Gemfile
127
- └── README.md
112
+ 📁 my_automation_project
113
+ ├── 📁 config
114
+ ├── 📁 features
115
+ │ ├── 📁 step_definitions
116
+ │ ├── 📁 support
117
+ │ │ ├── 📁 android
118
+ | | | └── 📁 screens
119
+ │ │ ├── 📁 ios
120
+ | | | └── 📁 screens
121
+ │ │ ├── 📁 mac
122
+ | | | └── 📁 screens
123
+ │ │ ├── 📄 env.rb
124
+ │ │ └── 📄 hooks.rb
125
+ ├── 📄 Gemfile
126
+ └── 📄 README.md
128
127
 
129
128
 
130
129
  You define a new `ScreenObject` as shown below:
131
130
  ```ruby
132
131
  class LoginScreen < TestCentricity::ScreenObject
133
132
  end
134
-
135
-
133
+
134
+
136
135
  class ProductsScreen < TestCentricity::ScreenObject
137
136
  end
138
-
139
-
137
+
138
+
140
139
  class CheckoutAddressScreen < TestCentricity::ScreenObject
141
140
  end
142
141
  ```
@@ -169,15 +168,15 @@ You define your screen's **Traits** as shown below:
169
168
  trait(:screen_locator) { { accessibility_id: 'login screen' } }
170
169
  trait(:deep_link) { 'mydemoapprn://login' }
171
170
  end
172
-
173
-
171
+
172
+
174
173
  class ProductsScreen < TestCentricity::ScreenObject
175
174
  trait(:screen_name) { 'Products' }
176
175
  trait(:screen_locator) { { accessibility_id: 'products screen' } }
177
176
  trait(:deep_link) { 'mydemoapprn://store-overview' }
178
177
  end
179
-
180
-
178
+
179
+
181
180
  class CheckoutAddressScreen < TestCentricity::ScreenObject
182
181
  trait(:screen_name) { 'Checkout - Address' }
183
182
  trait(:screen_locator) { { accessibility_id: 'checkout address screen' } }
@@ -194,7 +193,7 @@ buttons, etc. **UI Elements** are added to your `ScreenObject` class definition
194
193
  trait(:screen_name) { 'Login' }
195
194
  trait(:screen_locator) { { accessibility_id: 'login screen' } }
196
195
  trait(:deep_link) { 'mydemoapprn://login' }
197
-
196
+
198
197
  # Login screen UI elements
199
198
  labels username_label: { accessibility_id: 'Username'},
200
199
  password_label: { xpath: '(//XCUIElementTypeStaticText[@name="Password"])[1]'},
@@ -211,7 +210,7 @@ buttons, etc. **UI Elements** are added to your `ScreenObject` class definition
211
210
  trait(:screen_name) { 'Checkout - Address' }
212
211
  trait(:screen_locator) { { accessibility_id: 'checkout address screen' } }
213
212
  trait(:deep_link) { 'mydemoapprn://checkout-address' }
214
-
213
+
215
214
  # Checkout Address screen UI elements
216
215
  textfields fullname_field: { accessibility_id: 'Full Name* input field' },
217
216
  address1_field: { accessibility_id: 'Address Line 1* input field' },
@@ -234,7 +233,7 @@ class definition for interacting with the UI to hide implementation details, as
234
233
  trait(:screen_name) { 'Login' }
235
234
  trait(:screen_locator) { { accessibility_id: 'login screen' } }
236
235
  trait(:deep_link) { 'mydemoapprn://login' }
237
-
236
+
238
237
  # Login screen UI elements
239
238
  labels username_label: { accessibility_id: 'Username'},
240
239
  password_label: { xpath: '(//XCUIElementTypeStaticText[@name="Password"])[1]'},
@@ -244,7 +243,7 @@ class definition for interacting with the UI to hide implementation details, as
244
243
  textfields username_field: { accessibility_id: 'Username input field' },
245
244
  password_field: { accessibility_id: 'Password input field' }
246
245
  button :login_button, { accessibility_id: 'Login button' }
247
-
246
+
248
247
  def verify_screen_ui
249
248
  ui = {
250
249
  header_label => { visible: true, caption: 'Login' },
@@ -256,7 +255,7 @@ class definition for interacting with the UI to hide implementation details, as
256
255
  }
257
256
  verify_ui_states(ui)
258
257
  end
259
-
258
+
260
259
  def login(username, password)
261
260
  fields = {
262
261
  username_field => username,
@@ -265,7 +264,7 @@ class definition for interacting with the UI to hide implementation details, as
265
264
  populate_data_fields(fields)
266
265
  login_button.tap
267
266
  end
268
-
267
+
269
268
  def verify_entry_error(reason)
270
269
  ui = case reason.gsub(/\s+/, '_').downcase.to_sym
271
270
  when :invalid_password, :invalid_user
@@ -326,7 +325,7 @@ object. A `ScreenSection` may contain other `ScreenSection` objects.
326
325
 
327
326
  Below is an example of a footer navigation bar feature that is common to multiple screen -
328
327
 
329
- ![Navigation Footer](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/NavBar1.png "Navigation Footer") ![Navigation Footer](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/NavBar2.png "Navigation Footer")
328
+ ![Navigation Footer](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/NavBar1.png "Navigation Footer") ![Navigation Footer](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/NavBar2.png "Navigation Footer")
330
329
 
331
330
 
332
331
  ### Defining a ScreenSection
@@ -335,24 +334,24 @@ Your `ScreenSection` class definitions should be contained within individual `.r
335
334
  folder of your test automation project, where `<platform>` is typically `mac`, `ios`, or `android`. For each screen section in your
336
335
  app, you will typically have to define a `ScreenSection` for each platform version of your app.
337
336
 
338
- my_automation_project
339
- ├── config
340
- ├── features
341
- │ ├── step_definitions
342
- │ ├── support
343
- │ │ ├── android
344
- | | | ├── screens
345
- | | | └── sections
346
- │ │ ├── ios
347
- | | | ├── screens
348
- | | | └── sections
337
+ 📁 my_automation_project
338
+ ├── 📁 config
339
+ ├── 📁 features
340
+ │ ├── 📁 step_definitions
341
+ │ ├── 📁 support
342
+ │ │ ├── 📁 android
343
+ | | | ├── 📁 screens
344
+ | | | └── 📁 sections
345
+ │ │ ├── 📁 ios
346
+ | | | ├── 📁 screens
347
+ | | | └── 📁 sections
349
348
  │ │ ├── mac
350
- | | | ├── screens
351
- | | | └── sections
352
- │ │ ├── env.rb
353
- │ │ └── hooks.rb
354
- ├── Gemfile
355
- └── README.md
349
+ | | | ├── 📁 screens
350
+ | | | └── 📁 sections
351
+ │ │ ├── 📄 env.rb
352
+ │ │ └── 📄 hooks.rb
353
+ ├── 📄 Gemfile
354
+ └── 📄 README.md
356
355
 
357
356
 
358
357
  You define a new `ScreenSection` as shown below:
@@ -382,7 +381,7 @@ Elements** are added to your `ScreenSection` class definition as shown below:
382
381
  class NavMenu < TestCentricity::ScreenSection
383
382
  trait(:section_name) { 'Nav Menu' }
384
383
  trait(:section_locator) { { xpath: '//XCUIElementTypeScrollView' } }
385
-
384
+
386
385
  # Nav Menu UI elements
387
386
  buttons close_button: { accessibility_id: 'close menu' },
388
387
  webview_button: { accessibility_id: 'menu item webview' },
@@ -407,7 +406,7 @@ You can add methods to your `ScreenSection` class definition, as shown below:
407
406
  class NavMenu < TestCentricity::ScreenSection
408
407
  trait(:section_name) { 'Nav Menu' }
409
408
  trait(:section_locator) { { xpath: '//XCUIElementTypeScrollView' } }
410
-
409
+
411
410
  # Nav Menu UI elements
412
411
  buttons close_button: { accessibility_id: 'close menu' },
413
412
  webview_button: { accessibility_id: 'menu item webview' },
@@ -422,36 +421,36 @@ You can add methods to your `ScreenSection` class definition, as shown below:
422
421
  log_out_button: { accessibility_id: 'menu item log out' },
423
422
  api_calls_button: { accessibility_id: 'menu item api calls' },
424
423
  sauce_video_button: { accessibility_id: 'menu item sauce bot video' }
425
-
424
+
426
425
  def verify_ui
427
426
  ui = {
428
- self => { visible: true },
429
- close_button => { visible: true, enabled: true },
430
- webview_button => { visible: true, enabled: true, caption: 'Webview' },
431
- qr_code_button => { visible: true, enabled: true, caption: 'QR Code Scanner' },
432
- geo_location_button => { visible: true, enabled: true, caption: 'Geo Location' },
433
- drawing_button => { visible: true, enabled: true, caption: 'Drawing' },
434
- report_a_bug_button => { visible: true, enabled: true, caption: 'Report A Bug' },
435
- about_button => { visible: true, enabled: true, caption: 'About' },
436
- reset_app_button => { visible: true, enabled: true, caption: 'Reset App State' },
437
- biometrics_button => { visible: true, enabled: true, caption: 'FaceID' },
438
- log_in_button => { visible: true, enabled: true, caption: 'Log In' },
439
- log_out_button => { visible: true, enabled: true, caption: 'Log Out' },
440
- api_calls_button => { visible: true, enabled: true, caption: 'Api Calls' },
441
- sauce_video_button => { visible: true, enabled: true, caption: 'Sauce Bot Video' }
427
+ self => { visible: true },
428
+ close_button => { visible: true, enabled: true },
429
+ webview_button => { visible: true, enabled: true, caption: 'Webview' },
430
+ qr_code_button => { visible: true, enabled: true, caption: 'QR Code Scanner' },
431
+ geo_location_button => { visible: true, enabled: true, caption: 'Geo Location' },
432
+ drawing_button => { visible: true, enabled: true, caption: 'Drawing' },
433
+ report_a_bug_button => { visible: true, enabled: true, caption: 'Report A Bug' },
434
+ about_button => { visible: true, enabled: true, caption: 'About' },
435
+ reset_app_button => { visible: true, enabled: true, caption: 'Reset App State' },
436
+ biometrics_button => { visible: true, enabled: true, caption: 'FaceID' },
437
+ log_in_button => { visible: true, enabled: true, caption: 'Log In' },
438
+ log_out_button => { visible: true, enabled: true, caption: 'Log Out' },
439
+ api_calls_button => { visible: true, enabled: true, caption: 'Api Calls' },
440
+ sauce_video_button => { visible: true, enabled: true, caption: 'Sauce Bot Video' }
442
441
  }
443
442
  verify_ui_states(ui)
444
443
  end
445
-
444
+
446
445
  def close
447
446
  close_button.click
448
447
  self.wait_until_hidden(3)
449
448
  end
450
-
449
+
451
450
  def verify_closed
452
451
  ui = {
453
- self => { visible: true },
454
- close_button => { visible: false }
452
+ self => { visible: true },
453
+ close_button => { visible: false }
455
454
  }
456
455
  verify_ui_states(ui)
457
456
  end
@@ -526,6 +525,9 @@ Supported `AppUIElement` elementTypes and their declarations have the following
526
525
  switch :switch_name, { locator_strategy: locator_identifier }
527
526
  element :element_name, { locator_strategy: locator_identifier }
528
527
  alert :alert_name, { locator_strategy: locator_identifier }
528
+ menu :menu_name, { locator_strategy: locator_identifier }
529
+ menubar :menubar_name, { locator_strategy: locator_identifier }
530
+ table :table_name, { locator_strategy: locator_identifier }
529
531
  end
530
532
  ```
531
533
  *Multiple element declarations:*
@@ -552,6 +554,8 @@ Supported `AppUIElement` elementTypes and their declarations have the following
552
554
  image_X_name: { locator_strategy: locator_identifier }
553
555
  alerts alert_1_name: { locator_strategy: locator_identifier },
554
556
  alert_X_name: { locator_strategy: locator_identifier }
557
+ tables table_1_name: { locator_strategy: locator_identifier },
558
+ table_X_name: { locator_strategy: locator_identifier }
555
559
  end
556
560
  ```
557
561
  Refer to the Class List documentation for the `ScreenObject` and `ScreenSection` classes for details on the class methods
@@ -650,11 +654,11 @@ properties of multiple UI elements on a `ScreenObject` or `ScreenSection`. The `
650
654
  containing key/hash pairs of UI elements and their properties or attributes to be verified.
651
655
  ```ruby
652
656
  ui = {
653
- object1 => { property: expected_state },
654
- object2 => { property1: expected_state, property2: expected_state },
655
- object3 => { property: expected_state }
656
- }
657
- verify_ui_states(ui)
657
+ object1 => { property: expected_state },
658
+ object2 => { property1: expected_state, property2: expected_state },
659
+ object3 => { property: expected_state }
660
+ }
661
+ verify_ui_states(ui)
658
662
  ```
659
663
  The `verify_ui_states` method automatically scrolls UI elements that are expected to be visible into view. Auto-scrolling
660
664
  only occurs on the vertical axis (down, then up). Setting the `auto_scroll` parameter to `false` prevents automatic scrolling
@@ -713,6 +717,15 @@ The `verify_ui_states` method supports the following property/state pairs:
713
717
  :itemcount Integer
714
718
  :item_data Array of Hash
715
719
 
720
+ **Tables**
721
+
722
+ :rowcount Integer
723
+ :columncount Integer
724
+ :columnheaders Array of String
725
+ :cell Hash
726
+ :row Hash
727
+ :column Hash
728
+
716
729
  #### Comparison States
717
730
 
718
731
  The `verify_ui_states` method supports comparison states using property/comparison state pairs:
@@ -749,42 +762,42 @@ The `verify_ui_states` method also supports I18n string translations using prope
749
762
  The example below depicts the usage of the `verify_ui_states` method to verify that the captions for navigation menu items
750
763
  are correctly translated.
751
764
 
752
- ![Localized UI](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/LocalizedUI.png "Localized UI")
765
+ ![Localized UI](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/LocalizedUI.png "Localized UI")
753
766
  ```ruby
754
767
  def verify_menu
755
768
  ui = {
756
- menu_title => {
757
- visible: true,
758
- caption: { translate: 'NavMenu.title' }
759
- },
760
- recipes_item => {
761
- visible: true,
762
- caption: { translate: 'NavMenu.recipes' }
763
- },
764
- browser_item => {
765
- visible: true,
766
- caption: { translate: 'NavMenu.browser' }
767
- },
768
- groceries_item => {
769
- visible: true,
770
- caption: { translate: 'NavMenu.groceries' }
771
- },
772
- pantry_item => {
773
- visible: true,
774
- caption: { translate: 'NavMenu.pantry' }
775
- },
776
- meals_item => {
777
- visible: true,
778
- caption: { translate: 'NavMenu.meals' }
779
- },
780
- menus_item => {
781
- visible: true,
782
- caption: { translate: 'NavMenu.menus' }
783
- },
784
- settings_item => {
785
- visible: true,
786
- caption: { translate: 'NavMenu.settings' }
787
- }
769
+ menu_title => {
770
+ visible: true,
771
+ caption: { translate: 'NavMenu.title' }
772
+ },
773
+ recipes_item => {
774
+ visible: true,
775
+ caption: { translate: 'NavMenu.recipes' }
776
+ },
777
+ browser_item => {
778
+ visible: true,
779
+ caption: { translate: 'NavMenu.browser' }
780
+ },
781
+ groceries_item => {
782
+ visible: true,
783
+ caption: { translate: 'NavMenu.groceries' }
784
+ },
785
+ pantry_item => {
786
+ visible: true,
787
+ caption: { translate: 'NavMenu.pantry' }
788
+ },
789
+ meals_item => {
790
+ visible: true,
791
+ caption: { translate: 'NavMenu.meals' }
792
+ },
793
+ menus_item => {
794
+ visible: true,
795
+ caption: { translate: 'NavMenu.menus' }
796
+ },
797
+ settings_item => {
798
+ visible: true,
799
+ caption: { translate: 'NavMenu.settings' }
800
+ }
788
801
  }
789
802
  verify_ui_states(ui)
790
803
  end
@@ -838,9 +851,10 @@ Each supported language/locale combination has a corresponding `.yml` file. I18n
838
851
  | Language (Country) | File name |
839
852
  |-----------------------|-----------|
840
853
  | English | en.yml |
854
+ | English (Australia) | en-AU.yml |
841
855
  | English (Canada) | en-CA.yml |
842
- | French (Canada) | fr-CA.yml |
843
856
  | French | fr.yml |
857
+ | French (Canada) | fr-CA.yml |
844
858
  | Spanish | es.yml |
845
859
  | German | de.yml |
846
860
  | Portuguese (Brazil) | pt-BR.yml |
@@ -848,19 +862,23 @@ Each supported language/locale combination has a corresponding `.yml` file. I18n
848
862
 
849
863
  Baseline translation strings are stored in `.yml` files in the `config/locales/` folder.
850
864
 
851
- my_automation_project
852
- ├── config
853
- │ ├── locales
854
- │ │ ├── en.yml
855
- │ │ ├── es.yml
856
- │ │ ├── fr.yml
857
- │ │ ├── fr-CA.yml
858
- │ │ └── en-AU.yml
859
- │ ├── test_data
860
- └── cucumber.yml
861
- ├── features
862
- ├── Gemfile
863
- └── README.md
865
+ 📁 my_automation_project/
866
+ ├── 📁 config/
867
+ │ ├── 📁 locales/
868
+ │ │ ├── 📄 en.yml
869
+ │ │ ├── 📄 en-AU.yml
870
+ │ │ ├── 📄 es.yml
871
+ │ │ ├── 📄 de.yml
872
+ │ │ ├── 📄 fr.yml
873
+ ├── 📄 fr-CA.yml
874
+ │ ├── 📄 pt-BR.yml
875
+ │ │ └── 📄 pt-PT.yml
876
+ ├── 📁 test_data/
877
+ └── 📄 cucumber.yml
878
+ ├── 📁 downloads/
879
+ ├── 📁 features/
880
+ ├── 📄 Gemfile
881
+ └── 📄 README.md
864
882
 
865
883
 
866
884
  ### Working With Custom AppUIElements
@@ -872,13 +890,13 @@ be different for iOS vs. Android mobile platforms. Below is an example of the ve
872
890
  for a cross-platform application implemented using React Native (iOS version on the left, Android version on the right).
873
891
  Each ListView contains 30 items:
874
892
 
875
- ![Vertical Scrolling ListView](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/ListViews.png "Vertical Scrolling ListViews")
893
+ ![Vertical Scrolling ListView](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/ListViews.png "Vertical Scrolling ListViews")
876
894
 
877
895
  While the iOS and Android ListViews appear to be identical in the app, performing an inspection of each application's GUI
878
896
  using Appium Inspector reveals differences in the object hierarchy as depicted below (iOS version on left, Android version
879
897
  on the right):
880
898
 
881
- ![Vertical Scrolling ListView Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/ListHeirarchy.png "Vertical Scrolling ListView Hierarchy")
899
+ ![Vertical Scrolling ListView Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/ListHeirarchy.png "Vertical Scrolling ListView Hierarchy")
882
900
 
883
901
  The inspection of the ListView object hierarchy reveals that for the iOS version of the app, list items are made up of
884
902
  `XCUIElementTypeOther` objects, and that for the Android version of the app, list items are made up of `android.view.ViewGroup`
@@ -905,10 +923,10 @@ iOS Cloud List `ScreenObject`
905
923
  class CloudListScreen < TestCentricity::ScreenObject
906
924
  trait(:screen_name) { 'Cloud List' }
907
925
  trait(:screen_locator) { { class_chain: '**/XCUIElementTypeWindow/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther' } }
908
-
926
+
909
927
  # Cloud List screen UI elements
910
928
  list :cloud_list, { class_chain: '**/XCUIElementTypeScrollView/XCUIElementTypeOther' }
911
-
929
+
912
930
  def initialize
913
931
  super
914
932
  # define the list item element for the Cloud list object
@@ -923,10 +941,10 @@ Android CloudListScreen `ScreenObject`
923
941
  class CloudListScreen < TestCentricity::ScreenObject
924
942
  trait(:screen_name) { 'Cloud List' }
925
943
  trait(:screen_locator) { { xpath: '//android.widget.FrameLayout[@resource-id="android:id/content"]/android.view.ViewGroup' } }
926
-
944
+
927
945
  # Cloud List screen UI elements
928
946
  list :cloud_list, { xpath: '//android.widget.ScrollView/android.view.ViewGroup' }
929
-
947
+
930
948
  def initialize
931
949
  super
932
950
  # define the list item element for the Cloud list object
@@ -942,13 +960,13 @@ Android CloudListScreen `ScreenObject`
942
960
  Below is an example of a horizontal scrolling "Carousel" style ListView implementations on the Swipe screen of a cross-platform
943
961
  application. Each ListView contains 6 list items.
944
962
 
945
- ![Horizontal Scrolling Carousel ListView](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/Carousel.png "Horizontal Scrolling Carousel ListViews")
963
+ ![Horizontal Scrolling Carousel ListView](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/Carousel.png "Horizontal Scrolling Carousel ListViews")
946
964
 
947
965
  While the iOS and Android ListViews appear to be identical in the app, performing an inspection of each application's GUI
948
966
  using Appium Inspector reveals differences in the object hierarchy as depicted below (iOS version on left, Android version
949
967
  on the right):
950
968
 
951
- ![Horizontal Scrolling Carousel Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/CarouselHierarchy.png "Horizontal Scrolling Carousel Hierarchy")
969
+ ![Horizontal Scrolling Carousel Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/CarouselHierarchy.png "Horizontal Scrolling Carousel Hierarchy")
952
970
 
953
971
  As in the previous example for the vertical scrolling ListView, the inspection of the Carousel ListView object hierarchy
954
972
  reveals that for the iOS version of the app, list items are again made up of `XCUIElementTypeOther` objects, and that for
@@ -966,10 +984,10 @@ iOS Swipe `ScreenObject`
966
984
  class SwipeScreen < TestCentricity::ScreenObject
967
985
  trait(:screen_name) { 'Swipe' }
968
986
  trait(:screen_locator) { { accessibility_id: 'Swipe-screen' } }
969
-
987
+
970
988
  # Swipe screen UI elements
971
989
  list :carousel_list, { accessibility_id: 'Carousel' }
972
-
990
+
973
991
  def initialize
974
992
  super
975
993
  # define the list item element for the Carousel list object
@@ -987,10 +1005,10 @@ Android Swipe `ScreenObject`
987
1005
  class SwipeScreen < TestCentricity::ScreenObject
988
1006
  trait(:screen_name) { 'Swipe' }
989
1007
  trait(:screen_locator) { { accessibility_id: 'Swipe-screen' } }
990
-
1008
+
991
1009
  # Swipe screen UI elements
992
1010
  list :carousel_list, { accessibility_id: 'Carousel' }
993
-
1011
+
994
1012
  def initialize
995
1013
  super
996
1014
  # define the list item element for the Carousel list object
@@ -1000,7 +1018,7 @@ Android Swipe `ScreenObject`
1000
1018
  }
1001
1019
  carousel_list.define_list_elements(list_spec)
1002
1020
  end
1003
- end
1021
+ end
1004
1022
  ```
1005
1023
 
1006
1024
 
@@ -1009,12 +1027,12 @@ Android Swipe `ScreenObject`
1009
1027
  Below is an example of a PickerWheel (iOS) and Popup (Android) style ListView implementations on the Form Components screen
1010
1028
  of a cross-platform application.
1011
1029
 
1012
- ![PickerWheel and Popup ListViews](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/Popup_Picker.png "PickerWheel and Popup ListViews")
1030
+ ![PickerWheel and Popup ListViews](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/Popup_Picker.png "PickerWheel and Popup ListViews")
1013
1031
 
1014
1032
  Performing an inspection of each application's GUI using Appium Inspector reveals differences in the object hierarchy as
1015
1033
  depicted below (iOS version on left, Android version on the right):
1016
1034
 
1017
- ![PickerWheel and Popup ListView Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/PopupHeirarchy.png "PickerWheel and Popup ListView Hierarchy")
1035
+ ![PickerWheel and Popup ListView Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/PopupHeirarchy.png "PickerWheel and Popup ListView Hierarchy")
1018
1036
 
1019
1037
  The inspection of the PickerWheel and Popup ListView object hierarchies reveals that for the iOS version of the app, list
1020
1038
  items are again made up of `XCUIElementTypeOther` objects, and that for the Android version of the app, list items are made
@@ -1038,10 +1056,10 @@ Android FormScreen `ScreenObject`
1038
1056
  class FormScreen < TestCentricity::ScreenObject
1039
1057
  trait(:screen_name) { 'Form' }
1040
1058
  trait(:screen_locator) { { accessibility_id: 'Forms-screen' } }
1041
-
1059
+
1042
1060
  # Form screen UI elements
1043
1061
  list :drop_down_menu, { id: 'com.wdiodemoapp:id/select_dialog_listview' }
1044
-
1062
+
1045
1063
  def initialize
1046
1064
  super
1047
1065
  # define the list item element for the drop-down list object
@@ -1051,6 +1069,100 @@ Android FormScreen `ScreenObject`
1051
1069
  end
1052
1070
  ```
1053
1071
 
1072
+ #### Table AppUIElements
1073
+
1074
+ The basic iOS or MacOS `AppTable` element is typically composed of the parent `XCUIElementTypeTable` object, containing
1075
+ one or more rows (`XCUIElementTypeTableRow`), and containing one or more columns (`XCUIElementTypeTableColumn`). Tables
1076
+ can also include an optional header (`XCUIElementTypeGroup`) containing one or more header cells (`XCUIElementTypeButton`).
1077
+
1078
+ Table cells are typically embedded in each parent row object, and are implemented using a parent `XCUIElementTypeCell`
1079
+ with a child `XCUIElementTypeStaticText` object that provides access to the text content of the table cell.
1080
+
1081
+ As such, when an iOS or MacOS `AppTable` element is instantiated in a `ScreenObject` or `ScreenSection`, the table's component
1082
+ objects and their locator strategies are defined as follows:
1083
+ ```ruby
1084
+ {
1085
+ table_row: { class: 'XCUIElementTypeTableRow' },
1086
+ table_column: { class: 'XCUIElementTypeTableColumn' },
1087
+ table_cell: { class_chain: '**/XCUIElementTypeCell/XCUIElementTypeStaticText' },
1088
+ table_header: { class: 'XCUIElementTypeGroup' },
1089
+ table_header_cell: { class: 'XCUIElementTypeButton' }
1090
+ }
1091
+ ```
1092
+
1093
+ An example of this table implementation in the MacOS Shortcuts app, and its object hierarchy as viewed by Appium Inspector,
1094
+ is depicted below:
1095
+
1096
+ ![Shortcuts Table](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/Shortcuts.png "Shortcuts Table")
1097
+
1098
+ The above table object hierarchy for the Shortcuts table shows that access to the text content of the table's cells will
1099
+ fail using the typical implementation of `XCUIElementTypeCell/XCUIElementTypeStaticText` cell objects. This is due to the
1100
+ presence of an `XCUIElementTypeGroup` object between the `XCUIElementTypeCell` parent object and the `XCUIElementTypeStaticText`
1101
+ cell text caption object.
1102
+
1103
+ The `AppTable.define_table_elements` method provides a means of specifying the various elements that make up the key
1104
+ components of an `AppTable` object, or for overriding the default component locator strategy. The method accepts a hash
1105
+ of table component designators (key) and a locator strategy (value) for identifying the component in the `AppTable` object
1106
+ hierarchy.
1107
+
1108
+ Valid table component designators are:
1109
+ * `table_row:`
1110
+ * `table_column:`
1111
+ * `table_cell:`
1112
+ * `table_header:`
1113
+ * `table_header_cell:`
1114
+
1115
+ Valid component `locator_strategy` are:
1116
+ * `class:`
1117
+ * `xpath:`
1118
+ * `predicate:` (MacOS and iOS only)
1119
+ * `class_chain:` (MacOS and iOS only)
1120
+
1121
+ If you were developing automated tests for the MacOS Shortcuts app, your `ShortcutsAppScreen` screen object's `initialize`
1122
+ method would use the `AppTable.define_table_elements` method to define the locator strategy for the `table_cell` component.
1123
+ ```ruby
1124
+ class ShortcutsAppScreen < TestCentricity::ScreenObject
1125
+ trait(:screen_name) { 'Shortcuts' }
1126
+ trait(:screen_locator) { { class_chain: '**/XCUIElementTypeWindow' } }
1127
+
1128
+ # Shortcuts screen UI elements
1129
+ tables sidebar_table: { predicate: 'identifier == "library.sidebar"' },
1130
+ view_table: { predicate: 'identifier == "view.library.table"' }
1131
+
1132
+ def initialize
1133
+ super
1134
+ # define the cell element for the Shortcuts table object
1135
+ table_spec = { table_cell: { class_chain: '**/XCUIElementTypeCell/XCUIElementTypeGroup/XCUIElementTypeStaticText' } }
1136
+ view_table.define_table_elements(table_spec)
1137
+ end
1138
+ end
1139
+ ```
1140
+
1141
+ Another `AppTable` implementation using elements other than the default `AppTable` components can be found in the MacOS
1142
+ Activity Monitor app (see below). An inspection of the Activity Monitor's table object hierarchy for the app reveals that
1143
+ it is an `XCUIElementTypeOutline` object comprised of `XCUIElementTypeOutlineRow` elements representing the table's rows.
1144
+
1145
+ ![Activity Monitor Table](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/ActivityMonitor.png "Activity Monitor Table")
1146
+
1147
+ The `ActivityMonitorScreen` screen object's `initialize` method in the code snippet below demonstrates the use of the
1148
+ `AppTable.define_table_elements` method to define the locator strategy for the `table_row` component.
1149
+ ```ruby
1150
+ class ActivityMonitorScreen < TestCentricity::ScreenObject
1151
+ trait(:screen_name) { 'Activity Monitor' }
1152
+ trait(:screen_locator) { { class_chain: '**/XCUIElementTypeWindow' } }
1153
+
1154
+ # Shortcuts screen UI elements
1155
+ table :process_table, { class_chain: '**/XCUIElementTypeScrollView/XCUIElementTypeOutline' }
1156
+
1157
+ def initialize
1158
+ super
1159
+ # define the row element for the Activity Monitor table object
1160
+ table_spec = { table_row: { class: 'XCUIElementTypeOutlineRow' } }
1161
+ process_table.define_table_elements(table_spec)
1162
+ end
1163
+ end
1164
+ ```
1165
+
1054
1166
 
1055
1167
  ---
1056
1168
  ## MacOS Application Menu Bar and Menus
@@ -1077,6 +1189,20 @@ You define a new `MenuBar` as shown below:
1077
1189
  end
1078
1190
  ```
1079
1191
 
1192
+ ### Adding a MenuBar to your App's Primary ScreenObject
1193
+
1194
+ You add a `MenuBar` to your app's primary `ScreenObject` as shown below:
1195
+ ```ruby
1196
+ class CalculatorAppScreen < TestCentricity::ScreenObject
1197
+ # Calculator App screen UI elements
1198
+ menubar :calc_menu_bar, CalculatorMenuBar
1199
+ end
1200
+ ```
1201
+ Once your `ScreenObject` has been instantiated, you can call its `MenuBar` methods as shown below:
1202
+ ```ruby
1203
+ num_menus = calculator_screen.calc_menu_bar.get_item_count
1204
+ ```
1205
+
1080
1206
  ### Adding Menus to your MenuBar
1081
1207
 
1082
1208
  A `MenuBar` is typically made up of one or more `Menu` objects, which are added to your `MenuBar` class definition as shown
@@ -1094,7 +1220,7 @@ below:
1094
1220
  speech_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[7]' },
1095
1221
  window_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[8]' },
1096
1222
  help_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[9]' }
1097
- end
1223
+ end
1098
1224
  ```
1099
1225
 
1100
1226
  ### Adding Methods to your MenuBar
@@ -1113,7 +1239,7 @@ You can add methods to your `MenuBar` class definition, as shown below:
1113
1239
  speech_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[7]' },
1114
1240
  window_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[8]' },
1115
1241
  help_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[9]' }
1116
-
1242
+
1117
1243
  def choose_menu_item(menu, item, method = :mouse)
1118
1244
  menu_map = {
1119
1245
  calc: calc_menu,
@@ -1130,7 +1256,7 @@ You can add methods to your `MenuBar` class definition, as shown below:
1130
1256
  raise "#{menu} is not a supported menu" if menu_obj.nil?
1131
1257
  menu_obj.choose_menu_item(item, method)
1132
1258
  end
1133
-
1259
+
1134
1260
  def verify_menu_bar
1135
1261
  ui = {
1136
1262
  self => {
@@ -1181,11 +1307,11 @@ the `initialize` method of your app's `MenuBar` control.
1181
1307
  The code snippet below demonstrate the use of the `AppMenu.define_menu_elements` method in the `CalculatorMenuBar` object's
1182
1308
  `initialize` method to define the keyboard shortcut mapping for 4 of the menu items in the **View** menu and 1 of the menu
1183
1309
  items in the **Window** menu of the MacOS Calculator app. Keyboard shortcuts are assigned to the **View** menu items by
1184
- index (menu items 1,2,3, and 7) and to the **Window** menu by menu item caption (menu item *Show Paper Tape*).
1310
+ index (menu items 1, 2, 3, and 7) and to the **Window** menu by menu item caption (menu item *Show Paper Tape*).
1185
1311
 
1186
1312
  ```ruby
1187
1313
  class CalculatorMenuBar < TestCentricity::MenuBar
1188
-
1314
+
1189
1315
  def initialize(name, parent, locator, context)
1190
1316
  super
1191
1317
  # define key map for View menu
@@ -1219,19 +1345,26 @@ key. TestCentricity and XCTest defines the following possible bitmasks for modif
1219
1345
  ```
1220
1346
  Refer to [this page](https://github.com/appium/appium-mac2-driver?tab=readme-ov-file#macos-keys) for more information on MacOS keyboard `modifierFlags`.
1221
1347
 
1222
- ### Adding a MenuBar to your App's Primary ScreenObject
1348
+ ### Adding Popup Menus to your ScreenObject or ScreenSection
1223
1349
 
1224
- You add a `MenuBar` to your app's primary `ScreenObject` as shown below:
1350
+ A MocOS desktop app may have one or more popup menus that can appear on a `ScreenObject` or `ScreenSection`. Below is an
1351
+ example of two different popup menus that can be invoked on the MacOS Shortcuts app - one on sidebar list items, and the
1352
+ other on Shortcuts table items.
1353
+
1354
+ ![Popup Menus](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/PopupMenus.jpg "Popup Menus")
1355
+
1356
+ It is typically not necessary to define more than one popup menu object in your `ScreenObject` or `ScreenSection` class
1357
+ definition, even if there are multiple popups that can be interacted with. Since only one popup can be visible at a time,
1358
+ you only need to define a single popup menu object.
1359
+
1360
+ Popup menus are added to your `ScreenObject` or `ScreenSection` class definition as shown below:
1225
1361
  ```ruby
1226
- class CalculatorAppScreen < TestCentricity::ScreenObject
1227
- # Calculator App screen UI elements
1228
- menubar :calc_menu_bar, CalculatorMenuBar
1362
+ class ShortcutsAppScreen < TestCentricity::ScreenObject
1363
+ # Shortcuts App screen UI elements
1364
+ menu :popup_menu, { class_chain: '**/XCUIElementTypeTable/XCUIElementTypeMenu' }
1365
+ menubar :shortcuts_menu_bar, ShortcutsMenuBar
1229
1366
  end
1230
1367
  ```
1231
- Once your `ScreenObject` has been instantiated, you can call its `MenuBar` methods as shown below:
1232
- ```ruby
1233
- num_menus = calculator_screen.calc_menu_bar.get_item_count
1234
- ```
1235
1368
 
1236
1369
 
1237
1370
  ---
@@ -1287,7 +1420,7 @@ scenario is displayed below:
1287
1420
  Given I am on the Products screen
1288
1421
  When I tap the <screen_name> navigation menu item
1289
1422
  Then I expect the <screen_name> screen to be correctly displayed
1290
-
1423
+
1291
1424
  Examples:
1292
1425
  |screen_name |
1293
1426
  |Registration |
@@ -1490,13 +1623,13 @@ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml
1490
1623
  ##### Local iOS Simulators or Physical Devices using the `options` Hash
1491
1624
 
1492
1625
  When using the `options` hash, the following options and capabilities must be specified:
1493
- - `driver:` must be set to `:appium`
1494
- - `device_type:` must be set to `:tablet` or `:phone`
1495
- - `platformName:` must be set to `ios` in the `capabilities:` hash
1496
- - `'appium:automationName':` must be set to `xcuitest` in the `capabilities:` hash
1497
- - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
1498
- - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
1499
- - `'appium:app'`: must be set to path where iOS app can be accessed and loaded
1626
+ - `driver:` must be set to `:appium`
1627
+ - `device_type:` must be set to `:tablet` or `:phone`
1628
+ - `platformName:` must be set to `ios` in the `capabilities:` hash
1629
+ - `'appium:automationName':` must be set to `xcuitest` in the `capabilities:` hash
1630
+ - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
1631
+ - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
1632
+ - `'appium:app'`: must be set to path where iOS app can be accessed and loaded
1500
1633
 
1501
1634
  ```ruby
1502
1635
  options = {
@@ -1525,21 +1658,21 @@ Below is an example of an `options` hash for specifying a connection to a locall
1525
1658
  simulator. The `options` hash includes options for specifying the driver name, global driver scope, and setting the simulated
1526
1659
  device orientation to portrait mode.
1527
1660
  ```ruby
1528
- options = {
1529
- driver: :appium,
1530
- device_type: :tablet,
1531
- driver_name: :my_custom_ipad_driver,
1532
- global_driver: true,
1533
- capabilities: {
1534
- platformName: :ios,
1535
- 'appium:platformVersion': '15.4',
1536
- 'appium:deviceName': 'iPad Pro (12.9-inch) (5th generation)',
1537
- 'appium:automationName': 'XCUITest',
1538
- 'appium:orientation': 'PORTRAIT',
1539
- 'appium:app': Environ.current.ios_app_path
1540
- }
1661
+ options = {
1662
+ driver: :appium,
1663
+ device_type: :tablet,
1664
+ driver_name: :my_custom_ipad_driver,
1665
+ global_driver: true,
1666
+ capabilities: {
1667
+ platformName: :ios,
1668
+ 'appium:platformVersion': '15.4',
1669
+ 'appium:deviceName': 'iPad Pro (12.9-inch) (5th generation)',
1670
+ 'appium:automationName': 'XCUITest',
1671
+ 'appium:orientation': 'PORTRAIT',
1672
+ 'appium:app': Environ.current.ios_app_path
1541
1673
  }
1542
- AppiumConnect.initialize_appium(options)
1674
+ }
1675
+ AppiumConnect.initialize_appium(options)
1543
1676
  ```
1544
1677
 
1545
1678
  #### Connecting to Locally Hosted Android Simulators or Physical Devices
@@ -1577,13 +1710,13 @@ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml
1577
1710
  ##### Local Android Simulators or Physical Devices using the `options` Hash
1578
1711
 
1579
1712
  When using the `options` hash, the following options and capabilities must be specified:
1580
- - `driver:` must be set to `:appium`
1581
- - `device_type:` must be set to `:tablet` or `:phone`
1582
- - `platformName:` must be set to `Android` in the `capabilities:` hash
1583
- - `'appium:automationName':` must be set to `UiAutomator2` in the `capabilities:` hash
1584
- - `'appium:platformVersion':` must be set to the version of Android on the simulator or physical device
1585
- - `'appium:deviceName':` must be set to the Android Virtual Device ID
1586
- - `'appium:app'`: must be set to path where Android `.apk` file can be accessed and loaded
1713
+ - `driver:` must be set to `:appium`
1714
+ - `device_type:` must be set to `:tablet` or `:phone`
1715
+ - `platformName:` must be set to `Android` in the `capabilities:` hash
1716
+ - `'appium:automationName':` must be set to `UiAutomator2` in the `capabilities:` hash
1717
+ - `'appium:platformVersion':` must be set to the version of Android on the simulator or physical device
1718
+ - `'appium:deviceName':` must be set to the Android Virtual Device ID
1719
+ - `'appium:app'`: must be set to path where Android `.apk` file can be accessed and loaded
1587
1720
 
1588
1721
  ```ruby
1589
1722
  options = {
@@ -1606,7 +1739,7 @@ When using the `options` hash, the following options and capabilities must be sp
1606
1739
  >
1607
1740
  > ℹ️ If an `endpoint:` is not specified in the `options` hash, then the default remote endpoint URL of ``http://127.0.0.1:4723/wd/hub``
1608
1741
  will be used.
1609
- >
1742
+ >
1610
1743
  > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
1611
1744
 
1612
1745
 
@@ -1645,7 +1778,7 @@ tests, place the code shown below in your `hooks.rb` file.
1645
1778
  $server.start
1646
1779
  end
1647
1780
  end
1648
-
1781
+
1649
1782
  AfterAll do
1650
1783
  # close Appium driver
1651
1784
  TestCentricity::AppiumConnect.quit_driver
@@ -1682,7 +1815,7 @@ body of an example group:
1682
1815
  $server = TestCentricity::AppiumServer.new
1683
1816
  $server.start
1684
1817
  end
1685
-
1818
+
1686
1819
  after(:context) do
1687
1820
  # terminate Appium Server after all of the examples in this group
1688
1821
  $server.stop if Environ.driver == :appium && $server.running?
@@ -1709,9 +1842,9 @@ devices. BrowserStack uses only real physical devices - simulators are not avail
1709
1842
 
1710
1843
  Refer to the following pages for information on uploading your iOS `.ipa` or Android `.apk` app files to the BrowserStack
1711
1844
  servers:
1712
- - [Upload apps from filesystem](https://www.browserstack.com/docs/app-automate/appium/upload-app-from-filesystem)
1713
- - [Upload apps using public URL](https://www.browserstack.com/docs/app-automate/appium/upload-app-using-public-url)
1714
- - [Define custom ID for app](https://www.browserstack.com/docs/app-automate/appium/upload-app-define-custom-id)
1845
+ - [Upload apps from filesystem](https://www.browserstack.com/docs/app-automate/appium/upload-app-from-filesystem)
1846
+ - [Upload apps using public URL](https://www.browserstack.com/docs/app-automate/appium/upload-app-using-public-url)
1847
+ - [Define custom ID for app](https://www.browserstack.com/docs/app-automate/appium/upload-app-define-custom-id)
1715
1848
 
1716
1849
  The preferred method of uploading an app to BrowserStack is to define a custom test ID for your apps to avoid having to
1717
1850
  modify your test configuration data with a new `app_url` after every app upload. Use the same custom test ID every time
@@ -1755,13 +1888,13 @@ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml
1755
1888
  ##### BrowserStack Mobile Devices using the `options` Hash
1756
1889
 
1757
1890
  When using the `options` hash, the following options and capabilities must be specified:
1758
- - `driver:` must be set to `:browserstack`
1759
- - `device_type:` must be set to `:tablet` or `:phone`
1760
- - `platformName:` must be set to `ios` or `android` in the `capabilities:` hash
1761
- - `'appium:automationName':` must be set to to `XCUITest` for iOS or `UiAutomator2` for Android in the `capabilities:` hash
1762
- - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
1763
- - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
1764
- - `'appium:app'`: must be set to URL or custom test ID of uploaded iOS `.ipa` or Android `.apk` file
1891
+ - `driver:` must be set to `:browserstack`
1892
+ - `device_type:` must be set to `:tablet` or `:phone`
1893
+ - `platformName:` must be set to `ios` or `android` in the `capabilities:` hash
1894
+ - `'appium:automationName':` must be set to to `XCUITest` for iOS or `UiAutomator2` for Android in the `capabilities:` hash
1895
+ - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
1896
+ - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
1897
+ - `'appium:app'`: must be set to URL or custom test ID of uploaded iOS `.ipa` or Android `.apk` file
1765
1898
 
1766
1899
  ```ruby
1767
1900
  options = {
@@ -1963,7 +2096,7 @@ Refer to the following pages for information on uploading your iOS `.ipa` or `.a
1963
2096
  Sauce Labs servers:
1964
2097
  - [Mobile App Storage](https://docs.saucelabs.com/mobile-apps/app-storage/)
1965
2098
 
1966
- The TestCentricity For Apps gem does not currently support automatic upload of app files to Sauce Labs servers. Uploading
2099
+ The TestCentricity For Apps gem does not currently support automatic upload of app files to Sauce Labs servers. Uploading
1967
2100
  will have to be performed manually or via your CI workflow. If you have not specified a custom test ID for your apps, your
1968
2101
  tests will most likely fail as a new `app_url` will be generated, and you will have to update your test configuration data
1969
2102
  to use the new `app_url`. If you have specified a custom test ID for your apps, your tests should be able to run without
@@ -2042,16 +2175,16 @@ on other cloud hosting services that are currently not supported. You must call
2042
2175
  with an `options` hash - Environment Variables cannot be used to specify a user-defined custom Appium driver instance.
2043
2176
 
2044
2177
  Prior to calling the `AppiumConnect.initialize_appium` method, you must set the following `Environ` attributes:
2045
- - `Environ.platform` set to `:mobile`
2046
- - `Environ.device_os` to either `:ios` or `:android`
2047
- - `Environ.device` to either `:simulator` or `:device`, dependent on whether the target mobile platform is a real device
2048
- or simulator.
2049
- - `Environ.device_name` set to device name specified by hosting service
2178
+ - `Environ.platform` set to `:mobile`
2179
+ - `Environ.device_os` to either `:ios` or `:android`
2180
+ - `Environ.device` to either `:simulator` or `:device`, dependent on whether the target mobile platform is a real device
2181
+ or simulator.
2182
+ - `Environ.device_name` set to device name specified by hosting service
2050
2183
 
2051
2184
  The following options and capabilities must be specified:
2052
- - `driver:` must be set to `:custom`
2053
- - `device_type:` must be set to `:tablet` or `:phone`
2054
- - `endpoint:` must be set to the endpoint URL configuration specified by the hosting service
2185
+ - `driver:` must be set to `:custom`
2186
+ - `device_type:` must be set to `:tablet` or `:phone`
2187
+ - `endpoint:` must be set to the endpoint URL configuration specified by the hosting service
2055
2188
 
2056
2189
  All other required capabilities specified by the hosting service configuration documentation should be included in the
2057
2190
  `capabilities:` hash.
@@ -2092,11 +2225,9 @@ and authorization code for the cloud service(s) that you intend to connect with.
2092
2225
  > ⚠️ Cloud service credentials should not be stored as text in your `cucumber.yml` file where it can be exposed by anyone
2093
2226
  with access to your version control system.
2094
2227
 
2095
-
2096
2228
  #==============
2097
2229
  # conditionally load Screen Object implementations based on which target platform we're running on
2098
2230
  #==============
2099
-
2100
2231
  ios: PLATFORM=ios --tags @ios -r features/support/ios -e features/support/android
2101
2232
  android: PLATFORM=android --tags @android -r features/support/android -e features/support/ios
2102
2233
 
@@ -2104,7 +2235,6 @@ with access to your version control system.
2104
2235
  #==============
2105
2236
  # profiles for mobile device screen orientation
2106
2237
  #==============
2107
-
2108
2238
  landscape: ORIENTATION=landscape
2109
2239
  portrait: ORIENTATION=portrait
2110
2240
 
@@ -2121,7 +2251,6 @@ with access to your version control system.
2121
2251
  # profiles for native iOS apps hosted within XCode iOS simulators
2122
2252
  # NOTE: Requires installation of XCode, iOS version specific target simulators, and Appium
2123
2253
  #==============
2124
-
2125
2254
  appium_ios: DRIVER=appium --profile ios AUTOMATION_ENGINE=XCUITest APP_PLATFORM_NAME="iOS" NEW_COMMAND_TIMEOUT="30" <%= mobile %>
2126
2255
  app_ios_14: --profile appium_ios APP_VERSION="14.5"
2127
2256
  app_ios_15: --profile appium_ios APP_VERSION="15.4"
@@ -2136,7 +2265,6 @@ with access to your version control system.
2136
2265
  # profiles for native Android apps hosted within Android Studio Android Virtual Device emulators
2137
2266
  # NOTE: Requires installation of Android Studio, Android version specific virtual device simulators, and Appium
2138
2267
  #==============
2139
-
2140
2268
  appium_android: DRIVER=appium --profile android AUTOMATION_ENGINE=UiAutomator2 APP_PLATFORM_NAME="Android" <%= mobile %>
2141
2269
  app_android_12: --profile appium_android APP_VERSION="12.0"
2142
2270
  pixel_5_api31_sim: --profile app_android_12 DEVICE_TYPE=phone APP_DEVICE="Pixel_5_API_31"
@@ -2147,7 +2275,6 @@ with access to your version control system.
2147
2275
  # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2148
2276
  # to your version control system
2149
2277
  #==============
2150
-
2151
2278
  browserstack: DRIVER=browserstack BS_USERNAME="<INSERT USER NAME HERE>" BS_AUTHKEY="<INSERT PASSWORD HERE>" TEST_CONTEXT="TestCentricity"
2152
2279
 
2153
2280
  # BrowserStack iOS real device native app profiles
@@ -2166,7 +2293,6 @@ with access to your version control system.
2166
2293
  # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2167
2294
  # to your version control system
2168
2295
  #==============
2169
-
2170
2296
  saucelabs: DRIVER=saucelabs SL_USERNAME="<INSERT USER NAME HERE>" SL_AUTHKEY="<INSERT PASSWORD HERE>" DATA_CENTER="us-west-1" AUTOMATE_PROJECT="TestCentricity - SauceLabs"
2171
2297
 
2172
2298
  # SauceLabs iOS real device native app profiles
@@ -2184,7 +2310,6 @@ with access to your version control system.
2184
2310
  # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2185
2311
  # to your version control system
2186
2312
  #==============
2187
-
2188
2313
  testingbot: DRIVER=testingbot TB_USERNAME="<INSERT USER NAME HERE>" TB_AUTHKEY="<INSERT PASSWORD HERE>" AUTOMATE_PROJECT="TestCentricity - TestingBot"
2189
2314
 
2190
2315
  # TestingBot iOS real device native app profiles
@@ -2233,31 +2358,32 @@ in the `/features/support/<platform>/screens` folders, organized in functional a
2233
2358
  `ScreenSection` class definitions should be stored in the `/features/support/<platform>/sections` folder, where `<platform>`
2234
2359
  is typically `mac`, `ios`, or `android`.
2235
2360
 
2236
- my_automation_project
2237
- ├── config
2238
- │ ├── locales
2239
- │ ├── test_data
2240
- │ └── cucumber.yml
2241
- ├── features
2242
- ├── step_definitions
2243
- │ ├── support
2244
- ├── android
2245
- | | | ├── screens
2246
- | | | └── sections
2247
- ├── ios
2248
- | | | ├── screens
2249
- | | | └── sections
2250
- ├── mac
2251
- | | | ├── screens
2252
- | | | └── sections
2253
- ├── shared_components
2254
- | | | ├── screens
2255
- | | | └── sections
2256
- ├── env.rb
2257
- │ │ ├── hooks.rb
2258
- │ │ └── world_screens.rb
2259
- ├── Gemfile
2260
- └── README.md
2361
+ 📁 my_automation_project/
2362
+ ├── 📁 config/
2363
+ │ ├── 📁 locales/
2364
+ │ ├── 📁 test_data/
2365
+ │ └── 📄 cucumber.yml
2366
+ ├── 📁 downloads/
2367
+ ├── 📁 features/
2368
+ │ ├── 📁 step_definitions/
2369
+ │ ├── 📁 support
2370
+ ├── 📁 android
2371
+ | | | ├── 📁 screens
2372
+ | | | └── 📁 sections
2373
+ ├── 📁 ios
2374
+ | | | ├── 📁 screens
2375
+ | | | └── 📁 sections
2376
+ ├── 📁 mac
2377
+ | | | ├── 📁 screens
2378
+ | | | └── 📁 sections
2379
+ ├── 📁 shared_components
2380
+ | | | ├── 📁 screens
2381
+ | | | └── 📁 sections
2382
+ │ │ ├── 📄 env.rb
2383
+ │ │ ├── 📄 hooks.rb
2384
+ │ │ └── 📄 world_screens.rb
2385
+ ├── 📄 Gemfile
2386
+ └── 📄 README.md
2261
2387
 
2262
2388
 
2263
2389
  ---
@@ -2269,7 +2395,7 @@ is typically `mac`, `ios`, or `android`.
2269
2395
  ---
2270
2396
  ## Copyright and License
2271
2397
 
2272
- All TestCentricity™ Frameworks are Copyright (c) 2014-2024, A.J. Mrozinski.
2398
+ All TestCentricity™ Frameworks are Copyright (c) 2014-2025, A.J. Mrozinski.
2273
2399
  All rights reserved.
2274
2400
 
2275
2401
  Redistribution and use in source and binary forms, with or without