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.

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 {
  // 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 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.

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

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

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!"  
  )  
}

Activities and views

Activities and views can't be snapshotted automatically like compose previews can, but are still very easy to setup with standard Android instrumentation tests.

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.

How it works

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

🚧

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.

Uploading a build

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. You can find a link to the build in the Gradle output.

Variant support

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

  • device (support coming soon)
  • 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.
  • 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.