testcentricity_apps 4.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +193 -0
  4. data/LICENSE.md +27 -0
  5. data/README.md +2297 -0
  6. data/lib/testcentricity_apps/app_core/appium_connect_helper.rb +667 -0
  7. data/lib/testcentricity_apps/app_core/screen_object.rb +494 -0
  8. data/lib/testcentricity_apps/app_core/screen_objects_helper.rb +211 -0
  9. data/lib/testcentricity_apps/app_core/screen_section.rb +669 -0
  10. data/lib/testcentricity_apps/app_elements/alert.rb +152 -0
  11. data/lib/testcentricity_apps/app_elements/app_element.rb +728 -0
  12. data/lib/testcentricity_apps/app_elements/button.rb +10 -0
  13. data/lib/testcentricity_apps/app_elements/checkbox.rb +61 -0
  14. data/lib/testcentricity_apps/app_elements/image.rb +10 -0
  15. data/lib/testcentricity_apps/app_elements/label.rb +10 -0
  16. data/lib/testcentricity_apps/app_elements/list.rb +188 -0
  17. data/lib/testcentricity_apps/app_elements/menu.rb +159 -0
  18. data/lib/testcentricity_apps/app_elements/menubar.rb +78 -0
  19. data/lib/testcentricity_apps/app_elements/radio.rb +61 -0
  20. data/lib/testcentricity_apps/app_elements/selectlist.rb +126 -0
  21. data/lib/testcentricity_apps/app_elements/switch.rb +66 -0
  22. data/lib/testcentricity_apps/app_elements/textfield.rb +51 -0
  23. data/lib/testcentricity_apps/appium_server.rb +76 -0
  24. data/lib/testcentricity_apps/data_objects/data_objects_helper.rb +100 -0
  25. data/lib/testcentricity_apps/data_objects/environment.rb +423 -0
  26. data/lib/testcentricity_apps/exception_queue_helper.rb +160 -0
  27. data/lib/testcentricity_apps/utility_helpers.rb +48 -0
  28. data/lib/testcentricity_apps/version.rb +3 -0
  29. data/lib/testcentricity_apps/world_extensions.rb +61 -0
  30. data/lib/testcentricity_apps.rb +103 -0
  31. metadata +322 -0
data/README.md ADDED
@@ -0,0 +1,2297 @@
1
+ # TestCentricity™ For Apps
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/testcentricity_apps.svg)](https://badge.fury.io/rb/testcentricity_apps)
4
+ [![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](http://opensource.org/licenses/BSD-3-Clause)
5
+ ![Gem Downloads](https://img.shields.io/gem/dt/testcentricity_apps)
6
+ ![Maintained](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)
7
+ [![Docs](https://img.shields.io/badge/docs-rubydoc-blue.svg)](http://www.rubydoc.info/gems/testcentricity_apps)
8
+
9
+
10
+ The TestCentricity™ For Apps core framework for MacOS desktop apps and mobile iOS/iPadOS and Android app testing implements
11
+ a Screen Object Model DSL for use with Cucumber or RSpec and Appium version 2.x. It also facilitates the configuration of
12
+ the appropriate Appium capabilities and driver required to establish a connection with locally hosted MacOS desktop apps,
13
+ or locally or cloud hosted iOS and Android real devices or simulators.
14
+
15
+ The TestCentricity™ For Apps gem supports automated testing of MacOS desktop apps and native iOS and Android apps running
16
+ on the following mobile test targets:
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)
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)
24
+
25
+
26
+ ## What's New
27
+
28
+ A complete history of bug fixes and new features can be found in the [CHANGELOG](https://github.com/TestCentricity/testcentricity_apps/blob/master/CHANGELOG.md) file.
29
+
30
+ The RubyDocs for this gem can be found [here](https://www.rubydoc.info/gems/testcentricity_apps/).
31
+
32
+ Two example projects that demonstrates the implementation of a screen object model framework using TestCentricity™ For Apps
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)
36
+
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.
40
+
41
+
42
+ ### Which gem should I use?
43
+
44
+ * The [TestCentricity For **Apps** gem](https://rubygems.org/gems/testcentricity_apps) supports testing of MacOS desktop apps and native iOS and Android mobile apps
45
+ * The [TestCentricity For **Mobile** gem](https://rubygems.org/gems/testcentricity_mobile) supports testing of native iOS and Android mobile apps
46
+ * The [TestCentricity For **Web** gem](https://rubygems.org/gems/testcentricity_web) supports testing of web interfaces via desktop and mobile web browsers
47
+
48
+ | Tested platforms | TestCentricity For Apps | TestCentricity For Mobile | TestCentricity For Web |
49
+ |---------------------------------------------------|:-:|:-:|:-:|
50
+ | MacOS desktop apps | Yes | No | No |
51
+ | Native mobile iOS/iPadOS and/or Android apps only | Yes | Yes | No |
52
+ | Desktop/mobile web browsers only | No | No | Yes |
53
+
54
+
55
+ ## Installation
56
+
57
+ TestCentricity For Apps requires Ruby 3.0.0 or later. To install the TestCentricity For Apps gem, add this line to your
58
+ automation project's `Gemfile`:
59
+
60
+ gem 'testcentricity_apps'
61
+
62
+ And then execute:
63
+
64
+ $ bundle
65
+
66
+ Or install it yourself as:
67
+
68
+ $ gem install testcentricity_apps
69
+
70
+
71
+ ---
72
+ ## Setup
73
+ ### Using Cucumber
74
+
75
+ If you are using Cucumber, you need to require the following in your `env.rb` file:
76
+ ```ruby
77
+ require 'testcentricity_apps'
78
+ ```
79
+
80
+ ### Using RSpec
81
+
82
+ If you are using RSpec instead, you need to require the following in your `spec_helper.rb` file:
83
+ ```ruby
84
+ require 'testcentricity_apps'
85
+ ```
86
+
87
+ ---
88
+ ## ScreenObjects
89
+
90
+ The **Screen Object Model** is a test automation pattern that aims to create an abstraction of your MacOS desktop app or
91
+ native mobile app's User Interface that can be used in tests. The **Screen** Object Model in MacOS desktop apps or native
92
+ mobile app test automation is equivalent to the **Page** Object Model in web user interface test automation.
93
+
94
+ A **Screen Object** is an object that represents a single screen in your AUT (Application Under Test). **Screen Objects**
95
+ encapsulate the implementation details of a MacOS desktop or native mobile app screen and expose an API that supports
96
+ interaction with, and validation of the UI elements on the screen.
97
+
98
+ **Screen Objects** makes it easier to maintain automated tests because changes to screen UI elements are updated in only
99
+ one location - in the `ScreenObject` class definition. By adopting a **Screen Object Model**, Cucumber feature files and
100
+ step definitions are no longer required to hold specific information about a screen's UI objects, thus minimizing maintenance
101
+ requirements. If any element on, or property of a screen changes (text field attributes, button captions, element states,
102
+ etc.), maintenance is performed in the `ScreenObject` class definition only, typically with no need to update the affected
103
+ feature files, scenarios, or step definitions.
104
+
105
+
106
+ ### Defining a ScreenObject
107
+
108
+ Your `ScreenObject` class definitions should be contained within individual `.rb` files in the `features/support/<platform>/screens`
109
+ folder of your test automation project, where `<platform>` is typically `mac`, `ios`, or `android`. For each screen in your app,
110
+ you will typically have to define a `ScreenObject` for each platform version of your app.
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
127
+
128
+
129
+ You define a new `ScreenObject` as shown below:
130
+ ```ruby
131
+ class LoginScreen < TestCentricity::ScreenObject
132
+ end
133
+
134
+
135
+ class ProductsScreen < TestCentricity::ScreenObject
136
+ end
137
+
138
+
139
+ class CheckoutAddressScreen < TestCentricity::ScreenObject
140
+ end
141
+ ```
142
+
143
+ ### Adding Traits to your ScreenObject
144
+
145
+ Desktop and mobile app screens typically have names associated with them. Screens also typically have a unique object or
146
+ attribute that, when present, indicates that the screen's contents have fully loaded.
147
+
148
+ The `screen_name` trait is registered with the `ScreenManager` object, which includes a `find_screen` method that takes a
149
+ screen name as a parameter and returns an instance of the associated `ScreenObject`. If you intend to use the `ScreenManager`,
150
+ you must define a`screen_name` trait for each `ScreenObject` to be registered.
151
+
152
+ The `screen_name` trait is usually a `String` value that represents the name of the screen that will be matched by the
153
+ `ScreenManager.find_screen` method. `screen_name` traits are case and white-space sensitive. For screens that may be
154
+ referenced with multiple names, the `screen_name` trait may also be an `Array` of `String` values representing those
155
+ screen names.
156
+
157
+ The `screen_locator` trait specifies a locator for a unique object that exists once the screen's contents have been fully
158
+ rendered. The `screen_locator` trait is a locator strategy that uniquely identifies the object. The `ScreenObject.verify_screen_exists`
159
+ method waits for the `screen_locator` trait to exist, and raises an exception if the wait time exceeds the `default_max_wait_time`.
160
+
161
+ A `deep_link` trait should be defined if a screen can be directly loaded using a deep link. Specifying a `deep_link` trait
162
+ is optional, as not all screens can be directly accessed via a deep link.
163
+
164
+ You define your screen's **Traits** as shown below:
165
+ ```ruby
166
+ class LoginScreen < TestCentricity::ScreenObject
167
+ trait(:screen_name) { 'Login' }
168
+ trait(:screen_locator) { { accessibility_id: 'login screen' } }
169
+ trait(:deep_link) { 'mydemoapprn://login' }
170
+ end
171
+
172
+
173
+ class ProductsScreen < TestCentricity::ScreenObject
174
+ trait(:screen_name) { 'Products' }
175
+ trait(:screen_locator) { { accessibility_id: 'products screen' } }
176
+ trait(:deep_link) { 'mydemoapprn://store-overview' }
177
+ end
178
+
179
+
180
+ class CheckoutAddressScreen < TestCentricity::ScreenObject
181
+ trait(:screen_name) { 'Checkout - Address' }
182
+ trait(:screen_locator) { { accessibility_id: 'checkout address screen' } }
183
+ trait(:deep_link) { 'mydemoapprn://checkout-address' }
184
+ end
185
+ ```
186
+
187
+ ### Adding UI Elements to your ScreenObject
188
+
189
+ Desktop and mobile app screens are made up of UI elements like text fields, check boxes, radio buttons, switches, lists,
190
+ buttons, etc. **UI Elements** are added to your `ScreenObject` class definition as shown below:
191
+ ```ruby
192
+ class LoginScreen < TestCentricity::ScreenObject
193
+ trait(:screen_name) { 'Login' }
194
+ trait(:screen_locator) { { accessibility_id: 'login screen' } }
195
+ trait(:deep_link) { 'mydemoapprn://login' }
196
+
197
+ # Login screen UI elements
198
+ labels username_label: { accessibility_id: 'Username'},
199
+ password_label: { xpath: '(//XCUIElementTypeStaticText[@name="Password"])[1]'},
200
+ username_error: { accessibility_id: 'Username-error-message' },
201
+ password_error: { accessibility_id: 'Password-error-message' },
202
+ generic_error: { accessibility_id: 'generic-error-message' }
203
+ textfields username_field: { accessibility_id: 'Username input field' },
204
+ password_field: { accessibility_id: 'Password input field' }
205
+ button :login_button, { accessibility_id: 'Login button' }
206
+ end
207
+
208
+
209
+ class CheckoutAddressScreen < TestCentricity::ScreenObject
210
+ trait(:screen_name) { 'Checkout - Address' }
211
+ trait(:screen_locator) { { accessibility_id: 'checkout address screen' } }
212
+ trait(:deep_link) { 'mydemoapprn://checkout-address' }
213
+
214
+ # Checkout Address screen UI elements
215
+ textfields fullname_field: { accessibility_id: 'Full Name* input field' },
216
+ address1_field: { accessibility_id: 'Address Line 1* input field' },
217
+ address2_field: { accessibility_id: 'Address Line 2 input field' },
218
+ city_field: { accessibility_id: 'City* input field' },
219
+ state_region_field: { accessibility_id: 'State/Region input field' },
220
+ zip_code_field: { accessibility_id: 'Zip Code* input field' },
221
+ country_field: { accessibility_id: 'Country* input field' }
222
+ button :to_payment_button, { accessibility_id: 'To Payment button' }
223
+ end
224
+ ```
225
+
226
+ ### Adding Methods to your ScreenObject
227
+
228
+ It is good practice for your Cucumber step definitions to call high level methods in your your `ScreenObject` instead of
229
+ directly accessing and interacting with a screen object's UI elements. You can add high level methods to your `ScreenObject`
230
+ class definition for interacting with the UI to hide implementation details, as shown below:
231
+ ```ruby
232
+ class LoginScreen < TestCentricity::ScreenObject
233
+ trait(:screen_name) { 'Login' }
234
+ trait(:screen_locator) { { accessibility_id: 'login screen' } }
235
+ trait(:deep_link) { 'mydemoapprn://login' }
236
+
237
+ # Login screen UI elements
238
+ labels username_label: { accessibility_id: 'Username'},
239
+ password_label: { xpath: '(//XCUIElementTypeStaticText[@name="Password"])[1]'},
240
+ username_error: { accessibility_id: 'Username-error-message' },
241
+ password_error: { accessibility_id: 'Password-error-message' },
242
+ generic_error: { accessibility_id: 'generic-error-message' }
243
+ textfields username_field: { accessibility_id: 'Username input field' },
244
+ password_field: { accessibility_id: 'Password input field' }
245
+ button :login_button, { accessibility_id: 'Login button' }
246
+
247
+ def verify_screen_ui
248
+ ui = {
249
+ header_label => { visible: true, caption: 'Login' },
250
+ username_label => { visible: true, caption: 'Username' },
251
+ username_field => { visible: true, enabled: true },
252
+ password_label => { visible: true, caption: 'Password' },
253
+ password_field => { visible: true, enabled: true },
254
+ login_button => { visible: true, enabled: true, caption: 'Login' }
255
+ }
256
+ verify_ui_states(ui)
257
+ end
258
+
259
+ def login(username, password)
260
+ fields = {
261
+ username_field => username,
262
+ password_field => password
263
+ }
264
+ populate_data_fields(fields)
265
+ login_button.tap
266
+ end
267
+
268
+ def verify_entry_error(reason)
269
+ ui = case reason.gsub(/\s+/, '_').downcase.to_sym
270
+ when :invalid_password, :invalid_user
271
+ { generic_error => { visible: true, caption: 'Provided credentials do not match any user in this service.' } }
272
+ when :locked_account
273
+ { generic_error => { visible: true, caption: 'Sorry, this user has been locked out.' } }
274
+ when :no_username
275
+ { username_error => { visible: true, caption: 'Username is required' } }
276
+ when :no_password
277
+ { password_error => { visible: true, caption: 'Password is required' } }
278
+ else
279
+ raise "#{reason} is not a valid selector"
280
+ end
281
+ verify_ui_states(ui)
282
+ end
283
+ end
284
+ ```
285
+
286
+ Once your `ScreenObject` has been instantiated, you can call your methods as shown below:
287
+ ```ruby
288
+ login_screen.login('snicklefritz', 'Pa55w0rd')
289
+ login_screen.verify_entry_error('invalid user')
290
+ ```
291
+
292
+ ### Loading your App's ScreenObjects using Deeplinks
293
+
294
+ Users typically move between an app's screens (or a web portal's pages) by interacting with various navigation metaphors,
295
+ usually by tapping on buttons or links, or making selections from menu, grid, carousel, or list items. When testing web
296
+ interfaces using automated tests, time consuming interactions with the user interface can usually be reduced by using URLs
297
+ to quickly load pages without following a strict workflow.
298
+
299
+ Being able to use a combination of public or private APIs and URLs to bypass the time consuming interactions with a user
300
+ interface that may be undergoing refactoring during ongoing development (and which could lead to test failures due to bugs
301
+ in the new UI) can result in significant reduction in test execution time. While all UI interactions should be comprehensively
302
+ tested, most of the repetitive time intensive UI workflow interactions required to establish a stable base state for testing
303
+ downstream functionality can be avoided by leveraging testability "shortcuts" provided by your app's developers.
304
+
305
+ For example, in order to verify the functionality of finalizing the purchase of products via an ecommerce app or web portal,
306
+ a typical workflow might require a user to search for products to purchase, select product specific options (color, size,
307
+ quantity, etc.), add the products to a shopping cart, and log in to their account before they can finalize the purchase.
308
+ By utilizing developer provided APIs, URLs, or deeplinks, test execution time can be greatly reduced.
309
+
310
+ The `ScreenObject.load_screen` method is used to load a screen using its defined `deep_link` trait. When testing on physical
311
+ iOS devices running iOS/iPadOS versions earlier than version 16.4, deep links can only be opened by sending the deeplink URL
312
+ to the mobile Safari web browser, and then accepting the confirmation modal that pops up. The `load_screen` method handles
313
+ invoking deeplinks on Android and iOS/iPadOS simulators and physical devices.
314
+
315
+ Refer to the [Speeding Up Tests With Deep Links](https://appiumpro.com/editions/7-speeding-up-tests-with-deep-links) post on [AppiumPro](https://appiumpro.com/) for more information about deeplinks.
316
+
317
+
318
+ ---
319
+ ## ScreenSections
320
+
321
+ A `ScreenSection` is a collection of **UI Elements** that may appear in multiple locations on a screen, or on multiple
322
+ screens in an app. It is a collection of **UI Elements** that represent a conceptual area of functionality, like a menu,
323
+ a navigation bar, or a search capability. **UI Elements** and functional behavior are confined to the scope of a `ScreenSection`
324
+ object. A `ScreenSection` may contain other `ScreenSection` objects.
325
+
326
+ Below is an example of a footer navigation bar feature that is common to multiple screen -
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")
329
+
330
+
331
+ ### Defining a ScreenSection
332
+
333
+ Your `ScreenSection` class definitions should be contained within individual `.rb` files in the `features/support/<platform>/sections`
334
+ folder of your test automation project, where `<platform>` is typically `mac`, `ios`, or `android`. For each screen section in your
335
+ app, you will typically have to define a `ScreenSection` for each platform version of your app.
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
348
+ │ │ ├── mac
349
+ | | | ├── screens
350
+ | | | └── sections
351
+ │ │ ├── env.rb
352
+ │ │ └── hooks.rb
353
+ ├── Gemfile
354
+ └── README.md
355
+
356
+
357
+ You define a new `ScreenSection` as shown below:
358
+ ```ruby
359
+ class NavMenu < TestCentricity::ScreenSection
360
+ end
361
+ ```
362
+
363
+ ### Adding Traits to a ScreenSection
364
+
365
+ A `ScreenSection` typically has a root node object that encapsulates a collection of `UIElements`. The `section_locator`
366
+ trait specifies the CSS or Xpath expression that uniquely identifies that root node object.
367
+
368
+ You define your section's **Traits** as shown below:
369
+ ```ruby
370
+ class NavMenu < TestCentricity::ScreenSection
371
+ trait(:section_name) { 'Nav Menu' }
372
+ trait(:section_locator) { { xpath: '//XCUIElementTypeScrollView' } }
373
+ end
374
+ ```
375
+
376
+ ### Adding UI Elements to your ScreenSection
377
+
378
+ A `ScreenSection` is typically made up of UI elements like text fields, check boxes, switches, lists, buttons, etc. **UI
379
+ Elements** are added to your `ScreenSection` class definition as shown below:
380
+ ```ruby
381
+ class NavMenu < TestCentricity::ScreenSection
382
+ trait(:section_name) { 'Nav Menu' }
383
+ trait(:section_locator) { { xpath: '//XCUIElementTypeScrollView' } }
384
+
385
+ # Nav Menu UI elements
386
+ buttons close_button: { accessibility_id: 'close menu' },
387
+ webview_button: { accessibility_id: 'menu item webview' },
388
+ qr_code_button: { accessibility_id: 'menu item qr code scanner' },
389
+ geo_location_button: { accessibility_id: 'menu item geo location' },
390
+ drawing_button: { accessibility_id: 'menu item drawing' },
391
+ report_a_bug_button: { accessibility_id: 'menu item report a bug' },
392
+ about_button: { accessibility_id: 'menu item about' },
393
+ reset_app_button: { accessibility_id: 'menu item reset app' },
394
+ biometrics_button: { accessibility_id: 'menu item biometrics' },
395
+ log_in_button: { accessibility_id: 'menu item log in' },
396
+ log_out_button: { accessibility_id: 'menu item log out' },
397
+ api_calls_button: { accessibility_id: 'menu item api calls' },
398
+ sauce_video_button: { accessibility_id: 'menu item sauce bot video' }
399
+ end
400
+ ```
401
+
402
+ ### Adding Methods to your ScreenSection
403
+
404
+ You can add methods to your `ScreenSection` class definition, as shown below:
405
+ ```ruby
406
+ class NavMenu < TestCentricity::ScreenSection
407
+ trait(:section_name) { 'Nav Menu' }
408
+ trait(:section_locator) { { xpath: '//XCUIElementTypeScrollView' } }
409
+
410
+ # Nav Menu UI elements
411
+ buttons close_button: { accessibility_id: 'close menu' },
412
+ webview_button: { accessibility_id: 'menu item webview' },
413
+ qr_code_button: { accessibility_id: 'menu item qr code scanner' },
414
+ geo_location_button: { accessibility_id: 'menu item geo location' },
415
+ drawing_button: { accessibility_id: 'menu item drawing' },
416
+ report_a_bug_button: { accessibility_id: 'menu item report a bug' },
417
+ about_button: { accessibility_id: 'menu item about' },
418
+ reset_app_button: { accessibility_id: 'menu item reset app' },
419
+ biometrics_button: { accessibility_id: 'menu item biometrics' },
420
+ log_in_button: { accessibility_id: 'menu item log in' },
421
+ log_out_button: { accessibility_id: 'menu item log out' },
422
+ api_calls_button: { accessibility_id: 'menu item api calls' },
423
+ sauce_video_button: { accessibility_id: 'menu item sauce bot video' }
424
+
425
+ def verify_ui
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' }
441
+ }
442
+ verify_ui_states(ui)
443
+ end
444
+
445
+ def close
446
+ close_button.click
447
+ self.wait_until_hidden(3)
448
+ end
449
+
450
+ def verify_closed
451
+ ui = {
452
+ self => { visible: true },
453
+ close_button => { visible: false }
454
+ }
455
+ verify_ui_states(ui)
456
+ end
457
+ end
458
+ ```
459
+
460
+ ### Adding ScreenSections to your ScreenObject
461
+
462
+ You add a `ScreenSection` to its associated `ScreenObject` as shown below:
463
+ ```ruby
464
+ class BaseAppScreen < TestCentricity::ScreenObject
465
+ # Base App screen UI elements
466
+ label :header_label, { accessibility_id: 'container header' }
467
+ sections nav_bar: NavBar,
468
+ nav_menu: NavMenu
469
+ end
470
+ ```
471
+ Once your `ScreenObject` has been instantiated, you can call its `ScreenSection` methods as shown below:
472
+ ```ruby
473
+ base_screen.nav_menu.verify_ui
474
+ ```
475
+
476
+ ---
477
+ ## AppUIElements
478
+
479
+ Native app `ScreenObjects` and `ScreenSections` are typically made up of **UI Element** like text fields, switches, lists,
480
+ buttons, etc. **UI Elements** are declared and instantiated within the class definition of the `ScreenObject` or `ScreenSection`
481
+ in which they are contained. With TestCentricity, all native app screen UI elements are based on the `AppUIElement` class.
482
+
483
+
484
+ ### Declaring and Instantiating AppUIElements
485
+
486
+ Single `AppUIElement` declarations have the following format:
487
+
488
+ elementType :elementName, { locator_strategy: locator_identifier }
489
+
490
+ * The `elementName` is the unique name that you will use to refer to the UI element and is specified as a `Symbol`.
491
+ * The `locator_strategy` specifies the selector strategy that Appium will use to find the `AppUIElement`. Valid selectors are:
492
+ - `accessibility_id:`
493
+ - `id:`
494
+ - `name:`
495
+ - `class:`
496
+ - `xpath:`
497
+ - `predicate:` (MacOS and iOS only)
498
+ - `class_chain:` (MacOS and iOS only)
499
+ - `uiautomator:` (Android only)
500
+ - `css:` (WebViews in hybrid mobile apps only).
501
+ * The `locator_identifier` is the value or attribute that uniquely and unambiguously identifies the `AppUIElement`.
502
+
503
+ Refer to [this page](https://github.com/appium/appium-mac2-driver?tab=readme-ov-file#element-location) for information on selector strategies for MacOS.
504
+ Refer to [this page](https://appium.github.io/appium-xcuitest-driver/5.12/locator-strategies/) for information on selector strategies for iOS.
505
+ Refer to [this page](https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#element-location) for information on selector strategies for Android.
506
+
507
+ Multiple `AppUIElement` declarations for a collection of elements of the same type can be performed by passing a hash table
508
+ containing the names and locators of each individual element.
509
+
510
+ ### Example AppUIElement Declarations
511
+
512
+ Supported `AppUIElement` elementTypes and their declarations have the following format:
513
+
514
+ *Single element declarations:*
515
+ ```ruby
516
+ class SampleScreen < TestCentricity::ScreenObject
517
+ button :button_name, { locator_strategy: locator_identifier }
518
+ textfield :field_name, { locator_strategy: locator_identifier }
519
+ checkbox :checkbox_name, { locator_strategy: locator_identifier }
520
+ radio :radio_name, { locator_strategy: locator_identifier }
521
+ label :label_name, { locator_strategy: locator_identifier }
522
+ list :list_name, { locator_strategy: locator_identifier }
523
+ selectlist :selectlist_name, { locator_strategy: locator_identifier }
524
+ image :image_name, { locator_strategy: locator_identifier }
525
+ switch :switch_name, { locator_strategy: locator_identifier }
526
+ element :element_name, { locator_strategy: locator_identifier }
527
+ alert :alert_name, { locator_strategy: locator_identifier }
528
+ end
529
+ ```
530
+ *Multiple element declarations:*
531
+ ```ruby
532
+ class SampleScreen < TestCentricity::ScreenObject
533
+ buttons button_1_name: { locator_strategy: locator_identifier },
534
+ button_2_name: { locator_strategy: locator_identifier },
535
+ button_X_name: { locator_strategy: locator_identifier }
536
+ textfields field_1_name: { locator_strategy: locator_identifier },
537
+ field_2_name: { locator_strategy: locator_identifier },
538
+ field_X_name: { locator_strategy: locator_identifier }
539
+ checkboxes check_1_name: { locator_strategy: locator_identifier },
540
+ check_2_name: { locator_strategy: locator_identifier },
541
+ check_X_name: { locator_strategy: locator_identifier }
542
+ radios radio_1_name: { locator_strategy: locator_identifier },
543
+ radio_2_name: { locator_strategy: locator_identifier }
544
+ lists list_1_name: { locator_strategy: locator_identifier },
545
+ list_X_name: { locator_strategy: locator_identifier }
546
+ selectlists menu_1_name: { locator_strategy: locator_identifier },
547
+ menu_X_name: { locator_strategy: locator_identifier }
548
+ labels label_1_name: { locator_strategy: locator_identifier },
549
+ label_X_name: { locator_strategy: locator_identifier }
550
+ images image_1_name: { locator_strategy: locator_identifier },
551
+ image_X_name: { locator_strategy: locator_identifier }
552
+ alerts alert_1_name: { locator_strategy: locator_identifier },
553
+ alert_X_name: { locator_strategy: locator_identifier }
554
+ end
555
+ ```
556
+ Refer to the Class List documentation for the `ScreenObject` and `ScreenSection` classes for details on the class methods
557
+ used for declaring and instantiating `AppUIElements`. Examples of UI element declarations can be found in the ***Adding
558
+ UI Elements to your ScreenObject*** and ***Adding UI Elements to your ScreenSection*** sections above.
559
+
560
+
561
+ ### AppUIElement Inherited Methods
562
+
563
+ With TestCentricity, all native app UI elements are based on the `AppUIElement` class, and inherit the following methods:
564
+
565
+ **Action methods:**
566
+
567
+ element.click
568
+ element.tap
569
+ element.double_tap
570
+ element.long_press
571
+ element.scroll_into_view
572
+ element.drag_by(right_offset, down_offset)
573
+ element.drag_and_drop(target)
574
+ element.swipe_gesture(direction, distance)
575
+
576
+ **Object state methods:**
577
+
578
+ element.exists?
579
+ element.visible?
580
+ element.hidden?
581
+ element.enabled?
582
+ element.disabled?
583
+ element.selected?
584
+ element.tag_name
585
+ element.width
586
+ element.height
587
+ element.x_loc
588
+ element.y_loc
589
+ element.count
590
+ element.get_attribute(attrib)
591
+ element.identifier (MacOS only)
592
+
593
+ **Waiting methods:**
594
+
595
+ element.wait_until_exists(seconds)
596
+ element.wait_until_gone(seconds)
597
+ element.wait_until_visible(seconds)
598
+ element.wait_until_hidden(seconds)
599
+ element.wait_until_enabled(seconds)
600
+ element.wait_until_value_is(value, seconds)
601
+ element.wait_until_value_changes(seconds)
602
+
603
+
604
+ ### Populating your ScreenObject or ScreenSection with data
605
+
606
+ A typical automated test may be required to perform the entry of test data by interacting with various `AppUIElements` on
607
+ your `ScreenObject` or `ScreenSection`. This data entry can be performed using the various object action methods (listed
608
+ above) for each `AppUIElement` that needs to be interacted with.
609
+
610
+ The `ScreenObject.populate_data_fields` and `ScreenSection.populate_data_fields` methods support the entry of test data
611
+ into a collection of `AppUIElements`. The `populate_data_fields` method accepts a hash containing key/hash pairs of
612
+ `AppUIElements` and their associated data to be entered. Data values must be in the form of a `String` for `textfield`
613
+ controls. For `checkbox`, `radio`, and `switch` controls, data must either be a `Boolean` or a `String` that evaluates to
614
+ a `Boolean` value ('Yes', 'No', '1', '0', 'true', 'false').
615
+
616
+ The `populate_data_fields` method verifies that data attributes associated with each `AppUIElement` is not `nil` or `empty`
617
+ before attempting to enter data into the `AppUIElement`.
618
+
619
+ The optional `wait_time` parameter is used to specify the time (in seconds) to wait for each `AppUIElement` to become
620
+ viable for data entry (the `AppUIElement` must be visible and enabled) before entering the associated data value. This
621
+ option is useful in situations where entering data, or setting the state of a `AppUIElement` might cause other `AppUIElements`
622
+ to become visible or active. Specifying a wait_time value ensures that the subsequent `AppUIElements` will be ready to
623
+ be interacted with as states are changed. If the wait time is `nil`, then the default wait time will be 5 seconds.
624
+
625
+ If any of the specified UI elements are not currently visible, the `populate_data_fields` method will attempt to scroll
626
+ the UI object in view on the vertical axis (down, then up).
627
+ ```ruby
628
+ def enter_data(user_data)
629
+ fields = {
630
+ first_name_field => user_data.first_name,
631
+ last_name_field => user_data.last_name,
632
+ email_field => user_data.email,
633
+ phone_number_field => user_data.phone_number
634
+ }
635
+ populate_data_fields(fields, wait_time = 2)
636
+ end
637
+ ```
638
+
639
+ ### Verifying AppUIElements on your ScreenObject or ScreenSection
640
+
641
+ A typical automated test executes one or more interactions with the user interface, and then performs a validation to
642
+ verify whether the expected state of the UI has been achieved. This verification can be performed using the various object
643
+ state methods(listed above) for each `AppUIElement` that requires verification. Depending on the complexity and number of
644
+ `AppUIElements` to be verified, the code required to verify the presence of `AppUIElements` and their correct states can
645
+ become cumbersome.
646
+
647
+ The `ScreenObject.verify_ui_states` and `ScreenSection.verify_ui_states` methods support the verification of multiple
648
+ properties of multiple UI elements on a `ScreenObject` or `ScreenSection`. The `verify_ui_states` method accepts a hash
649
+ containing key/hash pairs of UI elements and their properties or attributes to be verified.
650
+ ```ruby
651
+ 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
+ ```
658
+ The `verify_ui_states` method automatically scrolls UI elements that are expected to be visible into view. Auto-scrolling
659
+ only occurs on the vertical axis (down, then up). Setting the `auto_scroll` parameter to `false` prevents automatic scrolling
660
+ from occurring.
661
+
662
+ The `verify_ui_states` method queues up any exceptions that occur while verifying each object's properties until all
663
+ `AppUIElements`and their properties have been checked, and then posts any exceptions encountered upon completion. Posted
664
+ exceptions include a screenshot of the screen where expected results did not match actual results.
665
+
666
+ The `verify_ui_states` method supports the following property/state pairs:
667
+
668
+ **All Objects:**
669
+
670
+ :exists Boolean
671
+ :enabled Boolean
672
+ :disabled Boolean
673
+ :visible Boolean
674
+ :hidden Boolean
675
+ :width Integer
676
+ :height Integer
677
+ :x Integer
678
+ :y Integer
679
+ :count Integer
680
+ :value String
681
+ :caption String
682
+ :attribute Hash
683
+ :class String
684
+
685
+ **Text Fields:**
686
+
687
+ :placeholder String
688
+ :readonly Boolean (WebViews only)
689
+ :maxlength Integer (WebViews only)
690
+
691
+ **Checkboxes and Switches:**
692
+
693
+ :checked Boolean
694
+
695
+ **Radio Buttons:**
696
+
697
+ :selected Boolean
698
+
699
+ **Lists and SelectLists**
700
+
701
+ :items Array of Strings
702
+ :itemcount Integer
703
+
704
+ **Menu Bars**
705
+
706
+ :items Array of Strings
707
+ :itemcount Integer
708
+
709
+ **Menus**
710
+
711
+ :items Array of Strings
712
+ :itemcount Integer
713
+ :item_data Array of Hash
714
+
715
+ #### Comparison States
716
+
717
+ The `verify_ui_states` method supports comparison states using property/comparison state pairs:
718
+
719
+ object => { property: { comparison_state: value } }
720
+
721
+ Comparison States:
722
+
723
+ :lt or :less_than Integer or String
724
+ :lt_eq or :less_than_or_equal Integer or String
725
+ :gt or :greater_than Integer or String
726
+ :gt_eq or :greater_than_or_equal Integer or String
727
+ :starts_with String
728
+ :ends_with String
729
+ :contains String
730
+ :not_contains or :does_not_contain Integer or String
731
+ :not_equal Integer, String, or Boolean
732
+
733
+
734
+ #### I18n Translation Validation
735
+
736
+ The `verify_ui_states` method also supports I18n string translations using property/I18n key name pairs:
737
+
738
+ object => { property: { translate_key: 'name of key in I18n compatible .yml file' } }
739
+
740
+ **I18n Translation Keys:**
741
+
742
+ :translate String
743
+ :translate_upcase String
744
+ :translate_downcase String
745
+ :translate_capitalize String
746
+ :translate_titlecase String
747
+
748
+ The example below depicts the usage of the `verify_ui_states` method to verify that the captions for navigation menu items
749
+ are correctly translated.
750
+
751
+ ![Localized UI](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/LocalizedUI.png "Localized UI")
752
+ ```ruby
753
+ def verify_menu
754
+ 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
+ }
787
+ }
788
+ verify_ui_states(ui)
789
+ end
790
+ ```
791
+ I18n `.yml` files contain key/value pairs representing the name of a translated string (key) and the string value. For the
792
+ menu example above, the translated strings for English, Spanish, and French are represented below:
793
+
794
+ **English** - `en.yml`
795
+ ```yaml
796
+ en:
797
+ NavMenu:
798
+ title: 'Main Menu'
799
+ recipes: 'Recipes'
800
+ browser: 'Browser'
801
+ groceries: 'Groceries'
802
+ pantry: 'Pantry'
803
+ meals: 'Meals'
804
+ menus: 'Menus'
805
+ settings: 'Settings'
806
+ ```
807
+ **Spanish** - `es.yml`
808
+ ```yaml
809
+ es:
810
+ NavMenu:
811
+ title: 'Menú principal'
812
+ recipes: 'Recetas'
813
+ browser: 'Navegador'
814
+ groceries: 'Compra'
815
+ pantry: 'Despensa'
816
+ meals: 'Comidas'
817
+ menus: 'Menús'
818
+ settings: 'Ajustes'
819
+ ```
820
+ **French** - `fr.yml`
821
+ ```yaml
822
+ fr:
823
+ NavMenu:
824
+ title: 'Menu principal'
825
+ recipes: 'Recettes'
826
+ browser: 'Navigateur'
827
+ groceries: 'Courses'
828
+ pantry: 'Provisions'
829
+ meals: 'Repas'
830
+ menus: 'Menus'
831
+ settings: 'Réglades'
832
+ ```
833
+
834
+ Each supported language/locale combination has a corresponding `.yml` file. I18n `.yml` file naming convention uses
835
+ [ISO-639 language codes and ISO-3166 country codes](https://docs.oracle.com/cd/E13214_01/wli/docs92/xref/xqisocodes.html). For example:
836
+
837
+ | Language (Country) | File name |
838
+ |-----------------------|-----------|
839
+ | English | en.yml |
840
+ | English (Canada) | en-CA.yml |
841
+ | French (Canada) | fr-CA.yml |
842
+ | French | fr.yml |
843
+ | Spanish | es.yml |
844
+ | German | de.yml |
845
+ | Portuguese (Brazil) | pt-BR.yml |
846
+ | Portuguese (Portugal) | pt-PT.yml |
847
+
848
+ Baseline translation strings are stored in `.yml` files in the `config/locales/` folder.
849
+
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
863
+
864
+
865
+ ### Working With Custom AppUIElements
866
+
867
+ #### Vertical Scrolling ListView
868
+
869
+ `AppUIElements` like ListViews (`AppList` class) are typically made up of multiple composite UI component types, which will
870
+ be different for iOS vs. Android mobile platforms. Below is an example of the vertical scrolling ListView implementations
871
+ for a cross-platform application implemented using React Native (iOS version on the left, Android version on the right).
872
+ Each ListView contains 30 items:
873
+
874
+ ![Vertical Scrolling ListView](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/ListViews.png "Vertical Scrolling ListViews")
875
+
876
+ While the iOS and Android ListViews appear to be identical in the app, performing an inspection of each application's GUI
877
+ using Appium Inspector reveals differences in the object hierarchy as depicted below (iOS version on left, Android version
878
+ on the right):
879
+
880
+ ![Vertical Scrolling ListView Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/ListHeirarchy.png "Vertical Scrolling ListView Hierarchy")
881
+
882
+ The inspection of the ListView object hierarchy reveals that for the iOS version of the app, list items are made up of
883
+ `XCUIElementTypeOther` objects, and that for the Android version of the app, list items are made up of `android.view.ViewGroup`
884
+ objects.
885
+
886
+ The other, more notable difference is that while the iOS inspection shows all 30 list items, only 13 list items are shown
887
+ in the inspection of the Android app, which corresponds to the list items that are visible on the Android device screen.
888
+ When testing Android apps using the `UiAutomator2` driver for Appium, UI objects that are not displayed on screen cannot
889
+ be detected by Appium Inspector or by Appium based frameworks until the objects are scrolled into view.
890
+
891
+ The `AppList.define_list_elements` method provides a means of specifying the objects that make up the list item components
892
+ of an `AppList` control, and the axis in which scrolling of the list items occurs. The method accepts a hash that can contain
893
+ up to two key-value pairs. Valid key designators are `:list_item`and `:scrolling`. The `AppList.define_list_elements` method
894
+ is typically called in the `initialize` method of the `ScreenObject` or `ScreenSection` that contains the associated `AppList`
895
+ control.
896
+
897
+ The code snippets below demonstrate the use of the `AppList.define_list_elements` method in the `CloudListScreen` screen
898
+ object's `initialize` method to define the list item components that make up the Clouds vertical scrolling ListView from
899
+ the above examples. It is not necessary to specify the scroll axis in the code below, as `:vertical` is the default scroll
900
+ axis that is set when instantiating an `AppList` element.
901
+
902
+ iOS Cloud List `ScreenObject`
903
+ ```ruby
904
+ class CloudListScreen < TestCentricity::ScreenObject
905
+ trait(:screen_name) { 'Cloud List' }
906
+ trait(:screen_locator) { { class_chain: '**/XCUIElementTypeWindow/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther' } }
907
+
908
+ # Cloud List screen UI elements
909
+ list :cloud_list, { class_chain: '**/XCUIElementTypeScrollView/XCUIElementTypeOther' }
910
+
911
+ def initialize
912
+ super
913
+ # define the list item element for the Cloud list object
914
+ list_spec = { list_item: { class: 'XCUIElementTypeOther' } }
915
+ cloud_list.define_list_elements(list_spec)
916
+ end
917
+ end
918
+ ```
919
+
920
+ Android CloudListScreen `ScreenObject`
921
+ ```ruby
922
+ class CloudListScreen < TestCentricity::ScreenObject
923
+ trait(:screen_name) { 'Cloud List' }
924
+ trait(:screen_locator) { { xpath: '//android.widget.FrameLayout[@resource-id="android:id/content"]/android.view.ViewGroup' } }
925
+
926
+ # Cloud List screen UI elements
927
+ list :cloud_list, { xpath: '//android.widget.ScrollView/android.view.ViewGroup' }
928
+
929
+ def initialize
930
+ super
931
+ # define the list item element for the Cloud list object
932
+ list_spec = { list_item: { class: 'android.view.ViewGroup' } }
933
+ cloud_list.define_list_elements(list_spec)
934
+ end
935
+ end
936
+ ```
937
+
938
+
939
+ #### Horizontal Scrolling ListView
940
+
941
+ Below is an example of a horizontal scrolling "Carousel" style ListView implementations on the Swipe screen of a cross-platform
942
+ application. Each ListView contains 6 list items.
943
+
944
+ ![Horizontal Scrolling Carousel ListView](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/Carousel.png "Horizontal Scrolling Carousel ListViews")
945
+
946
+ While the iOS and Android ListViews appear to be identical in the app, performing an inspection of each application's GUI
947
+ using Appium Inspector reveals differences in the object hierarchy as depicted below (iOS version on left, Android version
948
+ on the right):
949
+
950
+ ![Horizontal Scrolling Carousel Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/CarouselHierarchy.png "Horizontal Scrolling Carousel Hierarchy")
951
+
952
+ As in the previous example for the vertical scrolling ListView, the inspection of the Carousel ListView object hierarchy
953
+ reveals that for the iOS version of the app, list items are again made up of `XCUIElementTypeOther` objects, and that for
954
+ the Android version of the app, list items are again made up of `android.view.ViewGroup` objects.
955
+
956
+ As in the previous examples, the iOS inspection shows all 6 list items, while only 2 list items are shown in the inspection
957
+ of the Android app, which corresponds to the list items that are visible on the Android device screen.
958
+
959
+ The code snippets below demonstrate the use of the `AppList.define_list_elements` method in the `SwipeScreen` screen object's
960
+ `initialize` method to define the scroll axis and list item components that make up the Carousel horizontal scrolling ListView
961
+ from the above examples.
962
+
963
+ iOS Swipe `ScreenObject`
964
+ ```ruby
965
+ class SwipeScreen < TestCentricity::ScreenObject
966
+ trait(:screen_name) { 'Swipe' }
967
+ trait(:screen_locator) { { accessibility_id: 'Swipe-screen' } }
968
+
969
+ # Swipe screen UI elements
970
+ list :carousel_list, { accessibility_id: 'Carousel' }
971
+
972
+ def initialize
973
+ super
974
+ # define the list item element for the Carousel list object
975
+ list_spec = {
976
+ list_item: { xpath: '//XCUIElementTypeOther[contains(@name, "__CAROUSEL_ITEM_")]' },
977
+ scrolling: :horizontal
978
+ }
979
+ carousel_list.define_list_elements(list_spec)
980
+ end
981
+ end
982
+ ```
983
+
984
+ Android Swipe `ScreenObject`
985
+ ```ruby
986
+ class SwipeScreen < TestCentricity::ScreenObject
987
+ trait(:screen_name) { 'Swipe' }
988
+ trait(:screen_locator) { { accessibility_id: 'Swipe-screen' } }
989
+
990
+ # Swipe screen UI elements
991
+ list :carousel_list, { accessibility_id: 'Carousel' }
992
+
993
+ def initialize
994
+ super
995
+ # define the list item element for the Carousel list object
996
+ list_spec = {
997
+ list_item: { xpath: '//android.view.ViewGroup[contains(@resource-id, "__CAROUSEL_ITEM_")]' },
998
+ scrolling: :horizontal
999
+ }
1000
+ carousel_list.define_list_elements(list_spec)
1001
+ end
1002
+ end
1003
+ ```
1004
+
1005
+
1006
+ #### Popup and PickerWheel Style ListViews
1007
+
1008
+ Below is an example of a PickerWheel (iOS) and Popup (Android) style ListView implementations on the Form Components screen
1009
+ of a cross-platform application.
1010
+
1011
+ ![PickerWheel and Popup ListViews](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/Popup_Picker.png "PickerWheel and Popup ListViews")
1012
+
1013
+ Performing an inspection of each application's GUI using Appium Inspector reveals differences in the object hierarchy as
1014
+ depicted below (iOS version on left, Android version on the right):
1015
+
1016
+ ![PickerWheel and Popup ListView Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_mobile/main/.github/images/PopupHeirarchy.png "PickerWheel and Popup ListView Hierarchy")
1017
+
1018
+ The inspection of the PickerWheel and Popup ListView object hierarchies reveals that for the iOS version of the app, list
1019
+ items are again made up of `XCUIElementTypeOther` objects, and that for the Android version of the app, list items are made
1020
+ up of `android.widget.CheckedTextView` objects.
1021
+
1022
+ However, `XCUIElementTypePickerWheel` controls present testability challenges with Appium, as the `XCUIElementTypeOther`
1023
+ objects that comprise the individual list items cannot be reliably interacted with or validated. When inspecting each of
1024
+ the `XCUIElementTypeOther` list items of the `XCUIElementTypePickerWheel` control, there are no `text`, `accessibility_id`,
1025
+ `label`, or `value` element attributes available which could be used to determine whether the correct caption strings are
1026
+ displayed for each list item. The `AppList.get_item_count` or `get_list_items` methods do not support `XCUIElementTypePickerWheel`
1027
+ controls, and will raise an exception if called for such a control.
1028
+
1029
+ For the Android version of the app, the `android.widget.CheckedTextView` list items can be interacted with and validated,
1030
+ as the `text` element attribute for each list item are visible in the inspection.
1031
+
1032
+ The code snippet below demonstrate the use of the `AppList.define_list_elements` method in the `FormScreen` screen object's
1033
+ `initialize` method to define the list item components that make up the Android Popup style ListView from the above example.
1034
+
1035
+ Android FormScreen `ScreenObject`
1036
+ ```ruby
1037
+ class FormScreen < TestCentricity::ScreenObject
1038
+ trait(:screen_name) { 'Form' }
1039
+ trait(:screen_locator) { { accessibility_id: 'Forms-screen' } }
1040
+
1041
+ # Form screen UI elements
1042
+ list :drop_down_menu, { id: 'com.wdiodemoapp:id/select_dialog_listview' }
1043
+
1044
+ def initialize
1045
+ super
1046
+ # define the list item element for the drop-down list object
1047
+ list_spec = { list_item: { class: 'android.widget.CheckedTextView' } }
1048
+ drop_down_menu.define_list_elements(list_spec)
1049
+ end
1050
+ end
1051
+ ```
1052
+
1053
+
1054
+ ---
1055
+ ## MacOS Application Menu Bar and Menus
1056
+
1057
+ With MacOS desktop applications, the menu bar at the top of the screen displays the top-level menus associated with your
1058
+ app, and typically includes both system-provided menus and app-specific menus. Below is the menu bar and **View** menu
1059
+ associated with the MacOs Calculator application:
1060
+
1061
+ ![MacOS Calculator App MenuBar](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/MacOS_Menus.png "MacOS Calculator App MenuBar")
1062
+
1063
+ Performing an inspection of the Calculator application's GUI using Appium Inspector reveals that the `XCUIElementTypeMenuBar`
1064
+ object is not a child object of the Calculator app's main `XCUIElementTypeWindow`, but resides at the top level of the
1065
+ MacOS app object hierarchies, along with the app's windows.
1066
+
1067
+ ![Calculator App MenuBar Hierarchy](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/MenuBarHierarchy.png "Calculator App MenuBar Hierarchy")
1068
+
1069
+
1070
+ ### Defining a MenuBar
1071
+
1072
+ TestCentricity For Apps provides a `MenuBar` class object, that is a special subclass of the `ScreenSection` class object.
1073
+ You define a new `MenuBar` as shown below:
1074
+ ```ruby
1075
+ class CalculatorMenuBar < TestCentricity::MenuBar
1076
+ end
1077
+ ```
1078
+
1079
+ ### Adding Menus to your MenuBar
1080
+
1081
+ A `MenuBar` is typically made up of one or more `Menu` objects, which are added to your `MenuBar` class definition as shown
1082
+ below:
1083
+ ```ruby
1084
+ class CalculatorMenuBar < TestCentricity::MenuBar
1085
+ # Calculator Menu Bar UI elements
1086
+ menus calc_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[2]' },
1087
+ file_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[3]' },
1088
+ edit_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[4]' },
1089
+ view_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[5]' },
1090
+ view_sub_menu: { predicate: 'identifier == "_NS:335"' },
1091
+ convert_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[6]' },
1092
+ convert_sub_menu: { predicate: 'identifier == "_NS:352"' },
1093
+ speech_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[7]' },
1094
+ window_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[8]' },
1095
+ help_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[9]' }
1096
+ end
1097
+ ```
1098
+
1099
+ ### Adding Methods to your MenuBar
1100
+
1101
+ You can add methods to your `MenuBar` class definition, as shown below:
1102
+ ```ruby
1103
+ class CalculatorMenuBar < TestCentricity::MenuBar
1104
+ # Calculator Menu Bar UI elements
1105
+ menus calc_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[2]' },
1106
+ file_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[3]' },
1107
+ edit_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[4]' },
1108
+ view_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[5]' },
1109
+ view_sub_menu: { predicate: 'identifier == "_NS:335"' },
1110
+ convert_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[6]' },
1111
+ convert_sub_menu: { predicate: 'identifier == "_NS:352"' },
1112
+ speech_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[7]' },
1113
+ window_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[8]' },
1114
+ help_menu: { class_chain: '**/XCUIElementTypeMenuBarItem[9]' }
1115
+
1116
+ def choose_menu_item(menu, item, method = :mouse)
1117
+ menu_map = {
1118
+ calc: calc_menu,
1119
+ calculator: calc_menu,
1120
+ file: file_menu,
1121
+ edit: edit_menu,
1122
+ view: view_menu,
1123
+ view_sub_menu: view_sub_menu,
1124
+ convert: convert_menu,
1125
+ convert_sub_menu: convert_sub_menu,
1126
+ window: window_menu
1127
+ }
1128
+ menu_obj = menu_map[menu]
1129
+ raise "#{menu} is not a supported menu" if menu_obj.nil?
1130
+ menu_obj.choose_menu_item(item, method)
1131
+ end
1132
+
1133
+ def verify_menu_bar
1134
+ ui = {
1135
+ self => {
1136
+ enabled: true,
1137
+ items: %w[Apple Calculator File Edit View Convert Speech Window Help],
1138
+ itemcount: 9
1139
+ },
1140
+ calc_menu => {
1141
+ enabled: true,
1142
+ itemcount: 8,
1143
+ item_data: [
1144
+ { caption: 'About Calculator', enabled: true },
1145
+ { caption: '', enabled: false },
1146
+ { caption: 'Hide Calculator', enabled: true },
1147
+ { caption: 'Hide Others', enabled: true },
1148
+ { caption: 'Show All', enabled: false },
1149
+ { caption: '', enabled: false },
1150
+ { caption: 'Quit Calculator', enabled: true },
1151
+ { caption: 'Quit and Keep Windows', enabled: true }
1152
+ ]
1153
+ },
1154
+ speech_menu => {
1155
+ enabled: true,
1156
+ itemcount: 2,
1157
+ items: ['Speak Button Pressed', 'Speak Result']
1158
+ }
1159
+ }
1160
+ verify_ui_states(ui)
1161
+ end
1162
+ end
1163
+ ```
1164
+
1165
+ ### Defining Keyboard Shortcuts for your App's Menus
1166
+
1167
+ The Appium Mac2 Driver does not support element attributes that could be used to verify the checked state of a menu item
1168
+ or the keyboard shortcut assigned to a menu item. However, it is possible to verify that menu item keyboard shortcuts are
1169
+ functional by mapping the keyboard shortcuts to a particular menu item and then calling `AppMenu.choose_menu_item` with
1170
+ the `method` parameter set to `:keys` or `:keyboard` and then verifying that the expected menu-triggered action occurs.
1171
+
1172
+ The `AppMenu.define_menu_elements` method provides a means of specifying the objects that make up the menu item components
1173
+ of an `AppMenu` control, and the keyboard shortcut map for each menu item in a menu. The method accepts a hash that can
1174
+ contain up to two key-value pairs. Valid key designators are `:menu_item` and `:key_map`.
1175
+
1176
+ When `AppMenu` objects are instantiated, the `:menu_item` attribute is set to the `XCUIElementTypeMenuItem` class, which
1177
+ is the default menu item object for MacOS applications. The `AppMenu.define_menu_elements` method is typically called in
1178
+ the `initialize` method of your app's `MenuBar` control.
1179
+
1180
+ The code snippet below demonstrate the use of the `AppMenu.define_menu_elements` method in the `CalculatorMenuBar` object's
1181
+ `initialize` method to define the keyboard shortcut mapping for 4 of the menu items in the **View** menu and 1 of the menu
1182
+ 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*).
1184
+
1185
+ ```ruby
1186
+ class CalculatorMenuBar < TestCentricity::MenuBar
1187
+
1188
+ def initialize(name, parent, locator, context)
1189
+ super
1190
+ # define key map for View menu
1191
+ menu_items = {
1192
+ key_map: {
1193
+ 1 => [key: '1', modifierFlags: XCUIKeyModifierCommand],
1194
+ 2 => [key: '2', modifierFlags: XCUIKeyModifierCommand],
1195
+ 3 => [key: '3', modifierFlags: XCUIKeyModifierCommand],
1196
+ 7 => [key: 'r', modifierFlags: XCUIKeyModifierCommand]
1197
+ }
1198
+ }
1199
+ view_menu.define_menu_elements(menu_items)
1200
+ # define key map for Window menu
1201
+ menu_items = {
1202
+ key_map: { 'Show Paper Tape' => [key: 't', modifierFlags: XCUIKeyModifierCommand] }
1203
+ }
1204
+ window_menu.define_menu_elements(menu_items)
1205
+ end
1206
+ end
1207
+ ```
1208
+ The `modifierFlags` argument is an `unsigned long` type and defines the bitmask with depressed modifier keys for the given
1209
+ key. TestCentricity and XCTest defines the following possible bitmasks for modifier keys:
1210
+ ```ruby
1211
+ XCUIKeyModifierNone = 0
1212
+ XCUIKeyModifierCapsLock = (1 << 0)
1213
+ XCUIKeyModifierShift = (1 << 1)
1214
+ XCUIKeyModifierControl = (1 << 2)
1215
+ XCUIKeyModifierOption = (1 << 3)
1216
+ XCUIKeyModifierCommand = (1 << 4)
1217
+ XCUIKeyModifierFunction = (1 << 5)
1218
+ ```
1219
+ 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
+
1221
+ ### Adding a MenuBar to your App's Primary ScreenObject
1222
+
1223
+ You add a `MenuBar` to your app's primary `ScreenObject` as shown below:
1224
+ ```ruby
1225
+ class CalculatorAppScreen < TestCentricity::ScreenObject
1226
+ # Calculator App screen UI elements
1227
+ menubar :calc_menu_bar, CalculatorMenuBar
1228
+ end
1229
+ ```
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
+
1235
+
1236
+ ---
1237
+ ## Instantiating ScreenObjects and Utilizing the ScreenManager
1238
+
1239
+ Before you can call the methods in your `ScreenObjects` and `ScreenSections`, you must instantiate the `ScreenObjects` of
1240
+ your MacOS dektop app or native mobile application, as well as create instance variables which can be used when calling
1241
+ `ScreenObject` methods from your step definitions or specs.
1242
+
1243
+ The `ScreenManager` class provides methods for supporting the instantiation and management of `ScreenObjects`. In the code
1244
+ example below, the `screen_objects` method contains a hash table of your `ScreenObject` instances and their associated
1245
+ `ScreenObject` classes to be instantiated by `ScreenManager`:
1246
+ ```ruby
1247
+ module WorldScreens
1248
+ def screen_objects
1249
+ {
1250
+ login_screen: LoginScreen,
1251
+ registration_screen: RegistrationScreen,
1252
+ search_results_screen: SearchResultsScreen,
1253
+ products_grid_screen: ProductsCollectionScreen,
1254
+ product_detail_screen: ProductDetailScreen,
1255
+ shopping_basket_screen: ShoppingBasketScreen,
1256
+ payment_method_screen: PaymentMethodScreen,
1257
+ confirm_purchase_screen: PurchaseConfirmationScreen,
1258
+ my_account_screen: MyAccountScreen,
1259
+ my_order_history_screen: MyOrderHistoryScreen
1260
+ }
1261
+ end
1262
+ end
1263
+
1264
+ World(WorldScreens)
1265
+ ```
1266
+
1267
+ The `WorldScreens` module above should be defined in the `world_screens.rb` file in the `features/support` folder.
1268
+
1269
+ Include the code below in your `env.rb` file to ensure that your `ScreenObjects` are instantiated before your Cucumber
1270
+ scenarios are executed:
1271
+ ```ruby
1272
+ include WorldScreens
1273
+ WorldPages.instantiate_screen_objects
1274
+ ```
1275
+ **NOTE:** If you intend to use the `ScreenManager`, you must define a `screen_name` trait for each of the `ScreenObjects`
1276
+ to be registered.
1277
+
1278
+
1279
+ ### Leveraging the ScreenManager in your Cucumber tests
1280
+
1281
+ Many Cucumber based automated tests suites include scenarios that verify that mobile app screens are correctly loaded,
1282
+ displayed, or can be navigated to by clicking associated menus and navigation elements. One such Cucumber navigation
1283
+ scenario is displayed below:
1284
+ ```gherkin
1285
+ Scenario Outline: Verify screen navigation features
1286
+ Given I am on the Products screen
1287
+ When I tap the <screen_name> navigation menu item
1288
+ Then I expect the <screen_name> screen to be correctly displayed
1289
+
1290
+ Examples:
1291
+ |screen_name |
1292
+ |Registration |
1293
+ |Shopping Basket |
1294
+ |My Account |
1295
+ |My Order History |
1296
+ ```
1297
+ In the above example, the step definitions associated with the 3 steps can be implemented using the `ScreenManager.find_screen`
1298
+ method to match the specified `screen_name` argument with the corresponding `ScreenObject` as shown below:
1299
+ ```ruby
1300
+ include TestCentricity
1301
+
1302
+ When(/^I (?:load|am on) the (.*) screen$/) do |screen_name|
1303
+ # find and load the specified target screen
1304
+ target_screen = ScreenManager.find_screen(screen_name)
1305
+ target_screen.load_screen
1306
+ end
1307
+
1308
+
1309
+ When(/^I (?:click|tap) the ([^\"]*) navigation menu item$/) do |screen_name|
1310
+ # find and navigate to the specified target screen
1311
+ target_screen = ScreenManager.find_screen(screen_name)
1312
+ target_screen.navigate_to
1313
+ end
1314
+
1315
+
1316
+ Then(/^I expect the (.*) screen to be correctly displayed$/) do |screen_name|
1317
+ # find and verify that the specified target screen is loaded
1318
+ target_screen = ScreenManager.find_screen(screen_name)
1319
+ target_screen.verify_screen_exists
1320
+ # verify that target screen is correctly displayed
1321
+ target_screen.verify_screen_ui
1322
+ end
1323
+ ```
1324
+
1325
+
1326
+ ---
1327
+ ## Connecting to a MacOS Desktop Application
1328
+
1329
+ The `TestCentricity::AppiumConnect.initialize_appium` method configures the appropriate Appium capabilities required to
1330
+ establish a connection with a locally hosted MacOS desktop application. The `initialize_appium` method accepts an `options`
1331
+ hash for specifying desired capabilities (using the W3C protocol), driver type, driver name, and endpoint URL information.
1332
+
1333
+ The following options and capabilities must be specified in the `options` hash:
1334
+ - `driver:` must be set to `:appium`
1335
+ - `platformName:` must be set to `:mac` in the `capabilities:` hash
1336
+ - `'appium:automationName':` must be set to `mac2` in the `capabilities:` hash
1337
+ - `'appium:bundleId'`: must be set to the Bundle ID of the MacOS app to be tested
1338
+
1339
+ ```ruby
1340
+ options = {
1341
+ driver: :appium,
1342
+ capabilities: {
1343
+ platformName: :mac,
1344
+ 'appium:automationName': 'mac2',
1345
+ 'appium:bundleId': 'com.apple.calculator'
1346
+ }
1347
+ }
1348
+ AppiumConnect.initialize_appium(options)
1349
+ ```
1350
+ Additional options that can be specified in an `options` hash include the following:
1351
+
1352
+ | Option | Purpose |
1353
+ |------------------|--------------------------------------------------------------------------------|
1354
+ | `driver_name:` | optional driver name |
1355
+ | `endpoint:` | optional endpoint URL for local Appium server or cloud hosted service provider |
1356
+ | `global_driver:` | define new driver with global scope if `true` |
1357
+
1358
+ Refer to [this page](https://github.com/appium/appium-mac2-driver#capabilities) for information regarding specifying Appium capabilities that are
1359
+ specific to the Mac2 Driver driver.
1360
+
1361
+ The Appium server must be running prior to invoking Cucumber to run your features/scenarios on a locally hosted MacOS desktop
1362
+ application. Refer to [**Section 10.2.3 (Starting and Stopping Appium Server)**](#starting-and-stopping-appium-server) below.
1363
+
1364
+ ---
1365
+ ## Connecting to an iOS or Android Mobile Simulator or Device
1366
+
1367
+ The `AppiumConnect.initialize_appium` method configures the appropriate Appium capabilities required to establish a connection
1368
+ with a locally or cloud hosted target iOS or Android simulator or real device.
1369
+
1370
+ Since its inception, TestCentricity has provided support for establishing a single connection to a target iOS or Android
1371
+ simulator or real device by instantiating an Appium driver object. **Environment Variables** are used to specify the local
1372
+ or remote cloud hosted target platform, and the various Appium capability parameters required to configure the driver object.
1373
+ The appropriate **Environment Variables** are typically specified in the command line at runtime through the use of profiles
1374
+ set in a `cucumber.yml` file (Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml`)**](#using-configuration-specific-profiles-in-cucumber-yml) below).
1375
+
1376
+ However, due to the growing number of optional Appium capabilities that are being offered by cloud hosted service providers
1377
+ (like BrowserStack, Sauce Labs, TestingBot, or LambdaTest), **Environment Variables** may not effectively address.
1378
+
1379
+ Beginning with TestCentricity version 4.0.0, the `TestCentricity::AppiumConnect.initialize_appium` method accepts an optional
1380
+ `options` hash for specifying desired capabilities (using the W3C protocol), driver type, driver name, endpoint URL, and
1381
+ device type information.
1382
+
1383
+
1384
+ ### Specifying Options and Capabilities in the `options` Hash
1385
+
1386
+ For those test scenarios where cumbersome **Environment Variables** are less than ideal, call the `AppiumConnect.initialize_appium`
1387
+ method with an `options` hash that specifies the Appium desired capabilities, the driver type, and the device type, as depicted
1388
+ in the example below:
1389
+ ```ruby
1390
+ options = {
1391
+ driver: :appium,
1392
+ devicetype: :phone or :tablet,
1393
+ capabilities: {
1394
+ platformName: :ios or :android,
1395
+ 'appium:platformVersion': os_version,
1396
+ 'appium:deviceName': device_name,
1397
+ 'appium:automationName': 'XCUITest' or 'UiAutomator2',
1398
+ 'appium:app': path_to_app
1399
+ }
1400
+ }
1401
+ AppiumConnect.initialize_appium(options)
1402
+ ```
1403
+ Additional options that can be specified in an `options` hash include the following:
1404
+
1405
+ | Option | Purpose |
1406
+ |------------------|--------------------------------------------------------------------------------|
1407
+ | `driver_name:` | optional driver name |
1408
+ | `endpoint:` | optional endpoint URL for local Appium server or cloud hosted service provider |
1409
+ | `global_driver:` | define new driver with global scope if `true` |
1410
+
1411
+
1412
+ Details on specifying desired capabilities, driver type, endpoint URL, global driver scope, and default driver names are
1413
+ provided in each of the platform hosting sections below.
1414
+
1415
+ #### Specifying the Driver Type
1416
+
1417
+ The `driver:` type is a required entry in the `options` hash when instantiating an Appium driver object using the
1418
+ `initialize_appium` method. Valid `driver:` type values are listed in the table below:
1419
+
1420
+ | `driver:` | **Driver Type** |
1421
+ |-----------------|-----------------------------------------------------------------------|
1422
+ | `:appium` | locally hosted native iOS/Android device simulator or physical device |
1423
+ | `:browserstack` | remote hosted on BrowserStack |
1424
+ | `:saucelabs` | remote hosted on Sauce Labs |
1425
+ | `:testingbot` | remote hosted on TestingBot |
1426
+ | `:custom` | remote hosted on unsupported cloud based hosting services |
1427
+
1428
+ #### Specifying a Driver Name
1429
+
1430
+ An optional user defined `driver_name:` can be specified in the `options` hash when instantiating an Appium driver object
1431
+ using the `AppiumConnect.initialize_appium` method. If a driver name is not specified, the `initialize_appium` method will
1432
+ assign a default driver name comprised of the specified driver type (`driver:`) and the device OS and device type specified
1433
+ in the `capabilities:` hash. Details on default driver names are provided in each of the device/simulator hosting sections
1434
+ below.
1435
+
1436
+
1437
+ ### Connecting to Locally Hosted Simulators or Physical Devices
1438
+
1439
+ Refer to [this page](https://appium.io/docs/en/2.6/guides/caps/) for information regarding specifying Appium capabilities. The Appium server must be running prior
1440
+ to invoking Cucumber to run your features/scenarios on locally hosted iOS or Android simulators or physical devices. Refer
1441
+ to [**Section 10.2.3 (Starting and Stopping Appium Server)**](#starting-and-stopping-appium-server) below.
1442
+
1443
+ ⚠️ If you are running locally hosted mobile tests on iOS or Android simulators or devices using version 1.x of the Appium
1444
+ server, the `APPIUM_SERVER_VERSION` environment variable must be set to `1` in order to ensure that the correct Appium server
1445
+ endpoint is used.
1446
+
1447
+ #### Connecting to Locally Hosted iOS Simulators or Physical Devices
1448
+
1449
+ You can run your automated tests on locally hosted iOS simulators or physically connected devices using Appium and XCode
1450
+ on macOS. You must install Appium, XCode, and the iOS version-specific device simulators for XCode. Information about
1451
+ Appium setup and configuration requirements with the XCUITest driver for testing on physically connected iOS devices can
1452
+ be found on [this page](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/preparation/real-device-config.md). Refer to [this page](https://appium.github.io/appium-xcuitest-driver/latest/capabilities/) for information regarding specifying Appium capabilities that are
1453
+ specific to the XCUITest driver.
1454
+
1455
+ ##### Local iOS Simulators or Physical Devices using Environment Variables
1456
+
1457
+ If the `options` hash is not provided when calling the `TestCentricity::AppiumConnect.initialize_appium` method, the following
1458
+ **Environment Variables** must be set as described in the table below.
1459
+
1460
+ | **Environment Variable** | **Description** |
1461
+ |--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1462
+ | `DRIVER` | Must be set to `appium` |
1463
+ | `APP_PLATFORM_NAME` | Must be set to `iOS` |
1464
+ | `AUTOMATION_ENGINE` | Must be set to `XCUITest` |
1465
+ | `APP_VERSION` | Must be set to `17.4`, `16.2`, or which ever iOS version you wish to run within the XCode Simulator |
1466
+ | `APP_DEVICE` | Set to iOS device name supported by the iOS Simulator (`iPhone 13 Pro Max`, `iPad Pro (12.9-inch) (5th generation)`, etc.) or name of physically connected iOS device |
1467
+ | `DEVICE_TYPE` | Must be set to `phone` or `tablet` |
1468
+ | `APP` | Must be set to path where iOS app can be accessed and loaded |
1469
+ | `UDID` | UDID of physically connected iOS device (not used for simulators) |
1470
+ | `TEAM_ID` | unique 10-character Apple developer team identifier string (not used for simulators) |
1471
+ | `TEAM_NAME` | String representing a signing certificate (not used for simulators) |
1472
+ | `APP_NO_RESET` | [Optional] Don't reset app state after each test. Set to `true` or `false` |
1473
+ | `APP_FULL_RESET` | [Optional] Perform a complete reset. Set to `true` or `false` |
1474
+ | `WDA_LOCAL_PORT` | [Optional] Used to forward traffic from Mac host to real iOS devices over USB. Default value is same as port number used by WDA on device. |
1475
+ | `LOCALE` | [Optional] Locale to set for the simulator. e.g. `fr_CA` |
1476
+ | `LANGUAGE` | [Optional] Language to set for the simulator. e.g. `fr` |
1477
+ | `ORIENTATION` | [Optional] Set to `portrait` or `landscape` (only for iOS simulators) |
1478
+ | `NEW_COMMAND_TIMEOUT` | [Optional] Time (in Seconds) that Appium will wait for a new command from the client |
1479
+ | `SHOW_SIM_KEYBOARD` | [Optional] Show the simulator keyboard during text entry. Set to `true` or `false` |
1480
+ | `SHUTDOWN_OTHER_SIMS` | [Optional] Close any other running simulators. Set to `true` or `false`. See note below. |
1481
+
1482
+ The `SHUTDOWN_OTHER_SIMS` environment variable can only be set if you are running Appium Server with the `--relaxed-security`
1483
+ or `--allow-insecure=shutdown_other_sims` arguments passed when starting it from the command line, or when running the server
1484
+ from the Appium Server GUI app. A security violation error will occur without relaxed security enabled.
1485
+
1486
+ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml`)**](#using-configuration-specific-profiles-in-cucumber-yml) below.
1487
+
1488
+
1489
+ ##### Local iOS Simulators or Physical Devices using the `options` Hash
1490
+
1491
+ 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
1499
+
1500
+ ```ruby
1501
+ options = {
1502
+ driver: :appium,
1503
+ device_type: phone_or_tablet,
1504
+ capabilities: {
1505
+ platformName: :ios,
1506
+ 'appium:automationName': 'xcuitest',
1507
+ 'appium:platformVersion': ios_version,
1508
+ 'appium:deviceName': device_or_simulator_name,
1509
+ 'appium:app': path_to_ios_app
1510
+ },
1511
+ endpoint: 'http://127.0.0.1:4723/wd/hub'
1512
+ }
1513
+ AppiumConnect.initialize_appium(options)
1514
+ ```
1515
+ > ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to
1516
+ `appium_<device_os>_<device_type>` - e.g. `:appium_ios_phone` or `:appium_ios_tablet`.
1517
+ >
1518
+ > ℹ️ 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`
1519
+ will be used.
1520
+ >
1521
+ > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
1522
+
1523
+ Below is an example of an `options` hash for specifying a connection to a locally hosted mobile app running on an iPad Pro
1524
+ simulator. The `options` hash includes options for specifying the driver name, global driver scope, and setting the simulated
1525
+ device orientation to portrait mode.
1526
+ ```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
+ }
1540
+ }
1541
+ AppiumConnect.initialize_appium(options)
1542
+ ```
1543
+
1544
+ #### Connecting to Locally Hosted Android Simulators or Physical Devices
1545
+
1546
+ You can run your automated tests on locally hosted Android simulators or physically connected devices using Appium and Android
1547
+ Studio on macOS. You must install Android Studio, the desired Android version-specific virtual device emulators, and
1548
+ Appium. Refer to [this page](https://appium.io/docs/en/2.6/quickstart/uiauto2-driver/) for information on configuring Appium to work with the Android SDK. Refer to [this page](https://github.com/appium/appium-uiautomator2-driver)
1549
+ for information regarding specifying Appium capabilities that are specific to the UiAutomator2 driver.
1550
+
1551
+ ##### Local Android Simulators or Physical Devices using Environment Variables
1552
+
1553
+ If the `options` hash is not provided when calling the `TestCentricity::AppiumConnect.initialize_appium` method, the following
1554
+ **Environment Variables** must be set as described in the table below.
1555
+
1556
+ | **Environment Variable** | **Description** |
1557
+ |---------------------------|--------------------------------------------------------------------------------------------------------------------------------|
1558
+ | `DRIVER` | Must be set to `appium` |
1559
+ | `APP_PLATFORM_NAME` | Must be set to `Android` |
1560
+ | `AUTOMATION_ENGINE` | Must be set to `UiAutomator2` |
1561
+ | `APP_VERSION` | Must be set to `12.0`, or which ever Android OS version you wish to run with the Android Virtual Device |
1562
+ | `APP_DEVICE` | Set to Android Virtual Device ID (`Pixel_2_XL_API_26`, `Nexus_6_API_23`, etc.) found in Advanced Settings of AVD Configuration |
1563
+ | `DEVICE_TYPE` | Must be set to `phone` or `tablet` |
1564
+ | `APP` | Must be set to path where Android `.apk` file can be accessed and loaded || `UDID` | UDID of physically connected Android device (not used for simulators) |
1565
+ | `ORIENTATION` | [Optional] Set to `portrait` or `landscape` |
1566
+ | `APP_NO_RESET` | [Optional] Don't reset app state after each test. Set to `true` or `false` |
1567
+ | `APP_FULL_RESET` | [Optional] Perform a complete reset. Set to `true` or `false` |
1568
+ | `LOCALE` | [Optional] Locale to set for the simulator. e.g. `fr_CA` |
1569
+ | `LANGUAGE` | [Optional] Language to set for the simulator. e.g. `fr` |
1570
+ | `NEW_COMMAND_TIMEOUT` | [Optional] Time (in Seconds) that Appium will wait for a new command from the client |
1571
+ | `CHROMEDRIVER_EXECUTABLE` | [Optional] Absolute local path to ChromeDriver executable |
1572
+
1573
+ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml`)**](#using-configuration-specific-profiles-in-cucumber-yml) below.
1574
+
1575
+
1576
+ ##### Local Android Simulators or Physical Devices using the `options` Hash
1577
+
1578
+ 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
1586
+
1587
+ ```ruby
1588
+ options = {
1589
+ driver: :appium,
1590
+ device_type: phone_or_tablet,
1591
+ capabilities: {
1592
+ platformName: :android,
1593
+ 'appium:automationName': 'UiAutomator2',
1594
+ 'appium:platformVersion': android_version,
1595
+ 'appium:deviceName': simulator_name,
1596
+ 'appium:avd': simulator_name,
1597
+ 'appium:app': path_to_android_app
1598
+ },
1599
+ endpoint: 'http://localhost:4723/wd/hub'
1600
+ }
1601
+ AppiumConnect.initialize_appium(options)
1602
+ ```
1603
+ > ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to
1604
+ `appium_<device_os>_<device_type>` - e.g. `:appium_android_phone` or `:appium_android_tablet`.
1605
+ >
1606
+ > ℹ️ 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
+ will be used.
1608
+ >
1609
+ > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
1610
+
1611
+
1612
+ Below is an example of an `options` hash for specifying a connection to a locally hosted mobile app running on an Android
1613
+ tablet simulator. The `options` hash includes options for specifying the driver name and setting the simulated device orientation
1614
+ to landscape mode.
1615
+ ```ruby
1616
+ options = {
1617
+ driver: :appium,
1618
+ device_type: :tablet,
1619
+ driver_name: :admin_tablet,
1620
+ capabilities: {
1621
+ platformName: 'Android',
1622
+ 'appium:platformVersion': '12.0',
1623
+ 'appium:deviceName': 'Pixel_C_API_31',
1624
+ 'appium:avd': 'Pixel_C_API_31',
1625
+ 'appium:automationName': 'UiAutomator2',
1626
+ 'appium:orientation': 'LANDSCAPE',
1627
+ 'appium:app': Environ.current.android_apk_path
1628
+ }
1629
+ }
1630
+ AppiumConnect.initialize_appium(options)
1631
+ ```
1632
+ #### Starting and Stopping Appium Server
1633
+
1634
+ ##### Using Appium Server with Cucumber
1635
+
1636
+ The Appium server must be running prior to invoking Cucumber to run your features/scenarios on locally hosted mobile simulators
1637
+ or physical devices. To programmatically control the starting and stopping of Appium server with the execution of your automated
1638
+ tests, place the code shown below in your `hooks.rb` file.
1639
+ ```ruby
1640
+ BeforeAll do
1641
+ # start Appium Server if APPIUM_SERVER = 'run'
1642
+ if ENV['APPIUM_SERVER'] == 'run'
1643
+ $server = TestCentricity::AppiumServer.new
1644
+ $server.start
1645
+ end
1646
+ end
1647
+
1648
+ AfterAll do
1649
+ # close Appium driver
1650
+ TestCentricity::AppiumConnect.quit_driver
1651
+ # terminate Appium Server if command line option was specified and Appium server is running
1652
+ if ENV['APPIUM_SERVER'] == 'run' && Environ.driver == :appium && $server.running?
1653
+ $server.stop
1654
+ end
1655
+ end
1656
+ ```
1657
+ The `APPIUM_SERVER` environment variable must be set to `run` in order to programmatically start and stop the Appium server.
1658
+ This can be set by adding the following to your `cucumber.yml` file and including `-p run_appium` in your command line when
1659
+ starting your Cucumber test suite(s):
1660
+
1661
+ run_appium: APPIUM_SERVER=run
1662
+
1663
+ If you are running locally hosted mobile tests on iOS or Android simulators or devices using version 1.x of the Appium server,
1664
+ the `APPIUM_SERVER_VERSION` environment variable must be set to `1` in order to ensure that the correct Appium server endpoint
1665
+ is used. This can be set by adding the following to your `cucumber.yml` file and including `-p appium_1x` in your command line
1666
+ when starting your Cucumber test suite(s):
1667
+
1668
+ appium_1x: APPIUM_SERVER_VERSION=1
1669
+
1670
+ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml`)**](#using-configuration-specific-profiles-in-cucumber-yml) below.
1671
+
1672
+
1673
+ ##### Using Appium Server with RSpec
1674
+
1675
+ The Appium server must be running prior to executing test specs on locally hosted mobile simulators or physical device. To
1676
+ control the starting and stopping of the Appium server with the execution of your specs, place the code shown below in the
1677
+ body of an example group:
1678
+ ```ruby
1679
+ before(:context) do
1680
+ # start Appium server before all of the examples in this group
1681
+ $server = TestCentricity::AppiumServer.new
1682
+ $server.start
1683
+ end
1684
+
1685
+ after(:context) do
1686
+ # terminate Appium Server after all of the examples in this group
1687
+ $server.stop if Environ.driver == :appium && $server.running?
1688
+ end
1689
+ ```
1690
+ If you are running locally hosted mobile tests on iOS or Android simulators or devices using version 1.x of the Appium server,
1691
+ the `APPIUM_SERVER_VERSION` environment variable must be set to `1` in order to ensure that the correct Appium server endpoint
1692
+ is used.
1693
+
1694
+
1695
+ ### Connecting to Remote Cloud Hosted iOS and Android Simulators or Physical Devices
1696
+
1697
+ You can run your automated tests against remote cloud hosted iOS and Android simulators and real devices using the BrowserStack,
1698
+ SauceLabs, or TestingBot services.
1699
+
1700
+ #### Remote iOS and Android Mobile Devices on the BrowserStack service
1701
+
1702
+ For remotely hosted iOS and Android real devices on the BrowserStack service, refer to the [Browserstack-specific capabilities chart page](https://www.browserstack.com/app-automate/capabilities?tag=w3c)
1703
+ for information regarding the options and capabilities available for the various supported mobile operating systems and
1704
+ devices. BrowserStack uses only real physical devices - simulators are not available on this service.
1705
+
1706
+
1707
+ ##### Uploading your mobile app(s) to BrowserStack
1708
+
1709
+ Refer to the following pages for information on uploading your iOS `.ipa` or Android `.apk` app files to the BrowserStack
1710
+ 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)
1714
+
1715
+ The preferred method of uploading an app to BrowserStack is to define a custom test ID for your apps to avoid having to
1716
+ modify your test configuration data with a new `app_url` after every app upload. Use the same custom test ID every time
1717
+ you upload a new build of the app.
1718
+
1719
+ If the `UPLOAD_APP` Environment Variable is set to `true` prior to calling the `initialize_appium` method, your iOS `.ipa`
1720
+ or Android `.apk` file will automatically be uploaded to the BrowserStack servers prior to running your tests. If you have
1721
+ not specified a custom test ID for your apps, your tests will most likely fail as a new `app_url` will be generated, and
1722
+ you will have to update your test configuration data to use the new `app_url`. If you have specified a custom test ID for
1723
+ your apps, your tests should be able to run immediately after the app file upload has completed.
1724
+
1725
+
1726
+ ##### BrowserStack Mobile Devices using Environment Variables
1727
+
1728
+ If the `options` hash is not provided when calling the `TestCentricity::AppiumConnect.initialize_appium` method, the following
1729
+ **Environment Variables** must be set as described in the table below.
1730
+
1731
+ | **Environment Variable** | **Description** |
1732
+ |--------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
1733
+ | `DRIVER` | Must be set to `browserstack` |
1734
+ | `BS_USERNAME` | Must be set to your BrowserStack account user name |
1735
+ | `BS_AUTHKEY` | Must be set to your BrowserStack account access key |
1736
+ | `BS_OS` | Must be set to `ios` or `android` |
1737
+ | `BS_DEVICE` | Refer to `deviceName` capability in chart |
1738
+ | `BS_OS_VERSION` | Set to the OS version specified in the `platformVersion` capability in the chart |
1739
+ | `DEVICE_TYPE` | Must be set to `phone` or `tablet` |
1740
+ | `AUTOMATION_ENGINE` | Must be set to `XCUITest` for iOS or `UiAutomator2` for Android |
1741
+ | `APP` | Must be set to URL or custom test ID of uploaded iOS `.ipa` or Android `.apk` file |
1742
+ | `ORIENTATION` | [Optional] Set to `portrait` or `landscape` |
1743
+ | `RECORD_VIDEO` | [Optional] Enable screen video recording during test execution (`true` or `false`) |
1744
+ | `TIME_ZONE` | [Optional] Specify custom time zone. Refer to `browserstack.timezone` capability in chart |
1745
+ | `IP_GEOLOCATION` | [Optional] Specify IP Geolocation. Refer to [IP Geolocation](https://www.browserstack.com/ip-geolocation) to select a country code. |
1746
+ | `SCREENSHOTS` | [Optional] Generate screenshots for debugging (`true` or `false`) |
1747
+ | `NETWORK_LOGS` | [Optional] Capture network logs (`true` or `false`) |
1748
+ | `APPIUM_LOGS` | [Optional] Generate Appium logs (`true` or `false`) |
1749
+ | `UPLOAD_APP` | [Optional] Automatically upload the app to BrowserStack servers if true (`true` or `false`) |
1750
+
1751
+ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml`)**](#using-configuration-specific-profiles-in-cucumber-yml) below.
1752
+
1753
+
1754
+ ##### BrowserStack Mobile Devices using the `options` Hash
1755
+
1756
+ 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
1764
+
1765
+ ```ruby
1766
+ options = {
1767
+ driver: :browserstack,
1768
+ device_type: phone_or_tablet,
1769
+ capabilities: {
1770
+ platformName: platform,
1771
+ 'appium:automationName': automation_name,
1772
+ 'appium:platformVersion': os_version,
1773
+ 'appium:deviceName': device_name,
1774
+ 'appium:app': app_url_or_custom_ID,
1775
+ 'bstack:options': {
1776
+ userName: bs_account_user_name,
1777
+ accessKey: bs_account_access_key
1778
+ }
1779
+ }
1780
+ }
1781
+ AppiumConnect.initialize_appium(options)
1782
+ ```
1783
+ > ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to
1784
+ `:browserstack_<device_os>_<device_type>` - e.g. `:browserstack_ios_phone` or `:browserstack_android_tablet`.
1785
+ >
1786
+ > ℹ️ If an `endpoint:` is not specified in the `options` hash, then the default remote endpoint URL will be set to the following:
1787
+ >
1788
+ > `https://#{ENV['BS_USERNAME']}:#{ENV['BS_AUTHKEY']}@hub-cloud.browserstack.com/wd/hub`
1789
+ >
1790
+ > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
1791
+
1792
+ This default endpoint requires that the `BS_USERNAME` Environment Variable is set to your BrowserStack account user name and
1793
+ the `BS_AUTHKEY` Environment Variable is set to your BrowserStack access key.
1794
+
1795
+ Below is an example of an `options` hash for specifying a connection to a mobile app running on an iOS tablet hosted on
1796
+ BrowserStack. The `options` hash includes options for specifying the driver name, and capabilities for setting geoLocation,
1797
+ time zone, Appium version, device orientation, language, locale, and various test configuration options.
1798
+ ```ruby
1799
+ options = {
1800
+ driver: :browserstack,
1801
+ device_type: :tablet,
1802
+ driver_name: :admin_tablet,
1803
+ endpoint: "https://#{ENV['BS_USERNAME']}:#{ENV['BS_AUTHKEY']}@hub-cloud.browserstack.com/wd/hub",
1804
+ capabilities: {
1805
+ platformName: 'ios',
1806
+ 'appium:platformVersion': '17',
1807
+ 'appium:deviceName': 'iPad Pro 12.9 2021',
1808
+ 'appium:automationName': 'XCUITest',
1809
+ 'appium:app': 'RNDemoAppiOS',
1810
+ 'bstack:options': {
1811
+ userName: ENV['BS_USERNAME'],
1812
+ accessKey: ENV['BS_AUTHKEY'],
1813
+ projectName: 'ALP AP',
1814
+ buildName: "Test Build #{ENV['BUILD_NUM']}",
1815
+ sessionName: 'AU Regression Suite',
1816
+ appiumVersion: '2.0.1',
1817
+ geoLocation: 'AU',
1818
+ timezone: 'Perth',
1819
+ deviceOrientation: 'landscape'
1820
+ },
1821
+ language: 'En',
1822
+ locale: 'en_AU'
1823
+ }
1824
+ }
1825
+ AppiumConnect.initialize_appium(options)
1826
+ ```
1827
+
1828
+
1829
+ #### Remote iOS and Android Physical Devices and Simulators on the TestingBot service
1830
+
1831
+ For remotely hosted iOS and Android simulators and real devices on the TestingBot service, the following **Environment
1832
+ Variables** must be set as described in the table below. Refer to the [TestingBot List of Devices page](https://testingbot.com/support/devices) for information
1833
+ regarding the specific capabilities.
1834
+
1835
+
1836
+ ##### Uploading your mobile app(s) to TestingBot
1837
+
1838
+ Refer to the following pages for information on uploading your iOS `.ipa` or `.app` or Android `.apk` app files to the
1839
+ TestingBot servers:
1840
+ - [Upload your App](https://testingbot.com/support/mobile/upload.html)
1841
+ - [TestingBot Storage - Upload File API doc](https://testingbot.com/support/api#upload)
1842
+
1843
+ The preferred method of uploading an app to TestingBot is to define a custom test ID for your apps to avoid having to
1844
+ modify your test configuration data with a new `app_url` after every app upload. Use the same custom test ID every time
1845
+ you upload a new build of the app.
1846
+
1847
+ If the `UPLOAD_APP` Environment Variable is set to `true` prior to calling the `initialize_appium` method, your iOS `.ipa`
1848
+ or `.app`, or Android `.apk` file will automatically be uploaded to the TestingBot servers prior to running your tests. If
1849
+ you have not specified a custom test ID for your apps, your tests will most likely fail as a new `app_url` will be generated,
1850
+ and you will have to update your test configuration data to use the new `app_url`. If you have specified a custom test ID
1851
+ for your apps, your tests should be able to run immediately after the app file upload has completed.
1852
+
1853
+ When specifying you app's custom test ID in either the `APP` Environment Variable or as part of the `options` hash, the
1854
+ custom test ID is specified as `tb://your_custom_id`.
1855
+
1856
+
1857
+ ##### TestingBot Mobile Devices using Environment Variables
1858
+
1859
+ If the `options` hash is not provided when calling the `TestCentricity::AppiumConnect.initialize_appium` method, the following
1860
+ **Environment Variables** must be set as described in the table below.
1861
+
1862
+ | **Environment Variable** | **Description** |
1863
+ |--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
1864
+ | `DRIVER` | Must be set to `testingbot` |
1865
+ | `TB_USERNAME` | Must be set to your TestingBot account user name |
1866
+ | `TB_AUTHKEY` | Must be set to your TestingBot account access key |
1867
+ | `TB_OS` | Must be set to `ios` or `android` |
1868
+ | `TB_DEVICE` | Refer to `deviceName` capability in chart |
1869
+ | `TB_OS_VERSION` | Refer to `version` capability in chart |
1870
+ | `DEVICE_TYPE` | Must be set to `phone` or `tablet` |
1871
+ | `AUTOMATION_ENGINE` | Must be set to `XCUITest` for iOS or `UiAutomator2` for Android |
1872
+ | `REAL_DEVICE` | Must be set to `true` for real devices |
1873
+ | `APP` | Must be set to URL or custom test ID of uploaded iOS `.ipa` or `.app`, or Android `.apk` file |
1874
+ | `TIME_ZONE` | [Optional] Specify custom time zone. Refer to [list of time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) |
1875
+ | `IP_GEOLOCATION` | [Optional] Specify IP Geolocation. Refer to [Geolocation Testing](https://testingbot.com/support/mobile/options.html#geo) to select a country code. |
1876
+ | `RECORD_VIDEO` | [Optional] Enable screen video recording during test execution (`true` or `false`) |
1877
+ | `SCREENSHOTS` | [Optional] Generate screenshots for debugging (`true` or `false`) |
1878
+ | `UPLOAD_APP` | [Optional] Automatically upload the app to TestingBot servers if true (`true` or `false`) |
1879
+
1880
+ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml`)**](#using-configuration-specific-profiles-in-cucumber-yml) below.
1881
+
1882
+
1883
+ ##### TestingBot Mobile Devices using the `options` Hash
1884
+
1885
+ When using the `options` hash, the following options and capabilities must be specified:
1886
+ - `driver:` must be set to `:testingbot`
1887
+ - `device_type:` must be set to `:tablet` or `:phone`
1888
+ - `platformName:` must be set to `ios` or `android` in the `capabilities:` hash
1889
+ - `'appium:automationName':` must be set to to `XCUITest` for iOS or `UiAutomator2` for Android in the `capabilities:` hash
1890
+ - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
1891
+ - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
1892
+ - `'appium:realDevice':` must be set to `true` if testing on real physical device
1893
+ - `'appium:app'`: must be set to URL or custom test ID of uploaded iOS `.ipa` or `.app`, or Android `.apk` file
1894
+
1895
+ ```ruby
1896
+ options = {
1897
+ driver: :testingbot,
1898
+ device_type: phone_or_tablet,
1899
+ capabilities: {
1900
+ platformName: platform,
1901
+ 'appium:automationName': automation_name,
1902
+ 'appium:platformVersion': os_version,
1903
+ 'appium:deviceName': device_name,
1904
+ 'appium:realDevice': true_or_false,
1905
+ 'appium:app': app_url_or_custom_ID,
1906
+ 'tb:options': {
1907
+ # other platform specific options
1908
+ }
1909
+ }
1910
+ }
1911
+ AppiumConnect.initialize_appium(options)
1912
+ ```
1913
+ > ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to
1914
+ `:testingbot_<device_os>_<device_type>` - e.g. `:testingbot_ios_phone` or `:testingbot_android_tablet`.
1915
+ >
1916
+ > ℹ️ If an `endpoint:` is not specified in the `options` hash, then the default remote endpoint URL will be set to the following:
1917
+ >
1918
+ > `http://#{ENV['TB_USERNAME']}:#{ENV['TB_AUTHKEY']}@hub.testingbot.com/wd/hub`
1919
+ >
1920
+ > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
1921
+
1922
+ This default endpoint requires that the `TB_USERNAME` Environment Variable is set to your TestingBot account user name and
1923
+ the `TB_AUTHKEY` Environment Variable is set to your TestingBot access key.
1924
+
1925
+ Below is an example of an `options` hash for specifying a connection to a mobile app running on a real physical iPhone hosted
1926
+ on TestingBot. The `options` hash includes options for specifying the driver name, and capabilities for setting geoLocation,
1927
+ time zone, Appium version, and various test configuration options.
1928
+ ```ruby
1929
+ options = {
1930
+ driver: :testingbot,
1931
+ device_type: :phone,
1932
+ driver_name: :tb_ios_phone,
1933
+ endpoint: "http://#{ENV['TB_USERNAME']}:#{ENV['TB_AUTHKEY']}@hub.testingbot.com/wd/hub",
1934
+ capabilities: {
1935
+ platformName: 'ios',
1936
+ 'appium:platformVersion': '17.0',
1937
+ 'appium:deviceName': 'iPhone 14',
1938
+ 'appium:realDevice': true,
1939
+ 'appium:automationName': 'XCUITest',
1940
+ 'appium:app': 'tb://RNDemoAppiOS',
1941
+ 'tb:options': {
1942
+ name: ENV['AUTOMATE_PROJECT'],
1943
+ build: "Test Build #{ENV['BUILD_NUM']}",
1944
+ appiumVersion: '2.2.1'
1945
+ }
1946
+ }
1947
+ }
1948
+ AppiumConnect.initialize_appium(options)
1949
+ ```
1950
+
1951
+
1952
+ #### Remote iOS and Android Physical Devices and Simulators on the Sauce Labs service
1953
+
1954
+ For remotely hosted iOS and Android simulators and real devices on the Sauce Labs service, the following **Environment Variables**
1955
+ must be set as described in the table below. Refer to the [Platform Configurator page](https://saucelabs.com/platform/platform-configurator)
1956
+ to obtain information regarding the specific capabilities.
1957
+
1958
+
1959
+ ##### Uploading your mobile app(s) to Sauce Labs
1960
+
1961
+ Refer to the following pages for information on uploading your iOS `.ipa` or `.app` or Android `.apk` app files to the
1962
+ Sauce Labs servers:
1963
+ - [Mobile App Storage](https://docs.saucelabs.com/mobile-apps/app-storage/)
1964
+
1965
+ The TestCentricity For Apps gem does not currently support automatic upload of app files to Sauce Labs servers. Uploading
1966
+ 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
+ tests will most likely fail as a new `app_url` will be generated, and you will have to update your test configuration data
1968
+ 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
1969
+ modifying your test configs.
1970
+
1971
+
1972
+ ##### Sauce Labs Mobile Devices using Environment Variables
1973
+
1974
+ If the `options` hash is not provided when calling the `TestCentricity::AppiumConnect.initialize_appium` method, the following
1975
+ **Environment Variables** must be set as described in the table below.
1976
+
1977
+ | **Environment Variable** | **Description** |
1978
+ |--------------------------|-----------------------------------------------------------------------------------------------------------------|
1979
+ | `DRIVER` | Must be set to `saucelabs` |
1980
+ | `SL_USERNAME` | Must be set to your Sauce Labs account user name or email address |
1981
+ | `SL_AUTHKEY` | Must be set to your Sauce Labs account access key |
1982
+ | `SL_DATA_CENTER` | Must be set to your Sauce Labs account Data Center assignment (`us-west-1`, `eu-central-1`, `apac-southeast-1`) |
1983
+ | `SL_OS` | Must be set to `ios` or `android` |
1984
+ | `SL_DEVICE` | Refer to `deviceName` capability in chart |
1985
+ | `SL_OS_VERSION` | Refer to `platformVersion` capability in the Config Script section of the Platform Configurator page |
1986
+ | `AUTOMATION_ENGINE` | Must be set to `XCUITest` for iOS or `UiAutomator2` for Android |
1987
+ | `DEVICE_TYPE` | Must be set to `phone` or `tablet` |
1988
+ | `ORIENTATION` | [Optional] Set to `portrait` or `landscape` |
1989
+ | `RECORD_VIDEO` | [Optional] Enable screen video recording during test execution (`true` or `false`) |
1990
+ | `SCREENSHOTS` | [Optional] Generate screenshots for debugging (`true` or `false`) |
1991
+
1992
+ Refer to [**Section 10.4 (Using Configuration Specific Profiles in `cucumber.yml`)**](#using-configuration-specific-profiles-in-cucumber-yml) below.
1993
+
1994
+
1995
+ ##### Sauce Labs Mobile Devices using the `options` Hash
1996
+
1997
+ When using the `options` hash, the following options and capabilities must be specified:
1998
+ - `driver:` must be set to `:saucelabs`
1999
+ - `device_type:` must be set to `:tablet` or `:phone`
2000
+ - `platformName:` must be set to `ios` or `android` in the `capabilities:` hash
2001
+ - `'appium:automationName':` must be set to to `XCUITest` for iOS or `UiAutomator2` for Android in the `capabilities:` hash
2002
+ - `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device
2003
+ - `'appium:deviceName':` must be set to the name of the iOS simulator or physical device
2004
+ - `'appium:app'`: must be set to URL or custom test ID of uploaded iOS `.ipa` or `.app`, or Android `.apk` file
2005
+
2006
+ ```ruby
2007
+ options = {
2008
+ driver: :saucelabs,
2009
+ device_type: phone_or_tablet,
2010
+ capabilities: {
2011
+ platformName: platform,
2012
+ 'appium:automationName': automation_name,
2013
+ 'appium:platformVersion': os_version,
2014
+ 'appium:deviceName': device_name,
2015
+ 'appium:app': app_url_or_custom_ID,
2016
+ 'sauce:options': {
2017
+ # other platform specific options
2018
+ }
2019
+ }
2020
+ }
2021
+ AppiumConnect.initialize_appium(options)
2022
+ ```
2023
+ > ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to
2024
+ `:saucelabs_<device_os>_<device_type>` - e.g. `:saucelabs_ios_phone` or `:saucelabs_android_tablet`.
2025
+ >
2026
+ > ℹ️ If an `endpoint:` is not specified in the `options` hash, then the default remote endpoint URL will be set to the following:
2027
+ >
2028
+ > `https://#{ENV['SL_USERNAME']}:#{ENV['SL_AUTHKEY']}@ondemand.#{ENV['SL_DATA_CENTER']}.saucelabs.com:443/wd/hub`
2029
+ >
2030
+ > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
2031
+
2032
+ This default endpoint requires that the `SL_USERNAME` Environment Variable is set to your Sauce Labs account user name, the
2033
+ `SL_AUTHKEY` Environment Variable is set to your Sauce Labs access key, and the `SL_DATA_CENTER` Environment Variable is
2034
+ set to your Sauce Labs account Data Center assignment (`us-west-1`, `eu-central-1`, `apac-southeast-1`).
2035
+
2036
+
2037
+ #### Remote iOS and Android Physical Devices and Simulators on Unsupported Cloud Hosting Services
2038
+
2039
+ Limited support is provided for executing automated tests against remotely hosted iOS and Android simulators and real devices
2040
+ on other cloud hosting services that are currently not supported. You must call the `AppiumConnect.initialize_appium` method
2041
+ with an `options` hash - Environment Variables cannot be used to specify a user-defined custom Appium driver instance.
2042
+
2043
+ 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
2049
+
2050
+ 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
2054
+
2055
+ All other required capabilities specified by the hosting service configuration documentation should be included in the
2056
+ `capabilities:` hash.
2057
+ ```ruby
2058
+ # specify mobile platform, device type, device os, and device name
2059
+ Environ.platform = :mobile
2060
+ Environ.device = :device
2061
+ Environ.device_os = :ios
2062
+ Environ.device_name = device_name_from_chart
2063
+ # instantiate a cloud hosted mobile device or simulator on an unsupported hosting service
2064
+ options = {
2065
+ driver: :custom,
2066
+ device_type: :phone,
2067
+ endpoint: endpoint_url,
2068
+ capabilities: {
2069
+ # capabilities as specified by the hosting service
2070
+ }
2071
+ }
2072
+ AppiumConnect.initialize_appium(options)
2073
+ ```
2074
+ > ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to
2075
+ `:custom_<device_os>_<device_type>` - e.g. `:custom_ios_phone` or `:custom_android_tablet`.
2076
+ >
2077
+ > ℹ️ If `global_driver:` is not specified in the `options` hash, then the driver will be initialized without global scope.
2078
+
2079
+
2080
+ ### Using Configuration Specific Profiles in cucumber.yml
2081
+
2082
+ While you can set **Environment Variables** in the command line when invoking Cucumber, a preferred method of specifying
2083
+ and managing target platforms is to create platform specific **Profiles** that set the appropriate **Environment Variables**
2084
+ for each target platform in your `cucumber.yml` file.
2085
+
2086
+ Below is a list of Cucumber **Profiles** for supported locally and remotely hosted iOS and Android simulators and real
2087
+ devices (put these in in your `cucumber.yml` file). Before you can use the BrowserStack, SauceLabs, or TestingBot services,
2088
+ you will need to replace the *INSERT USER NAME HERE* and *INSERT PASSWORD HERE* placeholder text with your user account
2089
+ and authorization code for the cloud service(s) that you intend to connect with.
2090
+
2091
+ > ⚠️ Cloud service credentials should not be stored as text in your `cucumber.yml` file where it can be exposed by anyone
2092
+ with access to your version control system.
2093
+
2094
+
2095
+ #==============
2096
+ # conditionally load Screen Object implementations based on which target platform we're running on
2097
+ #==============
2098
+
2099
+ ios: PLATFORM=ios --tags @ios -r features/support/ios -e features/support/android
2100
+ android: PLATFORM=android --tags @android -r features/support/android -e features/support/ios
2101
+
2102
+
2103
+ #==============
2104
+ # profiles for mobile device screen orientation
2105
+ #==============
2106
+
2107
+ landscape: ORIENTATION=landscape
2108
+ portrait: ORIENTATION=portrait
2109
+
2110
+
2111
+ #==============
2112
+ # profile to start Appium Server prior to running locally hosted mobile app tests on iOS or Android simulators or
2113
+ # physical devices
2114
+ #==============
2115
+ run_appium: APPIUM_SERVER=run
2116
+ appium_1x: APPIUM_SERVER_VERSION=1
2117
+
2118
+
2119
+ #==============
2120
+ # profiles for native iOS apps hosted within XCode iOS simulators
2121
+ # NOTE: Requires installation of XCode, iOS version specific target simulators, and Appium
2122
+ #==============
2123
+
2124
+ appium_ios: DRIVER=appium --profile ios AUTOMATION_ENGINE=XCUITest APP_PLATFORM_NAME="iOS" NEW_COMMAND_TIMEOUT="30" <%= mobile %>
2125
+ app_ios_14: --profile appium_ios APP_VERSION="14.5"
2126
+ app_ios_15: --profile appium_ios APP_VERSION="15.4"
2127
+
2128
+ iphone_12PM_14_sim: --profile app_ios_14 DEVICE_TYPE=phone APP_DEVICE="iPhone 12 Pro Max"
2129
+ iphone_13PM_15_sim: --profile app_ios_15 DEVICE_TYPE=phone APP_DEVICE="iPhone 13 Pro Max"
2130
+ iphone_11_14_sim: --profile app_ios_14 DEVICE_TYPE=phone APP_DEVICE="iPhone 11"
2131
+ ipad_pro_12_15_sim: --profile app_ios_15 DEVICE_TYPE=tablet APP_DEVICE="iPad Pro (12.9-inch) (5th generation)"
2132
+
2133
+
2134
+ #==============
2135
+ # profiles for native Android apps hosted within Android Studio Android Virtual Device emulators
2136
+ # NOTE: Requires installation of Android Studio, Android version specific virtual device simulators, and Appium
2137
+ #==============
2138
+
2139
+ appium_android: DRIVER=appium --profile android AUTOMATION_ENGINE=UiAutomator2 APP_PLATFORM_NAME="Android" <%= mobile %>
2140
+ app_android_12: --profile appium_android APP_VERSION="12.0"
2141
+ pixel_5_api31_sim: --profile app_android_12 DEVICE_TYPE=phone APP_DEVICE="Pixel_5_API_31"
2142
+
2143
+
2144
+ #==============
2145
+ # profiles for remotely hosted devices on the BrowserStack service
2146
+ # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2147
+ # to your version control system
2148
+ #==============
2149
+
2150
+ browserstack: DRIVER=browserstack BS_USERNAME="<INSERT USER NAME HERE>" BS_AUTHKEY="<INSERT PASSWORD HERE>" TEST_CONTEXT="TestCentricity"
2151
+
2152
+ # BrowserStack iOS real device native app profiles
2153
+ bs_ios: --profile browserstack --profile ios BS_OS=ios <%= mobile %>
2154
+ bs_iphone: --profile bs_ios DEVICE_TYPE=phone
2155
+ bs_iphone13PM_15: --profile bs_iphone BS_OS_VERSION="15" BS_DEVICE="iPhone 13 Pro Max"
2156
+ bs_iphone11_14: --profile bs_iphone BS_OS_VERSION="14" BS_DEVICE="iPhone 11"
2157
+
2158
+ # BrowserStack Android real device native app profiles
2159
+ bs_android: --profile browserstack --profile android BS_OS=android <%= mobile %>
2160
+ bs_pixel5: --profile bs_android BS_DEVICE="Google Pixel 5" BS_OS_VERSION="12.0" DEVICE_TYPE=phone
2161
+
2162
+
2163
+ #==============
2164
+ # profiles for remotely hosted devices on the SauceLabs service
2165
+ # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2166
+ # to your version control system
2167
+ #==============
2168
+
2169
+ saucelabs: DRIVER=saucelabs SL_USERNAME="<INSERT USER NAME HERE>" SL_AUTHKEY="<INSERT PASSWORD HERE>" DATA_CENTER="us-west-1" AUTOMATE_PROJECT="TestCentricity - SauceLabs"
2170
+
2171
+ # SauceLabs iOS real device native app profiles
2172
+ sl_ios: --profile saucelabs --profile ios SL_OS=ios <%= mobile %>
2173
+ sl_iphone: --profile sl_ios DEVICE_TYPE=phone
2174
+ sl_iphone13PM_15: --profile sl_iphone SL_DEVICE="iPhone 13 Pro Max Simulator" SL_OS_VERSION="15.4"
2175
+
2176
+ # SauceLabs Android real device native app profiles
2177
+ sl_android: --profile saucelabs --profile android SL_OS=android <%= mobile %>
2178
+ sl_pixel5: --profile sl_android SL_DEVICE="Google Pixel 5 GoogleAPI Emulator" SL_OS_VERSION="12.0" DEVICE_TYPE=phone
2179
+
2180
+
2181
+ #==============
2182
+ # profiles for remotely hosted devices on the TestingBot service
2183
+ # WARNING: Credentials should not be stored as text in your cucumber.yml file where it can be exposed by anyone with access
2184
+ # to your version control system
2185
+ #==============
2186
+
2187
+ testingbot: DRIVER=testingbot TB_USERNAME="<INSERT USER NAME HERE>" TB_AUTHKEY="<INSERT PASSWORD HERE>" AUTOMATE_PROJECT="TestCentricity - TestingBot"
2188
+
2189
+ # TestingBot iOS real device native app profiles
2190
+ tb_ios: --profile testingbot --profile ios TB_OS=iOS <%= mobile %>
2191
+ tb_iphone: --profile tb_ios DEVICE_TYPE=phone
2192
+ tb_iphone11_14_dev: --profile tb_iphone TB_OS_VERSION="14.0" TB_DEVICE="iPhone 11" REAL_DEVICE=true
2193
+ tb_iphone11_14_sim: --profile tb_iphone TB_OS_VERSION="14.2" TB_DEVICE="iPhone 11"
2194
+ tb_iphone13PM_15_sim: --profile tb_iphone TB_OS_VERSION="15.4" TB_DEVICE="iPhone 13 Pro Max"
2195
+
2196
+ # TestingBot Android real device native app profiles
2197
+ tb_android: --profile testingbot --profile android TB_OS=Android <%= mobile %>
2198
+ tb_pixel_dev: --profile tb_android TB_DEVICE="Pixel" TB_OS_VERSION="9.0" DEVICE_TYPE=phone REAL_DEVICE=true
2199
+ tb_pixel6_sim: --profile tb_android TB_DEVICE="Pixel 6" TB_OS_VERSION="12.0" DEVICE_TYPE=phone
2200
+
2201
+
2202
+ To specify a mobile simulator or real device target using a profile at runtime, you use the flag `--profile` or `-p` followed
2203
+ by the profile name when invoking Cucumber in the command line. For instance, the following command specifies that Cucumber will
2204
+ run tests against an iPad Pro (12.9-inch) (5th generation) with iOS version 15.4 in an XCode Simulator in portrait orientation:
2205
+
2206
+ cucumber -p ipad_pro_12_15_sim -p portrait
2207
+
2208
+ NOTE: Appium must be running prior to executing this command
2209
+
2210
+ You can ensure that Appium Server is running by including `-p run_appium` in your command line:
2211
+
2212
+ cucumber -p ipad_pro_12_15_sim -p portrait -p run_appium
2213
+
2214
+ If you are running locally hosted mobile tests using version 1.x of Appium server, you must include `-p appium_1x` in
2215
+ your command line:
2216
+
2217
+ cucumber -p ipad_pro_12_15_sim -p landscape -p run_appium -p appium_1x
2218
+
2219
+
2220
+ The following command specifies that Cucumber will run tests against a cloud hosted iPhone 13 Pro Max running iOS 15.4 on the
2221
+ BrowserStack service:
2222
+
2223
+ cucumber -p bs_iphone13PM_15
2224
+
2225
+
2226
+ ---
2227
+ ## Recommended Project Organization and Structure
2228
+
2229
+ Below is an example of the project structure of a typical Cucumber based MacOS desktop app and native mobile iOS/Android
2230
+ app test automation framework with a Screen Object Model architecture. `ScreenObject` class definitions should be stored
2231
+ in the `/features/support/<platform>/screens` folders, organized in functional area sub-folders as needed. Likewise,
2232
+ `ScreenSection` class definitions should be stored in the `/features/support/<platform>/sections` folder, where `<platform>`
2233
+ is typically `mac`, `ios`, or `android`.
2234
+
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
2260
+
2261
+
2262
+ ---
2263
+ ## MacOS and Mobile Test Automation Framework Implementation
2264
+
2265
+ ![TestCentricity For Apps Framework Overview](https://raw.githubusercontent.com/TestCentricity/testcentricity_apps/main/.github/images/TC_Apps.jpg "TestCentricity For Apps Framework Overview")
2266
+
2267
+
2268
+ ---
2269
+ ## Copyright and License
2270
+
2271
+ All TestCentricity™ Frameworks are Copyright (c) 2014-2024, A.J. Mrozinski.
2272
+ All rights reserved.
2273
+
2274
+ Redistribution and use in source and binary forms, with or without
2275
+ modification, are permitted provided that the following conditions are met:
2276
+
2277
+ 1. Redistributions of source code must retain the above copyright notice,
2278
+ this list of conditions and the following disclaimer.
2279
+
2280
+ 2. Redistributions in binary form must reproduce the above copyright
2281
+ notice, this list of conditions and the following disclaimer in the
2282
+ documentation and/or other materials provided with the distribution.
2283
+
2284
+ 3. Neither the name of the copyright holder nor the names of its contributors
2285
+ may be used to endorse or promote products derived from this software without
2286
+ specific prior written permission.
2287
+
2288
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
2289
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2290
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2291
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
2292
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2293
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
2294
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2295
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2296
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2297
+ POSSIBILITY OF SUCH DAMAGE.