P3.1: iOS goldens @2x + PNG optimizer + Makefile record/verify targets
- SnapshotGalleryTests rendered at displayScale: 2.0 (was native 3.0)
→ 49MB → 15MB (~69% reduction)
- Records via SNAPSHOT_TESTING_RECORD=1 env var (no code edits needed)
- scripts/optimize_goldens.sh runs zopflipng (or pngcrush fallback)
over both iOS and Android golden dirs
- scripts/{record,verify}_snapshots.sh one-command wrappers
- Makefile targets: make {record,verify,optimize}-snapshots
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
81
scripts/optimize_goldens.sh
Executable file
81
scripts/optimize_goldens.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# optimize_goldens.sh — recursively optimize PNG goldens in-place.
|
||||
#
|
||||
# Runs after each `record` pass for both iOS and Android parity galleries.
|
||||
# Removes unnecessary PNG chunks (textual metadata, ancillary palette
|
||||
# entries) and re-encodes the image with a better DEFLATE strategy so the
|
||||
# image bytes on disk drop by 15–40% without touching a single pixel.
|
||||
#
|
||||
# Dependencies
|
||||
# ------------
|
||||
# zopflipng (preferred — brute-force DEFLATE, best compression)
|
||||
# pngcrush (fallback — faster, smaller savings)
|
||||
#
|
||||
# Install on macOS:
|
||||
# brew install zopfli pngcrush
|
||||
#
|
||||
# The script never fails if the tools are missing: it warns and exits 0,
|
||||
# leaving the goldens untouched. CI's size-gate will still fail loudly if
|
||||
# the PNGs would bust the 150 KB budget.
|
||||
#
|
||||
# Usage
|
||||
# -----
|
||||
# ./scripts/optimize_goldens.sh # default dirs (iOS + Android)
|
||||
# ./scripts/optimize_goldens.sh path1 path2 # specific dirs only
|
||||
#
|
||||
# Idempotent — re-running on already-optimized PNGs is a no-op.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
DIRS=("$@")
|
||||
if [ ${#DIRS[@]} -eq 0 ]; then
|
||||
DIRS=(
|
||||
"iosApp/HoneyDueTests/__Snapshots__"
|
||||
"composeApp/src/androidUnitTest/roborazzi"
|
||||
)
|
||||
fi
|
||||
|
||||
tool=""
|
||||
if command -v zopflipng >/dev/null 2>&1; then
|
||||
tool="zopflipng"
|
||||
elif command -v pngcrush >/dev/null 2>&1; then
|
||||
tool="pngcrush"
|
||||
else
|
||||
echo "WARNING: neither zopflipng nor pngcrush is installed — skipping PNG optimization."
|
||||
echo " Install with: brew install zopfli pngcrush"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "optimize_goldens: using ${tool}"
|
||||
|
||||
count=0
|
||||
saved=0
|
||||
for dir in "${DIRS[@]}"; do
|
||||
if [ ! -d "$dir" ]; then
|
||||
continue
|
||||
fi
|
||||
while IFS= read -r -d '' png; do
|
||||
before=$(stat -f%z "$png" 2>/dev/null || stat -c%s "$png")
|
||||
if [ "$tool" = "zopflipng" ]; then
|
||||
# -y : overwrite without prompt
|
||||
# --lossy_transparent : allow color rewrite under alpha=0 for extra savings
|
||||
zopflipng -y --lossy_transparent "$png" "$png" >/dev/null 2>&1 || true
|
||||
else
|
||||
# -ow : overwrite-in-place; -q : quiet
|
||||
pngcrush -q -ow "$png" >/dev/null 2>&1 || true
|
||||
fi
|
||||
after=$(stat -f%z "$png" 2>/dev/null || stat -c%s "$png")
|
||||
saved=$((saved + before - after))
|
||||
count=$((count + 1))
|
||||
done < <(find "$dir" -name '*.png' -print0)
|
||||
done
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
echo "optimize_goldens: no PNGs found in: ${DIRS[*]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Print a human-readable summary. `bc` is standard on macOS / most linuxes.
|
||||
mb=$(echo "scale=2; $saved/1048576" | bc)
|
||||
printf "optimize_goldens: %d PNGs processed, saved %.2f MB (%s)\n" "$count" "$mb" "$tool"
|
||||
83
scripts/record_snapshots.sh
Executable file
83
scripts/record_snapshots.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# record_snapshots.sh — regenerate every parity-gallery golden in one pass.
|
||||
#
|
||||
# Use this after an intentional UI change (new color token, redesigned
|
||||
# layout, etc.) so the committed baseline matches the new look. Reviewers
|
||||
# see the PNG diff alongside your code change in the PR — that dual-diff
|
||||
# is the whole point of the parity gallery.
|
||||
#
|
||||
# Usage
|
||||
# -----
|
||||
# ./scripts/record_snapshots.sh # iOS + Android
|
||||
# ./scripts/record_snapshots.sh --ios-only
|
||||
# ./scripts/record_snapshots.sh --android-only
|
||||
#
|
||||
# Pipeline
|
||||
# --------
|
||||
# 1. (Android) `./gradlew :composeApp:recordRoborazziDebug`
|
||||
# 2. (iOS) Delete iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests,
|
||||
# set SNAPSHOT_TESTING_RECORD=1, run xcodebuild test for
|
||||
# SnapshotGalleryTests. The env var is read by
|
||||
# SnapshotGalleryTests.swift to flip SnapshotTesting.record
|
||||
# between `.missing` (default — safe) and `.all` (overwrite).
|
||||
# 3. Run `scripts/optimize_goldens.sh` across both golden directories to
|
||||
# shrink the fresh PNGs.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
ROOT="$(pwd)"
|
||||
|
||||
platform="both"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--ios-only) platform="ios" ;;
|
||||
--android-only) platform="android" ;;
|
||||
-h|--help)
|
||||
sed -n '3,18p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "usage: $0 [--ios-only|--android-only]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------- Android ----------
|
||||
if [ "$platform" = "both" ] || [ "$platform" = "android" ]; then
|
||||
echo "==> Recording Android goldens…"
|
||||
./gradlew :composeApp:recordRoborazziDebug
|
||||
fi
|
||||
|
||||
# ---------- iOS ----------
|
||||
if [ "$platform" = "both" ] || [ "$platform" = "ios" ]; then
|
||||
echo "==> Recording iOS goldens…"
|
||||
rm -rf iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests
|
||||
(
|
||||
cd iosApp
|
||||
# SNAPSHOT_TESTING_RECORD=1 flips SnapshotTesting.isRecording to
|
||||
# `.all` (see SnapshotGalleryTests.swift).
|
||||
SNAPSHOT_TESTING_RECORD=1 xcodebuild test \
|
||||
-project honeyDue.xcodeproj \
|
||||
-scheme HoneyDue \
|
||||
-destination "${IOS_SIM_DESTINATION:-platform=iOS Simulator,name=iPhone 17,OS=latest}" \
|
||||
-only-testing:HoneyDueTests/SnapshotGalleryTests \
|
||||
2>&1 | tail -30
|
||||
)
|
||||
fi
|
||||
|
||||
# ---------- Optimize ----------
|
||||
echo "==> Optimizing PNGs…"
|
||||
"$ROOT/scripts/optimize_goldens.sh"
|
||||
|
||||
# ---------- Parity HTML (P4 follow-up) ----------
|
||||
if [ -x "$ROOT/scripts/build_parity_gallery.py" ]; then
|
||||
echo "==> Regenerating parity HTML gallery…"
|
||||
python3 "$ROOT/scripts/build_parity_gallery.py"
|
||||
else
|
||||
echo "==> (parity HTML generator not yet present — skipping)"
|
||||
fi
|
||||
|
||||
echo "==> Done. Review the PNG diff with your code change before committing."
|
||||
57
scripts/verify_snapshots.sh
Executable file
57
scripts/verify_snapshots.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# verify_snapshots.sh — verify every parity-gallery golden matches the
|
||||
# current code. Use as a pre-commit / CI gate.
|
||||
#
|
||||
# Exits non-zero if either platform's gallery drifts from its committed
|
||||
# baseline. Diff artifacts land under each platform's usual report dir:
|
||||
# Android: composeApp/build/outputs/roborazzi/
|
||||
# iOS: ~/Library/Developer/Xcode/DerivedData/.../HoneyDueTests/
|
||||
#
|
||||
# Usage
|
||||
# -----
|
||||
# ./scripts/verify_snapshots.sh # both platforms
|
||||
# ./scripts/verify_snapshots.sh --ios-only
|
||||
# ./scripts/verify_snapshots.sh --android-only
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
platform="both"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--ios-only) platform="ios" ;;
|
||||
--android-only) platform="android" ;;
|
||||
-h|--help)
|
||||
sed -n '3,14p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "usage: $0 [--ios-only|--android-only]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------- Android ----------
|
||||
if [ "$platform" = "both" ] || [ "$platform" = "android" ]; then
|
||||
echo "==> Verifying Android goldens…"
|
||||
./gradlew :composeApp:verifyRoborazziDebug
|
||||
fi
|
||||
|
||||
# ---------- iOS ----------
|
||||
if [ "$platform" = "both" ] || [ "$platform" = "ios" ]; then
|
||||
echo "==> Verifying iOS goldens…"
|
||||
(
|
||||
cd iosApp
|
||||
xcodebuild test \
|
||||
-project honeyDue.xcodeproj \
|
||||
-scheme HoneyDue \
|
||||
-destination "${IOS_SIM_DESTINATION:-platform=iOS Simulator,name=iPhone 17,OS=latest}" \
|
||||
-only-testing:HoneyDueTests/SnapshotGalleryTests \
|
||||
2>&1 | tail -30
|
||||
)
|
||||
fi
|
||||
|
||||
echo "==> All snapshot checks passed."
|
||||
Reference in New Issue
Block a user