Slide 1

Slide 1 text

How to create fast and stable UI tests @Alex_Zhukovich

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Non-UI testing UI testing Stress testing View Comparison testing

Slide 4

Slide 4 text

https://github.com/AlexZhukovich/ImBarista-App

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

public class SampleTest { private AndroidDriver driver; @Before public void setUp() throws MalformedURLException { DesiredCapabilities desiredCapabilities = new DesiredCapabilities(); desiredCapabilities.setCapability("platformName", "Android"); desiredCapabilities.setCapability("ensureWebviewsHavePages", true); URL remoteUrl = new URL("http://localhost:4723/wd/hub"); driver = new AndroidDriver(remoteUrl, desiredCapabilities); } @Test public void sampleTest() { MobileElement el7 = (MobileElement) driver.findElementById("com.alexzh.imbarista:id/emailEditText"); el7.sendKeys("[email protected]"); MobileElement el8 = (MobileElement) driver.findElementById("com.alexzh.imbarista:id/passwordEditText"); el8.sendKeys("test"); MobileElement el9 = (MobileElement) driver.findElementById("com.alexzh.imbarista:id/loginButton"); el9.click(); MobileElement el10 = (MobileElement) driver.findElementByAccessibilityId("Profile"); el10.click(); MobileElement el11 = (MobileElement) driver.findElementByAccessibilityId("More options"); el11.click(); MobileElement el12 = (MobileElement) driver.findElementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.ListView/android.widget.LinearLayout[2] /android.widget.LinearLayout/android.widget.RelativeLayout/android.widget.TextView"); el12.click(); } @After public void tearDown() { driver.quit(); } } Appium Recorder

Slide 8

Slide 8 text

@RunWith(AndroidJUnit4.class) public class RecorderActivityTest { @Rule public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(SplashActivity.class); @Test public void recorderActivityTest() { ViewInteraction textInputEditText = onView( allOf(withId(R.id.emailEditText), childAtPosition(childAtPosition(withId(R.id.emailInputLayout),0),0))); textInputEditText.perform(scrollTo(), replaceText("[email protected]"), closeSoftKeyboard()); ViewInteraction textInputEditText2 = onView( allOf(withId(R.id.passwordEditText),childAtPosition(childAtPosition(withId(R.id.passwordInputLayout), 0), 0))); textInputEditText2.perform(scrollTo(), replaceText("test"), closeSoftKeyboard()); ViewInteraction materialButton = onView( allOf(withId(R.id.loginButton), withText("Login"), childAtPosition(childAtPosition(withId(R.id.scrollView2), 0), 4))); materialButton.perform(scrollTo(), click()); ViewInteraction bottomNavigationItemView = onView( allOf( withId(R.id.navigation_profile), withContentDescription("Profile"), childAtPosition(childAtPosition(withId(R.id.navigation), 0), 2), isDisplayed() )); bottomNavigationItemView.perform(click()); ViewInteraction overflowMenuButton = onView( allOf(withContentDescription("More options"), childAtPosition(childAtPosition(withId(R.id.toolbar), 1), 0), isDisplayed())); overflowMenuButton.perform(click()); ViewInteraction appCompatTextView = onView( allOf(withId(R.id.title), withText("Log Out"), childAtPosition(childAtPosition(withId(R.id.content), 0), 0), isDisplayed())); appCompatTextView.perform(click()); } ... } Espresso Test Recorder

Slide 9

Slide 9 text

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())

Slide 10

Slide 10 text

Changes in app Network speed Unreadability Test data

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

WWW Android iOS

Slide 13

Slide 13 text

developer.android.com

Slide 14

Slide 14 text

@Test fun shouldDisplayErrorWhenPasswordIsBlank() { val email = "[email protected]" val password = "" onView(withId(R.id.emailEditText)) .perform(replaceText(email)) onView(withId(R.id.passwordEditText)) .perform(replaceText(password)) onView(withId(R.id.loginButton)) .perform(click()) onView(withText(R.string.password_is_blank)) .check(matches(isDisplayed())) }

Slide 15

Slide 15 text

onView(withId(R.id.emailEditText)) .perform(replaceText(email)) onView(withId(R.id.passwordEditText)) .perform(replaceText(password), closeSoftKeyboard()) onView(withId(R.id.loginButton)) .perform(click()) val progressBarIR = ViewVisibilityIdlingResource(...) IdlingRegistry.getInstance().register(progressBarIR) onView(withId(R.id.recyclerView)) .check(matches(withItemCount(13))) IdlingRegistry.getInstance().unregister(progressBarIR) onView(withId(R.id.navigation_profile)) .perform(click()) ...

Slide 16

Slide 16 text

// -- LOGIN -- onView(withId(R.id.emailEditText)) .perform(replaceText(email)) onView(withId(R.id.passwordEditText)) .perform(replaceText(password), closeSoftKeyboard()) onView(withId(R.id.loginButton)) .perform(click()) // -- HOME SCREEN -- val progressBarIR = ViewVisibilityIdlingResource(...) IdlingRegistry.getInstance().register(progressBarIR) onView(withId(R.id.recyclerView)) .check(matches(withItemCount(13))) IdlingRegistry.getInstance().unregister(progressBarIR) onView(withId(R.id.navigation_profile)) .perform(click()) ...

Slide 17

Slide 17 text

Domain Specific Language

Slide 18

Slide 18 text

Scenario: 1. Login to application

Slide 19

Slide 19 text

Scenario: 1. Login to application 2. Open Profile screen 3. Check name and email

Slide 20

Slide 20 text

Scenario: 1. Login to application 2. Open Profile screen 3. Check name and email 4. Open menu 5. Logout from application

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

// 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()) } }

Slide 24

Slide 24 text

// 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) } }

Slide 25

Slide 25 text

// 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) } } fun loginScreen( func: LoginScreenRobot.() -> Unit ) = LoginScreenRobot().apply { func() }

Slide 26

Slide 26 text

// 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() }

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Test case Screen Robot Basic operations

Slide 29

Slide 29 text

Readability Reusability Building complexity Framework independant

Slide 30

Slide 30 text

Approaches and tools

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

Clear data

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Mock Fake

Slide 35

Slide 35 text

End to End tests Functional tests 1 1 Entry point Start test from the main screen 2 Interaction with server Verification interaction with server 3 Navigation Verification navigation between screens Entry point Start application from any screen 2 Interaction with server Usually no interaction with prod server 3 Navigation Usually no navigation between screen Navigation between neighbor screens 4 UI verification Fast verification of UI components 4 App verification Slow verification of entire product

Slide 36

Slide 36 text

End To End tests Functional tests

Slide 37

Slide 37 text

How to start

Slide 38

Slide 38 text

App flow Priorities Analytical data

Slide 39

Slide 39 text

App flow Priorities Analytical data

Slide 40

Slide 40 text

App flow Priorities Analytical data

Slide 41

Slide 41 text

Flaky tests

Slide 42

Slide 42 text

Testing approaches Tools Parallel execution Test data

Slide 43

Slide 43 text

FAILED TESTS FLAKY TESTS

Slide 44

Slide 44 text

Android Test Orchestrator @Rule ClearPreferencesRule ClearDatabaseRule ClearFilesRule Interceptors @AllowFlaky @

Slide 45

Slide 45 text

Good practices

Slide 46

Slide 46 text

Use meaningful names for test cases

Slide 47

Slide 47 text

All test cases should be independent

Slide 48

Slide 48 text

Never use sleep in test code

Slide 49

Slide 49 text

Do not entirely rely on test automation

Slide 50

Slide 50 text

#ExploreMore UI testing alexzh.com @AlexZhukovich