diff --git a/Examples/DemoApp/DemoApp/TestViews/ExpandingView.swift b/Examples/DemoApp/DemoApp/TestViews/ExpandingView.swift
index ecdc81c2..77864fba 100644
--- a/Examples/DemoApp/DemoApp/TestViews/ExpandingView.swift
+++ b/Examples/DemoApp/DemoApp/TestViews/ExpandingView.swift
@@ -27,6 +27,6 @@ struct ExpandingView: View {
struct ExpandingView_Previews: PreviewProvider {
static var previews: some View {
ExpandingView()
- ExpandingView().emergeExpansion(false)
+ ExpandingView().snapshotExpansion(false)
}
}
diff --git a/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift b/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift
index 7301612f..1b2cc38f 100644
--- a/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift
+++ b/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift
@@ -74,7 +74,7 @@ extension NamedViewModifier {
@available(visionOS, unavailable)
@available(tvOS, unavailable)
static var accessibility: NamedViewModifier {
- .init(name: "Accessibility", value: { $0.emergeAccessibility(true) })
+ .init(name: "Accessibility", value: { $0.snapshotAccessibility(true) })
}
static var rtl: NamedViewModifier {
diff --git a/Examples/DemoApp/DemoApp/TestViews/RideShareButton.swift b/Examples/DemoApp/DemoApp/TestViews/RideShareButton.swift
index 655f4aed..1d1950d4 100644
--- a/Examples/DemoApp/DemoApp/TestViews/RideShareButton.swift
+++ b/Examples/DemoApp/DemoApp/TestViews/RideShareButton.swift
@@ -37,7 +37,7 @@ struct RideShareButtonView_Previews: PreviewProvider {
// This should never show as a diff
.diffThreshold(1.0)
#if os(iOS)
- .emergeRenderingMode(.coreAnimation)
+ .snapshotRenderingMode(.coreAnimation)
#endif
RideShareButtonView(title: "Request Ride") {
@@ -55,8 +55,8 @@ struct RideShareButtonView_Previews: PreviewProvider {
.padding()
.previewDisplayName("Ride Share Button View - Light")
#if os(iOS)
- .emergeRenderingMode(.coreAnimation)
- .emergeAccessibility(true)
+ .snapshotRenderingMode(.coreAnimation)
+ .snapshotAccessibility(true)
#endif
}
}
diff --git a/Examples/DemoApp/DemoModule/ConversationMessageView.swift b/Examples/DemoApp/DemoModule/ConversationMessageView.swift
index 2c731d71..98a66c98 100644
--- a/Examples/DemoApp/DemoModule/ConversationMessageView.swift
+++ b/Examples/DemoApp/DemoModule/ConversationMessageView.swift
@@ -68,6 +68,9 @@ struct ConversationCellView_Previews: PreviewProvider {
extension ProcessInfo {
var isPreviews: Bool {
- self.environment["EMERGE_IS_RUNNING_FOR_SNAPSHOTS"] == "1" || self.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
+ environment["RUNNING_FOR_SNAPSHOTS"] == "1"
+ || environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
+ // Deprecated alias from the legacy Emerge integration.
+ || environment["EMERGE_IS_RUNNING_FOR_SNAPSHOTS"] == "1"
}
}
diff --git a/Examples/UnitTestMigration/README.md b/Examples/UnitTestMigration/README.md
index 89e8f219..53f94708 100644
--- a/Examples/UnitTestMigration/README.md
+++ b/Examples/UnitTestMigration/README.md
@@ -1,10 +1,10 @@
# Unit Test Migration
-This example shows how to migrate from swift-snapshot-testing to preview providers. The example app contains a unit test target `UnitTestMigrationTests` with one [snapshot test](https://github.com/EmergeTools/SnapshotPreviews-iOS/blob/main/Examples/UnitTestMigration/UnitTestMigrationTests/ExampleSnapshotTest.swift). It has been migrated by copying/pasting the file into the main app target as [ExampleSnapshotTest_Migrated.swift](https://github.com/EmergeTools/SnapshotPreviews-iOS/blob/main/Examples/UnitTestMigration/UnitTestMigration/ExampleSnapshotTest_Migrated.swift). The test is modified by removing the XCTest/SnapshotTesting importants, changing the base class to `SnapshotTest` and adding the conformance to `PreviewProvider`.
+This example shows how to migrate from swift-snapshot-testing to preview providers. The example app contains a unit test target `UnitTestMigrationTests` with one [snapshot test](https://github.com/EmergeTools/SnapshotPreviews/blob/main/Examples/UnitTestMigration/UnitTestMigrationTests/ExampleSnapshotTest.swift). It has been migrated by copying/pasting the file into the main app target as [ExampleSnapshotTest_Migrated.swift](https://github.com/EmergeTools/SnapshotPreviews/blob/main/Examples/UnitTestMigration/UnitTestMigration/ExampleSnapshotTest_Migrated.swift). The test is modified by removing the XCTest/SnapshotTesting importants, changing the base class to `SnapshotTest` and adding the conformance to `PreviewProvider`.
## Migrating your own tests
-1. Add the file [SnapshotTest.swift](https://github.com/EmergeTools/SnapshotPreviews-iOS/blob/main/Examples/UnitTestMigration/UnitTestMigration/SnapshotTest.swift) to your app, this does the heavy lifting of converting snapshot test function calls into previews.
+1. Add the file [SnapshotTest.swift](https://github.com/EmergeTools/SnapshotPreviews/blob/main/Examples/UnitTestMigration/UnitTestMigration/SnapshotTest.swift) to your app, this does the heavy lifting of converting snapshot test function calls into previews.
2. Copy your test file from the unit test target to your application.
3. Remove imports of `XCTest` and `SnapshotTesting`.
4. Change the base class from `XCTestCase` to `SnapshotTest` and add a conformce to `PreviewProvider`.
diff --git a/Examples/UnitTestMigration/UnitTestMigration.xcodeproj/project.pbxproj b/Examples/UnitTestMigration/UnitTestMigration.xcodeproj/project.pbxproj
index 13614e9d..8be43c76 100644
--- a/Examples/UnitTestMigration/UnitTestMigration.xcodeproj/project.pbxproj
+++ b/Examples/UnitTestMigration/UnitTestMigration.xcodeproj/project.pbxproj
@@ -168,7 +168,7 @@
minimizedProjectReferenceProxies = 1;
packageReferences = (
FAB3A5C12C9C705300BF8930 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
- FAB3A5C42C9C706100BF8930 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */,
+ FAB3A5C42C9C706100BF8930 /* XCRemoteSwiftPackageReference "SnapshotPreviews" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = FA8220082C9C5DB400DA1302 /* Products */;
@@ -480,9 +480,9 @@
minimumVersion = 1.17.5;
};
};
- FAB3A5C42C9C706100BF8930 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */ = {
+ FAB3A5C42C9C706100BF8930 /* XCRemoteSwiftPackageReference "SnapshotPreviews" */ = {
isa = XCRemoteSwiftPackageReference;
- repositoryURL = "https://github.com/EmergeTools/SnapshotPreviews-iOS";
+ repositoryURL = "https://github.com/EmergeTools/SnapshotPreviews";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.0.0;
@@ -502,7 +502,7 @@
};
FAB3A5C52C9C706100BF8930 /* PreviewGallery */ = {
isa = XCSwiftPackageProductDependency;
- package = FAB3A5C42C9C706100BF8930 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */;
+ package = FAB3A5C42C9C706100BF8930 /* XCRemoteSwiftPackageReference "SnapshotPreviews" */;
productName = PreviewGallery;
};
/* End XCSwiftPackageProductDependency section */
diff --git a/README.md b/README.md
index 47090d20..a5b35da9 100644
--- a/README.md
+++ b/README.md
@@ -2,71 +2,51 @@
[](https://swiftpackageindex.com/EmergeTools/SnapshotPreviews)
[](https://swiftpackageindex.com/EmergeTools/SnapshotPreviews)
-[](https://www.emergetools.com/app/example/ios/snapshotpreviews-ios.PreviewGallery/release?utm_campaign=badge-data)
-[](https://www.emergetools.com/app/example/ios/snapshotpreviews-ios.SnapshottingTests/release?utm_campaign=badge-data)
-An all-in-one snapshot testing solution built on Xcode previews. Automatic browsable gallery of previews, and no-code snapshot generation with XCTest. Supports SwiftUI and UIKit previews using `PreviewProvider` or `#Preview` and works on all Apple platforms (iOS/macOS/watchOS/tvOS/visionOS).
+Generate snapshot images from your Xcode previews with zero test code, and export them to disk for upload to [Sentry Snapshots](https://docs.sentry.io/product/snapshots/) or any other visual diffing service. Works with SwiftUI and UIKit previews using `PreviewProvider` or `#Preview`, on all Apple platforms (iOS / macOS / watchOS / tvOS / visionOS).
-- 🖼️ Browse previews on device as part of your app using the `PreviewGallery`, no Xcode required.
-- 📸 Snapshot Xcode previews automatically in a XCTest without writing any test code.
-- ♿ Run accessibility audits on all your previews in a XCUITest, still without writing any test code.
-
-# Features
+# Installation
-## Preview Gallery
+Add the package as a Swift Package Manager dependency using the repository URL:
-`PreviewGallery` is an interactive UI built on top of snapshot extraction. It turns your Xcode previews into a gallery of components and features you can access from your application, for example in an internal settings screen. **Xcode is not required to view the previews.** You can use it to preview individual components (buttons/rows/icons/etc) or even entire interactive features.
+```
+https://github.com/EmergeTools/SnapshotPreviews
+```
-
+
-The public API of PreviewGallery is a single SwiftUI `View` named `PreviewGallery`. Displaying this view gives you access to the full gallery. For example, you could add a button to open the gallery like this:
-
-```swift
-import SwiftUI
-import PreviewGallery
-
-struct InternalSettingsView: View {
- var body: some View {
- NavigationStack {
- Form {
- Section("Previews") {
- NavigationLink("Open Gallery") { PreviewGallery() }
- }
- }
- }
- .navigationTitle("Internal Settings")
- }
-}
-```
+Link your XCTest target to the `SnapshottingTests` product. If you also want to customize per-preview rendering (e.g. precision, layout) you can link `SnapshotPreferences` to your app target.
-## Local Snapshot Generation
+# Generating Snapshots
-Generate PNGs for each Xcode preview with no code as part of an XCTest. Link your XCTest target to `SnapshottingTests` and create a test that inherits from `SnapshotTest` like this:
+Create a test class that inherits from `SnapshotTest`. There are no test functions to write — they're added at runtime, one per discovered preview:
```swift
import SnapshottingTests
class DemoAppPreviewTest: SnapshotTest {
- // Return the type names of previews like "MyApp.MyView._Previews" to selectively render only some previews
+ // Optional: return preview type names like "MyApp.MyView_Previews" to render only a subset.
override class func snapshotPreviews() -> [String]? {
return nil
}
- // Use this to exclude some previews from generating
+ // Optional: exclude specific previews from rendering.
override class func excludedSnapshotPreviews() -> [String]? {
return nil
}
}
```
-Note that there are no test functions; they are automatically added at runtime by `SnapshotTest`. You can return a list of previews from the `snapshotPreviews()` function based on what preview you are trying to locally validate. The snapshots will be added as attachments in Xcode’s test results.
+By default each rendered preview is attached to the XCTest results bundle as a PNG. To write the rendered snapshots to disk locally, run `xcodebuild test` with `TEST_RUNNER_SNAPSHOTS_EXPORT_DIR` set and inspect the generated PNG and JSON files in that directory. For CI use, see [Exporting snapshots for Sentry](#exporting-snapshots-for-sentry) below.
+
+
-### Module-level filters
+## Filtering by module
-You can also filter previews by module name using exact string matching:
+If your app links several frameworks, you can scope discovery to specific modules:
```swift
// Only snapshot previews from these modules
@@ -77,64 +57,108 @@ override class func excludedSnapshotPreviewModules() -> [String]? { ["LegacyModu
```
> [!NOTE]
-> When you use Preview macros (`#Preview("Display Name")`) the name of the snapshot uses the file path and the name, for example: "MyModule/MyFile.swift:Display Name"
+> Preview macros (`#Preview("Display Name")`) produce snapshot names based on file path and display name, for example: `MyModule/MyFile.swift:Display Name`.
-
+# Uploading Snapshots to Sentry
-The [EmergeTools snapshot testing service](https://docs.emergetools.com/docs/snapshot-testing) generates snapshots and diffs them in the cloud to control for sources of flakiness, store images outside of git, and optimize test performance. `SnapshotTest` is for locally debugging these snapshot tests. You can also use `PreviewLayoutTest` to get code coverage of all previews in your unit test without generating PNGs. This will validate that previews do not crash (such as a missing @EnvironmentObject) but runs faster because it does not render the views to images.
+`SnapshotPreviews` is the recommended iOS feeder for [Sentry Snapshots](https://docs.sentry.io/product/snapshots/). The flow has two steps: `xcodebuild test` writes PNGs + JSON sidecars to a directory, then `sentry-cli build snapshots` uploads that directory.
-## Accessibility Audits
+
-Xcode [accessibility audits](https://developer.apple.com/documentation/xctest/xcuiapplication/4191487-performaccessibilityaudit) can also be run locally on any preview. They are run in a UI test (not unit test). To enable these, inherit from `AccessibilityPreviewTest`. To customize the behavior you can override the following functions in your test:
+## 1. Export the snapshots from your test run
-```swift
-import SnapshottingTests
-import Snapshotting
+Set `TEST_RUNNER_SNAPSHOTS_EXPORT_DIR` on the test invocation. When set, `SnapshotTest` writes images directly to that directory instead of attaching them to the `.xcresult` bundle.
-class DemoAppAccessibilityPreviewTest: AccessibilityPreviewTest {
+```bash
+TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="$PWD/snapshot-images" \
+xcodebuild test \
+ -scheme MyApp \
+ -sdk iphonesimulator \
+ -destination 'platform=iOS Simulator,name=iPhone 15 Pro'
+```
- override func auditType() -> XCUIAccessibilityAuditType {
- return .all
- }
+> [!NOTE]
+> The `TEST_RUNNER_` prefix is how Xcode forwards an environment variable from `xcodebuild` into the test runner process. Inside the runner the variable is read as `SNAPSHOTS_EXPORT_DIR`.
- override func handle(issue: XCUIAccessibilityAuditIssue) -> Bool {
- return false
- }
+For every rendered preview, two files are written:
+
+- **`.png`** — the rendered preview image.
+- **`.json`** — metadata sidecar used by Sentry Snapshots.
+
+The sidecar includes:
+
+| Field | Description |
+| --- | --- |
+| `display_name` | Snapshot name shown in Sentry. Generated from the preview name, file path, and module so exported filenames stay stable and unambiguous. |
+| `group` | Grouping key Sentry uses to organize related snapshots. Generated from the preview name, file path, and module. |
+| `diff_threshold` | Allowed visual difference for this snapshot. See details below. |
+| `context` | Supporting metadata such as test name, simulator info, orientation, color scheme, source line, and preview attributes. These fields are surfaced on the snapshot detail page in Sentry's UI. |
+
+Use the `.diffThreshold(...)` view modifier from the `SnapshotPreferences` product to customize the allowed visual difference for a specific preview. For example, `.diffThreshold(0.05)` allows up to a 5% difference for that snapshot.
+
+```swift
+import SnapshotPreferences
+
+#Preview("Map") {
+ MapPreview()
+ .diffThreshold(0.05)
}
```
-See the demo app for a full example.
+No Xcode code-coverage data (`.profraw` / `.profdata`) is written by the exporter — only the PNGs and sidecars. If you need code coverage from the same test run, enable it on the scheme as usual; coverage output goes to the `.xcresult` bundle independently.
-
- How does it work?
+## 2. Upload to Sentry
- The XCTest dynamically inserts test functions by creating functions using the Objective-C runtime and overriding XCTest’s `testInvocations` function.
+Choose one of the upload options below.
- Previews are discovered in the binary by parsing the `__swift5_proto` Mach-O section to see what types conform to `PreviewProvider` (and similar protocols generated by the #Preview macro). Details of how this works in the Swift runtime can be found in our [blog post](https://www.emergetools.com/blog/posts/SwiftProtocolConformance).
-
+#### Option A: `sentry-cli`
-# Installation
+Use [sentry-cli](https://docs.sentry.io/cli/installation/) 3.4.0 or later and point it at the export directory:
-Add the package dependency to your Xcode project using the URL of this repository (https://github.com/EmergeTools/SnapshotPreviews).
+```bash
+sentry-cli build snapshots "$PWD/snapshot-images" \
+ --auth-token "$SENTRY_AUTH_TOKEN" \
+ --app-id com.example.MyApp \
+ --project my-ios-project
+```
-
-
-
+A complete GitHub Actions step:
-Link your app to `PreviewGallery` and (optionally) to `SnapshotPreferences` to customize the behavior of snapshot generation.
-Link your XCTest target to `SnapshottingTests`.
+```yaml
+- name: Run snapshot tests
+ env:
+ TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: ${{ github.workspace }}/snapshot-images
+ run: |
+ xcodebuild test \
+ -scheme MyApp \
+ -sdk iphonesimulator \
+ -destination 'platform=iOS Simulator,name=iPhone 15 Pro'
+
+- name: Upload snapshots to Sentry
+ env:
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+ run: |
+ sentry-cli build snapshots "$GITHUB_WORKSPACE/snapshot-images" \
+ --app-id com.example.MyApp \
+ --project my-ios-project
+```
+
+#### Option B: Fastlane
+
+Use Sentry's Fastlane integration if your CI already uploads Apple artifacts through Fastlane. See Sentry's [iOS Snapshots setup docs](https://docs.sentry.io/platforms/apple/guides/ios/snapshots/#step-3-integrate-into-ci) for the Fastlane configuration.
+
+See Sentry's [CI integration docs](https://docs.sentry.io/product/snapshots/integrating-into-ci/) for sharding across simulators and base/head SHA pinning.
# Tips
-### Unique names
+## Unique display names
-It’s strongly encouraged to use a display name for every preview, for example:
+Give every preview a unique display name. This is what shows up in XCTest results and in the exported filenames / metadata:
```swift
struct MyView_Previews: PreviewProvider {
- var previews: some View {
+ static var previews: some View {
MyView().previewDisplayName("My Display Name")
- // Note if you had more than one view here they should all have different display names.
}
}
@@ -143,44 +167,48 @@ struct MyView_Previews: PreviewProvider {
}
```
-The display name will show up in XCTest results and the EmergeTools UI. Display names should be unique within each PreviewProvider or within files in the case of preview macros.
+Display names should be unique within each `PreviewProvider`, or within a file when using preview macros.
+
+## Snapshot best practices
+
+Snapshot previews should be deterministic. Avoid live network calls, timers, animations that do not settle, locale-dependent data, and dates generated from the current clock. Prefer fixed fixtures and mocked dependencies so the same preview renders the same pixels in Xcode, local test runs, and CI.
-### Environment variables
+## Detecting the snapshot environment
-It’s recommended to set the environment variable `XCODE_RUNNING_FOR_PREVIEWS` to `1` in your unit test scheme. This is also set when snapshots are generated from the EmergeTools snapshot testing service. Combine it with the Xcode previews variable like this:
+Set `XCODE_RUNNING_FOR_PREVIEWS=1` in your unit test scheme to mirror the variable Xcode sets when rendering live previews. You can then disable preview-unfriendly behavior (logging, analytics, network calls) with a single check:
```swift
extension ProcessInfo {
var isRunningPreviews: Bool {
- environment["EMERGE_IS_RUNNING_FOR_SNAPSHOTS"] == "1" || environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
+ environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
}
}
```
-Check `ProcessInfo.isRunningPeviews` to disable behavior you don’t want in previews such as emitting logging data.
+## Snapshot modifiers
-#### CI export for Sentry Snapshots
+Link the `SnapshotPreferences` product to your app target to customize individual previews before they are rendered by `SnapshotTest`.
-Set `TEST_RUNNER_SNAPSHOTS_EXPORT_DIR` to a directory path in your CI environment to export snapshot PNGs and JSON metadata sidecars to disk instead of attaching them to the XCTest results bundle. This is used by [Sentry Snapshots](https://docs.sentry.io/product/snapshots/) to upload images for visual diffing.
+| Modifier | Use it when | Effect on the snapshot |
+| --- | --- | --- |
+| `.snapshotAccessibility(true)` | You want an accessibility-focused variant. | On iOS, renders the snapshot through the accessibility overlay wrapper configured by your `SnapshotTest`, showing accessibility elements and labels instead of the plain view. The exported sidecar also records that accessibility was enabled. |
+| `.snapshotRenderingMode(.coreAnimation)` | The default renderer misses, flakes on, or incorrectly draws a view. | Changes the pixel capture backend. For example, `.coreAnimation` uses the layer tree, `.uiView` uses UIKit hierarchy drawing, and `.window` captures the full window. Different modes can affect blur/materials, maps, animations, and other renderer-sensitive content. |
+| `.snapshotExpansion(false)` | You want to preserve the visible scroll viewport instead of capturing all scroll content. | By default, scroll views are expanded so the snapshot includes their full content. Setting this to `false` keeps the scroll view at its normal visible height. |
-```yaml
-env:
- TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ github.workspace }}/snapshot-images"
-```
-
-### Variants
+## Variants
> [!TIP]
-> Using PreviewVariants greatly simplifies snapshot testing, by ensuring a consistent set of variants and that every view is provided a name.
+> `PreviewVariants` simplifies snapshot testing by ensuring a consistent set of variants and that every view has a name.
+
+Rendering the same view under multiple variants (dark mode, RTL, large text, accessibility) gives you broader coverage from a single preview. `SnapshotTest` renders every variant emitted by the preview, so each `previewVariant(named:)` below becomes its own snapshot image and sidecar. SwiftUI provides most variant inputs (`.dynamicTypeSize(.xxxLarge)`, `.environment(\.layoutDirection, .rightToLeft)`, etc.). The package adds `.snapshotAccessibility(true)`, which overlays VoiceOver elements on the snapshot.
-Using multiple variants of the same view can ensure test coverage of all the ways users interact with your UI. Most are provided by SwiftUI, eg: `.dynamicTypeSize(.xxxLarge)`. There is one built into the package: `.emergeAccessibility(true)`. This function adds a visualization of voice over elements to your snapshot. You can automatically add variants using the [`PreviewVariants` View](https://github.com/EmergeTools/SnapshotPreviews/blob/main/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift) that is demonstrated in the example app. It adds RTL, landscape, accessibility, dark mode and large text variants. You can use it like this:
+The [`PreviewVariants` view](https://github.com/EmergeTools/SnapshotPreviews/blob/main/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift) in the example app automates RTL, landscape, accessibility, dark mode, and large-text variants:
```swift
struct MyView_Previews: PreviewProvider {
static var previews: some View {
PreviewVariants(layout: .sizeThatFits) {
MyView(mode: .loaded)
- // PreviewVariants requires that every view has a name, so you can’t create one without a display name
.previewVariant(named: "My View - Loaded")
MyView(mode: .loading)
@@ -193,12 +221,73 @@ struct MyView_Previews: PreviewProvider {
}
```
-# Star History
+# Additional Features
-[](https://star-history.com/#EmergeTools/SnapshotPreviews&Date)
+## Preview rendering check (no PNGs)
+
+If you only want to verify that every preview lays out without crashing — for example, to catch a missing `@EnvironmentObject` — inherit from `PreviewLayoutTest` instead of `SnapshotTest`. It runs the same discovery pipeline but skips the image render, so it's significantly faster. This gives you *preview coverage* (every preview was exercised); it does not produce Xcode code-coverage data.
+
+## Preview Gallery
+
+`PreviewGallery` is an interactive SwiftUI view that turns your previews into a browsable gallery of components — useful for internal builds where Xcode isn't available. Link your app to the `PreviewGallery` product and present it from wherever makes sense:
+
+
+
+
+
+```swift
+import SwiftUI
+import PreviewGallery
+
+struct InternalSettingsView: View {
+ var body: some View {
+ NavigationStack {
+ Form {
+ Section("Previews") {
+ NavigationLink("Open Gallery") { PreviewGallery() }
+ }
+ }
+ }
+ .navigationTitle("Internal Settings")
+ }
+}
+```
+
+## Accessibility audits
+
+Xcode [accessibility audits](https://developer.apple.com/documentation/xctest/xcuiapplication/4191487-performaccessibilityaudit) can run on every preview as part of a UI test. Inherit from `AccessibilityPreviewTest` and override the audit type / issue handler as needed:
+
+```swift
+import SnapshottingTests
+import Snapshotting
+
+class DemoAppAccessibilityPreviewTest: AccessibilityPreviewTest {
+
+ override func auditType() -> XCUIAccessibilityAuditType {
+ return .all
+ }
+
+ override func handle(issue: XCUIAccessibilityAuditIssue) -> Bool {
+ return false
+ }
+}
+```
+
+See the demo app under `Examples/` for a full example.
+
+
+ How does it work?
+
+ The XCTest dynamically inserts test functions by creating methods through the Objective-C runtime and overriding XCTest's `testInvocations`.
+
+ Previews are discovered in the test binary by parsing the `__swift5_proto` Mach-O section to find types that conform to `PreviewProvider` (and the related protocols generated by the `#Preview` macro). Background on how this works in the Swift runtime: [The Surprising Cost of Protocol Conformances in Swift](https://www.emergetools.com/blog/posts/SwiftProtocolConformance).
+
# Related Reading
-- [How to use VariadicView, SwiftUI's Private View API](https://www.emergetools.com/blog/posts/how-to-use-variadic-view): VariadicView is a core part of how multiple images are rendered for one PreviewProvider.
-- [The Surprising Cost of Protocol Conformances in Swift](https://www.emergetools.com/blog/posts/SwiftProtocolConformance): Details of how protocol conformances work in the runtime, which is how previews are discovered in app binaries.
-- [Emerge Android](https://github.com/EmergeTools/emerge-android): The android SDK for similar preview based snapshot testing, along with other EmergeTools features.
-- [ETTrace](https://github.com/EmergeTools/ETTrace): Another open source iOS project from EmergeTools.
+
+- [How to use VariadicView, SwiftUI's Private View API](https://www.emergetools.com/blog/posts/how-to-use-variadic-view) — `VariadicView` is how multiple images are rendered for one `PreviewProvider`.
+- [The Surprising Cost of Protocol Conformances in Swift](https://www.emergetools.com/blog/posts/SwiftProtocolConformance) — how preview types are discovered in app binaries.
+
+# Star History
+
+[](https://star-history.com/#EmergeTools/SnapshotPreviews&Date)
diff --git a/Sources/SnapshotPreferences/AccessibiltyPreference.swift b/Sources/SnapshotPreferences/AccessibiltyPreference.swift
index 91917f88..84558338 100644
--- a/Sources/SnapshotPreferences/AccessibiltyPreference.swift
+++ b/Sources/SnapshotPreferences/AccessibiltyPreference.swift
@@ -19,15 +19,15 @@ struct AccessibilityPreferenceKey: PreferenceKey {
}
extension View {
- /// Applies accessibility support to the view's snapshot.
+ /// Overlays accessibility elements on the view's snapshot.
///
/// Use this method to control whether the snapshot should render with accessibility elements
/// highlighted as well as a corresponding legend for them.
///
/// - Note: This method is only available on iOS. It is unavailable on macOS, watchOS, visionOS, and tvOS.
///
- /// - Parameter enabled: A Boolean value that determines whether the emerge accessibility
- /// features are applied. If `nil`, the effect will default to `false`.
+ /// - Parameter enabled: A Boolean value that determines whether the accessibility overlay
+ /// is applied. If `nil`, the effect will default to `false`.
///
/// - Returns: A view with the accessibility preference applied.
///
@@ -36,7 +36,7 @@ extension View {
/// struct ContentView: View {
/// var body: some View {
/// Text("Accessible Content")
- /// .emergeAccessibility(true)
+ /// .snapshotAccessibility(true)
/// }
/// }
/// ```
@@ -44,7 +44,7 @@ extension View {
@available(watchOS, unavailable)
@available(visionOS, unavailable)
@available(tvOS, unavailable)
- public func emergeAccessibility(_ enabled: Bool?) -> some View {
+ public func snapshotAccessibility(_ enabled: Bool?) -> some View {
preference(key: AccessibilityPreferenceKey.self, value: enabled)
}
}
diff --git a/Sources/SnapshotPreferences/AppStoreSnapshotPreference.swift b/Sources/SnapshotPreferences/AppStoreSnapshotPreference.swift
index 599b49c2..ea9fd839 100644
--- a/Sources/SnapshotPreferences/AppStoreSnapshotPreference.swift
+++ b/Sources/SnapshotPreferences/AppStoreSnapshotPreference.swift
@@ -19,8 +19,8 @@ struct AppStoreSnapshotPreferenceKey: PreferenceKey {
}
extension View {
- /// Marks a snapshot for use with our App Store screenshot editing tool. This should ideally be used with a
- /// full-size preview matching one of our supported devices.
+ /// Marks a snapshot as an App Store screenshot. This should ideally be used with a
+ /// full-size preview matching one of the supported devices.
///
/// - Note: This method is only available on iOS. It is unavailable on macOS, watchOS, visionOS, and tvOS.
///
@@ -34,7 +34,7 @@ extension View {
/// struct ContentView: View {
/// var body: some View {
/// Text("My App Store listing!")
- /// .emergeAppStoreSnapshot(true)
+ /// .snapshotAppStore(true)
/// }
/// }
/// ```
@@ -42,7 +42,7 @@ extension View {
@available(watchOS, unavailable)
@available(visionOS, unavailable)
@available(tvOS, unavailable)
- public func emergeAppStoreSnapshot(_ enabled: Bool?) -> some View {
+ public func snapshotAppStore(_ enabled: Bool?) -> some View {
preference(key: AppStoreSnapshotPreferenceKey.self, value: enabled)
}
}
diff --git a/Sources/SnapshotPreferences/ExpansionPreference.swift b/Sources/SnapshotPreferences/ExpansionPreference.swift
index 571609a7..f79e31f6 100644
--- a/Sources/SnapshotPreferences/ExpansionPreference.swift
+++ b/Sources/SnapshotPreferences/ExpansionPreference.swift
@@ -19,13 +19,13 @@ struct ExpansionPreferenceKey: PreferenceKey {
}
extension View {
- /// Applies an expansion effect to the view's snapshot.
+ /// Controls scroll-view expansion when snapshotting the view.
///
- /// Use this method to control the emerge expansion effect on a view. When enabled,
- /// the view's first scrollview will be expanded to show all content in the snapshot.
+ /// When enabled, the view's first scrollview is expanded to show all of its content
+ /// in the snapshot instead of being clipped to the visible area.
///
- /// - Parameter enabled: A Boolean value that determines whether the emerge expansion
- /// effect is applied. If `nil`, the effect will default to `true`.
+ /// - Parameter enabled: A Boolean value that determines whether expansion is applied.
+ /// If `nil`, the effect will default to `true`.
///
/// - Returns: A view with the expansion preference applied.
///
@@ -33,12 +33,12 @@ extension View {
/// ```swift
/// struct ContentView: View {
/// var body: some View {
- /// Text("Hello, World!")
- /// .emergeExpansion(false)
+ /// ScrollView { ... }
+ /// .snapshotExpansion(false)
/// }
/// }
/// ```
- public func emergeExpansion(_ enabled: Bool?) -> some View {
+ public func snapshotExpansion(_ enabled: Bool?) -> some View {
preference(key: ExpansionPreferenceKey.self, value: enabled)
}
}
diff --git a/Sources/SnapshotPreferences/PrecisionPreference.swift b/Sources/SnapshotPreferences/PrecisionPreference.swift
index a0e1237e..6d51af2a 100644
--- a/Sources/SnapshotPreferences/PrecisionPreference.swift
+++ b/Sources/SnapshotPreferences/PrecisionPreference.swift
@@ -15,32 +15,3 @@ struct PrecisionPreferenceKey: PreferenceKey {
static var defaultValue: Float? = nil
}
-
-extension View {
- /// Sets the precision level for the snapshot on the view.
- ///
- /// Use this method to control the precision of the snapshot, which will be used for
- /// the comparison logic. With precision level 1.0, the images fully match. With precision
- /// level 0, the snapshot will never be flagged for having differences.
- ///
- /// - Parameter precision: A Float value representing the desired precision level for
- /// emerge snapshot operations. If `nil`, the value will default to 1.0.
- ///
- /// - Returns: A view with the snapshot precision preference applied.
- ///
- /// # Example
- /// ```swift
- /// struct ContentView: View {
- /// var body: some View {
- /// Image("sample")
- /// .emergeSnapshotPrecision(0.8)
- /// }
- /// }
- /// ```
- ///
- /// - Note: The actual impact of the precision value may vary depending on the
- /// specific implementation of the emerge snapshot feature.
- public func emergeSnapshotPrecision(_ precision: Float?) -> some View {
- preference(key: PrecisionPreferenceKey.self, value: precision)
- }
-}
diff --git a/Sources/SnapshotPreferences/RenderingModePreference.swift b/Sources/SnapshotPreferences/RenderingModePreference.swift
index bd9ca01d..f2f262fd 100644
--- a/Sources/SnapshotPreferences/RenderingModePreference.swift
+++ b/Sources/SnapshotPreferences/RenderingModePreference.swift
@@ -18,7 +18,7 @@ struct RenderingModePreferenceKey: PreferenceKey {
}
extension View {
- /// Sets the emerge rendering mode for the view.
+ /// Sets the rendering mode used to snapshot the view.
///
/// Use this method to control how the view is rendered for snapshots. You can indicate whether
/// to use `.coreAnimation` which will use the CALayer from Quartz or `.uiView` which will use
@@ -36,8 +36,8 @@ extension View {
/// ```swift
/// struct ContentView: View {
/// var body: some View {
- /// Text("Emerge Effect")
- /// .emergeRenderingMode(.coreAnimation)
+ /// MyView()
+ /// .snapshotRenderingMode(.coreAnimation)
/// }
/// }
/// ```
@@ -46,7 +46,7 @@ extension View {
@available(watchOS, unavailable)
@available(visionOS, unavailable)
@available(tvOS, unavailable)
- public func emergeRenderingMode(_ renderingMode: EmergeRenderingMode?) -> some View {
+ public func snapshotRenderingMode(_ renderingMode: EmergeRenderingMode?) -> some View {
preference(key: RenderingModePreferenceKey.self, value: renderingMode?.rawValue)
}
}
diff --git a/Sources/SnapshotSharedModels/RenderingMode.swift b/Sources/SnapshotSharedModels/RenderingMode.swift
index 1b72318e..28d11e8f 100644
--- a/Sources/SnapshotSharedModels/RenderingMode.swift
+++ b/Sources/SnapshotSharedModels/RenderingMode.swift
@@ -1,5 +1,5 @@
//
-// EmergeRenderingMode.swift
+// RenderingMode.swift
//
//
// Created by Noah Martin on 10/6/23.
@@ -8,10 +8,14 @@
import Foundation
-/// Specifies the rendering mode for the Emerge framework.
+/// Specifies the rendering mode used to capture a snapshot.
///
/// This enum defines different methods for rendering views,
/// allowing you to choose between Core Animation and UIView-based rendering.
+///
+/// - Note: The `Emerge` prefix is retained for source compatibility with apps built
+/// against earlier versions of this library. Renaming it would be a binary-incompatible
+/// change to `SnapshotPreviewsCore`'s public API.
public enum EmergeRenderingMode: Int {
/// Renders using `CALayer.render(in:)`.
case coreAnimation
diff --git a/Sources/SnapshottingTests/AccessibilityPreviewTest.swift b/Sources/SnapshottingTests/AccessibilityPreviewTest.swift
index 8ed3aa7f..000c4605 100644
--- a/Sources/SnapshottingTests/AccessibilityPreviewTest.swift
+++ b/Sources/SnapshottingTests/AccessibilityPreviewTest.swift
@@ -103,6 +103,8 @@ open class AccessibilityPreviewTest: PreviewBaseTest, PreviewFilters {
}
var launchEnvironment = app.launchEnvironment
+ launchEnvironment["RUNNING_FOR_SNAPSHOTS"] = "1"
+ // Deprecated alias kept for back-compat with apps that still read the old variable.
launchEnvironment["EMERGE_IS_RUNNING_FOR_SNAPSHOTS"] = "1"
launchEnvironment["DYLD_INSERT_LIBRARIES"] = path