Thanks my colleage, Kane Shih, to cooperate. This article and share the experience about PageObject on Android application.

PageObject Pattern is a well-known design pattern in the Web application UI automation testing proposed by Martin Fowler. It efficiently models the UI elements as objects and allows developers to manipulate them without digging in the implementation details. However, it is not common in mobile app development yet.

Maintaining the Android UI testing codes is a big challenge today. There are two main reasons: 1.) Writing UI tests takes time for the developers who are not familiar with the testing libraries. 2.) The production code frequently changes due to the rapid market change, which quickly makes UI testing codes outdated.

In this paper, we’ll introduce how we solve this problem by applying the PageObject Pattern, which makes our UI testing codes easy to understand and modify.

Introduce

App Integration Test

The UI test cases run on the physical devices or the emulators to simulate the user behaviors and secure a part of the user flow it covers. When an engineer modifies the production code and accidentally changes the UI’s action, the corresponding test cases should fail; this helps developers to discover issues at the early stage. UI tests are also helpful in simulating the edge cases, which make the production code more robust. As the modern Android UI becomes more complicated today, the importance of writing and maintaining UI tests has grown.

Espresso

Most of the Android developers write UI tests with the testing library Espresso, which was released and is supported by Google. Espresso is a game-changer, which makes UI tests easier than before. Below is a typical test code with Espresso:

onView(withId(R.id.my_view))
 .perform(click())
 .check(matches(isDisplayed()))

Although Espresso is very easy to use, the test code still deeply depends on the production UI implementation details. When a View instance which involves many test cases is changed, the developer will need to update them all at the same time. This could make maintaining UI tests become a nightmare.

PageObject Pattern

In PageObject Pattern, each page object may represent an entire page or a complicated UI component (in the mobile framework, the page means the Fragment of Android or the UIViewController of iOS). We can consider an essential element on the page as one object, rather than a whole page. It encapsulates the detailed mechanics of the actual UI components.

Architecture

Basic Page Class

First of all, we defined a basic Page class that all other page objects inherit. It has a minor function fun <reified T : Page> **on**(): T that generates the Page instance of the generic type. In this way, we can concatenate **Page.on**<{PageObject}>() at any time and determine what the current page object is, entirely based on the test operations.

Page.on() receives a generic type T and yields an actual instance of T. We use T::class.constructors._first_().call() to get generic constructors and peek the first one, generally the non-parameterized constructor, to create an instance of T.

The basic Page class also implements the fun back(): Page. By chaining with **Page.on**<{PageObject}>(), it’s easy to understand which page object should be after back.

import android.os.SystemClock
	import androidx.test.espresso.Espresso

	open class Page {

	    inline fun <reified T : Page> on(): T {
	        // reference: https://blog.kotlin-academy.com/creating-a-random-instance-of-any-class-in-kotlin-b6168655b64a
	        val page = T::class.constructors.first().call()
	        page.verify()
	        return page
	    }

	    open fun verify(): Page {
	        assert(false)
	        return this
	    }

	    fun back(): Page {
	        Espresso.pressBack()
	        return this
	    }

	    fun wait(milliseconds: Long): Page {
	        SystemClock.sleep(milliseconds)
	        return this
	    }

	    companion object {
	        inline fun <reified T : Page> on(): T {
	            return Page().on()
	        }
	    }
	}
view raw
Page.kt hosted with ❤ by GitHub

Page Object Class

Every page object must inherit the basic Page class and override the function fun verify() to do their default verifications. For example, there is an ItemPage class that inherits the Page class and verifies whether R.id.productitem_name exists or not.

class ItemPage : Page() {

	    override fun verify(): Page {
	        Espresso.onView(withId(R.id.productitem_product_name))
	                .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
	        return this
	    }

	    fun withTitle(keyword: String): ItemPage {
	        Espresso.onView(withId(R.id.productitem_product_name))
	                .check(matches(ViewMatchers.withText(keyword)))
	        return this
	    }
	}
view raw
ItemPage.kt hosted with ❤ by GitHub

A page object doesn’t have to represent an entire page. In the following sample, SearchBoxPage represents a child UI component inside DiscoveryPage that represents DiscoveryFragment.

@Test
fun testSearchById() {
    Page.on<DiscoveryPage>()
        .on<SearchBoxPage>()
        .click()
        .on<SearchViewPage>()
        .searchKeyword(“7882691”)
        .on<ItemPage>()
        .withTitle(“A1NJ5J02”)
}

Image for post

#functional-testing #function

PageObject Pattern into App Integration Tests
1.35 GEEK