Snapshot testing for mobile applications: how it works, tools and examples

Author: IT Sectr Published: 2026-04-07 Reading time: 9 min

Snapshot testing is a method of automated user interface verification where the current screen state is compared with a reference image (snapshot) saved during the previous test run. Any visual discrepancy is recorded as a change requiring developer confirmation. Unlike UI tests that check for element presence, snapshot tests detect pixel-level changes — shifts, color deviations, and layout issues. According to Android Developers, 2024, snapshot testing detects up to 30% of visual regressions missed by traditional UI tests, making it an indispensable tool for maintaining a consistent interface.

Key Takeaways

  • Snapshot testing — a method of comparing the current screen render with a reference image to detect visual regressions.
  • Paparazzi — an Android library that renders Compose and View components in a test environment without launching an emulator.
  • Shot — an Android framework that takes screenshots of real screens in Instrumentation tests with support for various resolutions.
  • SnapshotTesting — a library from Point-Free for iOS that supports comparison of not only images but also text, JSON, and Core Data.
  • Updating references — a single command after an intentional interface change, replacing old snapshots with new ones.

What is Snapshot Testing?

Snapshot testing is a technique where a test renders a UI component, saves the resulting image as a reference, and on subsequent runs compares the current render against this reference. If the images match — the test passes. If differences are found — the test fails, and the developer receives a diff image highlighting the changed pixels. The technique was borrowed from web development (Jest snapshots) and adapted for mobile platforms.

The main value of snapshot tests is automatic detection of unexpected visual changes. A developer might change the color scheme in a global theme and accidentally affect a dozen screens. UI tests checking for button and text presence will not notice this. A snapshot test will capture every pixel change on every affected screen, providing a complete picture of the change impact.

According to the Mobile DevOps Summit 2023 survey, teams using snapshot testing in addition to classic UI tests reduce the number of visual defects in releases by 40%. This approach is especially effective in projects with design systems and component-based architectures, where changing one base component can affect dozens of application screens.

How Snapshot Tests Differ from UI Tests

The fundamental difference lies in the object of verification. UI tests check the presence, state, and behavior of interface elements: “the button is visible”, “the text contains an error message”, “pressing opens a new screen”. Snapshot tests check the entire visual appearance: element positioning, margins, colors, fonts, shadows, and rounded corners. A snapshot test answers the question “does the screen look as expected?”, while a UI test answers “does the screen work as expected?”

Execution speed also differs. UI tests run on an emulator or real device, require full application loading, and take from 10 seconds to a minute per scenario. Snapshot tests using libraries like Paparazzi render components in a virtual environment without launching an emulator, reducing test time to 100–500 milliseconds. A full set of snapshot tests (50–100 screens) executes in 2–5 minutes instead of 30–60 minutes for a comparable set of UI tests.

However, snapshot tests do not replace UI tests. The optimal strategy is a combination: snapshot tests cover visual regression (rendering each screen in basic states), while UI tests cover behavioral aspects (click scenarios, input validation, navigation). This combination provides 90% confidence in interface correctness with minimal CI run time.

Tools for Snapshot Testing

On Android, the main tools are Paparazzi and Shot. Paparazzi from Cash App renders components in a JVM test environment without an emulator, using Layoutlib gravity layout. Shot from Karumi performs Instrumentation screenshots on a real device or emulator and compares them against references using the AShot library, accounting for differences in resolution and pixel density.

Paparazzi for Android

Paparazzi does not require launching an emulator — rendering is done on the JVM through Layoutlib, providing speed comparable to unit tests. The library supports both the View system and Jetpack Compose. For Compose, use the paparazzi.snapshot { MyComposable() } modifier. References are stored in src/test/snapshots and are automatically compared on each run. The maximum difference percentage is configurable via maxPercentDifference.

SnapshotTesting for iOS

SnapshotTesting from Point-Free supports comparison of not only UIImage but also strings, JSON, Data, and entire Core Data stores. This makes it a versatile tool not only for UI snapshots but also for verifying serialization and decoding of JSON responses. For SwiftUI, use the assertSnapshot extension with the .image(on: .iPhone13) modifier. The record: true strategy creates references on first run.

Cross-Platform Approaches

For React Native, the popular solution is react-native-testing-library combined with jest-image-snapshot. The web approach to snapshot testing is ported to the mobile environment by rendering components in a Node.js environment followed by comparison of JSON snapshots of the virtual DOM. This approach is faster than native but less accurate — it does not account for platform-specific font rendering and system component features. For Flutter, golden testing is used through the goldens toolkit.

Code Examples for Snapshot Tests

Let’s look at snapshot tests for Android (Paparazzi) and iOS (SnapshotTesting). Both examples verify the appearance of a component — a user card with an avatar, name, and status. The test renders the component with test data and compares the result against a reference image stored in the repository.

Android: Snapshot Test with Paparazzi

Paparazzi uses the @Test annotation and snapshot() method to capture the render. References are saved in the src/test/snapshots folder and automatically loaded on the next run for comparison.

kotlin
class UserCardSnapshotTest {

    @get:Rule
    val paparazzi = Paparazzi(
        theme = "Theme.MyApp",
        maxPercentDifference = 0.1
    )

    @Test
    fun userCard_defaultState() {
        val card = UserCard(
            name = "Alice Johnson",
            status = "Online",
            avatarUrl = "https://example.com/avatar.png"
        )
        paparazzi.snapshot(card)
    }

    @Test
    fun userCard_offlineState() {
        val card = UserCard(
            name = "Bob Smith",
            status = "Offline",
            avatarUrl = null
        )
        paparazzi.snapshot(card, name = "user_card_offline")
    }
}

iOS: Snapshot Test with SnapshotTesting

SnapshotTesting uses the .snapshot() modifier inside assertSnapshot. The library automatically determines the format — UIImage for UIView, String for text, Data for binary data.

swift
import SnapshotTesting
import XCTest

class UserCardSnapshotTests: XCTestCase {
    func testUserCardDefaultState() {
        let card = UserCardView(
            name: "Alice Johnson",
            status: "Online",
            avatarURL: URL(string: "https://example.com/avatar.png")
        )
        let controller = UIHostingController(rootView: card)
        assertSnapshot(matching: controller, as: .image(on: .iPhone13))
    }

    func testUserCardOfflineState() {
        let card = UserCardView(
            name: "Bob Smith",
            status: "Offline",
            avatarURL: nil
        )
        assertSnapshot(matching: card, as: .image(on: .iPhone13))
    }
}

Snapshot Testing Workflow

A typical workflow includes four stages. First run (record mode): all snapshot tests execute in record mode — reference images are created and saved to the repository. This stage is performed during initial test setup or after an intentional interface change. After recording, references are committed together with the code — they become part of the project.

On subsequent runs, tests work in comparison mode: each new render is compared against the reference. If differences are found, a diff image is generated: pixels matching the reference are highlighted in green, differing ones in red. The developer reviews the diff and makes a decision: if the change is expected (conscious design change), the reference is updated with the record command; if unexpected, the bug is fixed. Reference updates are performed with a single command: for Paparazzi it’s `./gradlew recordPaparazzi`, for SnapshotTesting — `assertSnapshot(record: true)`.

According to the Spotify Engineering Blog (2022), teams using the described workflow spend an average of 2 minutes per test analyzing diff images. With a set of 50 snapshot tests, a full reference update cycle takes 15–20 minutes, significantly faster than manual verification of visual changes across 50 screens.

Limitations and Anti-Patterns of Snapshot Tests

Snapshot tests have fundamental limitations. Environment sensitivity: the same component may render differently across different OS versions, screen densities, and font configurations. References created on one machine may differ from renders on a CI server. The solution is to use fixed environment parameters: a specific Layoutlib version for Paparazzi or an exact device model for SnapshotTesting.

Anti-pattern #1: giant snapshots — a snapshot test capturing the entire screen fails with every minimal change to any component. The correct approach is to test individual components (button, card, input field) in isolation. Each component is tested independently, providing precise identification of the change source. Anti-pattern #2: ignoring diffs — automatically updating references without analyzing diff images reduces the value of snapshot tests to zero. Each diff requires a conscious developer decision.

According to the Better Engineering Blog (2023), snapshot tests provide the greatest value when covering design system components and key screens in basic states — empty, filled, error, and boundary. Covering animations and dynamic states through snapshot tests is inefficient due to the non-deterministic nature of timestamps in rendering — for such scenarios, video recording or manual QA is more suitable.

Frequently Asked Questions

Do snapshot tests replace UI tests?

No, snapshot tests check visual appearance, while UI tests check interface behavior. The optimal strategy is to combine both approaches: snapshots for visual regression, UI tests for scenarios and navigation. Snapshots answer “does it look right”, UI tests answer “does it work right”.

How often should reference images be updated?

References are updated with every conscious design change: a new theme color, modified margins, adding or removing elements. Updating is done through record mode, after which diff images are reviewed in code review to ensure the changes match designer expectations.

Which components should be covered with snapshot tests?

First and foremost, design system components — buttons, cards, input fields, modal windows. Then key screens in basic states. Do not test with snapshots animations, WebView, maps, and screens with dynamic content — snapshots produce false failures for these due to non-determinism.

How to handle false failures from different OS versions?

Use the same API Level for both record and test modes. For Paparazzi, specify a specific Layoutlib version in the configuration. For SnapshotTesting, fix the device model. References created on Android 14 may differ from renders on Android 12 due to changes in system fonts and Material theme.

Snapshot tests in CI — how to set up?

In CI, snapshot tests run in verify mode. If a test fails, CI shows the diff image in build artifacts. Record mode (reference update) is performed locally by the developer or in a separate CI task with manual trigger. Reference images must be committed to the repository.

Summary

  • Snapshot testing compares the current component render against a reference image, detecting pixel changes and visual regressions.
  • Paparazzi — a fast Android tool without an emulator; SnapshotTesting — a versatile iOS library from Point-Free.
  • Snapshot tests do not replace UI tests — they complement them: snapshots for visuals, UI tests for behavior.
  • Record mode creates reference images; verify mode compares current renders against them and generates diffs on mismatches.
  • Design system components are a priority target for snapshot tests, as their changes affect multiple screens.
  • Each diff requires a conscious developer decision — auto-replacing references without analysis nullifies the value of tests.
  • Reference images are committed to the repository and are part of the codebase alongside test source code.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also