| name | snapshot-testing |
| description | Expert guidance on snapshot / golden image testing across mobile stacks — Paparazzi (Android), swift-snapshot-testing (iOS), golden_toolkit (Flutter), and RN snapshot tests. Use when asked to lock down UI visuals or catch unintended rendering regressions. |
Snapshot Testing
Instructions
Snapshot tests render a UI component to a file (image or serialized tree) and fail if the next render differs. They excel at catching unintended visual regressions. They fail badly when used as a substitute for logic tests or when goldens are mass-regenerated without review.
1. Host-Rendered vs Device-Rendered
Always prefer host-rendered snapshot libraries — they run on the JVM / host Swift / host Dart and produce deterministic images without emulator skew.
- Android — Paparazzi (JVM) or Roborazzi (Robolectric + rendering). Avoid screenshotting via instrumentation unless you need a true-device render for a specific bug.
- iOS — swift-snapshot-testing runs on simulator but can be pinned to a specific simulator model/OS to stay deterministic.
- Flutter —
flutter test --update-goldens with matchesGoldenFile, optionally via golden_toolkit or alchemist for multi-config runs. Renders off-device on host.
- React Native —
react-test-renderer + jest's toMatchSnapshot for serialized trees; for pixel images use @storybook/test-runner + Chromatic or react-native-owl (device-based, slower).
2. Android — Paparazzi (Compose)
class CheckoutButtonScreenshot {
@get:Rule val paparazzi = Paparazzi(
deviceConfig = DeviceConfig.PIXEL_5,
theme = "android:Theme.Material.Light.NoActionBar",
)
@Test fun default() = paparazzi.snapshot {
AppTheme { CheckoutButton(state = Idle, onClick = {}) }
}
@Test fun loading() = paparazzi.snapshot {
AppTheme { CheckoutButton(state = Loading, onClick = {}) }
}
}
Goldens land under src/test/snapshots/images/. Commit them. Review diffs as code.
3. iOS — swift-snapshot-testing
final class CheckoutButtonSnapshotTests: XCTestCase {
func test_default_lightMode() {
let view = CheckoutButtonView(state: .idle)
assertSnapshot(of: view, as: .image(traits: .init(userInterfaceStyle: .light)))
}
func test_default_darkMode() {
let view = CheckoutButtonView(state: .idle)
assertSnapshot(of: view, as: .image(traits: .init(userInterfaceStyle: .dark)))
}
}
Pin the simulator in CI (platform=iOS Simulator,name=iPhone 15,OS=17.5); different iOS versions render font metrics differently.
4. Flutter — goldens with golden_toolkit
void main() {
testGoldens('CheckoutButton states', (tester) async {
final builder = DeviceBuilder()
..overrideDevicesForAllScenarios(devices: [Device.phone])
..addScenario(widget: const CheckoutButton(state: ButtonState.idle), name: 'idle')
..addScenario(widget: const CheckoutButton(state: ButtonState.loading), name: 'loading');
await tester.pumpDeviceBuilder(builder);
await screenMatchesGolden(tester, 'checkout_button_states');
});
}
Run flutter test --update-goldens locally to regenerate. In CI, run without the flag and fail on diff.
5. React Native — Component Tree Snapshot
import { render } from '@testing-library/react-native';
test('CheckoutButton renders in idle state', () => {
const { toJSON } = render(<CheckoutButton state="idle" />);
expect(toJSON()).toMatchSnapshot();
});
Tree snapshots catch structural drift. For pixel-accurate RN visuals, use Storybook + Chromatic or an image-diff tool; toMatchSnapshot alone will not catch style regressions.
6. Determinism Rules
- Pin the theme, locale, and density. Render both light and dark explicitly.
- Pin fonts. Embed the app's fonts in the test configuration; never rely on a system font the CI image may update.
- Freeze animation and time. Use
pump() / waitForIdle() / explicit animation-skipping.
- Avoid network and images. Inject a fake image loader that produces a deterministic placeholder.
7. Reviewing Golden Diffs
- PRs that touch goldens must show the image diff in review (most CI UIs render PNGs).
- Do not click "update all" without inspecting each diff.
- If a platform upgrade (e.g., new iOS) changes many goldens, update them in a separate PR tagged
chore(goldens) so logic changes do not hide behind visual churn.
8. What Snapshot Testing Does NOT Cover
- Accessibility — use accessibility tests.
- Behavior — a button can render correctly and still navigate nowhere.
- Data correctness — a list can render beautifully with the wrong items.
9. Checklist