iOS Snapshots

Uploading a build

Emerge's Snapshots automatically parses all SwiftUI types in your app binary and uses them to generate snapshots. This process also tests your previews to verify they render without errors. To ensure your preview types are included in the binary, we recommend uploading a debug build so that common code-stripping settings and compiler flags are disabled.

Other requirements

The upload must be an XCArchive and the app must support arm64 devices.

The app is re-signed before running for preview extraction, so it needs to be able to run without entitlements. For example, app groups and associated domains entitlements are removed.

UIKit

Snapshots support both SwiftUI and UIKit, to make your snapshots visible to Emerge they need to be accessible from a PreviewProvider. To snapshot a UIKit view or view controller, return it from your PreviewProvider using UIViewRepresentable or UIViewControllerRepresentable.

Preventing flakiness

To reliably detect differences in images and prevent flaky diffs, each preview needs to render reproducibly. You can check if the environment variable EMERGE_IS_RUNNING_FOR_SNAPSHOTS is set to 1 to enable overrides such as mocking potentially volatile data. Commonly this applies to build versions, random numbers, or network responses. To help control for flakiness, HTTP requests through NSURL are blocked while generating snapshots, and the current time is mocked so NSDate will always return a constant time.

Custom Precision

You can reduce the amount of precision required for images to be considered unchanged using EmergePrecisionModifier. Add it to your preview by linking SnapshotPreviewsCore from the SDK and calling .emergeSnapshotPrecision(...) For example the following preview will only be marked as changed if the diff is > 1%.

struct MyComponent_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
            .previewDisplayName("Light")
            .emergeSnapshotPrecision(0.99)
    }
}

The percentage difference is calculated based on the perceptual difference between pixels, with colors that are more different having a higher percent difference.

Components and variants

Each PreviewProvider type is a single component, but can provide multiple variants. For example if your code looks like this:

struct MyComponent_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
            .previewDisplayName("Light")
        MyView()
            .colorScheme(.dark)
            .previewDisplayName("Dark")
    }
}

Then MyComponent will have two variants, named Light and Dark. When a component has multiple variants, we recommend specifying a name for each case so they can be easily distinguished when viewing the generated snapshots.

Automatic variants

If your app supports dark mode or dynamic type, let us know if you want automatic generation of variants. For every variant in your binary, Emerge can create one in dark mode and one in light mode, or any combination of dynamic type settings.

Device variants

Snapshot Testing has support for 3 devices:

  • iPhone 11 Pro Max
  • iPhone 8
  • iPad Air (5th generation)

You can define the preview device with .previewDevice().

struct MyComponent_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
            .previewDevice("iPhone 8")
        MyView()
            .previewDevice("iPad Air (5th generation)")
    }
}

By default the selected device is the iPhone 11 Pro Max, and if you have a different one, they will be mapped using the following rules:

  • Any iPad (mini, Pro, Air) maps to iPad Air (5th generation).
  • All iPhones with home button map to iPhone 8.
  • Everything else will use the iPhone 11 Pro Max.

Layout

Emerge respects the preview layout in previewLayout() when generating snapshots. The default .device layout will result in a full screen preview. We recommend using a .fixed or .sizeThatFits layout to customize the dimensions of your snapshot.

Scroll views

Scroll views are automatically expanded to a height that fits their contentSize. This only happens for the first scroll view discovered in a preview, and only for the height (not width). Layouts with a fixed size (PreviewLayout.fixed) will not be expanded.

Interface orientation

Emerge can generate snapshots in both portrait and landscape orientations, respecting the selected orientation with previewInterfaceOrientation(). Keep in mind that interface orientation is different from device orientation, if your code is using the orientation, you must rely on the interface orientation provided by UIWindowsScene instead of UIDevice. A simple way to get it is with this code:

UIApplication.shared.windows
  .first?
  .windowScene?
  .interfaceOrientation

Code Coverage

Snapshot tests automatically generate code coverage reports as a ".profdata" file if your uploaded binary is built with code coverage information included. To test for code coverage information in the binary, look for the LLVM_COV segment in your binary.

Generate snapshots locally

Emerge also offers a Swift package generate snapshots locally for debugging.

  • Install the Swift Package in your project
  • Add a UI test target with SnapshottingTests as a dependency.
  • Create your test that inherits from SnapshottingTests.PreviewTest

See the repo for full documentation.