Android Testing

Android testing: UI Automator (Part 4)

Alex Zhukovich 9 min read
Android testing: UI Automator (Part 4)
Table of Contents

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?

Ui Automator Viewer

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.

AndroidManifest.xml for API < 18

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".

Configure 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".

Edit test configuration

After these changes you can find updated run menu and run your tests or an application.

Run tests in Android Studio

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.

Demo app: UiCollection

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.

Emulator: battery status

Resources:

Thank you for your time.
Have a nice day.


Mobile development with Alex

A blog about Android development & testing, Best Practices, Tips and Tricks

Share
More from Mobile development with Alex

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Mobile development with Alex.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.