Better Android automation with Mobile Prism
Espresso tests are pretty unwieldy out of the box. Here’s a take on better architecture through layers.
Lessons from the Browser Age
Web test automation has a best practice called “Page Object Models”, which separates test code into two parts:
- An abstraction layer which models the application under test
- Business use cases which leverage page models execute the tests.
A login test without POMs might look like this:
# login.rb
find_css(“input#username”).set(“joe9”)
find_css(“input#password”).set(“999”)
find_xpath(“//button[@title=’submit’]”).click
This sort of code doesn’t scale well. If your application changes — and of course it will, this is software we’re talking about — you have to update each test that exercises the changed code. Instead, POMs centralize our application references into one single place like so:
# login_page.rb
class LoginPage < SitePrism::Page
element :username, “input#username”
element :password, “input#password”
element :submit, :xpath, “//button[@title=’submit’]”
end
With this particular implementation we’re using the excellent SitePrism toolkit, also the namesake for our little sibling project we’ve got going here. (Thanks big bro!)
A test execution with POMs might like like so:
# login_test.rb
LoginScreen.username.set(‘joe9’)
LoginScreen.password.set(‘999’)
LoginScreen.submit.click
With POMs, when our app changes, we only need to modify the element or page that changed. Easier to maintain, cleaner, more intuitive. An established best practice in the browser world.
To the mobile, Alice
Let’s see if we can implement something similar in Java with Android Espresso. As our workbench we’ll use Omni Notes, an open source app for taking notes, making todo lists, and setting reminders. Out of the box, here’s what it looks like when we create a new note using Espresso Test Recorder:
From an architecture standpoint, this isn’t very great. We are combining how we find things, with what we are doing. A clear violation of Single Responsibility.
Test execution code should focus on business behavior
How about something like this instead:
With this code, we’re focused solely on describing the use case. The actual work of running the test, we can push into layers of complexity underneath, each layer with a discrete single responsibility.
Our first technical layer: Screen Models
We’ve got two top-level references in our createNewNote()
test, StartScreen
and NewNoteScreen
. These “screen models” are responsible for collecting all the elements we want to interact with, assigning a logical name or class field for our tests, and passing along whatever technical details are necessary for creating the element. In these cases so far, everything has a unique R id value, so it’s nice and simple:
It makes sense here to keep fields static ie., bound to the class. We’re never going to have multiple instances of a screen when we’re testing, so let’s not try and instantiate a screen object we won’t need. I love all-static classes. Keep it simple baby.
Second layer: PrismScreen
Our StartScreen
is derived from PrismScreen
, which uses genel(int rid)
to generate instances ofPrismElement
:
Third layer: PrismElement
Most of the work gets done by the PrismElement. Espresso runs actions like click, setting text, etc through a ViewInteraction, so let’s include a view interaction inside PrismElement upon construction like so:
We can then use the interaction to send actions like clicks and setting text:
How about some checks as well ?
You can see an open-source fork of the Omni Notes code with this Mobile Prism approach implemented here. It’s in active development and more articles are pending as we explore more technical interactions, while keeping things clean and compartmentalized.
Happy testing !