Spectatio is an open source test framework developed for testing Android Automotive OS (AAOS) on real and virtual devices. Spectatio provides APIs for testing apps on an automotive device and is an extendable and scalable solution used for verifying the capability and performance of AAOS and its apps.
High-level design
The Spectatio framework is adaptable and expandable for various AAOS UI implementations. It's used for testing the capability and performance of AAOS on device hardware, emulators, and virtualized environments.
The following figure explains the high-level design of the Spectatio framework.
Figure 1. Spectatio framework high-level design.
Built on top of UI Automator, the Spectatio framework provides a set of APIs to build UI tests that interact with user and system apps on AAOS. Automotive tests use the APIs provided by the Spectatio framework for testing, which makes these tests independent of the device under test (DUT) and scalable to test varied devices, if supported.
Figure 1 shows that the Spectatio framework is modularized based on reference apps such as Dialer, Medicenter, and Settings using app-specific interfaces and helpers, making it easily extendable for new apps. The Spectatio framework reuses the common standard and utility helper classes. The standard helper class is the parent class for all the app helper functions and provides standard functions that are device specific or applicable across apps. The utility helper classes provide utilities such as reading or writing files from the device.
Architecture
To provide a set of APIs to build UI tests, the Spectatio framework implements app-specific interfaces and helpers while extending the existing standard helper class and importing the utility helper classes.
Figure 2 illustrates the high-level architecture of the Spectatio framework and all the entities involved in implementing APIs for testing an app.
Figure 2. Spectatio framework high-level architecture.
The app helper interface provides a blueprint for the implementation of
an app helper. It consists of various helper functions that are needed
for testing apps. Each app has its own interface, such as IAutoSettingHelper
and IAutoDialHelper
.
For more information and a list of interface functions, see the app helper interface functions on AOSP.
The standard helper class consists of standard attributes and functions that are
required for device setup but aren’t specific to any app, such as pressHome
and scroll
. The standard helper class is defined in AbstractAutoStandardAppHelper.java
.
The utility helper classes are used by the framework. For
example, AutoJsonUtility.java
is a
utility class that loads the given device JSON configuration file and updates
the framework configurations at runtime.
The app helper implementation module is the core of the Spectatio
framework. It contains the implementation for the helper functions defined in
the app helper interface, which are required for testing apps on an
automotive device. Each app has its own implementation, such as SettingHelperImpl
and
DialHelperImpl
,
used by
the Automotive tests for testing the apps. For more information and a list of
implementations, see the app helper implementation functions
'on AOSP.
Automotive Tests
use the app helper implementation functions to test various operations
related to the app. Use the HelperAccessor
class
to gain access to the app helper implementation functions.
The following code shows the setup, cleanup, and execution of a sample automotive test.
@RunWith(AndroidJUnit4.class)
public class AutoApplicationTest {
static HelperAccessor<IAutoApplicationHelper> autoApplicationHelper =
new HelperAccessor<>(IAutoApplicationHelper.class);
public AutoApplicationTest() {
// constructor
// Initialize any attributes that are required for the test execution
}
@Before
public void beforeTest() {
// Initial setup before each test
// For example - open the app
autoApplicationHelper.open();
}
@After
public void afterTest() {
// Cleanup after each test.
// For example - exit the app
autoApplicationHelper.exit();
}
@Test
public void testApplicationFeature() {
// Test
// For example - Test if app is open
assertTrue("Application is not open.", autoApplicationHelper.isOpen());
}
}
Customization
The Spectatio framework is independent of the device UI, so it’s scalable for
testing devices with varied UIs and hardware. To achieve this scalability,
Spectatio uses default device configurations based on the reference device. To
support non-default device configurations, the framework uses a JSON
configuration file at runtime to set the desired UI changes for the device. A
JSON configuration file supports UI elements like TEXT
, DESCRIPTION
, and
RESOURCE_ID
, along with path
settings and must contain only the information
about the UI changes for the DUT. The rest of the UI elements use the default
configuration values provided in the framework.
Default device configurations
The following sample JSON configuration file shows the available device configurations and their default values.
Click here to display a sample JSON configuration file
{ "SETTINGS": { "APPLICATION_CONFIG": { "SETTINGS_TITLE_TEXT": "Settings", "SETTINGS_PACKAGE": "com.android.car.settings", "SETTINGS_RRO_PACKAGE": "com.android.car.settings.googlecarui.rro", "OPEN_SETTINGS_COMMAND": "am start -a android.settings.SETTINGS", "OPEN_QUICK_SETTINGS_COMMAND": "am start -n com.android.car.settings/com.android.car.settings.common.CarSettingActivity" }, "QUICK_SETTINGS": { "OPEN_MORE_SETTINGS": { "TYPE": "RESOURCE_ID", "VALUE": "toolbar_menu_item_1", "PACKAGE": "com.android.car.settings" }, "NIGHT_MODE": { "TYPE": "TEXT", "VALUE": "Night mode" } }, "DISPLAY": { "PATH": "Settings > Display", "OPTIONS": [ "Brightness level" ], "BRIGHTNESS_LEVEL": { "TYPE": "RESOURCE_ID", "VALUE": "seekbar", "PACKAGE": "com.android.car.settings" } }, "SOUND": { "PATH": "Settings > Sound", "OPTIONS": [ "Media volume", "Alarm volume" ] }, "NETWORK_AND_INTERNET": { "PATH": "Settings > Network & internet", "OPTIONS": [ ], "TOGGLE_WIFI": { "TYPE": "RESOURCE_ID", "VALUE": "master_switch", "PACKAGE": "com.android.car.settings" } }, "BLUETOOTH": { "PATH": "Settings > Bluetooth", "OPTIONS": [ ], "TOGGLE_BLUETOOTH": { "TYPE": "RESOURCE_ID", "VALUE": "car_ui_toolbar_menu_item_switch", "PACKAGE": "com.android.car.settings" } }, "APPS_AND_NOTIFICATIONS": { "PATH": "Settings > Apps & notifications", "OPTIONS": [ ], "SHOW_ALL_APPS": { "TYPE": "TEXT", "VALUE": "Show all apps" }, "ENABLE_DISABLE_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "car_ui_toolbar_menu_item_text", "PACKAGE": "com.android.car.settings" }, "DISABLE_BUTTON_TEXT": { "TYPE": "TEXT", "VALUE": "Disable" }, "ENABLE_BUTTON_TEXT": { "TYPE": "TEXT", "VALUE": "Enable" }, "DISABLE_APP_BUTTON": { "TYPE": "TEXT", "VALUE": "DISABLE APP" }, "FORCE_STOP_BUTTON": { "TYPE": "TEXT", "VALUE": "Force stop" }, "OK_BUTTON": { "TYPE": "TEXT", "VALUE": "OK" }, "PERMISSIONS_MENU": { "TYPE": "TEXT", "VALUE": "Permissions" }, "ALLOW_BUTTON": { "TYPE": "TEXT", "VALUE": "Allow" }, "DENY_BUTTON": { "TYPE": "TEXT", "VALUE": "Deny" }, "DENY_ANYWAY_BUTTON": { "TYPE": "TEXT", "VALUE": "Deny anyway" } }, "DATE_AND_TIME": { "PATH": "Settings > Date & time", "OPTIONS": [ "Automatic date & time", "Automatic time zone" ], "AUTOMATIC_DATE_AND_TIME": { "TYPE": "TEXT", "VALUE": "Automatic date & time" }, "AUTOMATIC_TIME_ZONE": { "TYPE": "TEXT", "VALUE": "Automatic time zone" }, "SET_DATE": { "TYPE": "TEXT", "VALUE": "Set date" }, "SET_TIME": { "TYPE": "TEXT", "VALUE": "Set time" }, "SELECT_TIME_ZONE": { "TYPE": "TEXT", "VALUE": "Select time zone" }, "USE_24_HOUR_FORMAT": { "TYPE": "TEXT", "VALUE": "Use 24-hour format" }, "OK_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "toolbar_menu_item_0", "PACKAGE": "com.android.car.settings" }, "NUMBER_PICKER_WIDGET": { "TYPE": "CLASS", "VALUE": "android.widget.NumberPicker" }, "EDIT_TEXT_WIDGET": { "TYPE": "CLASS", "VALUE": "android.widget.EditText" } }, "USERS": { "PATH": "Settings > Users", "OPTIONS": [ "Guest" ] }, "ACCOUNTS": { "PATH": "Settings > Accounts", "OPTIONS": [ "Automatically sync data" ], "ADD_ACCOUNT": { "TYPE": "TEXT", "VALUE": "ADD ACCOUNT" }, "ADD_GOOGLE_ACCOUNT": { "TYPE": "TEXT", "VALUE": "Google" }, "SIGN_IN_ON_CAR_SCREEN": { "TYPE": "TEXT", "VALUE": "Sign in on car screen" }, "GOOGLE_SIGN_IN_SCREEN": { "TYPE": "TEXT", "VALUE": "Sign in to your Google Account" }, "ENTER_EMAIL": { "TYPE": "CLASS", "VALUE": "android.widget.EditText" }, "ENTER_PASSWORD": { "TYPE": "CLASS", "VALUE": "android.widget.EditText" }, "NEXT_BUTTON": { "TYPE": "TEXT", "VALUE": "Next" }, "DONE_BUTTON": { "TYPE": "TEXT", "VALUE": "Done" }, "REMOVE_BUTTON": { "TYPE": "TEXT", "VALUE": "Remove" }, "REMOVE_ACCOUNT_BUTTON": { "TYPE": "TEXT", "VALUE": "Remove Account" } }, "SYSTEM": { "PATH": "Settings > System", "OPTIONS": [ "About", "Legal information" ], "ABOUT_MENU": { "TYPE": "TEXT", "VALUE": "About" }, "RESET_OPTIONS_MENU": { "TYPE": "TEXT", "VALUE": "Reset options" }, "LANGUAGES_AND_INPUT_MENU": { "TYPE": "TEXT", "VALUE": "Languages & input" }, "DEVICE_MODEL": { "TYPE": "TEXT", "VALUE": "Model" }, "ANDROID_VERSION": { "TYPE": "TEXT", "VALUE": "Android version" }, "ANDROID_SECURITY_PATCH_LEVEL": { "TYPE": "TEXT", "VALUE": "Android security patch level" }, "KERNEL_VERSION": { "TYPE": "TEXT", "VALUE": "Kernel version" }, "BUILD_NUMBER": { "TYPE": "TEXT", "VALUE": "Build number" }, "RECYCLER_VIEW_WIDGET": { "TYPE": "CLASS", "VALUE": "androidx.recyclerview.widget.RecyclerView" }, "RESET_NETWORK": { "TYPE": "TEXT", "VALUE": "Reset network" }, "RESET_SETTINGS": { "TYPE": "TEXT", "VALUE": "RESET SETTINGS" }, "RESET_APP_PREFERENCES": { "TYPE": "TEXT", "VALUE": "Reset app preferences" }, "RESET_APPS": { "TYPE": "TEXT", "VALUE": "RESET APPS" }, "LANGUAGES_MENU": { "TYPE": "TEXT", "VALUE": "Languages" }, "LANGUAGES_MENU_IN_SELECTED_LANGUAGE": { "TYPE": "TEXT", "VALUE": "Idiomas" } }, "SECURITY": { "PATH": "Settings > Security", "OPTIONS": [ ], "TITLE": { "TYPE": "RESOURCE_ID", "VALUE": "car_ui_toolbar_title", "PACKAGE": "com.android.car.settings.googlecarui.rro" }, "CHOOSE_LOCK_TYPE": { "TYPE": "TEXT", "VALUE": "Choose a lock type" }, "LOCK_TYPE_PASSWORD": { "TYPE": "TEXT", "VALUE": "Password" }, "LOCK_TYPE_PIN": { "TYPE": "TEXT", "VALUE": "PIN" }, "LOCK_TYPE_NONE": { "TYPE": "TEXT", "VALUE": "None" }, "CONTINUE_BUTTON": { "TYPE": "TEXT", "VALUE": "Continue" }, "CONFIRM_BUTTON": { "TYPE": "TEXT", "VALUE": "Confirm" }, "ENTER_PASSWORD": { "TYPE": "CLASS", "VALUE": "android.widget.EditText" }, "PIN_PAD": { "TYPE": "RESOURCE_ID", "VALUE": "pin_pad", "PACKAGE": "com.android.car.settings" }, "ENTER_PIN_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "key_enter", "PACKAGE": "com.android.car.settings" }, "REMOVE_BUTTON": { "TYPE": "TEXT", "VALUE": "Remove" } } }, "PHONE": { "APPLICATION_CONFIG": { "DIAL_PACKAGE": "com.android.car.dialer", "PHONE_ACTIVITY": "com.android.car.dialer/.ui.TelecomActivity", "OPEN_DIAL_PAD_COMMAND": "am start -a android.intent.action.DIAL" }, "IN_CALL_VIEW": { "DIALED_CONTACT_TITLE": { "TYPE": "RESOURCE_ID", "VALUE": "user_profile_title", "PACKAGE": "com.android.car.dialer" }, "DIALED_CONTACT_NUMBER": { "TYPE": "RESOURCE_ID", "VALUE": "user_profile_phone_number", "PACKAGE": "com.android.car.dialer" }, "END_CALL": { "TYPE": "RESOURCE_ID", "VALUE": "end_call_button", "PACKAGE": "com.android.car.dialer" }, "MUTE_CALL": { "TYPE": "RESOURCE_ID", "VALUE": "mute_button", "PACKAGE": "com.android.car.dialer" }, "SWITCH_TO_DIAL_PAD": { "TYPE": "RESOURCE_ID", "VALUE": "toggle_dialpad_button", "PACKAGE": "com.android.car.dialer" }, "CHANGE_VOICE_CHANNEL": { "TYPE": "RESOURCE_ID", "VALUE": "voice_channel_view", "PACKAGE": "com.android.car.dialer" }, "VOICE_CHANNEL_CAR": { "TYPE": "TEXT", "VALUE": "Car speakers" }, "VOICE_CHANNEL_PHONE": { "TYPE": "TEXT", "VALUE": "Phone" } }, "DIAL_PAD_VIEW": { "DIAL_PAD_MENU": { "TYPE": "TEXT", "VALUE": "Dial Pad" }, "DIAL_PAD_FRAGMENT": { "TYPE": "RESOURCE_ID", "VALUE": "dialpad_fragment", "PACKAGE": "com.android.car.dialer" }, "DIALED_NUMBER": { "TYPE": "RESOURCE_ID", "VALUE": "title", "PACKAGE": "com.android.car.dialer" }, "MAKE_CALL": { "TYPE": "RESOURCE_ID", "VALUE": "call_button", "PACKAGE": "com.android.car.dialer" }, "DELETE_NUMBER": { "TYPE": "RESOURCE_ID", "VALUE": "delete_button", "PACKAGE": "com.android.car.dialer" } }, "CONTACTS_VIEW": { "CONTACTS_MENU": { "TYPE": "TEXT", "VALUE": "Contacts" }, "CONTACT_INFO": { "TYPE": "RESOURCE_ID", "VALUE": "call_action_id", "PACKAGE": "com.android.car.dialer" }, "CONTACT_NAME": { "TYPE": "RESOURCE_ID", "VALUE": "title", "PACKAGE": "com.android.car.dialer" }, "CONTACT_DETAIL": { "TYPE": "RESOURCE_ID", "VALUE": "show_contact_detail_id", "PACKAGE": "com.android.car.dialer" }, "ADD_CONTACT_TO_FAVORITE": { "TYPE": "RESOURCE_ID", "VALUE": "contact_details_favorite_button", "PACKAGE": "com.android.car.dialer" }, "SEARCH_CONTACT": { "TYPE": "RESOURCE_ID", "VALUE": "menu_item_search", "PACKAGE": "com.android.car.dialer" }, "CONTACT_SEARCH_BAR": { "TYPE": "RESOURCE_ID", "VALUE": "car_ui_toolbar_search_bar", "PACKAGE": "com.android.car.dialer" }, "SEARCH_RESULT": { "TYPE": "RESOURCE_ID", "VALUE": "contact_name", "PACKAGE": "com.android.car.dialer" }, "CONTACT_SETTINGS": { "TYPE": "RESOURCE_ID", "VALUE": "menu_item_setting", "PACKAGE": "com.android.car.dialer" }, "CONTACT_ORDER": { "TYPE": "TEXT", "VALUE": "Contact order" }, "SORT_BY_FIRST_NAME": { "TYPE": "TEXT", "VALUE": "First name" }, "SORT_BY_LAST_NAME": { "TYPE": "TEXT", "VALUE": "Last Name" }, "CONTACT_TYPE_WORK": { "TYPE": "TEXT", "VALUE": "Work" }, "CONTACT_TYPE_MOBILE": { "TYPE": "TEXT", "VALUE": "Mobile" }, "CONTACT_TYPE_HOME": { "TYPE": "TEXT", "VALUE": "Home" } }, "CALL_HISTORY_VIEW": { "CALL_HISTORY_MENU": { "TYPE": "TEXT", "VALUE": "Recents" }, "CALL_HISTORY_INFO": { "TYPE": "RESOURCE_ID", "VALUE": "call_action_id", "PACKAGE": "com.android.car.dialer" } }, "FAVORITES_VIEW": { "FAVORITES_MENU": { "TYPE": "TEXT", "VALUE": "Favorites" } } }, "NOTIFICATIONS": { "APPLICATION_CONFIG": { "OPEN_NOTIFICATIONS_COMMAND": "service call statusbar 1" }, "EXPANDED_NOTIFICATIONS_SCREEN": { "NOTIFICATION_VIEW": { "TYPE": "RESOURCE_ID", "VALUE": "notification_view", "PACKAGE": "com.android.systemui" }, "CLEAR_ALL_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "clear_all_button", "PACKAGE": "com.android.systemui" }, "STATUS_BAR": { "TYPE": "RESOURCE_ID", "VALUE": "car_top_navigation_bar_container", "PACKAGE": "com.android.systemui" }, "APP_ICON": { "TYPE": "RESOURCE_ID", "VALUE": "app_icon", "PACKAGE": "com.android.systemui" }, "APP_NAME": { "TYPE": "RESOURCE_ID", "VALUE": "header_text", "PACKAGE": "com.android.systemui" }, "NOTIFICATION_TITLE": { "TYPE": "RESOURCE_ID", "VALUE": "notification_body_title", "PACKAGE": "com.android.systemui" }, "NOTIFICATION_BODY": { "TYPE": "RESOURCE_ID", "VALUE": "notification_body_content", "PACKAGE": "com.android.systemui" }, "CARD_VIEW": { "TYPE": "RESOURCE_ID", "VALUE": "card_view", "PACKAGE": "com.android.systemui" } } }, "MEDIA_CENTER": { "APPLICATION_CONFIG": { "MEDIA_CENTER_PACKAGE": "com.android.car.media", "MEDIA_ACTIVITY": "com.android.bluetooth/com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService" }, "MEDIA_CENTER_SCREEN": { "PLAY_PAUSE_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "play_pause_stop", "PACKAGE": "com.android.car.media" }, "NEXT_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "skip_next", "PACKAGE": "com.android.car.media" }, "PREVIOUS_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "skip_prev", "PACKAGE": "com.android.car.media" }, "SHUFFLE_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "overflow_on", "PACKAGE": "com.android.car.media" }, "PLAY_QUEUE_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "play_queue", "PACKAGE": "com.android.car.media" }, "MINIMIZED_MEDIA_CONTROLS": { "TYPE": "RESOURCE_ID", "VALUE": "minimized_playback_controls", "PACKAGE": "com.android.car.media" }, "TRACK_NAME": { "TYPE": "RESOURCE_ID", "VALUE": "title", "PACKAGE": "com.android.car.media" }, "TRACK_NAME_MINIMIZED_CONTROL": { "TYPE": "RESOURCE_ID", "VALUE": "minimized_control_bar_title", "PACKAGE": "com.android.car.media" }, "BACK_BUTTON": { "TYPE": "DESCRIPTION", "VALUE": "Back" } }, "MEDIA_CENTER_ON_HOME_SCREEN": { "PLAY_PAUSE_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "play_pause_stop", "PACKAGE": "com.android.car.carlauncher" }, "NEXT_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "skip_next", "PACKAGE": "com.android.car.carlauncher" }, "PREVIOUS_BUTTON": { "TYPE": "RESOURCE_ID", "VALUE": "skip_prev", "PACKAGE": "com.android.car.carlauncher" }, "TRACK_NAME": { "TYPE": "RESOURCE_ID", "VALUE": "title", "PACKAGE": "com.android.car.carlauncher" } } } }
Alternative device configurations
The following code sample shows an example of the JSON configuration file where default settings are overridden by the settings on the DUT. In this example:
Internet settings are named Network & internet on reference devices and Connectivity on the DUT.
The date and time settings are available at Settings > Date and time for reference devices and at Settings > System > Date and time for the DUT.
// Default configuration file
{
....
"SECURITY_SETTINGS_SCROLL_ELEMENT": {
"TYPE": "RESOURCE_ID",
"VALUE": "fragment_container",
},
....
}
// JSON configuration file for non-reference device
{
....
"SECURITY_SETTINGS_SCROLL_ELEMENT": {
"TYPE": "RESOURCE_ID",
"VALUE": "car_ui_recycler_view"
},
....
}
When the JSON configuration file is ready, it's provided at runtime as shown in the following code block:
# Push The JSON configuration file to the device
adb -s DEVICE-SERIAL push PATH-OF-JSON-FILE /data/local/tmp/runtimeSpectatioConfig.json
In this command:
DEVICE-SERIAL: Serial ID of the DUT. This parameter isn`t required if only one device is connected to the host.
PATH-TO-JSON-FILE: Path of the JSON file on the host machine.
Configuration format
There are five top-level objects in the configuration, with the following keys and values:
Object | Description |
---|---|
PACKAGES |
An object describing the main package for various apps, which are used to determine when that app is in the foreground. |
ACTIONS |
An object indicating action types and parameters for various actions. For example, whether to use buttons or a gesture to scroll. |
COMMANDS |
An object specifying commands that perform various actions. |
UI_ELEMENTS |
An object used to construct UI Automator `BySelectors` that select UI Elements (described in detail below). |
WORKFLOWS |
Sequences of actions that accomplish high-level tasks (described in detail below). |
UI elements
Each UI element has a TYPE
which specifies what UI Automator will look for to
identify the element (such as resource ID, text, and description) and
configuration values associated with that type. In general, whenever a helper
identifies an element on the screen using this configuration, it gets exactly
one element. If multiple elements match the configuration, an arbitrary one is
used in the test. Therefore, the configuration should (generally) be written
specifically enough that it narrows down to one element in the relevant context.
TEXT
This is the simplest UI element type. The UI element is identified by its text, and requires an exact match.
"CALL_HISTORY_MENU": {
"TYPE": "TEXT",
"VALUE": "Recents"
}
TEXT_CONTAINS
Same as TEXT
, except that the specified VALUE
only need appear somewhere in
the text of the element to be matched.
"PRIVACY_CALENDAR": {
"TYPE": "TEXT_CONTAINS",
"VALUE": "Calendar"
}
DESCRIPTION
Identify the element by its content description attribute, requiring an exact match.
"APP_GRID_SCROLL_BACKWARD_BUTTON": {
"TYPE": "DESCRIPTION",
"VALUE": "Scroll up"
}
RESOURCE_ID
Identify the element by its resource ID, optionally also checking the package
component of that ID. The PACKAGE
key is optional; if omitted, any package
will match, and only the portion of the ID following :id/
will be considered.
"APP_LIST_SCROLL_ELEMENT": {
"TYPE": "RESOURCE_ID",
"VALUE": "apps_grid",
"PACKAGE": "com.android.car.carlauncher"
}
CLICKABLE, SCROLLABLE
Identify the element based on whether it is (or isn't) clickable or scrollable.
These are very broad element types, and should generally only be used in a
MULTIPLE
to help narrow down another element type. The FLAG
key is optional,
and defaults to true
.
"SAMPLE_ELEMENT": {
"TYPE": "CLICKABLE",
"FLAG": false
}
CLASS
Identify the element based on its class.
"SECURITY_SETTINGS_ENTER_PASSWORD": {
"TYPE": "CLASS",
"VALUE": "android.widget.EditText"
}
HAS_ANCESTOR
Identify the element by looking up the widget hierarchy at its ancestors. The
ANCESTOR
key holds an object that identifies the ancestor. The DEPTH
key
specifies how far up the hierarchy to look. DEPTH
is optional and has a
default value of 1
.
"SAMPLE_ELEMENT": {
"TYPE": "HAS_ANCESTOR",
"DEPTH": 2,
"ANCESTOR": {
"TYPE": "CLASS",
"VALUE": "android.view.ViewGroup"
}
}
HAS_DESCENDANT
Identify the element by looking down the hierarchy at its children. The
DESCENDANT
key holds an object that specifies the child to look for. The
DEPTH
key specifies how far up the hierarchy to look. DEPTH
is optional and
has a default value of 1
.
"SAMPLE_ELEMENT": {
"TYPE": "HAS_DESCENDANT",
"DEPTH": 2,
"DESCENDANT": {
"TYPE": "CLASS",
"VALUE": "android.view.ViewGroup"
}
}
MULTIPLE
Identify the element based on multiple simultaneous conditions, all of which must be met.
"APP_INFO_SETTINGS_PERMISSION_MANAGER": {
"TYPE": "MULTIPLE",
"SPECIFIERS": [
{
"TYPE": "CLASS",
"VALUE": "android.widget.RelativeLayout"
},
{
"TYPE": "HAS_DESCENDANT",
"MAX_DEPTH": 2,
"DESCENDANT": {
"TYPE": "TEXT",
"VALUE": "Permission manager"
}
}
]
}
In this example, the configuration identifies a RelativeLayout
that has a
descendant at depth 2
, which has the text Permission manager
.
Workflows
A workflow represents a sequence of actions used to accomplish a particular task, which may differ enough from device type to device type and is more flexible to represent in configuration than in code.
"WORKFLOWS": {
"OPEN_SOUND_SETTINGS_WORKFLOW": [
{
"NAME": "Go to Home",
"TYPE": "PRESS",
"CONFIG": {
"TEXT": "HOME"
}
},
{
"NAME": "Open Settings",
"TYPE": "COMMAND",
"CONFIG": {
"TEXT": "am start -a android.settings.SETTINGS"
}
},
{
"NAME": "Open Sound Settings",
"TYPE": "SCROLL_TO_FIND_AND_CLICK",
"CONFIG": {
"UI_ELEMENT": {
"TYPE": "TEXT",
"VALUE": "Sound"
}
},
"SCROLL_CONFIG": {
"SCROLL_ACTION": "USE_GESTURE",
"SCROLL_DIRECTION": "VERTICAL",
"SCROLL_ELEMENT": {
"TYPE": "RESOURCE_ID",
"VALUE": "car_ui_recycler_view"
}
}
}
]
}
Each workflow is a key-value pair where the key is the name of the workflow and
the value is an array of actions to perform. Each action has a NAME
, a TYPE
,
(usually) a CONFIG
, and (sometimes) a SWIPE_CONFIG
or SCROLL_CONFIG
. For
most TYPEs, the CONFIG
is an object with a UI_ELEMENT
key whose value takes
the same form as a UI element entry (see above). Those TYPEs are:
PRESS LONG_PRESS CLICK LONG_CLICK CLICK_IF_EXIST |
HAS_UI_ELEMENT_IN_FOREGROUND SCROLL_TO_FIND_AND_CLICK SCROLL_TO_FIND_AND_CLICK_IF_EXIST SWIPE_TO_FIND_AND_CLICK SWIPE_TO_FIND_AND_CLICK_IF_EXIST |
For the other TYPEs, the configuration details are:
Object | Description |
---|---|
COMMAND |
An object with a TEXT value containing the command to execute. |
HAS_PACKAGE_IN_FOREGROUND |
An object with a TEXT value containing the package. |
SWIPE |
Omit the CONFIG key for a SWIPE action. This
uses only SWIPE_CONFIG |
WAIT_MS |
An object with a TEXT value containing the number of
milliseconds to wait. |
Scroll- and swipe-related actions require additional configuration, as follows:
SCROLL_CONFIG
Object | Description |
---|---|
SCROLL_ACTION |
Either USE_GESTURE or USE_BUTTON |
SCROLL_DIRECTION |
Either HORIZONTAL or VERTICAL |
SCROLL_ELEMENT |
An object indicating the container to scroll, using the same form as a UI Element configuration (see above). |
SCROLL_FORWARD , SCROLL_BACKWARD |
The forward and backward scroll buttons (required when
SCROLL_ACTION is USE_BUTTON ). |
SCROLL_MARGIN |
If SCROLL_ACTION is USE_GESTURE , the distance
from the edge of the container to start and stop the drag that will be used
to perform the scroll (Optional, default = 10). |
SCROLL_WAIT_TIME |
If SCROLL_ACTION is USE_GESTURE , the time in
milliseconds to wait between scroll gestures when searching for an object to
click.
(Optional, default = 1). |
SWIPE_CONFIG
Object | Description |
---|---|
SWIPE_DIRECTION |
Either TOP_TO_BOTTOM , BOTTOM_TO_TOP ,
LEFT_TO_RIGHT , or RIGHT_TO_LEFT |
SWIPE_FRACTION |
One of the following:
|
NUMBER_OF_STEPS |
The number of steps to be used to perform the swipe. See
segmentSteps .
|
Build and execute
The Spectatio framework is automatically built as part of the test APK. To build the test APK, the AOSP codebase must reside on the local workstation. After the test APK is built, the user must install the APK on the device and execute the test.
The following code sample shows the building, installation, and execution of a test APK.
# Build Test APK make TEST-APK-NAME
# Install Test APK adb -s DEVICE-SERIAL install -r PATH-FOR-BUILT-TEST-APK
# Execute Test with the JSON file adb -s DEVICE-SERIAL shell am instrument -w -r -e debug false -e config-file-path /data/local/tmp/jsonFile.json -e class TEST-PACKAGE.TEST-CLASSNAME TEST-PACKAGE/androidx.test.runner.AndroidJUnitRunner
In these commands:
TEST-APK-NAME: The name of the app to be tested. For example, set TEST-APK-NAME to
AndroidAutomotiveSettingsTests
to test the Wi-Fi settings as specified in theAndroid.bp
file. The name of the APK can be found in the respectiveAndroid.bp
file for the Automotive test.DEVICE-SERIAL: The serial ID of the DUT. This parameter isn't required if only one device is connected to the host.
config-file-path
: Optional parameter that is required only to provide nondefault device UI configurations as specified in the JSON configuration file. If not provided, the framework uses default values for executing the tests.PATH-FOR-BUILT-TEST-APK: The path where the test APK is built when the
make
command is executed.TEST-PACKAGE: The name of the test package.
TEST-CLASSNAME: The name of the test class. For example, for the Wifi Settings test, the test package is
android.platform.tests
and the test class name isWifiSettingTest
.
Automotive Snippet Library
The Automotive Snippet Library is a set of Android Test libraries for the Android Open Source Project (AOSP) designed to interact with automotive apps and services. It leverages Spectatio with a convenient mechanism for executing remote procedure calls (RPCs) from a host (test) machine to an Android-powered device.
Get started
Before you start, review these sections.
Prerequisites
- Python 3.x installed on the host machine.
- AOSP environment setup with necessary build tools.
- An Android automotive device (emulator or physical device) with adb access.
Compilation
To compile the various snippets provided by the Automotive Snippet Library, you
can use the provided android.bp
file. Following commands in the previous
section to compile the APK.
Deployment
After successfully compiling the snippet libraries, deploy the resulting APKs to
the target device using the adb install
command mentioned in the previous
section.
Run tests
The snippet libraries expose several RPC methods to interact with the automotive
system. These methods can be invoked through the Mobly framework from the host
machine. Assuming you have the Mobly test environment set up, you can use the
snippet_shell.py
script to open an interactive Python shell, where you can
manually invoke RPC methods on the device. Example invocation:
python3 snippet_shell.py com.google.android.mobly.snippet.bundled -s <serial>
Replace <serial>
with the device serial number, which you can obtain with
adb devices if multiple devices are connected.
Included libraries
The Automotive Snippet Library includes the following snippet libraries and helpers:
AutomotiveSnippet: Provides APIs related to vehicle operations, such as dialing, volume control, vehicle hard keys, and media center interaction.
PhoneSnippet: Provides telephony-related APIs, including call handling, contacts browsing, and SMS operations.
The Automotive snippet and the PhoneSnippet share some common logic.
Specifically, you can invade Bluetooth-related RCP calls to pair an automotive
and a phone device. This bt_discovery_test
shows how.
- TEST-CLASSNAME: The name of the test class. For example, for the
Wifi Settings test,
the test package is
android.platform.tests
and the test class name isWifiSettingTest
.