testcentricity_apps 4.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +193 -0
- data/LICENSE.md +27 -0
- data/README.md +2297 -0
- data/lib/testcentricity_apps/app_core/appium_connect_helper.rb +667 -0
- data/lib/testcentricity_apps/app_core/screen_object.rb +494 -0
- data/lib/testcentricity_apps/app_core/screen_objects_helper.rb +211 -0
- data/lib/testcentricity_apps/app_core/screen_section.rb +669 -0
- data/lib/testcentricity_apps/app_elements/alert.rb +152 -0
- data/lib/testcentricity_apps/app_elements/app_element.rb +728 -0
- data/lib/testcentricity_apps/app_elements/button.rb +10 -0
- data/lib/testcentricity_apps/app_elements/checkbox.rb +61 -0
- data/lib/testcentricity_apps/app_elements/image.rb +10 -0
- data/lib/testcentricity_apps/app_elements/label.rb +10 -0
- data/lib/testcentricity_apps/app_elements/list.rb +188 -0
- data/lib/testcentricity_apps/app_elements/menu.rb +159 -0
- data/lib/testcentricity_apps/app_elements/menubar.rb +78 -0
- data/lib/testcentricity_apps/app_elements/radio.rb +61 -0
- data/lib/testcentricity_apps/app_elements/selectlist.rb +126 -0
- data/lib/testcentricity_apps/app_elements/switch.rb +66 -0
- data/lib/testcentricity_apps/app_elements/textfield.rb +51 -0
- data/lib/testcentricity_apps/appium_server.rb +76 -0
- data/lib/testcentricity_apps/data_objects/data_objects_helper.rb +100 -0
- data/lib/testcentricity_apps/data_objects/environment.rb +423 -0
- data/lib/testcentricity_apps/exception_queue_helper.rb +160 -0
- data/lib/testcentricity_apps/utility_helpers.rb +48 -0
- data/lib/testcentricity_apps/version.rb +3 -0
- data/lib/testcentricity_apps/world_extensions.rb +61 -0
- data/lib/testcentricity_apps.rb +103 -0
- 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.
|