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

A to Z of snapshot testing in Android

A to Z of snapshot testing in Android

Aung Kyaw Paing

November 06, 2023
Tweet

More Decks by Aung Kyaw Paing

Other Decks in Technology

Transcript

  1. Snapshot Test UI Test ~ 1000 ~ 1600 ~ 2300

    ~30,000 handful ~ 500 ~ 20 ? Ref: “Building Mobile App At scale: 39 Engineering Challenge”
  2. When Card Render, 1. Text should show 2. Icon that

    looks like location pin with plus icon should show 3. Icon should be on left and Text on right 4. Content have padding of 64dp 5. It should have a background color of #XXXXXX and content tint color of #XXXXXX
  3. When Card Render, Text should show fun shouldShowText() { composeTestRule.setContent

    { ClickableCard() } composeTestRule .onNodeWithText("Click Me!") .assertIsDisplayed() }
  4. When Card Render, 1. Text should show 2. Icon that

    looks like location pin with plus icon should show 3. Icon should be on left and Text on right 4. Content have padding of 64dp 5. It should have a background color of #XXXXXX and content tint color of #XXXXXX
  5. When Card Render, 1. Text should show 2. Icon that

    looks like location pin with plus icon should show 3. Icon should be on left and Text on right 4. Content have padding of 64dp 5. It should have a background color of #XXXXXX and content tint color of #XXXXXX
  6. When Card Render, 1. Text should show 2. Icon that

    looks like location pin with plus icon should show 3. Icon should be on left and Text on right 4. Content have padding of 64dp 5. It should have a background color of #XXXXXX and content tint color of #XXXXXX
  7. When Card Render, 1. Text should show 2. Icon that

    looks like location pin with plus icon should show 3. Icon should be on left and Text on right 4. Content have padding of 64dp 5. It should have a background color of #XXXXXX and content tint color of #XXXXXX
  8. Photo by Erica Magugliani on Unsplash UI Test • Cannot

    test visual aspect of a component • Slow to execute • Require complex setup
  9. Visual Bugs 1. Changes from libraries 2. Styling, Padding, Margins,

    Colors, Theme etc 3. Layout edge cases a. Long texts b. Constraints c. Internationalization (RTL Language, fonts) d. Accessibility (Bigger Font Size, Color Contrast)
  10. Visual Bugs 1. Changes from libraries 2. Styling, Padding, Margins,

    Colors, Theme etc 3. Layout edge cases a. Long texts b. Constraints c. Internationalization (RTL Language, fonts) d. Accessibility (Bigger Font Size, Color Contrast)
  11. Visual Bugs 1. Changes from libraries 2. Styling, Padding, Margins,

    Colors, Theme etc 3. Layout edge cases a. Long texts b. Constraints c. Internationalization (RTL Language, fonts) d. Accessibility (Bigger Font Size, Color Contrast)
  12. Visual Bugs 1. Changes from libraries 2. Styling, Padding, Margins,

    Colors, Theme etc 3. Layout edge cases a. Long texts b. Constraints c. Internationalization (RTL Language, fonts) d. Accessibility (Bigger Font Size, Color Contrast)
  13. Visual Bugs 1. Changes from libraries 2. Styling, Padding, Margins,

    Colors, Theme etc 3. Layout edge cases a. Long texts b. Constraints c. Internationalization (RTL Language, fonts) d. Accessibility (Bigger Font Size, Color Contrast)
  14. Visual Bugs 1. Changes from libraries 2. Styling, Padding, Margins,

    Colors, Theme etc 3. Layout edge cases a. Long texts b. Constraints c. Internationalization (RTL Language, fonts) d. Accessibility (Bigger Font Size, Color Contrast)
  15. Snapshot Testing Validate app’s appearance by using a screenshot or

    a snapshot as a reference to compare against.
  16. Take a snapshot of the component and store it as

    a reference for verification Record Verify Compare the last snapshot against the current changes to see if it matches
  17. Record Process Dev make changes and record a new snapshot

    LGTM! PR submitted Store snapshot for verification for future changes
  18. Screen level snapshot testing 1. A lot of dependencies 2.

    Too many states 3. More components = More flakiness
  19. Screen level snapshot testing 1. A lot of dependencies 2.

    Too many states 3. More components = More flakiness 4. Hard to trace which changes breaks
  20. App Bar are likely not to change, not priority to

    test Using Library component, not priority to test
  21. App Bar are likely not to change, not priority to

    test • Main feature of the app • Has different states Important text Using Library component, not priority to test
  22. 1. Most used features 2. Components that are being iterated

    quickly 3. Important text bodies and messages
  23. Tooling - pedrovgs/Shot - shopify/android-testify* - dropbox/dropshots - takahirom/roborazzi -

    QuickBirdEng/kotlin-snapshot-testing - facebook/screenshot-tests-for-android
  24. @Composable fun ProjectCard() { val project = loadData() Column {

    ProjectIcon(project) Row { Text(project.name) ... } } }
  25. @Composable fun ProjectCard() { val project = loadData() Column {

    ProjectIcon(project) Row { Text(project.name) ... } } }
  26. Use a stateless composable @Composable fun ProjectCard() { val project

    = loadData() ProjectCardContent(project) } @Composable fun ProjectCardContent( val project: Project ) { // Render here } Snapshot test UI Test
  27. @RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest { @get:Rule val composeTestRule = createComposeRule()

    @Test fun screenshotTest() { composeTestRule.setContent { ProjectCard(...) } composeTestRule.onRoot().captureRoboImage() } }
  28. @RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest { @get:Rule val composeTestRule = createComposeRule()

    @Test fun screenshotTest() { composeTestRule.setContent { ProjectCard(...) } composeTestRule.onRoot().captureRoboImage() } }
  29. @RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest { @get:Rule val composeTestRule = createComposeRule()

    @Test fun screenshotTest() { composeTestRule.setContent { ProjectCard(...) } composeTestRule.onRoot().captureRoboImage() } }
  30. @RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest { @get:Rule val composeTestRule = createComposeRule()

    @Test fun screenshotTest() { composeTestRule.setContent { ProjectCard(...) } composeTestRule.onRoot().captureRoboImage() } }
  31. @RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest { @get:Rule val composeTestRule = createComposeRule()

    @Test fun screenshotTest() { composeTestRule.setContent { ProjectCard(...) } composeTestRule.onRoot().captureRoboImage() } }
  32. @RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest { @get:Rule val composeTestRule = createComposeRule()

    @Test fun screenshotTest() { composeTestRule.setContent { ProjectCard(...) } composeTestRule.onRoot().captureRoboImage() } }
  33. @RunWith(ParameterizedRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest( private val config: DeviceConfig ) {

    companion object { @JvmStatic @Parameters fun testParamsProvider() = listOf( DeviceConfig("Small_Phone_Font_Small", RobolectricDeviceQualifiers.SmallPhone, 0.5f), DeviceConfig("Small_Phone_Font_Normal", RobolectricDeviceQualifiers.SmallPhone, 1.0f), DeviceConfig("Medium_Tablet_Font_Large", RobolectricDeviceQualifiers.MediumTablet, 2.0f), ) } @Test fun screenshotTest() { RuntimeEnvironment.setFontScale(config.fontScale) RuntimeEnvironment.setQualifiers(config.qualifier) ... } } Use Parameterized Runner for testing multiple device configuration
  34. @RunWith(ParameterizedRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest( private val config: DeviceConfig ) {

    companion object { @JvmStatic @Parameters fun testParamsProvider() = listOf( DeviceConfig("Small_Phone_Font_Small", RobolectricDeviceQualifiers.SmallPhone, 0.5f), DeviceConfig("Small_Phone_Font_Normal", RobolectricDeviceQualifiers.SmallPhone, 1.0f), DeviceConfig("Medium_Tablet_Font_Large", RobolectricDeviceQualifiers.MediumTablet, 2.0f), ) } @Test fun screenshotTest() { RuntimeEnvironment.setFontScale(config.fontScale) RuntimeEnvironment.setQualifiers(config.qualifier) ... } } Use Parameterized Runner for testing multiple device configuration
  35. @RunWith(ParameterizedRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest( private val config: DeviceConfig ) {

    companion object { @JvmStatic @Parameters fun testParamsProvider() = listOf( DeviceConfig("Small_Phone_Font_Small", RobolectricDeviceQualifiers.SmallPhone, 0.5f), DeviceConfig("Small_Phone_Font_Normal", RobolectricDeviceQualifiers.SmallPhone, 1.0f), DeviceConfig("Medium_Tablet_Font_Large", RobolectricDeviceQualifiers.MediumTablet, 2.0f), ) } @Test fun screenshotTest() { RuntimeEnvironment.setFontScale(config.fontScale) RuntimeEnvironment.setQualifiers(config.qualifier) ... } } Use Parameterized Runner for testing multiple device configuration
  36. @RunWith(ParameterizedRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ProjectCardScreenshotTest( private val config: DeviceConfig ) {

    companion object { @JvmStatic @Parameters fun testParamsProvider() = listOf( DeviceConfig("Small_Phone_Font_Small", RobolectricDeviceQualifiers.SmallPhone, 0.5f), DeviceConfig("Small_Phone_Font_Normal", RobolectricDeviceQualifiers.SmallPhone, 1.0f), DeviceConfig("Medium_Tablet_Font_Large", RobolectricDeviceQualifiers.MediumTablet, 2.0f), ) } @Test fun screenshotTest() { RuntimeEnvironment.setFontScale(config.fontScale) RuntimeEnvironment.setQualifiers(config.qualifier) ... } } Use Parameterized Runner for testing multiple device configuration
  37. Small Phone with Font Scale 0.5 Small Phone with Font

    Scale 1.0 Medium Tablet with Font Scale 2.0
  38. Road to Snapshot Testing 1. Analyze which are to test,

    start with small & critical components 2. Scale up to different configurations 3. Integrate into your development workflow 4. Happy Snapshot Testing!
  39. Good read - Introduction to snapshot testing blog series -

    Android screenshot testing playground - AndroidUITesting Utils - NowinAndroid bit.ly/droid-snap-read