Screenshot Testing for Jetpack Compose

Quick answer: Compose Preview Screenshot Testing renders selected @PreviewTest composables on the host, compares them with approved reference images, and fails when pixels differ. Put deterministic preview tests in src/screenshotTest, generate references with updateDebugScreenshotTest, then run validateDebugScreenshotTest in local development and CI. Review every intended baseline update like a UI change.

Screenshot tests catch visual regressions that semantic UI tests cannot: a missing icon, changed spacing, broken typography, a dark-theme color error, or a layout overflow. They complement, rather than replace, the click, input, and accessibility tests in Click, Type, Scroll, and Test User Interactions in Compose.

This guide uses Android’s Compose Preview Screenshot Testing plugin. It is experimental and its alpha APIs and requirements can change. Check the current official setup guide before adopting a version in a production build.

How screenshot testing works

The workflow has three deliberate stages:

  1. Render a deterministic composable preview and save it as a reference image.
  2. Render the same preview after a code change.
  3. Compare the new image with the reference and inspect the report when they differ.

The Compose Preview tool runs these as host-side screenshot tests using the preview-rendering pipeline; it does not require an emulator for this workflow. Android’s screenshot testing overview explains the same reference-versus-actual comparison model and where host-side rendering fits.

Treat a reference image as reviewed product behavior. Updating one is not a way to silence a failure; it is an explicit decision that the visual change is correct.

Check the requirements first

At the time of writing, the current official guide lists these minimums for Gradle-task usage:

  • Android Gradle Plugin 8.5.0 or newer.
  • Compose Preview Screenshot Testing plugin 0.0.1-alpha15 or newer.
  • Kotlin 1.9.20 or newer, with Kotlin 2.0+ recommended for the Compose Compiler Gradle plugin.
  • JDK 17 or newer and Compose enabled.

Full Android Studio integration has stricter, preview-channel-specific requirements than running the underlying Gradle tasks. The tool is also designed for Android projects and does not support non-Android Kotlin Multiplatform targets. Keep these constraints visible in the project documentation so a teammate does not mistake a host or IDE mismatch for a UI failure.

Enable the screenshot test source set

First, enable the experimental property in the project-level gradle.properties file:

android.experimental.enableScreenshotTest=true

Then add the plugin and the test dependencies. A version catalog keeps the plugin and validation API on the same version.

# gradle/libs.versions.toml
[versions]
screenshot = "0.0.1-alpha15"

[plugins]
compose-screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot" }

[libraries]
screenshot-validation-api = { group = "com.android.tools.screenshot", name = "screenshot-validation-api", version.ref = "screenshot" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }

In the Android application or library module:

plugins {
    alias(libs.plugins.compose.screenshot)
}

android {
    experimentalProperties[
        "android.experimental.enableScreenshotTest"
    ] = true
}

dependencies {
    screenshotTestImplementation(libs.screenshot.validation.api)
    screenshotTestImplementation(libs.androidx.ui.tooling)
}

The plugin creates the screenshotTest source set. Keep screenshot tests there rather than in androidTest: they serve a different purpose and run differently. The official setup guide documents the property, plugin, and dependencies above.

Create a deterministic preview test

Put a preview test in src/screenshotTest/kotlin/. Annotate the composable with both @PreviewTest and @Preview, then render a stable UI state with local sample data.

package com.example.shop

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.android.tools.screenshot.PreviewTest

@PreviewTest
@Preview(showBackground = true, widthDp = 360)
@Composable
fun CheckoutLoadingScreenshot() {
    ShopTheme {
        CheckoutScreen(
            uiState = CheckoutUiState.Loading,
            onAction = {},
        )
    }
}

Keep a screenshot preview self-contained:

  • Supply a fixed UiState, not a ViewModel, repository, clock, or live network response.
  • Use stable fake data, including image placeholders and dates.
  • Wrap the component in the real app theme so typography, shape, and colors are covered.
  • Name the function after the visual state being protected, such as CheckoutErrorScreenshot or ProfileDarkScreenshot.

This is the same preview-friendly boundary described in Jetpack Compose Preview Guide: a composable that receives state and callbacks is easier to preview and easier to test than one that reaches directly into runtime dependencies.

Generate the first reference images

After adding a @PreviewTest, generate its baseline from the command line:

./gradlew :app:updateDebugScreenshotTest

For another module or variant, replace app and Debug accordingly. The generated references live under app/src/screenshotTestDebug/reference (or the matching module and variant path). Add those approved reference images to version control alongside the test source.

The plugin names reference images from the fully qualified test function name plus a hash of preview parameters. Avoid casually renaming a @PreviewTest function: the official guide notes that doing so breaks its association with existing reference images and requires baseline regeneration.

Validate visual changes

Run validation after changing UI code, theming, a Compose dependency, or an asset that affects the preview:

./gradlew :app:validateDebugScreenshotTest

The task renders the preview again and compares it with the reference. A mismatch fails the task and creates an HTML report at a path like:

app/build/reports/screenshotTest/preview/debug/index.html

Open the report to compare the reference, actual, and difference image before deciding what to do. If the visual change is intended, regenerate references and review the image diff in the pull request. If it is not intended, fix the UI and rerun validation. Do not blindly update a baseline from CI failure output.

Cover the variations users actually see

One default preview is a good start, not complete visual coverage. Add focused screenshot variants where your UI can regress differently:

VariationWhat it can reveal
Light and dark themesIncorrect color tokens, icons, elevation, or contrast.
Loading, content, empty, and error statesMissing placeholders, collapsed regions, or bad retry affordances.
Large font scaleClipped text, overlapping actions, and inflexible rows.
Narrow and wide sizesBreakpoints, wrapping, and adaptive navigation changes.
Long or localized stringsTruncation and assumptions about English label length.
Selected, disabled, and error controlsIncorrect state-layer, border, or icon treatment.

Use @Preview parameters and multi-preview annotations to make the variation explicit. Do not create a combinatorial matrix for every screen: prioritize the states and configurations that make the component visually risky. Broader adaptive design guidance is available in Adaptive NavigationSuiteScaffold for Phone and Tablet.

Keep screenshots deterministic

Screenshot tests are only useful when the same input renders the same image. Before adding a baseline, remove or control sources of visual drift:

  • Freeze sample data, dates, counters, and randomized IDs.
  • Avoid live network images; use fixed local assets or deterministic fakes.
  • Hold loading and animation state at a known frame rather than capturing an in-between transition.
  • Declare the preview’s device size, font scale, locale, and UI mode when they matter.
  • Make asynchronous state explicit in the preview instead of waiting for it to arrive.

If an image diff is caused by a platform renderer quirk, font availability, or unstable dependency, first reproduce it on the same build environment. Increasing a difference threshold should be a last, documented decision, not a default way to hide noise.

Screenshot tests and semantic UI tests answer different questions

Test typeBest at provingDoes not replace
Screenshot testThe rendered UI still matches an approved visual baseline.Interactions, navigation behavior, and accessibility semantics.
Compose semantic UI testA user can find, act on, and observe a meaningful UI outcome.Pixel-level styling and layout regressions.

Use both for an important screen. For example, a checkout error screenshot can protect the error layout and retry styling, while a semantic test clicks Retry and proves that it starts the recovery path. Find Nodes and Assert UI with Compose Test APIs shows how to keep that behavioral test tied to semantics instead of layout structure.

Common screenshot-testing mistakes

Recording production-dependent previews

A preview backed by a real ViewModel, current time, or network image produces fragile baselines—or fails to render at all. Pass fixed state and callbacks at the preview boundary.

Updating references without inspection

A changed reference is a visual code review artifact. Inspect the actual and diff images before accepting it, especially after theme, typography, or dependency updates.

Testing only the happy path

Loading, error, empty, disabled, dark-theme, and large-text states often carry the most visual risk. Add them selectively where they matter to a user journey.

Assuming a screenshot proves accessibility

An image cannot tell you whether an icon has a useful description, a custom surface has a click action, or focus order works. Keep semantic and accessibility tests alongside visual baselines.

Screenshot testing checklist

  • Confirm the plugin’s experimental version and AGP/JDK requirements against current documentation.
  • Put deterministic @PreviewTest functions in src/screenshotTest/kotlin.
  • Generate and commit reviewed reference images with update…ScreenshotTest.
  • Run validate…ScreenshotTest locally and in CI, then inspect HTML reports on failure.
  • Add variants for the few themes, states, sizes, and font scales that are visually high-risk.
  • Keep screenshot tests paired with semantic UI tests for important user interactions.

That workflow turns a visual regression into a reviewable image diff before it reaches users, while leaving behavior and accessibility to the tests designed to exercise them.