Skip to main content

UiAutomator and Watchers: Adding Async Robustness to UI Automation

"I'm looking over your shoulder... only because I've got your back." ~ Stephen Colbert

After my recent UiAutomator review a user brought up an important question about the use of UiWatcher. The watchers serve as async guardians of the test flow, making sure the odd dialog window doesn't completely frustrate your tests. Having a tool that automatically watches your back when you're focused on the functional flow of your tests is awesome. 100% pure awesomesauce. Since the API documentation on watchers is scant and the UI Testing tutorial on the Android dev guide doesn't cover their use in depth, I figured I should add a post here that goes over a simple scenario demonstrating how to use this fundamentally important UI automation tool.

In my example code below, I'm using uiautomator to launch the API Demo app (meaning run this against an Emulator built in API level 17 - I used the Galaxy Nexus image included in the latest ADT and platform tools). The test clicks down to the Alert Dialogs demo and automatically handles one of the dialogs popping up. It isn't a very legit test scenario but admittedly that's less the point than simply demonstrating the application of UiWatchers.

If I were testing the dialog itself, this could be a simple specification in BDD style:

  • Given the API Demo app on an emulator at API level 17
  • When I click on the first two OK Cancel dialog demo buttons
  • It Should display an OK Cancel dialog
However, if I were testing some other function in the UI and there were a random chance of the dialog to appear, I wouldn't assert the dialog in my test flow because it could happen at any time. UiWatchers function by registering undesired but potential conditions and the uiautomator runner checks the registered watchers with its checkForCondition() method. This happens whenever you create UiObjects or UiCollections or update or evaluate them. This means that anytime you're trying to capture the UI and create/update/evaluate elements of the UI, the UiWatchers will have your back if something in your flow goes sideways.

Your watchers are managed by the UiDevice class The typical logic flow for implementing watchers follows this pattern:
  1. Define new watcher
  2. Register your watcher
  3. Run your watcher
The UiDevice class allows for much more watcher management than those three steps but those are the minimum necessary to get you there. 

So on to the demo. 

I've tried to be verbose in my comments to help walk you through what I'm doing but if you want to see it yourself, simply follow the setup steps in the UI Testing demo on the dev guide and paste this code in (adjusting as needed to adapt it to your environment). Secondly you'll need to launch an emulator set up with API level 17 to get the full chewy goodness of the latest accessibility and uiautomator APIs. Finally, you should follow the build and deploy steps in the dev guide and run it. If you want extra insight into how the UiWatcher is being called, add logging at line 67 just before creating the okCancelDialog UiObject. This will show you how often the runner is checking conditions with this watcher. It is pretty fascinating (and reassuring).


Comments

  1. Ah, I see! Thank you for that example!

    ReplyDelete
  2. Do you know how to use constants, variables, methods from the other Class(.java) which extends UiAutomatorTestCase?

    ex)
    - HomeScreenTest.java (to be run)
    - Device.java (to be used, has screen width, height constants and unlock() method, etc.)

    ReplyDelete
  3. How can I check launch of an activity (which needs password to proceed) when that activity may or may not be launched depending upon the time the password was entered. For ex: if I entered password then that activity will not be launched till 10 minutes.. So If I have to automate checking of launch of that activity to enter password, how can I do that ?

    ReplyDelete
  4. That depends entirely on how long-running you want your test to be but for any asynchronous behavior the appropriate method in the new UIAutomator tools is to set up a UiWatcher. These are extremely useful and I highly recommend following the steps in the Tutorial I linked above to try out using watchers yourself. In your case you should create a watcher that is triggered by something unique to the activity you're checking for.

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Can i run the uiautomator command "adb shell uiautomator runtest LaunchSettings.jar -c com.uia.example.my.LaunchSettings" from an android application (for example on a button click) when I have already pushed the jar at /data/local/tmp/ location ?

    ReplyDelete
    Replies
    1. Just a quick trip to Google found the following:
      http://stackoverflow.com/questions/3054973/android-how-to-run-a-shell-command-from-within-code

      That should get you started and you won't even need to bother with the steps to manage sys directory access since your JAR lives in a more accessible space.

      Delete
  7. Hi Russell,

    Have you happened to run UIAutomator tests as a suite. For instance I want to test only log in related scenarios. In the example on developer.androdid the show how to deploy and run only one test.

    Thank you,
    Denis

    ReplyDelete
    Replies
    1. Holy wow, sorry for the late reply.

      The way to run only log-in related scenarios is to collect them into classes and pass -e class flags to uiautomator. If you really want to hate life, you can drill all the way down to the method level and pass a space-separated list.

      the uiautomator help content reads as follows under usage:

      shell@android:/ $ uiautomator -help
      Usage: uiautomator [options]

      Available subcommands:

      help: displays help message

      runtest: executes UI automation tests
      runtest [options]
      : < -c | -e class >
      : a list of jar files containing test classes and dependencies. If
      the path is relative, it's assumed to be under /data/local/tmp. Use
      absolute path if the file is elsewhere. Multiple files can be
      specified, separated by space.
      : a list of test class names to run, separated by comma. To
      a single method, use TestClass#testMethod format. The -e or -c option
      may be repeated. This option is not required and if not provided then
      all the tests in provided jars will be run automatically.
      options:
      --nohup: trap SIG_HUP, so test won't terminate even if parent process
      is terminated, e.g. USB is disconnected.
      -e debug [true|false]: wait for debugger to connect before starting.
      -e runner [CLASS]: use specified test runner class instead. If
      unspecified, framework default runner will be used.
      -e : other name-value pairs to be passed to test classes.
      May be repeated.
      -e outputFormat simple | -s: enabled less verbose JUnit style output.

      dump: creates an XML dump of current UI hierarchy
      dump [file]
      [file]: the location where the dumped XML should be stored, default is
      /storage/emulated/legacy/window_dump.xml

      events: prints out accessibility events until terminated

      Delete
  8. Hi Russel,

    How to achieve long press in UiAutomator?

    Thanks,
    Goku

    ReplyDelete
    Replies
    1. Goku,

      For a given UiObject with a criteria set, such as .textContains(), e.g. something like:

      "UiObject fooBar = UiObjectnew UiObject(new UiSelector().FooCriteria)"

      You can try to do fooBar.longClick() to simulate a long click, or one of its variants. This method often fails for whatever reason, however. You might achieve greater success using the swipe() method. This is discussed on stackoverflow: http://stackoverflow.com/questions/16061478/android-uiautomator-long-click-on-device

      Delete
  9. Hi Russel,

    Request you to let me know the way to capture the logs simultaneously in an automated way while the uiautomator test case is running. I want to capture all the device logs for each of my test case seperately. As of now I am doing this manually. Initially I thought of using "adb logcat -d" after the execution of each test case. But this will loose some logs due to limited buffer size on the device.

    Thanks,
    Udish.

    ReplyDelete
    Replies
    1. Hi Udish,

      I have recently published a tool UIautomator-bot which does exactly what you need. Please check out the project here at http://sourceforge.net/projects/uiautomator/
      http://uiautomator-bot.blogspot.in/

      Hope this will help you.

      Delete
    2. Hi Syed,

      I have missed your response and so unfortunately have missed to use an awesome tool. I just started using it and my first comment on your tool is
      "AWESOME". Thanks for pointing me to it.

      Delete
  10. Hi Udish,
    If you're willing to do a little more digging, I'd suggest looking up how to execute shell commands from within JUnit on Android and use the logcat commands directly from within your setup() and tearDown() methods for your tests (e.g. clear the log, save the current log using the test method name and a timestamp, etc). I'm using shell commands from within my own app to do things like clear app data, stop all services, relaunch using specific parameters. I'll publish an article soon describing all of this too.

    ReplyDelete
    Replies
    1. Actually, I realized that the relevant commands for Logcat are blocking commands so it isn't entirely that simple. I wound up solving this in a different way in my test job on my CI server. I'm working on a post describing how I did this and the value of really good logging soon.

      Delete
  11. Is there a way to use watchers to avoid using wait() statements?

    ReplyDelete
    Replies
    1. I'm sure it is possible but that's really not what they're supposed to be for in the first place. Watchers are still matchers but they're only triggered when an element your test case is expecting isn't found. The test runner will then cascade through all registered UiWatchers to see if any of them evaluate positively.If none of them do, the test case fails with a UiElementNotFound exception.

      If you really want to get speed-optimized UI test automation going, I'd recommend a couple things:
      1) disable animations in your developer settings on your device (unless those are specifically required by your test scenarios)
      2) use Espresso instead of UiAutomator as your test framework. It was specifically built to handle asynchronous behavior and eliminate wait() calls slowing down test execution.

      https://code.google.com/p/android-test-kit/wiki/Espresso

      Delete
  12. Good one, Thanks for your valuable information and views.. it is very useful for me to learn and understand...
    Software Testing Training in chennai | Software Testing Training in velachery

    ReplyDelete
  13. The Blog shared are very much useful My sincere thanks for sharing this post Please Continue to share this post
    Software Testing Training in Chennai

    ReplyDelete
  14. nice blog has been shared by you. before i read this blog i didn't have any knowledge about this but now i got some knowledge. so keep on sharing such kind of an interesting blogs.
    software testing training in chennai

    ReplyDelete
  15. These ways are very simple and very much useful, as a beginner level these helped me a lot thanks fore sharing these kinds of useful and knowledgeable information.
    ios App Development Company
    Mobile App Development Company
    Best Mobile App Development Company

    ReplyDelete
  16. We are provide a TOP Level IT companies with high end package. besant Offers the Authorized Test center of Pearson Vue with placement assurance.
    Pearson Vue Exam Center in Bangalore |
    Pearson Vue Exam Centers in Bangalore |

    ReplyDelete
  17. Practical assessment is built into our system. In our initial paperwork, we ask people to think about and express why they are doing this and then use our experience to read between the lines.
    Franchise Opportunities | Franchise Opportunities India

    ReplyDelete

Post a Comment

Popular posts from this blog

Jenkins + Devices + AndroidJUnitRunner

New Android build system and test runner, same goose chase using undocumented features and hacks

As I've posted before, I am a big fan of Jenkins. It is extremely flexible, open source, and supported by a staggering array of plugins actively developed by engineers running over 100,000 instances of the server worldwide. With it's distributed node model, you can even build your own device cloud for hosting enterprise-scale automation, economizing hardware investments by sharing resources across multiple projects as well as speeding up automation by parallelizing test runs. I had been using a Jenkins-based system in the past to support instrumentation automation with Robotium quite happily. For the last couple years however, my work hasn't required that as much and I've found myself doing a lot more manual testing and using UiAutomator which didn't require a tight integration between the product codebase and the test code. As a result, I've been slow to adopt the…

Run-As Like the Wind: Getting private app data off non-rooted devices using adb run-as and a debuggable app

"You're some kind of big, fat, smart-bug aren't you?"
~Johnny Rico, Starship Troopers (1997) One of the most important things about writing bugs is making them descriptive but concise. Screenshots, video, debug logging, and hardware snapshots are all fairly standard or available to Android testers these days and can save you enormously on text when communicating what constitutes the bug. Sometimes though, the app gets into a weird state due to some transient data issue where you may not own the API or the complexity with forcing the app data into a certain state is prohibitively high. In those cases it is very handy to directly inspect the data the app has in its own directories.

Getting at this data is trivial on emulators and rooted devices but due to file system permissions, this data is otherwise completely private to the app itself. If you're like me, you tend to test using devices rather than emulators and you probably prefer not to root your devices since t…