Experience with UIAutomator 🚧

Photo by ray rui on Unsplash

Since the pandemic starts, my work has shifted to remote working, and this changes a lot of things for me. But you’re not here to read about me talking about good and bad of remote work. So, I’m just gonna go ahead and give you a little background on why I thought of playing around with UIAutomator in the first place. We’re using a human resources management system called BetterHR. If you haven’t checked it out, I recommend you do. it has an extensive set of features with a spotless design. One of these features is called “Manual check-in” where you can submit the check-in manually, instead of scanning a qr code or using your fingerprint, which is useful for cases where you might be out-of-office because of a meeting, and you want to let your manager know that you are right at the place of meeting at that exact time. Ever since the office is in lock down now so we can’t do check in with QR code anymore, and now we have to resort to using manual check in. The problem for me was when I want to submit my attendance, I have to select the time because partly my fault; I always forgot to open the app on time as I leave the phone on Zen Mode soon as work start. On top of that, I have to re-select my current project manager which is different from my assigned default-selected manager. (Submitted a feature request to auto-select the last selected manager instead of default). And then in reason, I have to fill up “Work from home”. After a month of routine, this has become a tedious process for me to do it every single day. So what do I do? Well, automate it.


Why UIAutomator?

The better option I can use is to put a packet analyzer and log all the network requests, where afterwards I can ping the API. However, being an Android developer and wanting to play around with something I never have before, I went with the more complex task of automating on the phone using UIAutomator. In this post. For those who are unfamiliar with UIAutomator. It’s basically a black box testing tool that can be used to make sure your app behaves the way you want. In this post, I will details some tips & tricks I learned from the process.

Zero documentation

The first big problem for me was that there was little to no documentation with no proper guide on what does what. The little information provided on official Android developer site doesn’t really help that much when you start to dive deep. Even on StackOverflow, there wasn’t many questions around it, so be prepared to face a lot of trials and errors during the process. Not to worry much though is that I found out that if you have experience with Espresso, then the APIs are quite similar except here you can’t access the view directly, and can only get reference to it, which is a UiObject2 class. The reference to view has some basic functions like click, fling, swipe, changing text and some more which should be enough theoretically for all your automation/testing tasks.

How to find resource id inside Android framework

I ran into a problem when dealing with TimePickerDialog for automating the time selection. The problem was that I didn’t know the resource ids used within views provided by the Android framework. There are several ways you can check the ids. First method is to dig inside TimePickerDialog source. I used Android Code Search. I found out that it uses RadialTimePickerView underneath the hood which draws the text instead of using id for each number, which means we can’t no longer access with id and set the time to what we want. Luckily, TimePickerDialog provides two modes: radial and text input. So I went with the latter where I leverage the TextInputTimePickerView since the EditTexts can be accessed directly with id.

The second method is to just replicate the whole view in another App. After you run the app, you can use the Layout Inspector and check the ids as well.

Can check Ids now!

Pull actual ids through code

The last way I found out you can get the id is through the code. Going back to the time picker, you will find that you can’t directly access them with the following function, even though its id is input_minute .

device.findObject(By.res("input_minute")).click() //Does not work

Let’s dive into why this does not work. At first glance, it may look like we’re referencing the id correctly. However, it actually has a different id underneath the hood. A trick to access these ids was to use a function that can get view references by class type. Since what I want to access is essentially a TextView, (EditText extends TextView) we can execute this line.

device.findObjects(By.clazz(TextView::class.java)) //Return list of TextView type

We can receive a list of all the TextView on the screen. From there, we just need to map to id and pull the id of every single TextView on the screen. You can either use a debugger and break points or print it out to see all the resources id.

device.findObject(By.res("input_minute")).click() //Does not work

Back on topic, the reason that we can’t access it earlier was that Android append its internal android resources with android:id/ . minutes would become android:id/minutes . It make sense after all, our resources belong to our package id, so the id under Android framework view should belong to android resources. A warning tho, you can’t execute By.res and provide your package id when you want to access these Android internal views. You have to execute the single parameter method and only pass the name of the resource.

device.findObjects(By.clazz(TextView::class.java)) //Return list of TextView type

Thread.sleep

It is not recommend to use Thread.sleeep for UIAutomator. Depending on the device, it could potentially take longer or slower than a constant time. Instead, you should use UIDevice.wait where you can specify how long you want the device to wait with a specific condition; for example, until you can finally access the view you want, or until something is clickable. The best part about this API is that you can also specify a timeout, so you won’t be locked forever as well in case the condition is never satisfied. In short, Instead of forcing the tread to wait a specific fixed amount of time, use UIDevice.wait to wait with the timeout.device.wait(Until.hasObject(By.res(PACKAGE_NAME, "tv_select_time")), TIMEOUT) //I set timeout to 10 seconds

End Result

Here’s the end result where I can run the automated test. I can create a similar process for check out. Since both check in and check out are in separate tests, I can run them separately if needed as well. 💯

Automated Check-in!

If you enjoy this article, follow our podcast for more contents. Me and a buddy of mine, Lin Min Phyo, are running the first ever Burmese Tech Podcast, named “Techshaw Cast”, where we talk about tech scenes in Burma, in our native Burmese language. If you haven’t already, gave us a subscribe! 😄