Android Snapshots

Emerge offers a full-suite snapshotting solution on Android, allowing for snapshotting of Composables, Activities and Views. Emerge handles the complicated aspects, like emulator coordination, file storage, image comparisons & diffs, allowing you to focus on writing tests.

Concepts

Automatic Compose snapshot tests

Emerge automatically generates snapshot tests for any Compose function annotated with @Preview. There is no extra test code to maintain.

Management & diffing

When snapshot builds are uploaded to Emerge using the Emerge Gradle Plugin, Emerge automatically handles storing the resulting snapshot images and diffing between them.

For details on how Emerge manages and diffs snapshots, see Snapshot management & diffing.

Explicit or automatic composable snapshot tests

Emerge generates Composable snapshot tests from @Preview annotated functions from any source set of your choosing using a custom KSP processor and Gradle plugin. This allows full flexibility of keeping snapshot tests explicitly separate as test code, or automatically generated for all main source set previews.

Support for legacy (XML/Custom) views & Activities

Emerge snapshots provide full support for manual snapshot testing of legacy XML/custom views as well as full activities.

Quickstart (~10 min)

Emerge snapshotting requires the usage of Emerge's Gradle plugin and the com.emergetools.snapshots:snapshots library.

Additionally, Emerge offers a custom KSP processor, com.emergetools.snapshots:snapshots-processor for automatic generation of Composable @Preview snapshots.

Setup Emerge Gradle Plugin

See Gradle Plugin (Android) for full setup instructions, but for a quick setup, add the Emerge plugin to your app's build.gradle.kts file:

plugins {
  id("com.emergetools.android")
}

emerge {
  // Emerge uses the EMERGE_API_TOKEN env variable by default, so no need to set this explicitly, this is just for example purposes
  apiToken.set(System.getenv("EMERGE_API_TOKEN"))
}

The above setup assumes you've created an API token and set as the EMERGE_API_TOKEN env variable. Full instructions can be found at obtain an API key.

Add Snapshot SDKs

Emerge snapshot SDKs are published to Maven Central and should be added as dependencies to your app's build.gradle.kts file.

Compose @Preview snapshot generation relies on a KSP Processor to automatically generate snapshot tests for Compose Previews, which requires the KSP plugin to be added to your project. For more details, see Add the KSP plugin to your project from the Android developer documentation.

plugins {
  // ...
  
  // For Compose @Preview snapshot generation
  id("com.google.devtools.ksp")
}

// ...

dependencies {
  androidTestImplementation("com.emergetools.snapshots:snapshots:<latest_version>")
  
  // For Compose @Preview snapshot generation from the main source set
  ksp("com.emergetools.snapshots:snapshots-processor:<latest_version>")
}

See the emerge-android repo for the latest snapshots SDK versions.

Create your first snapshot test

Composables

Emerge's KSP snapshot-processor automatically generates snapshot tests for compose previews in the
source set the KSP processor is applied to. No additional setup work needs to be done!

// /src/main/com/myapp/MyComposable.kt

@Preview  
@Composable  
fun MyComposablePreview() {  
  MyComposable(  
    text = "Hello, World!"  
  )  
}

That's it! Using KSP, Emerge's snapshot-processor will automatically generate snapshot tests for all @Preview annotated functions in the main source set of the module the processor is running on.

🚧

Current limitations

  • No-arg @Preview annotated composable functions are supported.
  • @Preview annotated functions must be public to be snapshotted. Experimental internal support is available through the snapshots.experimentalInternalSnapshotsEnabled Gradle plugin property.
  • Preview parameters are currently not supported, but long term plans to support are planned. For mocking data, instantiate data directly in the composable preview function.

Variant support

Emerge currently supports a subset of @Preview annotation parameters for variant generation:

  • fontScale
  • locale
  • uiMode (dark/light mode)

Emerge will automatically generate a snapshot test for each Preview annotation present. For example,
for the following composable:

// src/main/com/myapp/MyComposable.kt

@Preview
@Preview(fontScale = 1.5f)
@Composable
fun MyComposablePreview() {
  MyComposable(
    text = "Hello, World!"
  )
}

Emerge will generate two snapshots, one default, and one with 1.5f font scale.

Multipreview support

Emerge has full support for Multipreview annotations and stacked multipreviews.

As an example, the following annotation can be used to generate multiple previews when the custom annotation is used in place of multiple @Preview annotations.

// src/main/com/myapp/SnapshotTestingPreview.kt

@Preview
@Preview(fontScale = 1.5f)
@Preview(uiMode = UI_MODE_NIGHT_YES)
annotation class SnapshotTestingPreview

Using SnapshotTestingPreview, the following preview will produce 3 variants, a default (@Preview annotation), a large font (@Preview(fontScale = 1.5f)) and a dark mode (@Preview(uiMode = UI_MODE_NIGHT_YES)).

// src/main/com/myapp/MyComposable.kt

@SnapshotTestingPreview
@Composable
fun MyComposablePreview() {
  MyComposable(
    text = "Hello, World!"
  )
}

Android API levels

Emerge supports specifying the system API level for snapshotting all previews through the snapshots.apiVersion Gradle plugin property

emerge {
  // ...
  
  snapshots {
    apiVersion.set(31) // must be 29, 31, 33 or 34
  }
}

By default, Emerge snapshots using API level 34 (Android 14). A full list of API levels can be found here. Currently, Emerge only supports API levels 29, 31, 33 and 34. Should you have a use case for other API levels, let us know and we can likely accomodate!

Long term, Emerge plans to support the @Preview annotation's apiLevel parameter for more granular API level support.

Generating Preview snapshots from the androidTest source set

Emerge can automatically generate snapshot tests for all composable previews in the main source
set, but sometimes creating explicit previews for snapshot testing is more desired.

Apply the Emerge KSP processor as a kspAndroidTest dependency instead of ksp to generate
snapshot tests from the androidTest source set.

plugins {
  id("com.emergetools.android")
  id("com.google.devtools.ksp")
}

emerge {
  // ..
}

dependencies {
  androidTestImplementation("com.emergetools.snapshots:snapshots:<latest_version>")
  // For Compose @Preview snapshot generation from androidTest source set:
  kspAndroidTest("com.emergetools.snapshots:snapshots-processor:<latest_version>")
}

As an example, assuming the following preview lives in the androidTest source set:

// /src/androidTest/com/myapp/MyComposablePreviewTest.kt

@Preview
@Composable
fun MyComposablePreviewTest() {
  MyComposable(
    text = "Hello, World!"
  )
}

Emerge will automatically generate a preview snapshot test for MyComposablePreviewTest().

Activities and views

Create a basic test class that uses the EmergeSnapshot rule to generate snapshots.

@RunWith(AndroidJUnit4::class)  
class EmergeSnapshotTests {

  @get:Rule  
  val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)

  @get:Rule  
  val snapshots = EmergeSnapshot()

  @Test  
  fun mainActivitySnapshot() {  
    val scenario = activityScenarioRule.scenario  
    scenario.onActivity {  
      snapshots.take(name = "MainActivity", activity = it)  
    }  
  }

  @Test  
  fun customViewSnapshot() {  
    val view = CustomView(InstrumentationRegistry.getInstrumentation().targetContext)  
    snapshots.take(name = "CustomView", view = view)  
  }
}

Each name parameter must be unique as it's used as the primary key for saving & diffing.

Upload snapshots to Emerge

The Emerge gradle plugin offers a single command to build & upload screenshot tests packages to Emerge. Emerge will handle generating, storing, and diffing the snapshots against a base build for you.

./gradlew :app:emergeUploadSnapshotBundleDebug

Once uploaded, snapshots and diffs are viewable directly in the Emerge UI.

Diffing snapshots

Emerge will automatically diff snapshots based onsha/baseSha parameters. These parameters can be specified through the vcs block of the Emerge Gradle plugin extension, and by default Emerge will attempt to set these automatically through local git information.

emerge {
  // ...
  
  vcs {
    sha.set("...")
    baseSha.set("...")
  }
}

When Emerge finds a matching base build whose sha is equal to the head build's baseSha, Emerge will automatically diff snapshots with the base build. Diffing is done using a hash of the Preview function's fully qualified name combined with the @Preview annotation parameters specified.

Generate snapshots locally

Emerge also offers a command to generate snapshots locally for debugging or saving locally.

./gradlew :app:emergeLocalSnapshotsDebug

Additionally, you can specify where to save the snapshots to using the snapshots extension of the Emerge Gradle plugin. By default, snapshots are saved to the /build/emerge/snapshots/outputs directory.

emerge {
  ..
  
  snapshots {
    snapshotsStorageDirectory.set("/app/src/main/snapshots") // Storage of locally generated snapshots
  }
}

Additional resources

For details on how Emerge manages and diffs snapshots, see Snapshot management & diffing.

Full source code and SDK documentation is available in the emerge-android repository.