Mobile development

Alex Zhukovich

Android testing: UI Automator (Part 4)

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.

ui_automator_integrate_api_less_18

The AndroidManifest.xml file (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”.

tests_edit_confihuration

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

run_test_android_studio

Use gradle

If you want to run instrumentation test use gradle. You can use next command

./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 about this class.

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.

ui_collection_demo_app

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 UI Automator 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();
    }
}

And 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 UiObject2 object just for some actions we can use next line of code:

mDevice.findObject(By.desc("Apps")).click();

Creating test of notifications

First of all, need to open application list.

UiObject2 allApps = mDevice.findObject(By.desc("Apps"));
allApps.click();

After it 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(notificationTitle.getText(), "Battery saver is on");

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

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

charger_connection_emulator

Resources:

  1. Testing UI for Multiple Apps
  2. Testing Support Library

Thank you for your time.
Have a nice day.

Share Share on Reddit0Share on VKTweet about this on TwitterShare on LinkedIn0Share on Google+3Share on Facebook3Flattr the authorEmail this to someoneShare on Tumblr0
« »

© 2017 Mobile development. Theme by Anders Norén.