androidengineers.Book a session

Testing Architecture

Screenshot Testing Basics

article15 minMedium

Screenshot testing captures a rendered image of a UI component and compares it to a stored baseline. Any pixel difference fails the test, catching visual regressions without manual visual inspection.

Why Screenshot Tests?

Standard unit and integration tests verify behavior. Screenshot tests verify appearance:

  • Text truncation changed
  • Padding accidentally removed
  • Color or font changed after a theme update
  • Dark mode rendering broken

Paparazzi: JVM Screenshot Tests (No Device Needed)

Paparazzi renders Compose components on the JVM using Android's layout engine — no emulator required:

// testImplementation("app.cash.paparazzi:paparazzi:1.3.4")

@RunWith(JUnit4::class)
class ArticleCardSnapshotTest {

    @get:Rule
    val paparazzi = Paparazzi(
        deviceConfig = DeviceConfig.PIXEL_6,
        theme = "Theme.App"
    )

    @Test
    fun `article card renders correctly`() {
        val article = Article(
            id = "a1",
            title = "How to build Android apps",
            author = "Akshay Nandwana",
            publishedAt = "July 28, 2026",
            imageUrl = null
        )

        paparazzi.snapshot {
            AppTheme {
                ArticleCard(article = article, onClick = {})
            }
        }
        // First run: creates baseline image
        // Subsequent runs: compares to baseline — fails on any pixel difference
    }

    @Test
    fun `article card dark mode`() {
        paparazzi.snapshot(name = "dark_mode") {
            AppTheme(darkTheme = true) {
                ArticleCard(article = testArticle, onClick = {})
            }
        }
    }
}

Baseline Workflow

# Record baselines (first run / after intentional visual change)
./gradlew recordPaparazziDebug

# Verify against baselines (CI — fails if any pixel changed)
./gradlew verifyPaparazziDebug

# Baseline images are committed to git
# git add src/test/snapshots/

Roborazzi: Alternative with Robolectric

Roborazzi captures screenshots via Robolectric — can test more complex interactions:

// testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.19.0")
// testImplementation("io.github.takahirom.roborazzi:roborazzi-junit-rule:1.19.0")

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [33])
class ProfileScreenSnapshotTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun `profile screen matches baseline`() {
        composeTestRule.setContent {
            AppTheme {
                ProfileScreen(profile = testProfile)
            }
        }

        composeTestRule.onRoot()
            .captureRoboImage("profile_screen.png")
    }
}

What to Screenshot Test

✅ Good candidates❌ Poor candidates
Reusable UI components (cards, buttons)Screens with dynamic content (timestamps, avatars)
Complex layout combinationsLoading states
Dark/light theme variantsError states with variable messages
RTL layout variantsAnimated elements
Typography-heavy screensScreens that depend on real data

Handling Dynamic Content

@Test
fun `article card with fixed test data`() {
    paparazzi.snapshot {
        AppTheme {
            // Use fixed data — no timestamps, no random content
            ArticleCard(
                article = Article(
                    id = "a1",
                    title = "Fixed Title for Snapshot",
                    author = "Test Author",
                    publishedAt = "Jan 1, 2026",  // fixed date, not today's date
                    imageUrl = null               // no image loading
                )
            )
        }
    }
}

CI Integration

Screenshot tests typically run in two modes:

  • PR: verifyPaparazzi — fails on any change
  • Main branch after merge: recordPaparazzi — update baselines if tests pass

Key Takeaways

RuleWhy
Commit baselines to gitNeeded for CI comparison
Fixed test dataNo timestamps or dynamic values — same image on every run
Test components, not full screensSmaller scope = less noise from unrelated changes
Dark + light themesMost apps support both; screenshot both separately
Paparazzi for speedJVM-based — no device or emulator needed

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Screenshot Testing Basics | Android System Design | Android Engineers