The definitive guide of Android UI Automator with Kotlin
How to write automated test scripts for Android apps even without access to the applications source code
Automation is a key point of Software Testing once it make possible to reproduce test steps as many times as needed, across the different software versions, which can be tedious to be done manually and very error prone once it is likely to a human to forget to perform some required set up or misunderstand some test step, resulting in invalid results.
This article will explain how to write an UI Automator script to automatically test any Android application. We will write a simple test for the Android Settings app that adds a Wi-Fi network and check if the device is able to connect to it.

The example from this article was written using an Android Virtual Device with the system image R API level 30 ABI x86 target Android 11 (Google Play). You can use any image with Android 4.3 (API level 18) or higher and adapt the code according to its UI.
UI Automator
UI Automator is an Android testing framework that allows us to write scripts that can interact to any application installed in the device. UI Automator doesn’t require having access to the application source code to work. Because of that, the script can navigate and interact with the application tray, Settings application, third party applications or any other app you want to.
Creating the project
Create a new Project on Android Studio with no activity and minimum SDK 18 or higher. The Android Studio will create three different source sets in the project: main
, androidTest
and test
. This is the default project structure in which main
contains the application code, the test
contains the unit tests that runs on the development machine and androidTest
that is were the instrumented tests, like UI Automator ones, go by default.

If you are writing you scripts in the same project of your application, then you can just create your scripts inside androidTest
directory, but in this article we are creating a project with UI Automator scripts only, to test another application which we don’t have access to its source code, so we will make some changes before starting it.
Open the build.gradle
of the app
module and add the following code inside android
block:
This will change the directory of our instrumented tests to the main
directory. Now you can delete both test
and androidTest
source sets. Also delete some resources that will no longer be used:
app/src/main/res/values/themes.xml
app/src/main/res/values-night/themes.xml
app/src/main/res/values/colors.xml
With our source sets configured, let’s add the required dependencies. Once our scripts are part of the main
source set, we need to add the dependencies using implementation
instead of androidTestImplementation
. Also you can remove dependencies that will not be used:
After the change this is how your build.gradle
will look like:
We are almost done configuring the project, we just need to modify the AndroidManifest.xml
in order to remove reference to the deleted files and add the instrumentation. Open the manifest file and remove the theme
atribute from application
tag once we deleted theme XML files previously.
Once we are creating the scripts in the main source set, we need to manually add the instrumentation
tag to the manifest files. Add the instrumentation
inside the manifest
tag with the name
and targetPackage
attributes. The name
must be defined as androidx.test.runner.AndroidJUnitRunner
and the targetPackage
must be the same package of your application (which is com.paceli.wifitest
, in this example).
You can see the AndroidManifest.xml
with the changes below:
Writing our script
Having created and configured the project, we are good to proceed creating our first script. Create a new class and add the annotation @RunWith(AndroidJUnit4::class)
in order to define AndroidJUnit4
as the test runner.
As usual in JUnit, the test methods must be annotated with @Test
. Set up and tear down methods must be annotated with @Before
and @After
respectively. For those who are not familiar with this approach, the code below shows the order that the methods are executed:
Running this example will produce the following logcat output:
Before Class
Before
Test
After
After Class
So let’s create a method named validateWifi
in out test class and annotate it with @Test
. In order to click on buttons, read text from screen, perform swipe gestures, and any other interaction with the UI, we need to get an instance of the UiDevice class. To achieve that, we declared a property to the class and added the init
block with the code to get an instance. This is how our class is looking at this moment:
To interact with the elements from the screen, we need to get a reference to them using the UiDevice instance. To do that, we will call the method findObject passing the properties of the element we want to interact with. Some properties, like the text, are visible to the user, but there are several others that we cannot see on the device screen, so we need to dump the screen using the UI Automator Viewer tool that is available in the Android SDK at $ANDROID_HOME/tools/bin/uiautomatorviewer
.

By clicking on the Device Screenshot
button on the top left, the tool will show a screenshot and the dump of the screen currently displayed from the device connected to ADB. The elements can be chosen by clicking on them at the screenshot or at hierarchy view on the right side. Properties like index
, text
, content-desc
, enabled
, etc. are displayed in the Node detail
view and.
The UiObject2 class represents the elements from screen and an instance of it is returned by the findObject method. In order to launch the Settings app, the user need to perform a scroll gesture on home screen to launch the applications list, and then click on Settings icon. So we obtained an instance of UiObject2 representing the workspace
and another one for the Settings icon in the apps list screen, and then call the methods scroll and click respectively.
If some element takes some time to be displayed in the screen, we can use the method wait instead of the findObject. This method receives a SearchCondition and a timeout. Let’s use this approach to open the Network & internet
section and then proceed up to the Add network
screen.
In the Add network
screen, there is a text field where the user must input the network SSID. To input text in a UI Automator script we just need to obtain the UiObject2 instance of this field and call the setText passing the string we want to input.

We will input the default name of Wi-Fi network in Android Virtual Device AndroidWifi
(update accordingly to your needs) and then click on Save button to add it.
If everything went right, now the added Wi-Fi network must be listed in the the list of networks and the Android device must be connected to it (for simplicity’s sake, we are assuming there is no other saved Wi-Fi network before running this test).

In order to check if the Wi-Fi was correctly added and the Android device is connected to it, we can simply check if the word Connected
is displayed below the network we just added. To do that let’s use the method hasObject that returns a boolean indicating whether some element is currently being displayed on screen.
We also have the option to use the Android APIs in order to get the SSID of current Wi-Fi the device is connected to. This is possible because the UI Automator script is installed as an Android application, so it has access to the APIs commonly used during applications development like intents, system services, contexts, etc.
To be able to get the Wi-Fi SSID, add these permissions to the AndroidManifest.xml
:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
The following method get the Context of the application (UI Automator script) and use it to get an instance of WifiManager and obtain the Wi-Fi SSID. We will use the return value to compare to the name of the network we added before.
Now the test method is ready. It will look like this:
To have a complete test script, we need to perform some set up before the actual test is executed. If you tried running the script while the screen was other than the home screen, you noticed that a NullPointerException
occurs. That happens because we assumed the device was in the home screen when the test starts. In order to guarantee that, add the following set up method with the @Before
annotation.
Now the Home
key will always be pressed before running the test. You may want to return to home screen after after the test execution as well, to do that simply add another method with the @After
annotation similar to what we did with the set up method.
In summary set up methods are used to make sure that the device is in an required initial state. Tear down methods are responsible to perform a clean up after running the test in order to not influence the subsequent ones. Let’s suppose you are writing a script that adds a password to the device. This password must be removed on the tear down method, otherwise next scripts may be stuck in password screen.
Running the script using ADB
Once you created the scripts you probably may want to execute them outside Android Studio. To do that you need to build .apk
file, install it on Android device and execute the test using ADB.
To build the .apk
you can simply go to Build -> Make Project
on Android Studio, or run the Gradle command:
# On Linux
./gradlew assembleDebug# On Windows
gradlew.bat assembleDebug
The .apk
with the scripts will be available at app/build/outputs/apk/debug
(app is the module’s name). With the .apk
built, install it like any other application with the command:
adb install -r -g app-debug.apk
Finally, start the execution by running the following command:
adb shell am instrument -w -e class 'com.paceli.wifitest.WifiTest' com.paceli.wifitest/androidx.test.runner.AndroidJUnitRunner
com.paceli.wifitest.WifiTest — is the class name of the test script to be executed.
com.paceli.wifitest/androidx.test.runner.AndroidJUnitRunner — is the test package name and the runner class in the format <test_package_name>/<runner_class>
.
See more details in the official documentation.
Conclusion
This is is how we create a UI Automator test script with no access to the application source code. I hope this article may have helped you creating your own test scripts, improving test coverage for you application and reducing manual effort.
The full project with the code presented in this article is available in this GitHub repository. Fell free to clone and modify it according to your needs. A good exercise may be to modify the code in order to launch the Wi-Fi activity directly, using intents, reducing the execution time, or to modify the set up ensuring that there isn’t any saved network before running the test. It’s up to you.
If you find this article helpful, please don’t forget to clap. I would also appreciate your feedback in the comments section. Thanks!