home / skills / rshankras / claude-code-apple-skills / snapshot-test-setup
This skill helps you set up SwiftUI snapshot tests with swift-snapshot-testing, generating boilerplate and CI configuration for reliable UI regression checks.
npx playbooks add skill rshankras/claude-code-apple-skills --skill snapshot-test-setupReview the files below or copy the command above to add this skill to your agents.
---
name: snapshot-test-setup
description: Set up SwiftUI visual regression testing with swift-snapshot-testing. Generates snapshot test boilerplate and CI configuration. Use for UI regression prevention.
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
---
# Snapshot Test Setup
Generate SwiftUI snapshot/visual regression tests using Point-Free's swift-snapshot-testing library. Catches unintended UI changes by comparing rendered views against reference images.
## When This Skill Activates
Use this skill when the user:
- Wants "snapshot tests" or "visual regression tests"
- Says "I want to catch UI regressions"
- Asks about "screenshot testing" or "preview testing"
- Wants to verify SwiftUI views don't change unexpectedly
- Mentions "swift-snapshot-testing" or "Point-Free"
## Why Snapshot Tests
```
Without snapshots: With snapshots:
Change a modifier Change a modifier
→ Looks fine locally → Snapshot test fails
→ Push to main → Shows exact visual diff
→ User reports UI bug → Fix before merging
→ Embarrassing → Confidence in UI changes
```
## Pre-Setup Checks
### 1. Project Context
```
Glob: **/Package.swift or **/*.xcodeproj
Grep: "swift-snapshot-testing" (already added?)
Grep: "SnapshotTesting" in test files
```
### 2. Configuration Questions
Ask via AskUserQuestion:
1. **Package manager?**
- Swift Package Manager
- CocoaPods
- Tuist
2. **Platform?**
- iOS
- macOS
- Both
3. **What to test?**
- Specific views (user provides names)
- All screens
- Component library
## Setup Process
### Step 1: Add Dependency
#### Swift Package Manager
```swift
// Package.swift
dependencies: [
.package(
url: "https://github.com/pointfreeco/swift-snapshot-testing",
from: "1.17.0"
)
]
// Test target
.testTarget(
name: "YourAppTests",
dependencies: [
"YourApp",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
]
)
```
#### Xcode Project
1. File → Add Package Dependencies
2. URL: `https://github.com/pointfreeco/swift-snapshot-testing`
3. Add `SnapshotTesting` to your test target
### Step 2: Create Snapshot Test Base
```swift
import Testing
import SnapshotTesting
import SwiftUI
@testable import YourApp
// MARK: - Snapshot Configuration
enum SnapshotConfig {
// iOS devices to test
static let iPhoneConfigs: [String: ViewImageConfig] = [
"iPhone_SE": .iPhoneSe,
"iPhone_16": .iPhone13, // Similar dimensions
"iPhone_16_Pro_Max": .iPhone13ProMax
]
// macOS window sizes
static let macOSConfigs: [String: CGSize] = [
"compact": CGSize(width: 400, height: 600),
"regular": CGSize(width: 800, height: 600),
"wide": CGSize(width: 1200, height: 800)
]
// Color schemes to test
static let colorSchemes: [ColorScheme] = [.light, .dark]
}
```
### Step 3: Generate Snapshot Tests
#### iOS View Snapshot
```swift
@Suite("Snapshots: HomeView")
struct HomeViewSnapshotTests {
@Test("matches reference - light mode")
func lightMode() {
let view = HomeView(items: Item.sampleList)
assertSnapshot(
of: UIHostingController(rootView: view),
as: .image(on: .iPhone13)
)
}
@Test("matches reference - dark mode")
func darkMode() {
let view = HomeView(items: Item.sampleList)
.environment(\.colorScheme, .dark)
assertSnapshot(
of: UIHostingController(rootView: view),
as: .image(on: .iPhone13)
)
}
@Test("matches reference - empty state")
func emptyState() {
let view = HomeView(items: [])
assertSnapshot(
of: UIHostingController(rootView: view),
as: .image(on: .iPhone13)
)
}
@Test("matches reference - dynamic type XXL")
func dynamicTypeXXL() {
let view = HomeView(items: Item.sampleList)
.environment(\.sizeCategory, .accessibilityExtraExtraLarge)
assertSnapshot(
of: UIHostingController(rootView: view),
as: .image(on: .iPhone13)
)
}
}
```
#### macOS View Snapshot
```swift
@Suite("Snapshots: SettingsView")
struct SettingsViewSnapshotTests {
@Test("matches reference - standard size")
func standardSize() {
let view = SettingsView()
.frame(width: 500, height: 400)
assertSnapshot(
of: NSHostingController(rootView: view),
as: .image(size: CGSize(width: 500, height: 400))
)
}
@Test("matches reference - dark mode")
func darkMode() {
let view = SettingsView()
.frame(width: 500, height: 400)
.environment(\.colorScheme, .dark)
assertSnapshot(
of: NSHostingController(rootView: view),
as: .image(size: CGSize(width: 500, height: 400))
)
}
}
```
#### Component Snapshot (Reusable)
```swift
@Suite("Snapshots: ItemCard")
struct ItemCardSnapshotTests {
@Test("default state")
func defaultState() {
let view = ItemCard(item: .sample)
.frame(width: 300)
assertSnapshot(of: view, as: .image)
}
@Test("selected state")
func selectedState() {
let view = ItemCard(item: .sample, isSelected: true)
.frame(width: 300)
assertSnapshot(of: view, as: .image)
}
@Test("long title wraps")
func longTitle() {
let item = Item(title: "This is a very long title that should wrap to multiple lines")
let view = ItemCard(item: item)
.frame(width: 300)
assertSnapshot(of: view, as: .image)
}
}
```
### Step 4: Recording Reference Images
First run records reference images (golden masters):
```bash
# Record all snapshots (first run)
xcodebuild test -scheme YourApp \
-destination 'platform=iOS Simulator,name=iPhone 16'
```
**Important:** Reference images are stored in `__Snapshots__/` directories next to test files. Commit these to git.
```
Tests/SnapshotTests/
├── __Snapshots__/
│ └── HomeViewSnapshotTests/
│ ├── lightMode.1.png
│ ├── darkMode.1.png
│ ├── emptyState.1.png
│ └── dynamicTypeXXL.1.png
├── HomeViewSnapshotTests.swift
└── ItemCardSnapshotTests.swift
```
### Step 5: Re-record When Intentional Changes
When you intentionally change a view:
```swift
// Temporarily set record mode
@Test("matches reference - light mode")
func lightMode() {
withSnapshotTesting(record: .all) {
let view = HomeView(items: Item.sampleList)
assertSnapshot(
of: UIHostingController(rootView: view),
as: .image(on: .iPhone13)
)
}
}
```
Or use environment variable:
```bash
SNAPSHOT_TESTING_RECORD=all xcodebuild test -scheme YourApp
```
## What to Snapshot
### High Value (Always Snapshot)
- Screens/pages with multiple states (empty, loaded, error)
- Reusable components in all configurations
- Dark mode vs. light mode
- Dynamic type at standard and accessibility sizes
### Medium Value (Selectively Snapshot)
- Navigation flows (each step)
- Onboarding screens
- Paywall/subscription views
- Settings screens
### Low Value (Skip)
- Views that are 100% system components (plain List, NavigationStack)
- Views that change frequently during active development
- Views dependent on live data
## CI Integration
### GitHub Actions
```yaml
- name: Run Snapshot Tests
run: |
xcodebuild test \
-scheme YourApp \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=18.0' \
-only-testing "YourAppTests/Snapshots" \
-resultBundlePath TestResults.xcresult
- name: Upload Failed Snapshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: failed-snapshots
path: "**/Failures/**"
```
### Xcode Cloud
```bash
# ci_scripts/ci_post_xcodebuild.sh
if [ "$CI_XCODEBUILD_ACTION" = "test" ]; then
# Upload snapshot failures as artifacts
if [ -d "$CI_DERIVED_DATA_PATH" ]; then
find "$CI_DERIVED_DATA_PATH" -name "Failures" -type d \
-exec cp -r {} "$CI_RESULT_BUNDLE_PATH/" \;
fi
fi
```
## Output Format
```markdown
## Snapshot Tests Setup
### Dependency Added
swift-snapshot-testing 1.17.0 via SPM
### Tests Generated
| View | Configurations | Tests |
|------|---------------|-------|
| HomeView | light, dark, empty, XXL type | 4 |
| SettingsView | light, dark | 2 |
| ItemCard | default, selected, long title | 3 |
| **Total** | | **9** |
### Files Created
- `Tests/SnapshotTests/HomeViewSnapshotTests.swift`
- `Tests/SnapshotTests/SettingsViewSnapshotTests.swift`
- `Tests/SnapshotTests/ItemCardSnapshotTests.swift`
### Next Steps
1. Run tests once to record reference images
2. Commit `__Snapshots__/` directories to git
3. Add snapshot test step to CI pipeline
```
## References
- [swift-snapshot-testing](https://github.com/pointfreeco/swift-snapshot-testing)
- `generators/test-generator/` — for unit/integration test generation
- `testing/tdd-feature/` — for TDD workflow with UI features
This skill sets up SwiftUI visual regression (snapshot) testing using Point-Free’s swift-snapshot-testing. It generates test boilerplate, configures device and color-scheme matrices, and adds CI steps to run and surface snapshot failures. Use it to prevent unintended UI changes and keep visual regressions visible in CI.
The skill adds the SnapshotTesting dependency to your test target (SPM, CocoaPods, or Tuist), creates reusable snapshot configuration and test suites for views and components, and scaffolds recording and re-recording flows. It also generates CI steps to run tests on simulators and upload failure artifacts so diffs are accessible when tests fail.
How do I record reference images the first time?
Run tests locally with xcodebuild test targeting a simulator. The first run records reference images into __Snapshots__ folders next to the test files—commit those to git.
How do I update snapshots when a change is intentional?
Run tests in record mode (set SNAPSHOT_TESTING_RECORD=all or use the provided record wrapper in the test) to overwrite reference images, then verify and commit the new snapshots.