This is a fourth article from series of articles about testing Android project.
First article about "Unit testing" you can read here.
Second article about "Mockito and Robolectric" you can read here.
Third article about "Espresso" you can read here.
Full source code you can find on GitHub.
What is UI Automator?
The UI Automator testing framework, provided by the Android Testing Support Library, provides set of APIs to build UI tests which can interact with any applications. This framework is well-suited for writing black box-style automated tests.
The key-features of the UI Automator:
- UI Automator View;
- Interacting with any applications;
- Access to device state
- Change the device rotation
- Press the Back, Home, or Menu buttons
- Open the notification shade
- Take a screenshot of the current window
Unfortunately, UI Automator requires Android 4.3 (API 18) or higher.
What is UI Automator View?
The UIAutomatorView tool provides a really convenient application to scan and analyze the UI component of anything that displaying on Android device. Information from UIAutomatorView allows you create good UI Automator tests.
You can find uiautomatorview application is located in the "{ANDROID-SDK}/tools/" directory or you can launch this application from Android Device Monitor (Tools/Android/Android Device Monitor from Android Studio).
Intergrate UI Automator to the project
First of all, need to add dependencies and a runner to build.gradle
file.
android {
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
...
}
dependencies {
...
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
}
Unfortunately, UI Automator has an additional problem that it minimum version of API is 18.
I would like to show how we can solve this problem. We can start from adding of additional AndroidManifest.xml
file. You need to add this file to androidTest folder.
The AndroidManifest.xml
file in "androidTest" folder.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.alexzh.simplecoffeeorder">
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="23"
tools:overrideLibrary="android.support.test.uiautomator.v18"/>
</manifest>
This file contains required information for support UIAutomator framework. After it you can use @SdkSuppress(minSdkVersion = 18)
annotation for tests sets with Ui Automator framework. If you also use Espresso tests in your project it’s not a problem all Espresso tests is possible to run on devices with API > 8.
Running UI Automator tests
When we are talking about running UI Automator tests, it’s the same story as for running any instrumentation tests.
Use Android Studio
First of all, you need to create configuration for instrumentation tests. You can choose "Run/Edit Configurations ..." for it. After you can choose "+" icon and click to "Android Tests".
Then you need to set up module for Android tests. In my case I changed Name to "UI tests" and Module to "app".
After these changes you can find updated run menu and run your tests or an application.
Use gradle
If you want to run instrumentation test use gradle. You can use ./gradlew connectedAndroidTest
or ./gradlew cAT
.
When this command has been finished you can find a report with test results inside your build folder: "{PROJECT}/{APPLICATION_MODULE}/build/reports/androidTests/connected/index.html".
Writing UI Automator tests
Basic UI Automator components
There are general UI Automator components:
- By - utility class which enables the creation of BySelectors in a concise manner;
- BySelector - specifies criteria for matching UI elements during a call to findObject(BySelector);
- UiCollection - uses to enumerate screen elements for the purpose of counting, or targeting sub elements by text or description, etc. Represents a collection of items, as example list of coffees in a coffee order application;
- UiDevice - provides access to state information about the device;
- UiObject - contains information about view, based on UiSelector;
- UiObject2 - a UiObject2 slightly differ from UiObject. The UiObject2 also represents a UI element.
- UiScrollable - used to searching elements in a scrollable UI;
- UiSelector - uses to searching UI elements that is visible on the device screen; You can use different attributes for it, like text, class, content description, etc;
- Configurator - allows you to set key parameters for running UI Automator tests.
UiDevice example of using
The UiDevice provides access to state information about the device.
There are general UiDevice actions:
- Press "Home" button (
mDevice.pressHome();
); - Press "Back" button (
mDevice.pressBack();
); - Press power button (
mDevice.wakeUp();
); - Click at arbitrary coordinates (
mDevice.click(300, 215);
); - Open the notifications (
mDevice.openNotification();
).
You can find more information here.
UiCollection example of using
The UiCollection used to enumerate screen elements for the purpose of counting, or targeting sub elements by text, description, index, etc. Represents a collection of items, as example list of coffees in a coffee order application.
The following snippet of code display how to initialize collection. Root object of this collection is RelativeLayout.
UiCollection coffees = new UiCollection(
new UiSelector().className("android.widget.RelativeLayout")
);
If you want to get the number of coffees in this collection, which displayed on screen.
int count = coffees.getChildCount(
new UiSelector().className("android.widget.LinearLayout")
);
You also can get can a specific coffee that is called "Espresso" from the collection.
UiObject espresso = coffees.getChildByText(
new UiSelector().className("android.widget.LinearLayout"),
"Espresso"
);
You can find more information here about this class.
UiObject example of using
The UiObject
represents a UI element.
You can find a UI element on the screen use next line of code.
UiObject payButton = new UiObject(new UiSelector().text("Pay"));
Unfortunately, the "payButton" can be null, better way check it before using
if (payButton.exists()) {
payButton.click();
}
You can find more information here about this class.
UiObject2 example of using
The UiObject2
represents a UI element.
I would like the same example as in case about the UiObject.
UiObject2 payButton = mDevice.findObject (By.text("Pay"));
if (payButton.exists()) {
payButton.click();
}
You can find more information here about this class.
UiScrollable example of using
The UiScrollable
uses to searching elements in a scrollable UI.
You can scroll this object to different direction, like backward, forward, etc.
UiScrollable appViews = new UiScrollable(new UiSelector().scrollable(true));
appViews.setAsHorizontalList();
appViews.scrollBackward();
appViews.scrollForward();
You also can scroll until finding text, as example scroll to Settings.
appViews.scrollTextIntoView("Settings");
You can find more information here about this class.
UiSelector example of using
The UiSelector
uses to searching UI elements that is visible on the device screen; You can use different attributes for it, like text, resource id, content description, etc.
You can create a new UiObject from many different attributes, like
- Text
UiObject notificationTitle = mDevice.findObject(
new UiSelector().text("Coffee order app")
);
- Resource ID
UiObject notificationTitle = mDevice.findObject(
new UiSelector().resourceId("title")
);
- Content description
UiObject clearAllNotifications = mDevice.findObject(
new UiSelector().description("Clear all notifications.")
);
If more than one element is matching UiSelector returns the first element of hierarchy. You can find more information here about this class.
BySelector example of using
The BySelector
uses for matching UI elements during a call to findObject(BySelector). You can use different attributes for it, like text, content description, resource id, etc.
- Text
UiObject2 notificationTitle = mDevice.findObject(
By.text("Coffee order app")
);
- Resource ID
UiObject2 notificationTitle = mDevice.findObject(
By.res("android", "title")
);
- Content description
UiObject2 clearAllNotifications = mDevice.findObject(
By.desc("Clear all notifications.")
);
You can find more information here about this class.
Configurator example of using
The Configurator
allows you to set key parameters for running UI Automator tests. As example this class allows Set the timeout for waiting for the user interface to go into an idle state before starting a uiautomator action. You can find more information here about this class.
Creating test of system application
In first test we check basic calculator application. First of all, need to start this application, after it we can summarize two numbers, as example 2 and 3. In the end, we check the result.
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class CalculatorAppTest {
private UiDevice mDevice;
@Before
public void setup() throws UiObjectNotFoundException {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mDevice.pressHome();
//open list of application
UiObject2 allApps = mDevice.findObject(By.desc("Apps"));
allApps.click();
//open calculator application
mDevice.wait(Until.hasObject(By.text("Calculator")), 3000);
UiObject2 calculatorApp = mDevice.findObject(By.desc("Calculator"));
calculatorApp.click();
}
@Test
public void shouldSumTwoNumbers() throws UiObjectNotFoundException {
mDevice.wait(Until.hasObject(By.text("5")), 3000);
//create objects, which we will for emulating user behaviour
UiObject2 two = mDevice.findObject(By.text("2"));
UiObject2 three = mDevice.findObject(By.text("3"));
UiObject2 plus = mDevice.findObject(By.text("+"));
UiObject2 equal = mDevice.findObject(By.text("="));
//sequence of emulating actions
two.click();
plus.click();
three.click();
equal.click();
//get results
UiObject2 result = mDevice.findObject(By.res("com.android.calculator2", "formula"));
//verify result
assertEquals("5", result.getText());
}
@After
public void tearDown() {
mDevice.pressBack();
}
}
Finally, few comments about this code. I used setup method which calls before each test for opening "Calculator" application. Probably you will test the same application in one test suite and open application better way before each test inside method with @Before
annotation.
If we need to use UiObject2
object just for some actions we can use next line of code:
mDevice.findObject(By.desc("Apps")).click();
Creating test for notifications
First of all, need to open application list.
UiObject2 allApps = mDevice.findObject(By.desc("Apps"));
allApps.click();
After it, we need to find and launch Settings application.
UiScrollable appViewsList = new UiScrollable(
new UiSelector().scrollable(true)
);
appViewsList.scrollTextIntoView("Settings");
mDevice.findObject(
new UiSelector().description("Settings")
).click();
Next step is searching Battery setting and open it.
UiScrollable settingList = new UiScrollable(
new UiSelector().scrollable(true)
);
settingList.scrollTextIntoView("Battery");
mDevice.findObject(By.text("Battery"))
.click();
After it, we can open additional menu and choose "Battery saver" item.
mDevice.wait(Until.hasObject(By.desc("More options")), TIMEOUT);
mDevice.findObject(By.desc("More options"))
.click();
mDevice.wait(Until.hasObject(By.desc("Battery saver")), TIMEOUT);
mDevice.findObject(By.text("Battery saver"))
.click();
When "Battery Saver" launched need change status of Battery saver.
mDevice.wait(Until.hasObject(By.text("OFF")), TIMEOUT);
mDevice.findObject(By.text("OFF"))
.click();
In the end, we must open notification and find notification about Battery saver.
mDevice.openNotification();
mDevice.wait(Until.hasObject(By.text("Battery saver is on")), TIMEOUT);
UiObject2 notificationTitle = mDevice.findObject(
By.text("Battery saver is on")
);
assertEquals("Battery saver is on", notificationTitle.getText());
And finally we need to return default state of device, it’s mean turn of battery saver mode.
mDevice.findObject(By.text("Turn off battery saver"))
.click();
mDevice.wait(Until.hasObject(By.text("OFF")), TIMEOUT);
UiObject2 status = mDevice.findObject(By.text("OFF"));
assertEquals("OFF", status.getText());
Full source code of this test
```java
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class SettingsTest {
private static final int TIMEOUT = 1000;
private UiDevice mDevice;
@Before
public void setup() throws UiObjectNotFoundException {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mDevice.pressHome();
//open list of application
mDevice.findObject(By.desc("Apps")).click();
}
@Test
public void shouldVerifyBatteryNotification() throws UiObjectNotFoundException {
UiScrollable appViewsList = new UiScrollable(new UiSelector().scrollable(true));
appViewsList.scrollTextIntoView("Settings");
mDevice.findObject(new UiSelector().description("Settings")).click();
UiScrollable settingList = new UiScrollable(new UiSelector().scrollable(true));
settingList.scrollTextIntoView("Battery");
mDevice.findObject(By.text("Battery")).click();
mDevice.wait(Until.hasObject(By.desc("More options")), TIMEOUT);
mDevice.findObject(By.desc("More options")).click();
mDevice.wait(Until.hasObject(By.desc("Battery saver")), TIMEOUT);
mDevice.findObject(By.text("Battery saver")).click();
mDevice.wait(Until.hasObject(By.text("OFF")), TIMEOUT);
mDevice.findObject(By.text("OFF")).click();
mDevice.openNotification();
mDevice.wait(Until.hasObject(By.text("Battery saver is on")), TIMEOUT);
UiObject2 notificationTitle = mDevice.findObject(By.text("Battery saver is on"));
assertEquals(notificationTitle.getText(), "Battery saver is on");
mDevice.findObject(By.text("Turn off battery saver")).click();
mDevice.wait(Until.hasObject(By.text("OFF")), TIMEOUT);
UiObject2 status = mDevice.findObject(By.text("OFF"));
assertEquals("OFF", status.getText());
}
@After
public void tearDown() {
mDevice.pressBack();
}
}
Of course test system application, it’s not really good idea, but right now you know how to test notifications. I also recommend use wait method, when you change activity of have an animation.
Note: If you want to run this test use emulator, need to change "Charger Connection" to None status, because this mode is available just when your device does not connect to charger.
Resources:
Thank you for your time.
Have a nice day.