Skip to content

feat(core): Freeze UIActivityIndicatorView spinners during snapshots - #279

Merged
NicoHinderling merged 4 commits into
mainfrom
feat/freeze-activity-indicator-spinners
Sep 15, 2026
Merged

NicoHinderling merged 4 commits into
mainfrom
feat/freeze-activity-indicator-spinners

Conversation

@NicoHinderling

@NicoHinderling NicoHinderling commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Disabling UIView and SwiftUI animations does not stop UIActivityIndicatorView, which drives its own layer animation. Indeterminate spinners, including the default SwiftUI ProgressView, get captured mid-rotation and produce flaky diffs.

This swizzles startAnimating to a no-op from RenderingStrategy.setup(), so every rendering path gets it. Since hidesWhenStopped defaults to true, indicators are hidden rather than frozen at a fixed frame, which matches what the hosted runner produced and keeps existing baselines valid. iOS and tvOS only; AppKit has no equivalent view and watchOS has no activity indicator.

Opt-out: set SNAPSHOTS_DISABLE_FREEZE_SPINNERS=1 in the test environment to keep spinners animating. Existing baselines that currently show a spinner will change once on upgrade, so this flag is the escape hatch for anyone who needs the old output.

Verified end to end with a new ModuleA Loading Indicator preview in MultiModuleDemo, which CI now renders and uploads. Locally, two runs with freezing on produced byte-identical images with no spinner, and two runs with the opt-out set rendered the spinner. Also covered by unit tests for the swizzle and the opt-out, the full package suite on iOS simulator, and builds of all schemes on macOS, Mac Catalyst, iOS, and watchOS.

Disabling UIView and SwiftUI animations does not stop UIActivityIndicatorView,
which drives its own layer animation. Indeterminate spinners, including the
default SwiftUI ProgressView, were captured mid-rotation and produced flaky
diffs.

Swizzle startAnimating to a no-op from RenderingStrategy.setup so every
rendering path skips spinners. Because hidesWhenStopped defaults to true, the
indicator is hidden rather than frozen at a fixed frame. This matches the
behavior the hosted Emerge runner previously injected outside the library.
Comment thread Sources/SnapshotPreviewsCore/ActivityIndicatorFreezing.swift
@sentry

sentry Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Sentry Snapshot Testing

Name Added Removed Changed Renamed Unchanged Skipped Status
com.example.multimoduledemo
com.example.multimoduledemo
- - - - - - ✅ Approved (comparison failed)

⚙️ snapshot-previews Snapshot Settings

Set EMERGE_DISABLE_FREEZE_SPINNERS=1 to keep UIActivityIndicatorView animating,
mirroring the existing EMERGE_DISABLE_FIX_TIME opt-out for the clock hook.

watchOS ships UIKit without UIActivityIndicatorView, so guard the swizzle the
same way other UIKit-only code in this target does. Mark the replacement
method dynamic to follow the standard swizzling idiom.
Add a ModuleA preview containing an indeterminate ProgressView so the CI
snapshot upload renders a spinner case. With spinner freezing in place the
image contains only the label, and any regression shows up as a diff.
Comment thread Sources/SnapshotPreviewsCore/ActivityIndicatorFreezing.swift
Replace company-branded names in the new spinner opt-out and swizzled
selector. Keep the Objective-C hook product-specific to avoid collisions,
and update the environment-variable test and documentation together.
let original = class_getInstanceMethod(UIActivityIndicatorView.self, #selector(UIActivityIndicatorView.startAnimating)),
let replacement = class_getInstanceMethod(UIActivityIndicatorView.self, #selector(UIActivityIndicatorView.snapshotPreviews_frozenStartAnimating))
else { return }
method_exchangeImplementations(original, replacement)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you all tried this in the past, but could/should we just disable core animation 'actions' around the point where the snapshots are rendered? Or does that not work for some reason?

@NicoHinderling NicoHinderling Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @noahsmartin

(not going to treat this as a blocker btw)

@NicoHinderling
NicoHinderling merged commit 9327570 into main Sep 15, 2026
28 of 29 checks passed
@NicoHinderling
NicoHinderling deleted the feat/freeze-activity-indicator-spinners branch September 15, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants