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

Fast and stable UI tests in Android

Alex Zhukovich
June 18, 2020
290

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. View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. adb shell
    am instrument
    Instrumentation
    >_

    View Slide

  6. View Slide

  7. View Slide

  8. 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 Slide

  9. View Slide

  10. Approaches and tools

    View Slide

  11. View Slide

  12. Clear data

    View Slide

  13. View Slide

  14. View Slide

  15. End to End Functional

    View Slide

  16. 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 Slide

  17. 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 Slide

  18. 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 Slide

  19. Espresso UiAutomator Appium
    Support of Android resources

    View Slide

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

    View Slide

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

    View Slide

  22. Clear data

    View Slide

  23. 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 Slide

  24. 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 Slide

  25. 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 Slide

  26. @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 Slide

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

    View Slide

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

    View Slide

  29. @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 Slide

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

    View Slide

  31. 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 Slide

  32. View Slide

  33. View Slide

  34. @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 Slide

  35. View Slide

  36. Domain Specific
    Language

    View Slide

  37. Scenario:

    View Slide

  38. Scenario:

    View Slide

  39. Scenario:

    View Slide

  40. 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 Slide

  41. // 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 Slide

  42. // 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 Slide

  43. // 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 Slide

  44. // 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 Slide

  45. Test case
    Screen Robot
    Basic operations

    View Slide

  46. View Slide

  47. Shared tests

    View Slide

  48. View Slide

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

    View Slide

  50. How to start

    View Slide

  51. App flow

    View Slide

  52. Priorities

    View Slide

  53. Analytical data

    View Slide

  54. More

    View Slide