Android also offers a way to run UI tests. Several libraries are available but the latest officially supported libraries seem to be Espresso and UIAutomator. UIAutomator is newer but requires API Level 14. You can find a short description here:
http://developer.android.com/training/testing/ui-testing/uiautomator-testing.html
Espresso needs only API level 8 but is more limited and cannot create screenshots on its own:
http://developer.android.com/training/testing/ui-testing/espresso-testing.html
While they are similar to the iOS UI tests they seem to be much more hardware dependent. E.g. where iOS can also find and tap table rows that are outside the screen (which makes it easier to support different screen sizes for the tests) Android “sees” only what is on the screen, making it necessary to add swipe gestures, which makes the tests dependent on a certain screen size.
To find out how to access a certain screen element you can use the “uiautomatorviewer” program.
Automatically starting the app on multiple devices
If your app is compatible with older Android versions but you are also using new features, you have to take care not to load the classes that are only available on newer Android versions on old Android versions, otherwise your app will crash. So it is also useful to run the tests on multiple emulators with different Android versions. For this purpose you can add some lines to your build.gradle file:
task testemulator << { emulatorstart("Test16") emulatorstart("Test23") emulatorstart("Test40") } def emulatorstart(emulatorname) { Process process = ("emulator -avd " + emulatorname + " -gpu off").execute() Thread.sleep(60000) "adb -e shell input keyevent 82".execute().waitFor() // unlock screen "adb -e uninstall com.example.app".execute().waitFor() "adb -e install build/outputs/apk/example.apk".execute().waitFor() "adb -e shell am start -n com.example.app/com.example.app.Main".execute().waitFor() Thread.sleep(10000) // Here you could run your tests. In this example a screenshot is saved. "adb shell screencap -p /sdcard/screen.png".execute().waitFor() ("adb pull /sdcard/screen.png " + emulatorname + ".png").execute().waitFor() "adb shell rm /sdcard/screen.png".execute().waitFor() process.waitForOrKill(10000) }
By running
gradle testemulator
your app will be installed on all specified emulators (e.g. "Test16", "Test23" and "Test40") and started. Afterward a screenshot with the name of the emulator will be created. This way you can run it (which takes a while) and then check the screenshots to see if it was successfully started on all these emulators and Android versions.
Screenshots
Screenshots on newer Android devices can be created in the way described above. However e.g. Android 2.x does not have the "screencap" program installed. If you would like to create screenshots with older Android versions or if "screencap" does not work, you can use a Java program that you can find here. To integrate it into the gradle script above just replace the three lines
"adb shell screencap -p /sdcard/screen.png".execute().waitFor() ("adb pull /sdcard/screen.png " + emulatorname + ".png").execute().waitFor() "adb shell rm /sdcard/screen.png".execute().waitFor()
with
screenshot(emulatorname);
and add the following function to your gradle file:
def screenshot(emulatorname) { exec { executable "java" args "-jar", "/path/to/your/downloaded/screenshot.jar", "-e", "screenshots/" + emulatorname + ".png" } }
Opening all screenshots
To open all screenshots under MacOS to review them, just use the command
open screenshots/*
This will start Preview and open all images in a single window with a list of thumbnails so that you can quickly see if your app was started correctly on all these devices.
Selecting ListView elements
When displaying lists it can often happen that you have to click on an entry that is not visible on the screen. When it isn't visible on the screen then there is not even a view for it, so the normal test functions cannot find and click that view. That's what "onData" is for. Instead of searching for a visible view it searches the adapters of the visible views for matching elements. E.g.
onData(hasToString("Test")).perform(click());
clicks on the row of any ListView (or Spinner or other element with an adapter) that returns "Test" from its "toString()" method. If you have multiple objects with an adapter on the screen (e.g. a ListView and Spinners), you can limit the commmand to the ListView
onData(hasToString("Test")).inAdapterView(withClassName(Matchers.is(ListView.class.getName()))).perform(click());