Android Snapshots

A full end-to-end snapshot testing suite, ready to go in 10 minutes

šŸ“˜

Only compatible with Emerge Gradle plugin 3.0+

You must be using snapshots 1.0.0 with any 3.0.0+ version of Emerge's Gradle Plugin for snapshots to properly work. See the emerge-android repo for latest snapshot versions.

Latest gradle plugin version: Gradle Plugin Portal Version

Latest snapshots version: Maven Central

Setup (~10 min)

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

Add Emerge Gradle Plugin

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

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

emerge {
  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 API key instructions can be found at obtain an API key.

Add Snapshot SDK

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 Gradle plugin instrumentation to modify Compose Previews to be visible at runtime. Our snapshot SDK can then handle everything else, invoking the Compose Preview and saving the resulting snapshot image.

Latest version: Maven Central

dependencies {
  // ..
  
  androidTestImplementation("com.emergetools.snapshots:snapshots:<latest_version>")
}

Run preflight check

Let's check that everything's set up properly by running the snapshots preflight task. A preflight check is automatically generated for all debug variants of your app:

./gradlew :<app>:emergeSnapshotsPreflightDebug

The preflight task will give detailed output about the Emerge snapshots integration:

ā€ŗ ./gradlew :app:emergeSnapshotsPreflightDebug

> Task :app:emergeSnapshotsPreflightDebug

ā•”ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•—
ā•‘ Snapshots preflight check was successful (3/3) ā•‘
ā• ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
ā• ā• āœ… Emerge API token set
ā• ā• āœ… Snapshots enabled
ā•šā• āœ… Snapshots SDK is an androidTestImplementation dependency

ā•”ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•—
ā•‘ VCS Info check was successful (4/4) ā•‘
ā• ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
ā• ā• āœ… SHA: 123456789..
ā• ā• āœ… Base SHA: 987654321..
ā• ā• āœ… Branch name: main
ā•šā• āœ… PR number: 123

Snapshots preflight was successful!
Check snapshots locally with ./gradlew :app:emergeLocalSnapshotsDebug (make sure to have an emulator or connected device running)
Upload & run snapshots on Emerge with ./gradlew :app:emergeUploadSnapshotBundleDebug

If there are any issues or warnings, the preflight task should help you identify and address them before uploading to Emerge.

Upload to Emerge

The Emerge Gradle plugin offers a single command to build & upload snapshot tests 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. You can find a link to the build in the Gradle output.

šŸ‘

That's it!

Emerge recommends running snapshot tests on every commit. Emerge will handle generating, storing and diffing based on Git information.

Continue reading for full details on how Emerge generates and manages snapshots.

How it works

Under the hood, Emerge is instrumenting the preview annotations to expose them at runtime in your app's debug build. Emerge can then find and snapshot all previews from the instrumented build, with no work on your part.

Supported types

Composables

Emerge's gradle plugin automatically instruments snapshot tests for compose previews in the main
source set.

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

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

šŸš§

Current limitations

  • @PreviewParameter is not currently supported, but long term plans to support it are planned. For mocking data, instantiate data directly in the composable preview function.

Legacy views

Emerge is 100% invested in Compose, but we know your codebase might not be fully there yet.

Emerge can snapshot legacy views through Compose Previews. Wrap the legacy view in an AndroidView (docs) and add to any Preview!

@Preview
@Composable
fun LegacyTitleViewPreview() {
  AndroidView(
    factory = { context: Context ->
      LegacyTitleView(context).apply {
        layoutParams = ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT
        )
      }
    },
    update = { view ->
      // Configure legaacy view here.
      view.setTitle("Hello, Legacy View!")
      view.setSubtitle("I'm a legacy view in a Compose preview")
    }
  )
}

class LegacyTitleView @JvmOverloads constructor(
  context: Context,
  attrs: AttributeSet? = null,
  defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

  //  ...
}

Variant support

As of the latest snapshots release, Emerge currently supports all @Preview annotation parameters for variants except:

  • apiLevel - per-preview apiLevel support is not yet possible, but the snapshots.apiVersion gradle property can be used to select apiLevel for all previews. See Android API levels for details.
  • showSystemUi support is planned in a future release of Emerge snapshots
  • wallpaper

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 even 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 accommodate!

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

Including Preview snapshots from a non-main source set

Emerge will automatically generate snapshot tests for all composable previews and their supported variants in the main source set by default, but sometimes creating previews for snapshot testing in a separate source set is more desired.

Emerge uses your main source set as the source of truth, so simply add your snapshot testing source set to your target variant's build configuration.

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

emerge {
  // ..
}

android {
  // .. 

  sourceSets {
    // Apply for any variant you'd like, Emerge will generate a snapshot task for all debuggable variants
    getByName("debug") {
      kotlin.srcDirs(
        "src/main/kotlin",
        "src/snapshots/kotlin", // Will include Previews from the snapshots source set
      )
    }
  }
}

dependencies {
  androidTestImplementation("com.emergetools.snapshots:snapshots:<latest_version>")
}

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

// /src/snapshot/kotlin/com/myapp/MyComposablePreviewTest.kt

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

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

Additional resources

For details on how Emerge manages and diffs snapshots, see Reviewing diffs.

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