Mask unstable content in snapshots with snapshotMasked() - #171
Open
BarredEwe wants to merge 1 commit into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
Applying the modifier to a container masks its whole subtree with a single plate, which also covers the "mask the entire preview" case.
Implementation
SnapshotMaskModifierreads an internal environment valueisPrefireSnapshotRendering. While rendering a snapshot it drawscontent.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, noredactedheuristics.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.PlaybookViewrendersPreviewModel.contentdirectly and never touchesPrefireSnapshot, 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 outsidePrefireSnapshotstill renders its real content.make test56/56,make test-cli22/22,make buildclean.