Skip to content

Mask unstable content in snapshots with snapshotMasked() - #171

Open
BarredEwe wants to merge 1 commit into
mainfrom
feature/snapshot-masking
Open

Mask unstable content in snapshots with snapshotMasked()#171
BarredEwe wants to merge 1 commit into
mainfrom
feature/snapshot-masking

Conversation

@BarredEwe

Copy link
Copy Markdown
Owner

Dynamic content — dates, timers, remote avatars, random data — makes snapshot tests flaky, and teams end up disabling them. This adds a way to replace such areas with a fixed plate before comparison.

Text(Date.now.formatted()).snapshotMasked()
AvatarView(url: url).snapshotMasked(color: .prefireSnapshotMask)

Applying the modifier to a container masks its whole subtree with a single plate, which also covers the "mask the entire preview" case.

Implementation

SnapshotMaskModifier reads an internal environment value isPrefireSnapshotRendering. While rendering a snapshot it draws content.hidden().overlay(color).hidden() keeps the view in the layout pass, so its size and the surrounding layout are unchanged and the overlay fills exactly that frame. No image post-processing, no redacted heuristics.

The environment value is set in PrefireSnapshot.loadViewWithPreferences() — the single choke point both platform paths go through, so iOS/tvOS and macOS are covered by one line. PlaybookView renders PreviewModel.content directly and never touches PrefireSnapshot, so the Playbook keeps real content. Outside a snapshot the modifier returns the content untouched, so it stays a no-op in the Xcode canvas.

No generator or config changes: masking is purely a runtime concern.

Tests

Five pixel-level tests in MacOSSnapshotTests: masked views with different text produce byte-identical PNGs; the same views unmasked differ (the control proving rendering captures content); the masked area's center pixel matches the plate color; masked and unmasked hosts have identical layout size; and the same masked view hosted outside PrefireSnapshot still renders its real content.

make test 56/56, make test-cli 22/22, make build clean.

Dynamic content — dates, timers, remote images, random data — makes snapshot
tests flaky. `snapshotMasked()` hides the View and draws an opaque placeholder
in its place while a snapshot is rendered: the View is still laid out, so its
size and the surrounding layout stay the same.

Masking is driven by an environment value turned on only in
`PrefireSnapshot.loadViewWithPreferences()`, so it applies on iOS, tvOS and
macOS alike, and is a no-op in Xcode Canvas, in the Playbook and in the app.
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.

1 participant