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.
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. đŻ
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! đ