Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fast and stable UI tests in Android

Alex Zhukovich
June 18, 2020
330

Fast and stable UI tests in Android

Mobile apps are growing. They become more complex and require more testing. It means that it is time to integrate fast and stable automated tests to your project.
This talk will cover the following topics:
- How to create fast and stable UI tests
- How to avoid flaky tests
- How to share UI tests between local and instrumentation tests
- How DSL can speed up adding stable UI tests to the project

Alex Zhukovich

June 18, 2020
Tweet

Transcript

  1. adb shell
    am instrument
    Instrumentation
    >_

    View full-size slide

  2. val textInputEditText = onView(
    allOf(
    withId(R.id.emailEditText),
    childAtPosition(
    childAtPosition(
    withId(R.id.emailInputLayout),
    0
    ),
    0
    )
    )
    )
    textInputEditText.perform(scrollTo(), replaceText("[email protected]"),
    closeSoftKeyboard())
    val textInputEditText2 = onView(
    allOf(
    withId(R.id.passwordEditText),
    childAtPosition(
    childAtPosition(
    withId(R.id.passwordInputLayout),
    0
    ),
    0
    )
    )
    )
    textInputEditText2.perform(scrollTo(), replaceText("test"), closeSoftKeyboard())
    val materialButton = onView(
    allOf(
    withId(R.id.loginButton), withText("Login"),
    childAtPosition(
    childAtPosition(
    withId(R.id.scrollView2),
    0
    ),
    4
    )
    )
    )
    materialButton.perform(scrollTo(), click())
    onView(withId(R.id.emailEditText))
    .perform(replaceText("[email protected]"))
    onView(withId(R.id.passwordEditText))
    .perform(replaceText("test"))
    onView(withId(R.id.loginButton))
    .perform(click())

    View full-size slide

  3. Approaches and tools

    View full-size slide

  4. End to End Functional

    View full-size slide

  5. Espresso
    onView(withId(R.id.email))
    .perform(replaceText(EMAIL)
    onView(withId(R.id.password))
    .perform(replaceText(PASSWORD)
    onView(withId(R.id.login))
    .check(matchers(isDisplayed()))

    View full-size slide

  6. UiAutomator
    val email =
    device.findObject(By.res(PACKAGE, "email"))
    email.text = EMAIL
    val password =
    device.findObject(By.res(PACKAGE, "password"))
    email.text = PASSWORD
    val login =
    device.findObject(By.res(PACKAGE, "login"))
    if (login != null) { ... }

    View full-size slide

  7. Appium
    val email = driver.findElement(By.id("email"))
    email.value = EMAIL
    val password =
    driver.findElement(By.id("password"))
    password.value = PASSWORD
    val login = driver.findElement(By.id("login"))
    login.isDisplayed()

    View full-size slide

  8. Espresso UiAutomator Appium
    Support of Android resources

    View full-size slide

  9. Espresso UiAutomator Appium
    Support of Android resources
    Test cases type

    View full-size slide

  10. Espresso UiAutomator Appium
    Support of Android resources
    Test cases type
    Execution time

    View full-size slide

  11. writeTo(R.id.email, EMAIL)
    writeTo(R.id.password, PASSWORD)
    clickOn(R.id.login)
    assertDisplayed(R.id.name, USER_NAME)
    assertDisplayed(R.id.email, EMAIL)
    Kakao

    View full-size slide

  12. writeTo(R.id.email, EMAIL)
    writeTo(R.id.password, PASSWORD)
    clickOn(R.id.login)
    assertDisplayed(R.id.name, USER_NAME)
    assertDisplayed(R.id.email, EMAIL)
    @Rule ClearPreferencesRule
    @Rule ClearDatabaseRule
    @Rule ClearFilesRule
    Kakao

    View full-size slide

  13. writeTo(R.id.email, EMAIL)
    writeTo(R.id.password, PASSWORD)
    clickOn(R.id.login)
    assertDisplayed(R.id.name, USER_NAME)
    assertDisplayed(R.id.email, EMAIL)
    @Rule ClearPreferencesRule
    @Rule ClearDatabaseRule
    @Rule ClearFilesRule
    @Test
    @AllowFlaky(attempts = 5)
    @Repeat(times = 5)
    fun flaky_test() {
    ...
    }
    Kakao

    View full-size slide

  14. @Test
    fun should_verify_account_information() {
    val toolbarVisibilityIR = ViewVisibilityIdlingResource(R.id.toolbar, View.VISIBLE)
    IdlingRegistry.getInstance().register(toolbarVisibilityIR)
    assertDisplayed(R.id.toolbar)
    IdlingRegistry.getInstance().unregister(toolbarVisibilityIR)
    // LOGIN
    writeTo(R.id.emailEditText, EMAIL)
    writeTo(R.id.passwordEditText, PASSWORD)
    closeKeyboard()
    clickOn(R.id.loginButton)
    // NAVIGATE TO PROFILE
    val progressBarIR = ViewVisibilityIdlingResource(R.id.progressBar, View.GONE)
    IdlingRegistry.getInstance().register(progressBarIR)
    assertDisplayed(R.id.recyclerView)
    IdlingRegistry.getInstance().unregister(progressBarIR)
    clickOn(R.id.navigation_profile)
    // VERIFY PROFILE INFO
    val progressProfileIR = ViewVisibilityIdlingResource(R.id.progressBar, View.GONE)
    IdlingRegistry.getInstance().register(progressProfileIR)
    assertDisplayed(R.id.nameTextView, USER_NAME)
    assertDisplayed(R.id.emailTextView, EMAIL)
    IdlingRegistry.getInstance().unregister(progressProfileIR)
    // LOGOUT
    clickMenu(R.id.logout_action)
    // LOGIN SCREEN
    intended(hasComponent(LoginActivity::class.java.name))
    }
    Kakao

    View full-size slide

  15. Kakao
    onScreen {
    email.replaceText(EMAIL)
    password {
    replaceText(PASSWORD)
    closeSoftKeyboard()
    }
    login.click()
    }

    View full-size slide

  16. onScreen {
    email.replaceText(EMAIL)
    password {
    replaceText(PASSWORD)
    closeSoftKeyboard()
    }
    login.click()
    }
    Kakao

    View full-size slide

  17. @Test
    fun should_verify_account_information() {
    onScreen {
    email.replaceText(EMAIL)
    password {
    replaceText(PASSWORD)
    closeSoftKeyboard()
    }
    login.click()
    }
    onScreen {
    waitCoffeeDrinkList()
    profileMenuItem.click()
    }
    onScreen {
    waitForProfileInfo()
    name.hasText(USER_NAME)
    email.hasText(EMAIL)
    openMenu()
    logout.click()
    }
    onScreen {
    loginScreenIntent.intended()
    }
    }
    Kakao

    View full-size slide

  18. onScreen {
    email.replaceText(EMAIL)
    password {
    replaceText(PASSWORD)
    closeSoftKeyboard()
    }
    login.click()
    }
    Kakao

    View full-size slide

  19. Kakao
    @Test
    fun should_verify_account_information() =
    beforeTest {
    activityTestRule.launchActivity(intent)
    }.afterTest {
    ...
    }.run {
    onScreen {
    email.replaceText(EMAIL)
    password {
    replaceText(PASSWORD)
    closeSoftKeyboard()
    }
    login.click()
    }
    onScreen {
    waitCoffeeDrinkList()
    profileMenuItem.click()
    }
    onScreen {
    waitForProfileInfo()
    name.hasText(USER_NAME)
    email.hasText(EMAIL)
    openMenu()
    logout.click()
    }
    onScreen {
    loginScreenIntent.intended()
    }
    }

    View full-size slide

  20. @RunWith(AndroidJUnit4::class)
    class LoginActivityTest {
    @get:Rule
    val activityRule = ActivityTestRule(
    LoginActivity::class.java,
    true,
    false
    )
    @Test
    fun superImportantTest() {
    val intent = ...
    ativityRule.launchActivity(intent)
    ...
    }
    }
    @RunWith(AndroidJUnit4::class)
    class LoginActivityTest {
    @Test
    fun superImportantTest() {
    launchFragmentInContainer()
    ...
    }
    }

    View full-size slide

  21. Domain Specific
    Language

    View full-size slide

  22. loginScreen {
    login(EMAIL, PASSWORD)
    }
    homeScreen {
    navigateToProfile()
    }
    profileScreen {
    hasEmail(EMAIL)
    hasUserName(USER_NAME)
    logout()
    }
    loginScreen {
    isOpen()
    }
    loginScreen {
    enterEmail(EMAIL)
    enterPassword(PASSWORD)
    pressLogin()
    }
    homeScreen {
    waitCoffeeDrinks()
    pressProfile()
    }
    profileScreen {
    waitUserData()
    hasEmail(EMAIL)
    hasUserName(USER_NAME)
    openMenu()
    pressLogout()
    }
    loginScreen {
    isOpen()
    }

    View full-size slide

  23. // LOGIN
    onView(withId(R.id.email))
    .perform(replaceText(EMAIL))
    onView(withId(R.id.password))
    .perform(replaceText(PASSWORD))
    onView(withId(R.id.login))
    .perform(click())

    View full-size slide

  24. // LOGIN
    onView(withId(R.id.email))
    .perform(replaceText(EMAIL))
    onView(withId(R.id.password))
    .perform(replaceText(PASSWORD))
    onView(withId(R.id.login))
    .perform(click())
    open class BaseTestRobot {
    fun enterText(viewId: Int, text: String) {
    onView(withId(viewId))
    .perform(replaceText(text))
    }
    fun clickOnView(viewId: Int) {
    onView(withId(viewId))
    .perform(click())
    }
    }

    View full-size slide

  25. // LOGIN
    onView(withId(R.id.email))
    .perform(replaceText(EMAIL))
    onView(withId(R.id.password))
    .perform(replaceText(PASSWORD))
    onView(withId(R.id.login))
    .perform(click())
    open class BaseTestRobot {
    fun enterText(viewId: Int, text: String) {
    onView(withId(viewId))
    .perform(replaceText(text))
    }
    fun clickOnView(viewId: Int) {
    onView(withId(viewId))
    .perform(click())
    }
    }
    class LoginScreenRobot : BaseTestRobot() {
    fun login(email: String, password: String) {
    enterText(R.id.email, email)
    enterText(R.id.password, password)
    clickOnView(R.id.loginButton)
    }
    }

    View full-size slide

  26. // LOGIN
    onView(withId(R.id.email))
    .perform(replaceText(EMAIL))
    onView(withId(R.id.password))
    .perform(replaceText(PASSWORD))
    onView(withId(R.id.login))
    .perform(click())
    loginScreen {
    login(EMAIL, PASSWORD)
    }
    open class BaseTestRobot {
    fun enterText(viewId: Int, text: String) {
    onView(withId(viewId))
    .perform(replaceText(text))
    }
    fun clickOnView(viewId: Int) {
    onView(withId(viewId))
    .perform(click())
    }
    }
    class LoginScreenRobot : BaseTestRobot() {
    fun login(email: String, password: String) {
    enterText(R.id.email, email)
    enterText(R.id.password, password)
    clickOnView(R.id.loginButton)
    }
    }
    fun loginScreen(func: LoginScreenRobot.() -> Unit) =
    LoginScreenRobot().apply { func() }

    View full-size slide

  27. Test case
    Screen Robot
    Basic operations

    View full-size slide

  28. Shared tests

    View full-size slide

  29. android {
    ...
    sourceSets {
    androidTest {
    java.srcDirs += "src/sharedTest/java"
    }
    test {
    java.srcDirs += "src/sharedTest/java"
    }
    }
    }
    sharedTest

    View full-size slide

  30. How to start

    View full-size slide

  31. Analytical data

    View full-size slide