Android Espresso — Test Automation Organization

Ronell Lukasik
2 min readDec 19, 2022

--

I wanted our tests to be easier to read, follow, maintain especially for new QA who started or other non-QA members on the team who want to look at our tests to determine expected functionality.

We use a page object model that is pretty common when setting up test cases, but our tests were still cumbersome looking.

You might see something like this (basic example):

@Test
fun myExampleTest() {
loginPage.navigateToLoginPage()

loginPage.welcomeText.check(matches(isDisplayed()))
loginPage.welcomeText.check(matches(withText("Welcome to my app!")))
loginPage.loginUsername.check(matches(isDisplayed()))
loginPage.loginPassword.check(matches(isDisplayed()))
loginPage.loginButton.check(matches(isDisplayed()))
}

@Test
fun myExampleTest() {
loginPage.navigateToLoginPage()

loginPage.loginUsername.perform(typeText("Tom"))
loginPage.loginPassword.perform(typeText("abc123"))
loginPage.loginButton.perform(click())
homePage.appLogo.check(matches(isDisplayed()))
}

To improve our tests, I created a ElementHelper file that would reduce the duplication around the element interactions.

fun clickElement(element: ViewInteraction) {
element.perform(click())
}

fun typeTextInElement(element: ViewInteraction, text: String) {
element.perform(typeText(text))
}

fun verifyElementIsDisplayed(element: ViewInteraction) {
element.check(matches(isDisplayed()))
}

Then our page objects would have functions to handle using the functions within the ElementHelper file to help abstract the locator objects being used from the test readers.

Login Page object, example functions:

val loginButton: ViewInteraction = onView(withId(R.id.login_button))
val loginPassword: ViewInteraction = onView(lwithId(R.id.login_password))
val loginUsername: ViewInteraction = onView(withId(R.id.login_username))

fun enterUsername(name: String) {
typeTextInElement(loginUsername, name)
}

fun enterUsername(name: String) {
typeTextInElement(loginPassword, name)
}

fun selectLoginButton() {
waitForLoginButtonToExist()
clickElement(loginButton)
}

fun verifyLoginButtonIsDisplayed() {
verifyElementIsDisplayed(loginButton)
}

fun verifyPasswordIsDisplayed() {
verifyElementIsDisplayed(loginPassword)
}

fun verifyUserNameIsDisplayed() {
waitForElementUntilDisplayed(loginUsername)
verifyElementIsDisplayed(loginUsername)
}

This allows us to write our test files like this then 🌟:

@Test
fun myExampleTest() {
loginPage.apply {
navigateToLoginPage()

verifyWelcomeTextIsDisplayed()
verifyWelcomeText("Welcome to my app!")

verifyLoginUsernameIsDisplayed()

verifyLoginPasswordIsDisplayed()

verifyLoginButtonIsDisplayed()
}
}

@Test
fun myExampleTest() {
loginPage.apply {
navigateToLoginPage()

enterUsrename("Tom")
enterPassword("abc123")
selectLoginButton()
}

homePage
.verifyAppLogoIsDisplayed()
}

The code elements are abstracted away making the tests more readable 😃

Note: apply is a scope function in Kotlin that lets us use this object for all calls within that scope without the need for variable name duplication.

--

--

Ronell Lukasik
Ronell Lukasik

No responses yet