testcentricity_apps 4.0.14 → 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
@@ -29,14 +29,14 @@ A complete history of bug fixes and new features can be found in the [CHANGELOG]
29
29
 
30
30
  The RubyDocs for this gem can be found [here](https://www.rubydoc.info/gems/testcentricity_apps/).
31
31
 
32
- Two example projects that demonstrates the implementation of a screen object model framework using TestCentricity™ For Apps
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_mobile_react_native_demo](https://github.com/TestCentricity/tc_mobile_react_native_demo)
35
- * [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)
36
37
 
37
- Refer to [this wiki page](https://github.com/TestCentricity/testcentricity_apps/wiki/XCUItest-driver-bug-impacts-iOS-dialogs-managed-by-com.apple.springboard) for
38
- information on a bug with the latest versions of the XCUItest driver that affects Appium's ability to interact with and
39
- 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.
40
40
 
41
41
 
42
42
  ### Which gem should I use?
@@ -54,7 +54,7 @@ verify iOS system level modal dialogs.
54
54
 
55
55
  ## Installation
56
56
 
57
- 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
58
58
  automation project's `Gemfile`:
59
59
 
60
60
  gem 'testcentricity_apps'
@@ -109,33 +109,33 @@ Your `ScreenObject` class definitions should be contained within individual `.rb
109
109
  folder of your test automation project, where `<platform>` is typically `mac`, `ios`, or `android`. For each screen in your app,
110
110
  you will typically have to define a `ScreenObject` for each platform version of your app.
111
111
 
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
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
127
127
 
128
128
 
129
129
  You define a new `ScreenObject` as shown below:
130
130
  ```ruby
131
131
  class LoginScreen < TestCentricity::ScreenObject
132
132
  end
133
-
134
-
133
+
134
+
135
135
  class ProductsScreen < TestCentricity::ScreenObject
136
136
  end
137
-
138
-
137
+
138
+
139
139
  class CheckoutAddressScreen < TestCentricity::ScreenObject
140
140
  end
141
141
  ```
@@ -168,15 +168,15 @@ You define your screen's **Traits** as shown below:
168
168
  trait(:screen_locator) { { accessibility_id: 'login screen' } }
169
169
  trait(:deep_link) { 'mydemoapprn://login' }
170
170
  end
171
-
172
-
171
+
172
+
173
173
  class ProductsScreen < TestCentricity::ScreenObject
174
174
  trait(:screen_name) { 'Products' }
175
175
  trait(:screen_locator) { { accessibility_id: 'products screen' } }
176
176
  trait(:deep_link) { 'mydemoapprn://store-overview' }
177
177
  end
178
-
179
-
178
+
179
+
180
180
  class CheckoutAddressScreen < TestCentricity::ScreenObject
181
181
  trait(:screen_name) { 'Checkout - Address' }
182
182
  trait(:screen_locator) { { accessibility_id: 'checkout address screen' } }
@@ -193,7 +193,7 @@ buttons, etc. **UI Elements** are added to your `ScreenObject` class definition
193
193
  trait(:screen_name) { 'Login' }
194
194
  trait(:screen_locator) { { accessibility_id: 'login screen' } }
195
195
  trait(:deep_link) { 'mydemoapprn://login' }
196
-
196
+
197
197
  # Login screen UI elements
198
198
  labels username_label: { accessibility_id: 'Username'},
199
199
  password_label: { xpath: '(//XCUIElementTypeStaticText[@name="Password"])[1]'},
@@ -210,7 +210,7 @@ buttons, etc. **UI Elements** are added to your `ScreenObject` class definition
210
210
  trait(:screen_name) { 'Checkout - Address' }
211
211
  trait(:screen_locator) { { accessibility_id: 'checkout address screen' } }
212
212
  trait(:deep_link) { 'mydemoapprn://checkout-address' }
213
-
213
+
214
214
  # Checkout Address screen UI elements
215
215
  textfields fullname_field: { accessibility_id: 'Full Name* input field' },
216
216
  address1_field: { accessibility_id: 'Address Line 1* input field' },
@@ -233,7 +233,7 @@ class definition for interacting with the UI to hide implementation details, as
233
233
  trait(:screen_name) { 'Login' }
234
234
  trait(:screen_locator) { { accessibility_id: 'login screen' } }
235
235
  trait(:deep_link) { 'mydemoapprn://login' }
236
-
236
+
237
237
  # Login screen UI elements
238
238
  labels username_label: { accessibility_id: 'Username'},
239
239
  password_label: { xpath: '(//XCUIElementTypeStaticText[@name="Password"])[1]'},
@@ -243,7 +243,7 @@ class definition for interacting with the UI to hide implementation details, as
243
243
  textfields username_field: { accessibility_id: 'Username input field' },
244
244
  password_field: { accessibility_id: 'Password input field' }
245
245
  button :login_button, { accessibility_id: 'Login button' }
246
-
246
+
247
247
  def verify_screen_ui
248
248
  ui = {
249
249
  header_label => { visible: true, caption: 'Login' },
@@ -255,7 +255,7 @@ class definition for interacting with the UI to hide implementation details, as
255
255
  }
256
256
  verify_ui_states(ui)
257
257
  end
258
-
258
+
259
259
  def login(username, password)
260
260
  fields = {
261
261
  username_field => username,
@@ -264,7 +264,7 @@ class definition for interacting with the UI to hide implementation details, as
264
264
  populate_data_fields(fields)
265
265
  login_button.tap
266
266
  end
267
-
267
+
268
268
  def verify_entry_error(reason)
269
269
  ui = case reason.gsub(/\s+/, '_').downcase.to_sym
270
270
  when :invalid_password, :invalid_user
@@ -325,7 +325,7 @@ object. A `ScreenSection` may contain other `ScreenSection` objects.
325
325
 
326
326
  Below is an example of a footer navigation bar feature that is common to multiple screen -
327
327
 
328
- ![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")
329
329
 
330
330
 
331
331
  ### Defining a ScreenSection
@@ -334,24 +334,24 @@ Your `ScreenSection` class definitions should be contained within individual `.r
334
334
  folder of your test automation project, where `<platform>` is typically `mac`, `ios`, or `android`. For each screen section in your
335
335
  app, you will typically have to define a `ScreenSection` for each platform version of your app.
336
336
 
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
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
348
348
  │ │ ├── mac
349
- | | | ├── screens
350
- | | | └── sections
351
- │ │ ├── env.rb
352
- │ │ └── hooks.rb
353
- ├── Gemfile
354
- └── README.md
349
+ | | | ├── 📁 screens
350
+ | | | └── 📁 sections
351
+ │ │ ├── 📄 env.rb
352
+ │ │ └── 📄 hooks.rb
353
+ ├── 📄 Gemfile
354
+ └── 📄 README.md
355
355
 
356
356
 
357
357
  You define a new `ScreenSection` as shown below:
@@ -381,7 +381,7 @@ Elements** are added to your `ScreenSection` class definition as shown below:
381
381
  class NavMenu < TestCentricity::ScreenSection
382
382
  trait(:section_name) { 'Nav Menu' }
383
383
  trait(:section_locator) { { xpath: '//XCUIElementTypeScrollView' } }
384
-
384
+
385
385
  # Nav Menu UI elements
386
386
  buttons close_button: { accessibility_id: 'close menu' },
387
387
  webview_button: { accessibility_id: 'menu item webview' },
@@ -406,7 +406,7 @@ You can add methods to your `ScreenSection` class definition, as shown below:
406
406
  class NavMenu < TestCentricity::ScreenSection
407
407
  trait(:section_name) { 'Nav Menu' }
408
408
  trait(:section_locator) { { xpath: '//XCUIElementTypeScrollView' } }
409
-
409
+
410
410
  # Nav Menu UI elements
411
411
  buttons close_button: { accessibility_id: 'close menu' },
412
412
  webview_button: { accessibility_id: 'menu item webview' },
@@ -421,36 +421,36 @@ You can add methods to your `ScreenSection` class definition, as shown below:
421
421
  log_out_button: { accessibility_id: 'menu item log out' },
422
422
  api_calls_button: { accessibility_id: 'menu item api calls' },
423
423
  sauce_video_button: { accessibility_id: 'menu item sauce bot video' }
424
-
424
+
425
425
  def verify_ui
426
426
  ui = {
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' }
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' }
441
441
  }
442
442
  verify_ui_states(ui)
443
443
  end
444
-
444
+
445
445
  def close
446
446
  close_button.click
447
447
  self.wait_until_hidden(3)
448
448
  end
449
-
449
+
450
450
  def verify_closed
451
451
  ui = {
452
- self => { visible: true },
453
- close_button => { visible: false }
452
+ self => { visible: true },
453
+ close_button => { visible: false }
454
454
  }
455
455
  verify_ui_states(ui)
456
456
  end
@@ -525,6 +525,9 @@ Supported `AppUIElement` elementTypes and their declarations have the following
525
525
  switch :switch_name, { locator_strategy: locator_identifier }
526
526
  element :element_name, { locator_strategy: locator_identifier }
527
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 }
528
531
  end
529
532
  ```
530
533
  *Multiple element declarations:*
@@ -551,6 +554,8 @@ Supported `AppUIElement` elementTypes and their declarations have the following
551
554
  image_X_name: { locator_strategy: locator_identifier }
552
555
  alerts alert_1_name: { locator_strategy: locator_identifier },
553
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 }
554
559
  end
555
560
  ```
556
561
  Refer to the Class List documentation for the `ScreenObject` and `ScreenSection` classes for details on the class methods
@@ -649,11 +654,11 @@ properties of multiple UI elements on a `ScreenObject` or `ScreenSection`. The `
649
654
  containing key/hash pairs of UI elements and their properties or attributes to be verified.
650
655
  ```ruby
651
656
  ui = {
652
- object1 => { property: expected_state },
653
- object2 => { property1: expected_state, property2: expected_state },
654
- object3 => { property: expected_state }
655
- }
656
- 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)
657
662
  ```
658
663
  The `verify_ui_states` method automatically scrolls UI elements that are expected to be visible into view. Auto-scrolling
659
664
  only occurs on the vertical axis (down, then up). Setting the `auto_scroll` parameter to `false` prevents automatic scrolling
@@ -712,6 +717,15 @@ The `verify_ui_states` method supports the following property/state pairs:
712
717
  :itemcount Integer
713
718
  :item_data Array of Hash
714
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
+
715
729
  #### Comparison States
716
730
 
717
731
  The `verify_ui_states` method supports comparison states using property/comparison state pairs:
@@ -748,42 +762,42 @@ The `verify_ui_states` method also supports I18n string translations using prope
748
762
  The example below depicts the usage of the `verify_ui_states` method to verify that the captions for navigation menu items
749
763
  are correctly translated.
750
764
 
751
- ![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")
752
766
  ```ruby
753
767
  def verify_menu
754
768
  ui = {
755
- menu_title => {
756
- visible: true,
757
- caption: { translate: 'NavMenu.title' }
758
- },
759
- recipes_item => {
760
- visible: true,
761
- caption: { translate: 'NavMenu.recipes' }
762
- },
763
- browser_item => {
764
- visible: true,
765
- caption: { translate: 'NavMenu.browser' }
766
- },
767
- groceries_item => {
768
- visible: true,
769
- caption: { translate: 'NavMenu.groceries' }
770
- },
771
- pantry_item => {
772
- visible: true,
773
- caption: { translate: 'NavMenu.pantry' }
774
- },
775
- meals_item => {
776
- visible: true,
777
- caption: { translate: 'NavMenu.meals' }
778
- },
779
- menus_item => {
780
- visible: true,
781
- caption: { translate: 'NavMenu.menus' }
782
- },
783
- settings_item => {
784
- visible: true,
785
- caption: { translate: 'NavMenu.settings' }
786
- }
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
+ }
787
801
  }
788
802
  verify_ui_states(ui)
789
803
  end
@@ -837,9 +851,10 @@ Each supported language/locale combination has a corresponding `.yml` file. I18n
837
851
  | Language (Country) | File name |
838
852
  |-----------------------|-----------|
839
853
  | English | en.yml |
854
+ | English (Australia) | en-AU.yml |
840
855
  | English (Canada) | en-CA.yml |
841
- | French (Canada) | fr-CA.yml |
842
856
  | French | fr.yml |
857
+ | French (Canada) | fr-CA.yml |
843
858
  | Spanish | es.yml |
844
859
  | German | de.yml |
845
860
  | Portuguese (Brazil) | pt-BR.yml |
@@ -847,19 +862,23 @@ Each supported language/locale combination has a corresponding `.yml` file. I18n
847
862
 
848
863
  Baseline translation strings are stored in `.yml` files in the `config/locales/` folder.
849
864
 
850
- my_automation_project
851
- ├── config
852
- │ ├── locales
853
- │ │ ├── en.yml
854
- │ │ ├── es.yml
855
- │ │ ├── fr.yml
856
- │ │ ├── fr-CA.yml
857
- │ │ └── en-AU.yml
858
- │ ├── test_data
859
- └── cucumber.yml
860
- ├── features
861
- ├── Gemfile
862
- └── 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
863
882
 
864
883
 
865
884
  ### Working With Custom AppUIElements
@@ -871,13 +890,13 @@ be different for iOS vs. Android mobile platforms. Below is an example of the ve
871
890
  for a cross-platform application implemented using React Native (iOS version on the left, Android version on the right).
872
891
  Each ListView contains 30 items:
873
892
 
874
- ![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")
875
894
 
876
895
  While the iOS and Android ListViews appear to be identical in the app, performing an inspection of each application's GUI
877
896
  using Appium Inspector reveals differences in the object hierarchy as depicted below (iOS version on left, Android version
878
897
  on the right):
879
898
 
880
- ![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")
881
900
 
882
901
  The inspection of the ListView object hierarchy reveals that for the iOS version of the app, list items are made up of
883
902
  `XCUIElementTypeOther` objects, and that for the Android version of the app, list items are made up of `android.view.ViewGroup`
@@ -904,10 +923,10 @@ iOS Cloud List `ScreenObject`
904
923
  class CloudListScreen < TestCentricity::ScreenObject
905
924
  trait(:screen_name) { 'Cloud List' }
906
925
  trait(:screen_locator) { { class_chain: '**/XCUIElementTypeWindow/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther' } }
907
-
926
+
908
927
  # Cloud List screen UI elements
909
928
  list :cloud_list, { class_chain: '**/XCUIElementTypeScrollView/XCUIElementTypeOther' }
910
-
929
+
911
930
  def initialize
912
931
  super
913
932
  # define the list item element for the Cloud list object
@@ -922,10 +941,10 @@ Android CloudListScreen `ScreenObject`
922
941
  class CloudListScreen < TestCentricity::ScreenObject
923
942
  trait(:screen_name) { 'Cloud List' }
924
943
  trait(:screen_locator) { { xpath: '//android.widget.FrameLayout[@resource-id="android:id/content"]/android.view.ViewGroup' } }
925
-
944
+
926
945
  # Cloud List screen UI elements
927
946
  list :cloud_list, { xpath: '//android.widget.ScrollView/android.view.ViewGroup' }
928
-
947
+
929
948
  def initialize
930
949
  super
931
950
  # define the list item element for the Cloud list object
@@ -941,13 +960,13 @@ Android CloudListScreen `ScreenObject`
941
960
  Below is an example of a horizontal scrolling "Carousel" style ListView implementations on the Swipe screen of a cross-platform
942
961
  application. Each ListView contains 6 list items.
943
962
 
944
- ![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")
945
964
 
946
965
  While the iOS and Android ListViews appear to be identical in the app, performing an inspection of each application's GUI
947
966
  using Appium Inspector reveals differences in the object hierarchy as depicted below (iOS version on left, Android version
948
967
  on the right):
949
968
 
950
- ![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")
951
970
 
952
971
  As in the previous example for the vertical scrolling ListView, the inspection of the Carousel ListView object hierarchy
953
972
  reveals that for the iOS version of the app, list items are again made up of `XCUIElementTypeOther` objects, and that for
@@ -965,10 +984,10 @@ iOS Swipe `ScreenObject`
965
984
  class SwipeScreen < TestCentricity::ScreenObject
966
985
  trait(:screen_name) { 'Swipe' }
967
986
  trait(:screen_locator) { { accessibility_id: 'Swipe-screen' } }
968
-
987
+
969
988
  # Swipe screen UI elements
970
989
  list :carousel_list, { accessibility_id: 'Carousel' }
971
-
990
+
972
991
  def initialize
973
992
  super
974
993
  # define the list item element for the Carousel list object
@@ -986,10 +1005,10 @@ Android Swipe `ScreenObject`
986
1005
  class SwipeScreen < TestCentricity::ScreenObject
987
1006
  trait(:screen_name) { 'Swipe' }
988
1007
  trait(:screen_locator) { { accessibility_id: 'Swipe-screen' } }
989
-
1008
+
990
1009
  # Swipe screen UI elements
991
1010
  list :carousel_list, { accessibility_id: 'Carousel' }
992
-
1011
+
993
1012
  def initialize
994
1013
  super
995
1014
  # define the list item element for the Carousel list object
@@ -999,7 +1018,7 @@ Android Swipe `ScreenObject`
999
1018
  }
1000
1019
  carousel_list.define_list_elements(list_spec)
1001
1020
  end
1002
- end
1021
+ end
1003
1022
  ```
1004
1023
 
1005
1024
 
@@ -1008,12 +1027,12 @@ Android Swipe `ScreenObject`
1008
1027
  Below is an example of a PickerWheel (iOS) and Popup (Android) style ListView implementations on the Form Components screen
1009
1028
  of a cross-platform application.
1010
1029
 
1011
- ![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")
1012
1031
 
1013
1032
  Performing an inspection of each application's GUI using Appium Inspector reveals differences in the object hierarchy as
1014
1033
  depicted below (iOS version on left, Android version on the right):
1015
1034
 
1016
- ![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")
1017
1036
 
1018
1037
  The inspection of the PickerWheel and Popup ListView object hierarchies reveals that for the iOS version of the app, list
1019
1038
  items are again made up of `XCUIElementTypeOther` objects, and that for the Android version of the app, list items are made
@@ -1037,10 +1056,10 @@ Android FormScreen `ScreenObject`
1037
1056
  class FormScreen < TestCentricity::ScreenObject
1038
1057
  trait(:screen_name) { 'Form' }
1039
1058
  trait(:screen_locator) { { accessibility_id: 'Forms-screen' } }
1040
-
1059
+
1041
1060
  # Form screen UI elements
1042
1061
  list :drop_down_menu, { id: 'com.wdiodemoapp:id/select_dialog_listview' }
1043
-
1062
+
1044
1063
  def initialize
1045
1064
  super
1046
1065
  # define the list item element for the drop-down list object
@@ -1050,6 +1069,100 @@ Android FormScreen `ScreenObject`
1050
1069
  end
1051
1070
  ```
1052
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
+
1053
1166
 
1054
1167
  ---
1055
1168
  ## MacOS Application Menu Bar and Menus
@@ -1076,6 +1189,20 @@ You define a new `MenuBar` as shown below:
1076
1189
  end
1077
1190
  ```
1078
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
+
1079
1206
  ### Adding Menus to your MenuBar
1080
1207
 
1081
1208
  A `MenuBar` is typically made up of one or more `Menu` objects, which are added to your `MenuBar` class definition as shown
@@ -1093,7 +1220,7 @@ below:
1093
1220
  speech_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[7]' },
1094
1221
  window_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[8]' },
1095
1222
  help_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[9]' }
1096
- end
1223
+ end
1097
1224
  ```
1098
1225
 
1099
1226
  ### Adding Methods to your MenuBar
@@ -1112,7 +1239,7 @@ You can add methods to your `MenuBar` class definition, as shown below:
1112
1239
  speech_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[7]' },
1113
1240
  window_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[8]' },
1114
1241
  help_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[9]' }
1115
-
1242
+
1116
1243
  def choose_menu_item(menu, item, method = :mouse)
1117
1244
  menu_map = {
1118
1245
  calc: calc_menu,
@@ -1129,7 +1256,7 @@ You can add methods to your `MenuBar` class definition, as shown below:
1129
1256
  raise "#{menu} is not a supported menu" if menu_obj.nil?
1130
1257
  menu_obj.choose_menu_item(item, method)
1131
1258
  end
1132
-
1259
+
1133
1260
  def verify_menu_bar
1134
1261
  ui = {
1135
1262
  self => {
@@ -1180,11 +1307,11 @@ the `initialize` method of your app's `MenuBar` control.
1180
1307
  The code snippet below demonstrate the use of the `AppMenu.define_menu_elements` method in the `CalculatorMenuBar` object's
1181
1308
  `initialize` method to define the keyboard shortcut mapping for 4 of the menu items in the **View** menu and 1 of the menu
1182
1309
  items in the **Window** menu of the MacOS Calculator app. Keyboard shortcuts are assigned to the **View** menu items by
1183
- 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*).
1184
1311
 
1185
1312
  ```ruby
1186
1313
  class CalculatorMenuBar < TestCentricity::MenuBar
1187
-
1314
+
1188
1315
  def initialize(name, parent, locator, context)
1189
1316
  super
1190
1317
  # define key map for View menu
@@ -1218,19 +1345,26 @@ key. TestCentricity and XCTest defines the following possible bitmasks for modif
1218
1345
  ```
1219
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`.
1220
1347
 
1221
- ### Adding a MenuBar to your App's Primary ScreenObject
1348
+ ### Adding Popup Menus to your ScreenObject or ScreenSection
1222
1349
 
1223
- 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:
1224
1361
  ```ruby
1225
- class CalculatorAppScreen < TestCentricity::ScreenObject
1226
- # Calculator App screen UI elements
1227
- 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
1228
1366
  end
1229
1367
  ```
1230
- Once your `ScreenObject` has been instantiated, you can call its `MenuBar` methods as shown below:
1231
- ```ruby
1232
- num_menus = calculator_screen.calc_menu_bar.get_item_count
1233
- ```
1234
1368
 
1235
1369
 
1236
1370
  ---
@@ -1286,7 +1420,7 @@ scenario is displayed below:
1286
1420
  Given I am on the Products screen
1287
1421
  When I tap the <screen_name> navigation menu item
1288
1422
  Then I expect the <screen_name> screen to be correctly displayed
1289
-
1423
+
1290
1424
  Examples:
1291
1425
  |screen_name |
1292
1426
  |Registration |
@@ -1489,13 +1623,13 @@ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml
1489
1623
  ##### Local iOS Simulators or Physical Devices using the `options` Hash
1490
1624
 
1491
1625
  When using the `options` hash, the following options and capabilities must be specified:
1492
- - `driver:` must be set to `:appium`
1493
- - `device_type:` must be set to `:tablet` or `:phone`
1494
- - `platformName:` must be set to `ios` in the `capabilities:` hash
1495
- - `'appium:automationName':` must be set to `xcuitest` in the `capabilities:` hash
1496
- - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
1497
- - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
1498
- - `'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
1499
1633
 
1500
1634
  ```ruby
1501
1635
  options = {
@@ -1524,21 +1658,21 @@ Below is an example of an `options` hash for specifying a connection to a locall
1524
1658
  simulator. The `options` hash includes options for specifying the driver name, global driver scope, and setting the simulated
1525
1659
  device orientation to portrait mode.
1526
1660
  ```ruby
1527
- options = {
1528
- driver: :appium,
1529
- device_type: :tablet,
1530
- driver_name: :my_custom_ipad_driver,
1531
- global_driver: true,
1532
- capabilities: {
1533
- platformName: :ios,
1534
- 'appium:platformVersion': '15.4',
1535
- 'appium:deviceName': 'iPad Pro (12.9-inch) (5th generation)',
1536
- 'appium:automationName': 'XCUITest',
1537
- 'appium:orientation': 'PORTRAIT',
1538
- 'appium:app': Environ.current.ios_app_path
1539
- }
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
1540
1673
  }
1541
- AppiumConnect.initialize_appium(options)
1674
+ }
1675
+ AppiumConnect.initialize_appium(options)
1542
1676
  ```
1543
1677
 
1544
1678
  #### Connecting to Locally Hosted Android Simulators or Physical Devices
@@ -1576,13 +1710,13 @@ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml
1576
1710
  ##### Local Android Simulators or Physical Devices using the `options` Hash
1577
1711
 
1578
1712
  When using the `options` hash, the following options and capabilities must be specified:
1579
- - `driver:` must be set to `:appium`
1580
- - `device_type:` must be set to `:tablet` or `:phone`
1581
- - `platformName:` must be set to `Android` in the `capabilities:` hash
1582
- - `'appium:automationName':` must be set to `UiAutomator2` in the `capabilities:` hash
1583
- - `'appium:platformVersion':` must be set to the version of Android on the simulator or physical device
1584
- - `'appium:deviceName':` must be set to the Android Virtual Device ID
1585
- - `'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
1586
1720
 
1587
1721
  ```ruby
1588
1722
  options = {
@@ -1605,7 +1739,7 @@ When using the `options` hash, the following options and capabilities must be sp
1605
1739
  >
1606
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``
1607
1741
  will be used.
1608
- >
1742
+ >
1609
1743
  > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
1610
1744
 
1611
1745
 
@@ -1644,7 +1778,7 @@ tests, place the code shown below in your `hooks.rb` file.
1644
1778
  $server.start
1645
1779
  end
1646
1780
  end
1647
-
1781
+
1648
1782
  AfterAll do
1649
1783
  # close Appium driver
1650
1784
  TestCentricity::AppiumConnect.quit_driver
@@ -1681,7 +1815,7 @@ body of an example group:
1681
1815
  $server = TestCentricity::AppiumServer.new
1682
1816
  $server.start
1683
1817
  end
1684
-
1818
+
1685
1819
  after(:context) do
1686
1820
  # terminate Appium Server after all of the examples in this group
1687
1821
  $server.stop if Environ.driver == :appium && $server.running?
@@ -1708,9 +1842,9 @@ devices. BrowserStack uses only real physical devices - simulators are not avail
1708
1842
 
1709
1843
  Refer to the following pages for information on uploading your iOS `.ipa` or Android `.apk` app files to the BrowserStack
1710
1844
  servers:
1711
- - [Upload apps from filesystem](https://www.browserstack.com/docs/app-automate/appium/upload-app-from-filesystem)
1712
- - [Upload apps using public URL](https://www.browserstack.com/docs/app-automate/appium/upload-app-using-public-url)
1713
- - [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)
1714
1848
 
1715
1849
  The preferred method of uploading an app to BrowserStack is to define a custom test ID for your apps to avoid having to
1716
1850
  modify your test configuration data with a new `app_url` after every app upload. Use the same custom test ID every time
@@ -1754,13 +1888,13 @@ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml
1754
1888
  ##### BrowserStack Mobile Devices using the `options` Hash
1755
1889
 
1756
1890
  When using the `options` hash, the following options and capabilities must be specified:
1757
- - `driver:` must be set to `:browserstack`
1758
- - `device_type:` must be set to `:tablet` or `:phone`
1759
- - `platformName:` must be set to `ios` or `android` in the `capabilities:` hash
1760
- - `'appium:automationName':` must be set to to `XCUITest` for iOS or `UiAutomator2` for Android in the `capabilities:` hash
1761
- - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
1762
- - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
1763
- - `'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
1764
1898
 
1765
1899
  ```ruby
1766
1900
  options = {
@@ -1962,7 +2096,7 @@ Refer to the following pages for information on uploading your iOS `.ipa` or `.a
1962
2096
  Sauce Labs servers:
1963
2097
  - [Mobile App Storage](https://docs.saucelabs.com/mobile-apps/app-storage/)
1964
2098
 
1965
- 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
1966
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
1967
2101
  tests will most likely fail as a new `app_url` will be generated, and you will have to update your test configuration data
1968
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
@@ -2041,16 +2175,16 @@ on other cloud hosting services that are currently not supported. You must call
2041
2175
  with an `options` hash - Environment Variables cannot be used to specify a user-defined custom Appium driver instance.
2042
2176
 
2043
2177
  Prior to calling the `AppiumConnect.initialize_appium` method, you must set the following `Environ` attributes:
2044
- - `Environ.platform` set to `:mobile`
2045
- - `Environ.device_os` to either `:ios` or `:android`
2046
- - `Environ.device` to either `:simulator` or `:device`, dependent on whether the target mobile platform is a real device
2047
- or simulator.
2048
- - `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
2049
2183
 
2050
2184
  The following options and capabilities must be specified:
2051
- - `driver:` must be set to `:custom`
2052
- - `device_type:` must be set to `:tablet` or `:phone`
2053
- - `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
2054
2188
 
2055
2189
  All other required capabilities specified by the hosting service configuration documentation should be included in the
2056
2190
  `capabilities:` hash.
@@ -2091,11 +2225,9 @@ and authorization code for the cloud service(s) that you intend to connect with.
2091
2225
  > ⚠️ Cloud service credentials should not be stored as text in your `cucumber.yml` file where it can be exposed by anyone
2092
2226
  with access to your version control system.
2093
2227
 
2094
-
2095
2228
  #==============
2096
2229
  # conditionally load Screen Object implementations based on which target platform we're running on
2097
2230
  #==============
2098
-
2099
2231
  ios: PLATFORM=ios --tags @ios -r features/support/ios -e features/support/android
2100
2232
  android: PLATFORM=android --tags @android -r features/support/android -e features/support/ios
2101
2233
 
@@ -2103,7 +2235,6 @@ with access to your version control system.
2103
2235
  #==============
2104
2236
  # profiles for mobile device screen orientation
2105
2237
  #==============
2106
-
2107
2238
  landscape: ORIENTATION=landscape
2108
2239
  portrait: ORIENTATION=portrait
2109
2240
 
@@ -2120,7 +2251,6 @@ with access to your version control system.
2120
2251
  # profiles for native iOS apps hosted within XCode iOS simulators
2121
2252
  # NOTE: Requires installation of XCode, iOS version specific target simulators, and Appium
2122
2253
  #==============
2123
-
2124
2254
  appium_ios: DRIVER=appium --profile ios AUTOMATION_ENGINE=XCUITest APP_PLATFORM_NAME="iOS" NEW_COMMAND_TIMEOUT="30" <%= mobile %>
2125
2255
  app_ios_14: --profile appium_ios APP_VERSION="14.5"
2126
2256
  app_ios_15: --profile appium_ios APP_VERSION="15.4"
@@ -2135,7 +2265,6 @@ with access to your version control system.
2135
2265
  # profiles for native Android apps hosted within Android Studio Android Virtual Device emulators
2136
2266
  # NOTE: Requires installation of Android Studio, Android version specific virtual device simulators, and Appium
2137
2267
  #==============
2138
-
2139
2268
  appium_android: DRIVER=appium --profile android AUTOMATION_ENGINE=UiAutomator2 APP_PLATFORM_NAME="Android" <%= mobile %>
2140
2269
  app_android_12: --profile appium_android APP_VERSION="12.0"
2141
2270
  pixel_5_api31_sim: --profile app_android_12 DEVICE_TYPE=phone APP_DEVICE="Pixel_5_API_31"
@@ -2146,7 +2275,6 @@ with access to your version control system.
2146
2275
  # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2147
2276
  # to your version control system
2148
2277
  #==============
2149
-
2150
2278
  browserstack: DRIVER=browserstack BS_USERNAME="<INSERT USER NAME HERE>" BS_AUTHKEY="<INSERT PASSWORD HERE>" TEST_CONTEXT="TestCentricity"
2151
2279
 
2152
2280
  # BrowserStack iOS real device native app profiles
@@ -2165,7 +2293,6 @@ with access to your version control system.
2165
2293
  # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2166
2294
  # to your version control system
2167
2295
  #==============
2168
-
2169
2296
  saucelabs: DRIVER=saucelabs SL_USERNAME="<INSERT USER NAME HERE>" SL_AUTHKEY="<INSERT PASSWORD HERE>" DATA_CENTER="us-west-1" AUTOMATE_PROJECT="TestCentricity - SauceLabs"
2170
2297
 
2171
2298
  # SauceLabs iOS real device native app profiles
@@ -2183,7 +2310,6 @@ with access to your version control system.
2183
2310
  # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2184
2311
  # to your version control system
2185
2312
  #==============
2186
-
2187
2313
  testingbot: DRIVER=testingbot TB_USERNAME="<INSERT USER NAME HERE>" TB_AUTHKEY="<INSERT PASSWORD HERE>" AUTOMATE_PROJECT="TestCentricity - TestingBot"
2188
2314
 
2189
2315
  # TestingBot iOS real device native app profiles
@@ -2232,31 +2358,32 @@ in the `/features/support/<platform>/screens` folders, organized in functional a
2232
2358
  `ScreenSection` class definitions should be stored in the `/features/support/<platform>/sections` folder, where `<platform>`
2233
2359
  is typically `mac`, `ios`, or `android`.
2234
2360
 
2235
- my_automation_project
2236
- ├── config
2237
- │ ├── locales
2238
- │ ├── test_data
2239
- │ └── cucumber.yml
2240
- ├── features
2241
- ├── step_definitions
2242
- │ ├── support
2243
- ├── android
2244
- | | | ├── screens
2245
- | | | └── sections
2246
- ├── ios
2247
- | | | ├── screens
2248
- | | | └── sections
2249
- ├── mac
2250
- | | | ├── screens
2251
- | | | └── sections
2252
- ├── shared_components
2253
- | | | ├── screens
2254
- | | | └── sections
2255
- ├── env.rb
2256
- │ │ ├── hooks.rb
2257
- │ │ └── world_screens.rb
2258
- ├── Gemfile
2259
- └── 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
2260
2387
 
2261
2388
 
2262
2389
  ---
@@ -2268,7 +2395,7 @@ is typically `mac`, `ios`, or `android`.
2268
2395
  ---
2269
2396
  ## Copyright and License
2270
2397
 
2271
- All TestCentricity™ Frameworks are Copyright (c) 2014-2024, A.J. Mrozinski.
2398
+ All TestCentricity™ Frameworks are Copyright (c) 2014-2025, A.J. Mrozinski.
2272
2399
  All rights reserved.
2273
2400
 
2274
2401
  Redistribution and use in source and binary forms, with or without