diff --git a/docs/platforms/apple/guides/ios/snapshots/index.mdx b/docs/platforms/apple/guides/ios/snapshots/index.mdx new file mode 100644 index 0000000000000..81244ee7577c2 --- /dev/null +++ b/docs/platforms/apple/guides/ios/snapshots/index.mdx @@ -0,0 +1,77 @@ +--- +title: Snapshots +sidebar_order: 4800 +sidebar_section: features +description: Catch unintended visual changes in your iOS app before they reach users. +beta: true +--- + +Snapshots helps you catch unintended visual changes in your Apple apps before they reach users. Below are instructions for common approaches for Snapshotting on Apple Platforms. Additional docs Include: + +- [Uploading Snapshots](/product/snapshots/uploading-snapshots/) - Platform-agnostic details on how to upload snapshots to Sentry +- [Reviewing Snapshots](/product/snapshots/reviewing-snapshots/) - How to review and approve snapshots in Sentry + +## How It Works + +1. Add a snapshot testing library to your project +2. Generate images in ci +3. Upload images to Sentry +4. Sentry posts results to your PR + +## Generating Snapshot Images + +You can use any snapshot testing library that produces image files. We have guides for two popular approaches: + +- **[Using SnapshotPreviews](./using-snapshotpreviews/)** (Recommended) — Automatically discovers and renders your Previews as snapshots, with built-in CI export support and metadata. +- **[Using swift-snapshot-testing](./using-swift-snapshot-testing/)** — A popular library from [Point-Free](https://www.pointfree.co/) for snapshot testing any view or view controller. + +Any other tool that outputs PNGs or JPEGs also works: see [Upload Structure](/product/snapshots/uploading-snapshots/#upload-structure) for the generic instructions. + +## Uploading to Sentry + +You can upload snapshots with either `sentry-cli` or the [Sentry Fastlane plugin](https://github.com/getsentry/sentry-fastlane-plugin). See [uploading snapshots](/product/snapshots/uploading-snapshots/) for sentry-cli details. + +### Using Fastlane + +1. Install the [Sentry Fastlane plugin](https://github.com/getsentry/sentry-fastlane-plugin): + + ```ruby + bundle exec fastlane add_plugin fastlane-plugin-sentry + ``` + +2. Add an upload lane to your `Fastfile`: + + ```ruby + desc 'Upload snapshots to Sentry' + lane :upload_snapshots do + sentry_upload_snapshots( + path: 'snapshot-images', + app_id: 'com.example.your-app', + auth_token: ENV['SENTRY_AUTH_TOKEN'], + org_slug: 'your-org', + project_slug: 'your-project' + ) + end + ``` + + Adjust `path` based on where your snapshot library writes its output. For swift-snapshot-testing, this would be `YourAppTests/__Snapshots__/YourSnapshotTests`. + +3. Call the lane after your test step in CI: + + ```yaml + - name: Upload snapshots to Sentry + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: bundle exec fastlane ios upload_snapshots + ``` + +## Best Practices + +- **Pin your simulator device.** Use the same simulator model and OS version across CI runs to avoid spurious diffs caused by rendering differences. +- **Keep snapshot tests isolated.** Use a dedicated test class or test plan for snapshots so you can run them independently with `only-testing`. + +## misc todos + +- maybe move this into "common"? unsure +- add some pics where relevant? +- some blurb that you can use multiple libraries diff --git a/docs/platforms/apple/guides/ios/snapshots/using-snapshotpreviews/index.mdx b/docs/platforms/apple/guides/ios/snapshots/using-snapshotpreviews/index.mdx new file mode 100644 index 0000000000000..43edbfc772969 --- /dev/null +++ b/docs/platforms/apple/guides/ios/snapshots/using-snapshotpreviews/index.mdx @@ -0,0 +1,130 @@ +--- +title: Using SnapshotPreviews +sidebar_order: 4810 +description: Generate snapshot images from SwiftUI Previews and upload them to Sentry. +--- + +[SnapshotPreviews](https://github.com/EmergeTools/SnapshotPreviews) automatically discovers Previews in your app and renders them as snapshot images. You don't have to maintain any explicit test code to produce snapshot images. SnapshotPreviews includes a CI export mode that writes PNG files and JSON metadata sidecars directly to a directory you specify, ready for upload to Sentry. + +## Generating Images + +### 1. Add the Dependency + +Add the `SnapshotPreviews` package (v0.12.0 or later) to your project. In your `Package.swift` or via Xcode's package manager: + +``` +https://github.com/EmergeTools/SnapshotPreviews +``` + +Link the `SnapshottingTests` library to your **test target**: + +```swift +// In Package.swift +.testTarget( + name: "YourAppTests", + dependencies: [ + .product(name: "SnapshottingTests", package: "SnapshotPreviews"), + ] +) +``` + +Or in Xcode, add `SnapshottingTests` under your test target's **Frameworks and Libraries**. + +### 2. Create a Snapshot Test + +Create a new test file that subclasses `SnapshotTest`: + +```swift +import SnapshottingTests + +class YourAppSnapshotTest: SnapshotTest { + override class func snapshotPreviews() -> [String]? { + // Return nil to snapshot all previews, + // or return an array of preview names to include. + // Regex patterns are also supported. + return nil + } + + override class func excludedSnapshotPreviews() -> [String]? { + // Return an array of preview names to exclude, or nil. + return nil + } +} +``` + +`SnapshotTest` automatically discovers all SwiftUI `#Preview` and `PreviewProvider` declarations in your app and test targets, renders each one, and generates a snapshot image. + +### 3. Enable CI Export + +Set the `TEST_RUNNER_SNAPSHOTS_EXPORT_DIR` environment variable in your CI environment to specify where snapshot images should be written: + +```bash +export TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="$PWD/snapshot-images" +``` + +When this variable is set, SnapshotPreviews writes PNG images and JSON metadata sidecars to the specified directory. + +When running locally without this variable, snapshots are attached to the XCTest results — useful for development but not for Sentry uploads. + +### 4. Run the Tests + +```bash +xcodebuild test \ + -scheme YourScheme \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -only-testing:YourAppTests/YourAppSnapshotTest \ + CODE_SIGNING_ALLOWED=NO +``` + +After tests complete, the export directory contains your snapshot images ready for upload. The SnapshotPreviews library automatically uses (TODO: WHAT GETS PULLED INTO THE METADATA??). + +## Integrating into CI + +To see a live example you can refer to this repo (TODO: HACKERNEWS LINK MAYBE WE HAVE THIS HIGHER AS WELL). Below is a sample Github Action workflow for uploading snapshots to Sentry: + +```yaml {filename:.github/workflows/snapshots.yml} +name: iOS Snapshots + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + snapshots: + runs-on: macos-15 + + env: + TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ github.workspace }}/snapshot-images" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Boot simulator + run: xcrun simctl boot "iPhone 17 Pro" || true + + - name: Generate snapshots + run: | + set -o pipefail && xcodebuild test \ + -scheme YourScheme \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ + -only-testing:YourAppTests/YourAppSnapshotTest \ + CODE_SIGNING_ALLOWED=NO \ + | xcpretty + + - name: Upload snapshots to Sentry + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: | + sentry-cli build snapshots --app-id com.example.your-app snapshot-images +``` + +You can also upload with Fastlane — see [Uploading to Sentry](../#uploading-to-sentry). + +## MISC TODOS + +- i think we may need to redo the readme on the repo to be more supplementary + more complete reference. i.e. i've sent https://github.com/EmergeTools/SnapshotPreviews/blob/main/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift to people frequently, but now that's just a hidden example diff --git a/docs/platforms/apple/guides/ios/snapshots/using-swift-snapshot-testing/index.mdx b/docs/platforms/apple/guides/ios/snapshots/using-swift-snapshot-testing/index.mdx new file mode 100644 index 0000000000000..e0286ed503483 --- /dev/null +++ b/docs/platforms/apple/guides/ios/snapshots/using-swift-snapshot-testing/index.mdx @@ -0,0 +1,139 @@ +--- +title: Using swift-snapshot-testing +sidebar_order: 4820 +description: Generate snapshot images with Point-Free's swift-snapshot-testing and upload them to Sentry. +--- + +[swift-snapshot-testing](https://github.com/pointfreeco/swift-snapshot-testing) is a popular open-source library from Point-Free that supports snapshot testing for SwiftUI views, UIKit views, and view controllers. Refer to the [repo](https://github.com/pointfreeco/swift-snapshot-testing) for full documentation. + +## Generating Images + +### 1. Add the Dependency + +Add the `swift-snapshot-testing` package to your project: + +``` +https://github.com/pointfreeco/swift-snapshot-testing +``` + +Link `SnapshotTesting` to your test target. + +### 2. Write Snapshot Tests + +Create test cases using `assertSnapshot`: + +```swift +import SnapshotTesting +import SwiftUI +import XCTest + +@testable import YourApp + +final class YourSnapshotTests: XCTestCase { + + @MainActor + func testHomeScreen() { + let view = HomeScreen() + let hostingController = UIHostingController(rootView: view) + hostingController.view.frame = UIScreen.main.bounds + + assertSnapshot( + of: hostingController, + as: .image(on: .iPhone13Pro), + named: "HomeScreen-iPhone13Pro" + ) + + assertSnapshot( + of: hostingController, + as: .image( + on: .iPhone13Pro, + traits: .init(userInterfaceStyle: .dark) + ), + named: "HomeScreen-iPhone13Pro-DarkMode" + ) + } +} +``` + +Snapshot images are saved to a `__Snapshots__` directory next to your test file, organized by test class name: + +``` +YourAppTests/__Snapshots__/YourSnapshotTests/ +``` + +This is the directory you'll upload to Sentry. + +### 3. Configure Recording for CI + +By default, swift-snapshot-testing compares against existing reference images and fails if they don't match. For Sentry, you want CI to always generate fresh images for upload. + +Set the `TEST_RUNNER_SNAPSHOT_TESTING_RECORD` environment variable to `all` in your CI environment: + +```bash +export TEST_RUNNER_SNAPSHOT_TESTING_RECORD=all +``` + +This tells the library to record all snapshots. Locally (without this variable), tests default to comparison mode — so developers can still use reference-based diffing during development. + +> **Note:** In record mode, swift-snapshot-testing reports every test as a "failure" since it's recording rather than comparing. Use `continue-on-error: true` on your CI test step to ensure the upload step still runs. + +### 4. Run the Tests + +```bash +xcodebuild test \ + -scheme YourScheme \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -only-testing:YourAppTests/YourSnapshotTests \ + CODE_SIGNING_ALLOWED=NO +``` + +After tests complete, the `__Snapshots__/YourSnapshotTests/` directory contains your snapshot images ready for upload. You can optionally add [JSON metadata](/product/snapshots/uploading-snapshots/#json-metadata) alongside the image files to supplement how the image is displayed in Sentry. + +## Integrating into CI + +Sample Github Action workflow for uploading snapshots to Sentry: + +```yaml {filename:.github/workflows/snapshots.yml} +name: iOS Snapshots + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + snapshots: + runs-on: macos-15 + + env: + TEST_RUNNER_SNAPSHOT_TESTING_RECORD: all + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Boot simulator + run: xcrun simctl boot "iPhone 17 Pro" || true + + - name: Generate snapshots + continue-on-error: true + run: | + set -o pipefail && xcodebuild test \ + -scheme YourScheme \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ + -only-testing:YourAppTests/YourSnapshotTests \ + CODE_SIGNING_ALLOWED=NO \ + | xcpretty + + - name: Upload snapshots to Sentry + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + APP_ID: com.example.your-app + run: | + sentry-cli build snapshots --app-id $APP_ID YourAppTests/__Snapshots__/YourSnapshotTests +``` + +You can also upload with Fastlane — see [Uploading to Sentry](../#uploading-to-sentry).