From c2b918eeb57dccd60b3d2c1d52ae90c73f3686bd Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 7 Apr 2026 12:11:16 +0100 Subject: [PATCH 01/23] Use SnapshotPreviews from `cameroncooke/snapshot-ci` branch --- ios/HackerNews.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/HackerNews.xcodeproj/project.pbxproj b/ios/HackerNews.xcodeproj/project.pbxproj index 742c85a8..1e0bd730 100644 --- a/ios/HackerNews.xcodeproj/project.pbxproj +++ b/ios/HackerNews.xcodeproj/project.pbxproj @@ -1271,7 +1271,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/EmergeTools/SnapshotPreviews"; requirement = { - branch = main; + branch = "cameroncooke/snapshot-ci"; kind = branch; }; }; diff --git a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f74453c2..f50f674d 100644 --- a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "cd279061071a9be804a18a197dd9701ac5708de79c458383a585d24b3f0df7d0", + "originHash" : "77abd4b0c51c017fe6ce592791015e863646bc679a165e2613e1adb9a21f5e59", "pins" : [ { "identity" : "accessibilitysnapshotios26", @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/EmergeTools/SnapshotPreviews", "state" : { - "branch" : "main", - "revision" : "1b3bd9bb341de85d927a78ad8874a35d081a8a66" + "branch" : "cameroncooke/snapshot-ci", + "revision" : "67121902c54afa7c338491d5374232bc48c7eff2" } }, { From 56fdd63c8a1d15dbb6af7399ce126c0b2bab39fe Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 1 Apr 2026 16:07:20 +0100 Subject: [PATCH 02/23] Upload workflow to support new SnapshotPreviews image exports --- .../workflows/ios_sentry_upload_snapshots.yml | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/ios_sentry_upload_snapshots.yml diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml new file mode 100644 index 00000000..504440f6 --- /dev/null +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -0,0 +1,78 @@ +name: Sentry iOS Upload (Snapshots) + +on: + push: + branches: [main] + paths: [ios/**, .github/workflows/ios*] + +jobs: + upload_sentry_snapshots: + runs-on: macos-26 + + defaults: + run: + working-directory: ./ios + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Ruby env + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.10 + bundler-cache: true + + - name: Setup gems + run: exec ../.github/scripts/ios/setup.sh + + - name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/org.swift.swiftpm + ~/Library/Developer/Xcode/DerivedData/HackerNews-*/SourcePackages + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: ${{ runner.os }}-spm- + + - name: Boot simulator + run: xcrun simctl boot "iPhone 17 Pro Max" || true + + - name: Generate snapshot images (iPhone) + run: | + set -o pipefail && TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="snapshot-images/" xcodebuild test \ + -scheme HackerNews \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ + -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + -resultBundlePath ../SnapshotResults-iphone.xcresult \ + ONLY_ACTIVE_ARCH=YES \ + TARGETED_DEVICE_FAMILY=1 \ + SUPPORTS_MACCATALYST=NO \ + | xcpretty + + - name: Generate snapshot images (iPad) + run: | + set -o pipefail && TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="snapshot-images/" xcodebuild test \ + -scheme HackerNews \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M3)' \ + -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + -resultBundlePath ../SnapshotResults-ipad.xcresult \ + ONLY_ACTIVE_ARCH=YES \ + TARGETED_DEVICE_FAMILY="1,2" \ + SUPPORTS_MACCATALYST=NO \ + | xcpretty + + - name: List generated images + run: | + echo "Generated snapshot images:" + ls -1 snapshot-images/ + echo "Total: $(ls -1 snapshot-images/ | wc -l | tr -d ' ') images" + + - name: Upload snapshots to Sentry + env: + SENTRY_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_SENTRY_AUTH_TOKEN }} + run: bundle exec fastlane ios upload_sentry_preview_snapshots \ No newline at end of file From 81230e500c02f8801f9a7996ff19345bd11d602c Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 1 Apr 2026 16:47:47 +0100 Subject: [PATCH 03/23] fix: use absolute path for snapshot export dir to avoid test bootstrap crash The test process working directory differs from the shell's, so a relative path caused the SnapshotCIExportCoordinator to fail creating the export directory, triggering a preconditionFailure (SIGTRAP) during test bootstrapping. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios_sentry_upload_snapshots.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 504440f6..4d555a0a 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -42,7 +42,7 @@ jobs: - name: Generate snapshot images (iPhone) run: | - set -o pipefail && TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="snapshot-images/" xcodebuild test \ + set -o pipefail && TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="$PWD/snapshot-images" xcodebuild test \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ From 676b945e38bafdc1cec7c7fa550962b90038a5e3 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Fri, 3 Apr 2026 09:47:06 +0100 Subject: [PATCH 04/23] Update package reference --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f50f674d..da8fde36 100644 --- a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -78,7 +78,7 @@ "location" : "https://github.com/EmergeTools/SnapshotPreviews", "state" : { "branch" : "cameroncooke/snapshot-ci", - "revision" : "67121902c54afa7c338491d5374232bc48c7eff2" + "revision" : "8c623c52c5474c74392b191490fa829370e0ab0c" } }, { From 4b99f38ebea2f78bd8cfae5eef6482e69ec2b170 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 7 Apr 2026 12:06:49 +0100 Subject: [PATCH 05/23] ci(ios): Hoist snapshot env vars to job level and boot iPad sim separately Move TEST_RUNNER_SNAPSHOTS_EXPORT_DIR and XCODE_RUNNING_FOR_PREVIEWS into the job-level env block so both iPhone and iPad test steps share the same absolute export path. Add a dedicated boot step for the iPad simulator and clarify the existing boot step name. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios_sentry_upload_snapshots.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 4d555a0a..15ef8c2b 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -13,6 +13,10 @@ jobs: run: working-directory: ./ios + env: + TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ github.workspace }}/ios/snapshot-images" + XCODE_RUNNING_FOR_PREVIEWS: 1 + steps: - name: Checkout uses: actions/checkout@v6 @@ -37,12 +41,12 @@ jobs: key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: ${{ runner.os }}-spm- - - name: Boot simulator + - name: Boot iPhone simulator run: xcrun simctl boot "iPhone 17 Pro Max" || true - name: Generate snapshot images (iPhone) run: | - set -o pipefail && TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="$PWD/snapshot-images" xcodebuild test \ + set -o pipefail && xcodebuild test \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ @@ -53,9 +57,12 @@ jobs: SUPPORTS_MACCATALYST=NO \ | xcpretty + - name: Boot iPad simulator + run: xcrun simctl boot "iPad Air 11-inch (M3)" || true + - name: Generate snapshot images (iPad) run: | - set -o pipefail && TEST_RUNNER_SNAPSHOTS_EXPORT_DIR="snapshot-images/" xcodebuild test \ + set -o pipefail && xcodebuild test \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M3)' \ From 5f81e4c440a3b10e207b964b5b3f16ba4f0b507e Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 7 Apr 2026 12:28:01 +0100 Subject: [PATCH 06/23] ci(ios): Add pull_request trigger to snapshot upload workflow Run the snapshot upload workflow on PRs targeting main in addition to pushes, scoped to ios and workflow file paths for both triggers. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios_sentry_upload_snapshots.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 15ef8c2b..a630709a 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -4,6 +4,9 @@ on: push: branches: [main] paths: [ios/**, .github/workflows/ios*] + pull_request: + branches: [main] + paths: [ios/**, .github/workflows/ios*] jobs: upload_sentry_snapshots: From 1c6351645b168971b9b18152b58cf4467fc1f5f7 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 7 Apr 2026 12:33:08 +0100 Subject: [PATCH 07/23] Disable iPad run --- .../workflows/ios_sentry_upload_snapshots.yml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index a630709a..a58ce4b6 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -60,21 +60,21 @@ jobs: SUPPORTS_MACCATALYST=NO \ | xcpretty - - name: Boot iPad simulator - run: xcrun simctl boot "iPad Air 11-inch (M3)" || true + # - name: Boot iPad simulator + # run: xcrun simctl boot "iPad Air 11-inch (M3)" || true - - name: Generate snapshot images (iPad) - run: | - set -o pipefail && xcodebuild test \ - -scheme HackerNews \ - -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M3)' \ - -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ - -resultBundlePath ../SnapshotResults-ipad.xcresult \ - ONLY_ACTIVE_ARCH=YES \ - TARGETED_DEVICE_FAMILY="1,2" \ - SUPPORTS_MACCATALYST=NO \ - | xcpretty + # - name: Generate snapshot images (iPad) + # run: | + # set -o pipefail && xcodebuild test \ + # -scheme HackerNews \ + # -sdk iphonesimulator \ + # -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M3)' \ + # -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + # -resultBundlePath ../SnapshotResults-ipad.xcresult \ + # ONLY_ACTIVE_ARCH=YES \ + # TARGETED_DEVICE_FAMILY="1,2" \ + # SUPPORTS_MACCATALYST=NO \ + # | xcpretty - name: List generated images run: | From 86400e69e9deea055b822e28c8138ccb556f1776 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 7 Apr 2026 13:02:59 +0100 Subject: [PATCH 08/23] feat(fastlane): Add lane to upload SnapshotPreviews snapshots to Sentry Add upload_sentry_preview_snapshots lane that uploads snapshot images generated by SnapshotPreviews to Sentry, separate from the existing swift-snapshot-testing upload lane. Co-Authored-By: Claude Opus 4.6 (1M context) --- ios/fastlane/Fastfile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index cfcccf66..41a56dc2 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -291,6 +291,17 @@ platform :ios do UI.success("Successfully replaced app in XCArchive with thinned build for #{device_type}") end + desc 'Upload SnapshotPreviews snapshots to Sentry' + lane :upload_sentry_preview_snapshots do + sentry_upload_snapshots( + path: 'snapshot-images', + app_id: 'com.emergetools.hackernews', + auth_token: ENV['SENTRY_SENTRY_AUTH_TOKEN'], + org_slug: 'sentry', + project_slug: 'hackernews-ios' + ) + end + desc 'Upload swift-snapshot-testing snapshots to Sentry' lane :upload_sentry_snapshots do sentry_upload_snapshots( From 1a07a3c5ef8c405ff4153a332c58532a088dcf62 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 7 Apr 2026 14:43:20 +0100 Subject: [PATCH 09/23] ci(ios): Cache DerivedData and split build/test phases for snapshots Add branch-scoped DerivedData caching and split the single xcodebuild test into build-for-testing + test-without-building to improve CI wall-clock time and give better visibility into build vs test duration. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../workflows/ios_sentry_upload_snapshots.yml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index a58ce4b6..579ab3d0 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -44,12 +44,32 @@ jobs: key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: ${{ runner.os }}-spm- + - name: Cache DerivedData + uses: actions/cache@v4 + with: + path: ~/Library/Developer/Xcode/DerivedData/HackerNews-* + key: ${{ runner.os }}-dd-${{ github.head_ref || github.ref_name }} + restore-keys: ${{ runner.os }}-dd- + - name: Boot iPhone simulator run: xcrun simctl boot "iPhone 17 Pro Max" || true + - name: Build for testing (iPhone) + run: | + set -o pipefail && xcodebuild build-for-testing \ + -scheme HackerNews \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ + -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + ONLY_ACTIVE_ARCH=YES \ + TARGETED_DEVICE_FAMILY=1 \ + SUPPORTS_MACCATALYST=NO \ + CODE_SIGNING_ALLOWED=NO \ + | xcpretty + - name: Generate snapshot images (iPhone) run: | - set -o pipefail && xcodebuild test \ + set -o pipefail && xcodebuild test-without-building \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ From 5a524f32e9dad0fffc160f9c0fc529ab27dd6e69 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 7 Apr 2026 15:16:12 +0100 Subject: [PATCH 10/23] ci(ios): Pin DD path, skip SPM updates, enable compilation caching - Move sim boot before cache steps so it boots during cache restore - Pin DerivedData to a fixed path with -derivedDataPath so cache reliably hits across runs - Add -skipPackageUpdates to avoid ~2min SPM resolution (Package.resolved is committed) - Enable COMPILATION_CACHING, EAGER_LINKING, FUSE_BUILD_SCRIPT_PHASES for faster incremental and zero-change builds - Strip redundant build settings from test-without-building step - Simplify SPM cache path (DD cache now covers SourcePackages) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../workflows/ios_sentry_upload_snapshots.yml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 579ab3d0..e21429be 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -35,36 +35,39 @@ jobs: - name: Setup gems run: exec ../.github/scripts/ios/setup.sh + - name: Boot iPhone simulator + run: xcrun simctl boot "iPhone 17 Pro Max" || true + - name: Cache Swift Package Manager uses: actions/cache@v4 with: - path: | - ~/Library/Caches/org.swift.swiftpm - ~/Library/Developer/Xcode/DerivedData/HackerNews-*/SourcePackages + path: ~/Library/Caches/org.swift.swiftpm key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: ${{ runner.os }}-spm- - name: Cache DerivedData uses: actions/cache@v4 with: - path: ~/Library/Developer/Xcode/DerivedData/HackerNews-* + path: ${{ github.workspace }}/ios/build/DerivedData key: ${{ runner.os }}-dd-${{ github.head_ref || github.ref_name }} restore-keys: ${{ runner.os }}-dd- - - name: Boot iPhone simulator - run: xcrun simctl boot "iPhone 17 Pro Max" || true - - name: Build for testing (iPhone) run: | set -o pipefail && xcodebuild build-for-testing \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ + -derivedDataPath build/DerivedData \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + -skipPackageUpdates \ ONLY_ACTIVE_ARCH=YES \ TARGETED_DEVICE_FAMILY=1 \ SUPPORTS_MACCATALYST=NO \ CODE_SIGNING_ALLOWED=NO \ + COMPILATION_CACHING=YES \ + EAGER_LINKING=YES \ + FUSE_BUILD_SCRIPT_PHASES=YES \ | xcpretty - name: Generate snapshot images (iPhone) @@ -73,11 +76,9 @@ jobs: -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ + -derivedDataPath build/DerivedData \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ -resultBundlePath ../SnapshotResults-iphone.xcresult \ - ONLY_ACTIVE_ARCH=YES \ - TARGETED_DEVICE_FAMILY=1 \ - SUPPORTS_MACCATALYST=NO \ | xcpretty # - name: Boot iPad simulator From 4693c5ce46c90f0183aa66418e34c89f10adec0c Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 8 Apr 2026 09:42:55 +0100 Subject: [PATCH 11/23] ci: trigger snapshot workflow to test warm cache From 7832aa8d828e83ea72fd8b14795f4d58e884c4d6 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 8 Apr 2026 10:11:13 +0100 Subject: [PATCH 12/23] ci(ios): Remove redundant SPM cache step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DerivedData cache already contains SourcePackages, and -skipPackageUpdates prevents xcodebuild from fetching. The SPM cache was downloading 759MB for no benefit — removing it saves ~4 minutes. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios_sentry_upload_snapshots.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index e21429be..f26d9192 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -38,13 +38,6 @@ jobs: - name: Boot iPhone simulator run: xcrun simctl boot "iPhone 17 Pro Max" || true - - name: Cache Swift Package Manager - uses: actions/cache@v4 - with: - path: ~/Library/Caches/org.swift.swiftpm - key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} - restore-keys: ${{ runner.os }}-spm- - - name: Cache DerivedData uses: actions/cache@v4 with: From 7ffad21bb8b806062ebcdcd4e08490e3186a0b63 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 8 Apr 2026 10:30:33 +0100 Subject: [PATCH 13/23] ci(ios): Recombine build and test into single xcodebuild invocation The split into build-for-testing + test-without-building was paying ~2min xcodebuild startup tax twice. For 23 snapshot tests that run in 10s, the visibility tradeoff isn't worth ~1min of added overhead. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../workflows/ios_sentry_upload_snapshots.yml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index f26d9192..acdea9a0 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -45,14 +45,15 @@ jobs: key: ${{ runner.os }}-dd-${{ github.head_ref || github.ref_name }} restore-keys: ${{ runner.os }}-dd- - - name: Build for testing (iPhone) + - name: Generate snapshot images (iPhone) run: | - set -o pipefail && xcodebuild build-for-testing \ + set -o pipefail && xcodebuild test \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ -derivedDataPath build/DerivedData \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + -resultBundlePath ../SnapshotResults-iphone.xcresult \ -skipPackageUpdates \ ONLY_ACTIVE_ARCH=YES \ TARGETED_DEVICE_FAMILY=1 \ @@ -63,17 +64,6 @@ jobs: FUSE_BUILD_SCRIPT_PHASES=YES \ | xcpretty - - name: Generate snapshot images (iPhone) - run: | - set -o pipefail && xcodebuild test-without-building \ - -scheme HackerNews \ - -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ - -derivedDataPath build/DerivedData \ - -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ - -resultBundlePath ../SnapshotResults-iphone.xcresult \ - | xcpretty - # - name: Boot iPad simulator # run: xcrun simctl boot "iPad Air 11-inch (M3)" || true From 887a063bd3cee8d9d55a8f9aa83570872cb6474d Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 8 Apr 2026 11:40:23 +0100 Subject: [PATCH 14/23] Update to use latest SnapshotPreviews branch commit --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index da8fde36..5bbce42a 100644 --- a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -78,7 +78,7 @@ "location" : "https://github.com/EmergeTools/SnapshotPreviews", "state" : { "branch" : "cameroncooke/snapshot-ci", - "revision" : "8c623c52c5474c74392b191490fa829370e0ab0c" + "revision" : "c58a343e2aad105ee24eb4d4b954ac26946c35da" } }, { From 05b0f544aef90f01b70d910be97c2eaa897dc6d5 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 8 Apr 2026 11:52:19 +0100 Subject: [PATCH 15/23] ci(ios): Replace -skipPackageUpdates with -skipPackagePluginValidation -skipPackageUpdates prevents xcodebuild from fetching new commits when Package.resolved changes, causing failures. Since the DD cache is keyed on branch name (not Package.resolved), we need to allow SPM to fetch deltas. -skipPackagePluginValidation still skips plugin trust prompts. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios_sentry_upload_snapshots.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index acdea9a0..af42c41d 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -54,7 +54,7 @@ jobs: -derivedDataPath build/DerivedData \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ -resultBundlePath ../SnapshotResults-iphone.xcresult \ - -skipPackageUpdates \ + -skipPackagePluginValidation \ ONLY_ACTIVE_ARCH=YES \ TARGETED_DEVICE_FAMILY=1 \ SUPPORTS_MACCATALYST=NO \ From bf734e69ee30b0004e4f1df2a79e56cbe7f9aba1 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 8 Apr 2026 12:15:04 +0100 Subject: [PATCH 16/23] ci(ios): Remove DD cache, restore original SPM cache DerivedData cache download time was too variable (1.5-5 min) on GHA network, often outweighing build savings. Restore the original SPM cache which is smaller and more predictable. Keep COMPILATION_CACHING and other build settings which work independently of DD caching. Also removed -derivedDataPath to use default location (matches SPM cache paths). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios_sentry_upload_snapshots.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index af42c41d..c3cd2a98 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -38,12 +38,14 @@ jobs: - name: Boot iPhone simulator run: xcrun simctl boot "iPhone 17 Pro Max" || true - - name: Cache DerivedData + - name: Cache Swift Package Manager uses: actions/cache@v4 with: - path: ${{ github.workspace }}/ios/build/DerivedData - key: ${{ runner.os }}-dd-${{ github.head_ref || github.ref_name }} - restore-keys: ${{ runner.os }}-dd- + path: | + ~/Library/Caches/org.swift.swiftpm + ~/Library/Developer/Xcode/DerivedData/HackerNews-*/SourcePackages + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: ${{ runner.os }}-spm- - name: Generate snapshot images (iPhone) run: | @@ -51,7 +53,6 @@ jobs: -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ - -derivedDataPath build/DerivedData \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ -resultBundlePath ../SnapshotResults-iphone.xcresult \ -skipPackagePluginValidation \ From d363a7c124eb0cdfb4672aa1d3ea466fe2f632f8 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 8 Apr 2026 14:51:05 +0100 Subject: [PATCH 17/23] ci(ios): Pin arm64 arch in simulator destination to skip disambiguation xcodebuild was matching both arm64 and x86_64 for the same simulator, triggering a warning and potentially slowing destination resolution. Pinning arch=arm64 eliminates the ambiguity on Apple Silicon runners. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios_sentry_upload_snapshots.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index c3cd2a98..37a1aa4c 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -52,7 +52,7 @@ jobs: set -o pipefail && xcodebuild test \ -scheme HackerNews \ -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max,arch=arm64' \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ -resultBundlePath ../SnapshotResults-iphone.xcresult \ -skipPackagePluginValidation \ From 22a179e27bd0fb13dba1c2fe78250788b0df57c8 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Wed, 15 Apr 2026 13:16:35 +0100 Subject: [PATCH 18/23] ci(ios): Add swift-snapshot-testing workflow for Sentry uploads Add a new GitHub Actions workflow that runs SwiftSnapshotTest and uploads the generated images to Sentry. Recording is controlled via the SNAPSHOT_TESTING_RECORD env var instead of hardcoding record: .all in the test file, so local runs default to reference comparison while CI always records fresh snapshots. - New workflow: ios_sentry_upload_swift_snapshots.yml - Remove invokeTest() override from SwiftSnapshotTest.swift - Use continue-on-error on test step since record mode fails assertions Refs EME-1030 Co-Authored-By: Claude --- .../workflows/ios_sentry_upload_snapshots.yml | 5 ++ .../ios_sentry_upload_swift_snapshots.yml | 78 +++++++++++++++++++ ios/HackerNewsTests/SwiftSnapshotTest.swift | 9 --- 3 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/ios_sentry_upload_swift_snapshots.yml diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 37a1aa4c..6998bf43 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -76,9 +76,14 @@ jobs: # -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M3)' \ # -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ # -resultBundlePath ../SnapshotResults-ipad.xcresult \ + # -skipPackagePluginValidation \ # ONLY_ACTIVE_ARCH=YES \ # TARGETED_DEVICE_FAMILY="1,2" \ # SUPPORTS_MACCATALYST=NO \ + # CODE_SIGNING_ALLOWED=NO \ + # COMPILATION_CACHING=YES \ + # EAGER_LINKING=YES \ + # FUSE_BUILD_SCRIPT_PHASES=YES \ # | xcpretty - name: List generated images diff --git a/.github/workflows/ios_sentry_upload_swift_snapshots.yml b/.github/workflows/ios_sentry_upload_swift_snapshots.yml new file mode 100644 index 00000000..886bb53e --- /dev/null +++ b/.github/workflows/ios_sentry_upload_swift_snapshots.yml @@ -0,0 +1,78 @@ +name: Sentry iOS Upload (Swift Snapshot Testing) + +on: + push: + branches: [main] + paths: [ios/**, .github/workflows/ios*] + pull_request: + branches: [main] + paths: [ios/**, .github/workflows/ios*] + +jobs: + upload_sentry_swift_snapshots: + runs-on: macos-26 + + defaults: + run: + working-directory: ./ios + + env: + XCODE_RUNNING_FOR_PREVIEWS: 1 + TEST_RUNNER_SNAPSHOT_TESTING_RECORD: all + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Ruby env + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.10 + bundler-cache: true + + - name: Setup gems + run: exec ../.github/scripts/ios/setup.sh + + - name: Boot iPhone simulator + run: xcrun simctl boot "iPhone 17 Pro Max" || true + + - name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/org.swift.swiftpm + ~/Library/Developer/Xcode/DerivedData/HackerNews-*/SourcePackages + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: ${{ runner.os }}-spm- + + - name: Generate snapshot images + continue-on-error: true + run: | + set -o pipefail && xcodebuild test \ + -scheme HackerNews \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max,arch=arm64' \ + -only-testing:HackerNewsTests/SwiftSnapshotTest \ + -resultBundlePath ../SnapshotResults-swift-snapshots.xcresult \ + -skipPackagePluginValidation \ + ONLY_ACTIVE_ARCH=YES \ + TARGETED_DEVICE_FAMILY=1 \ + SUPPORTS_MACCATALYST=NO \ + CODE_SIGNING_ALLOWED=NO \ + COMPILATION_CACHING=YES \ + EAGER_LINKING=YES \ + FUSE_BUILD_SCRIPT_PHASES=YES \ + | xcpretty + + - name: List generated images + run: | + echo "Generated snapshot images:" + ls -1 HackerNewsTests/__Snapshots__/SwiftSnapshotTest/ || echo "No snapshots found" + echo "Total: $(ls -1 HackerNewsTests/__Snapshots__/SwiftSnapshotTest/ 2>/dev/null | wc -l | tr -d ' ') images" + + - name: Upload snapshots to Sentry + env: + SENTRY_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_SENTRY_AUTH_TOKEN }} + run: bundle exec fastlane ios upload_sentry_snapshots diff --git a/ios/HackerNewsTests/SwiftSnapshotTest.swift b/ios/HackerNewsTests/SwiftSnapshotTest.swift index d602a30f..c4831e89 100644 --- a/ios/HackerNewsTests/SwiftSnapshotTest.swift +++ b/ios/HackerNewsTests/SwiftSnapshotTest.swift @@ -14,15 +14,6 @@ import Common final class SwiftSnapshotTest: XCTestCase { - override func invokeTest() { - // record: .all always records new snapshots and fails every test. - // Change to .missing to only record when no reference image exists, - // or remove this override entirely to compare against existing snapshots. - withSnapshotTesting(record: .all) { - super.invokeTest() - } - } - @MainActor func testPostListScreen() { @State var appViewModel = AppViewModel( bookmarkStore: FakeBookmarkDataStore(), From 10b9207a8ff3f70153450e709c5c3b6682d92a06 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Thu, 16 Apr 2026 18:35:13 +0100 Subject: [PATCH 19/23] Revert SnapshotPreviews pin to main Upstream SnapshotPreviews CI-export changes have merged to main, so the temporary cameroncooke/snapshot-ci branch pin is no longer needed. Reverts both project.pbxproj and Package.resolved to match main. Co-Authored-By: Claude Opus 4.7 (1M context) --- ios/HackerNews.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/HackerNews.xcodeproj/project.pbxproj b/ios/HackerNews.xcodeproj/project.pbxproj index 1e0bd730..742c85a8 100644 --- a/ios/HackerNews.xcodeproj/project.pbxproj +++ b/ios/HackerNews.xcodeproj/project.pbxproj @@ -1271,7 +1271,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/EmergeTools/SnapshotPreviews"; requirement = { - branch = "cameroncooke/snapshot-ci"; + branch = main; kind = branch; }; }; diff --git a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5bbce42a..84fc15e4 100644 --- a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/EmergeTools/SnapshotPreviews", "state" : { - "branch" : "cameroncooke/snapshot-ci", - "revision" : "c58a343e2aad105ee24eb4d4b954ac26946c35da" + "branch" : "main", + "revision" : "c04636cedbdbdf3b22181b522ad4025f63b78126" } }, { From 73af48bd471bf10ea3a254c454dd086cc97f2871 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Fri, 17 Apr 2026 12:16:40 +0100 Subject: [PATCH 20/23] ci(ios): Re-enable iPad snapshots and restructure upload paths Split SnapshotPreviews output into iphone/ and ipad/ subdirectories under a shared base dir, re-enable the previously commented-out iPad snapshot test run on iPad Air 11-inch (M4), and upload the combined tree to Sentry in one call. Rename the Fastfile lanes so each tool has a clear name: the SnapshotPreviews lane becomes `upload_sentry_snapshots` and accepts a `path` option, while the swift-snapshot-testing lane becomes `upload_sentry_snapshots_swift_snapshot_testing`. Update both workflows to call the renamed lanes. Also strip trailing whitespace in `export_thinned_build`. --- .../workflows/ios_sentry_upload_snapshots.yml | 60 +++++++++++-------- .../ios_sentry_upload_swift_snapshots.yml | 2 +- ios/fastlane/Fastfile | 36 +++++------ 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 6998bf43..85087146 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -17,7 +17,7 @@ jobs: working-directory: ./ios env: - TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ github.workspace }}/ios/snapshot-images" + SNAPSHOT_UPLOAD_BASE_DIR: "${{ github.workspace }}/ios/snapshot-images" XCODE_RUNNING_FOR_PREVIEWS: 1 steps: @@ -47,7 +47,14 @@ jobs: key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: ${{ runner.os }}-spm- + - name: Prepare snapshot export directories + run: | + mkdir -p "${SNAPSHOT_UPLOAD_BASE_DIR}/iphone" + mkdir -p "${SNAPSHOT_UPLOAD_BASE_DIR}/ipad" + - name: Generate snapshot images (iPhone) + env: + TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ env.SNAPSHOT_UPLOAD_BASE_DIR }}/iphone" run: | set -o pipefail && xcodebuild test \ -scheme HackerNews \ @@ -65,34 +72,37 @@ jobs: FUSE_BUILD_SCRIPT_PHASES=YES \ | xcpretty - # - name: Boot iPad simulator - # run: xcrun simctl boot "iPad Air 11-inch (M3)" || true + - name: Boot iPad simulator + run: xcrun simctl boot "iPad Air 11-inch (M4)" || true - # - name: Generate snapshot images (iPad) - # run: | - # set -o pipefail && xcodebuild test \ - # -scheme HackerNews \ - # -sdk iphonesimulator \ - # -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M3)' \ - # -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ - # -resultBundlePath ../SnapshotResults-ipad.xcresult \ - # -skipPackagePluginValidation \ - # ONLY_ACTIVE_ARCH=YES \ - # TARGETED_DEVICE_FAMILY="1,2" \ - # SUPPORTS_MACCATALYST=NO \ - # CODE_SIGNING_ALLOWED=NO \ - # COMPILATION_CACHING=YES \ - # EAGER_LINKING=YES \ - # FUSE_BUILD_SCRIPT_PHASES=YES \ - # | xcpretty + - name: Generate snapshot images (iPad) + env: + TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ env.SNAPSHOT_UPLOAD_BASE_DIR }}/ipad" + run: | + set -o pipefail && xcodebuild test \ + -scheme HackerNews \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M4)' \ + -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + -resultBundlePath ../SnapshotResults-ipad.xcresult \ + -skipPackagePluginValidation \ + ONLY_ACTIVE_ARCH=YES \ + TARGETED_DEVICE_FAMILY="1,2" \ + SUPPORTS_MACCATALYST=NO \ + CODE_SIGNING_ALLOWED=NO \ + COMPILATION_CACHING=YES \ + EAGER_LINKING=YES \ + FUSE_BUILD_SCRIPT_PHASES=YES \ + | xcpretty - - name: List generated images + - name: List generated snapshot files run: | - echo "Generated snapshot images:" - ls -1 snapshot-images/ - echo "Total: $(ls -1 snapshot-images/ | wc -l | tr -d ' ') images" + echo "Generated snapshot files:" + find "${SNAPSHOT_UPLOAD_BASE_DIR}" -type f | sort + echo + echo "Total PNG images: $(find "${SNAPSHOT_UPLOAD_BASE_DIR}" -type f -name '*.png' | wc -l | tr -d ' ')" - name: Upload snapshots to Sentry env: SENTRY_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_SENTRY_AUTH_TOKEN }} - run: bundle exec fastlane ios upload_sentry_preview_snapshots \ No newline at end of file + run: bundle exec fastlane ios upload_sentry_snapshots path:"${SNAPSHOT_UPLOAD_BASE_DIR}" \ No newline at end of file diff --git a/.github/workflows/ios_sentry_upload_swift_snapshots.yml b/.github/workflows/ios_sentry_upload_swift_snapshots.yml index 886bb53e..d9d93c28 100644 --- a/.github/workflows/ios_sentry_upload_swift_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_swift_snapshots.yml @@ -75,4 +75,4 @@ jobs: - name: Upload snapshots to Sentry env: SENTRY_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_SENTRY_AUTH_TOKEN }} - run: bundle exec fastlane ios upload_sentry_snapshots + run: bundle exec fastlane ios upload_sentry_snapshots_swift_snapshot_testing diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 41a56dc2..4d05004a 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -209,24 +209,24 @@ platform :ios do lane :export_thinned_build do |options| xcarchive_path = lane_context[SharedValues::XCODEBUILD_ARCHIVE] UI.message("Using archive: #{xcarchive_path}") - + # Device type configuration for thinning, default is iPhone 16 Pro device_type = options[:device_type] || 'iPhone17,1' UI.message("Thinning for device type: #{device_type}") - + # Export options for thinning export_options = { method: 'ad-hoc', thinning: device_type, # This creates a thinned build for the specific device type team_id: ENV['APPLE_TEAM_ID'] } - + # Add provisioning profiles if available app_identifier = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier) bundle_identifier = ENV['BUNDLE_ID'] || app_identifier profile_name = 'HackerNews AdHoc Distribution' widget_profile_name = 'HackerNews AdHoc HomeWidget' - + export_options[:provisioningProfiles] = { bundle_identifier => profile_name, 'com.emergetools.hackernews.HackerNewsHomeWidget' => widget_profile_name @@ -240,10 +240,10 @@ platform :ios do sh("#{xcsafe} -exportArchive -archivePath #{xcarchive_path} -exportOptionsPlist export_options.plist -exportPath ./build") plist_data = Plist.parse_xml('./build/app-thinning.plist') - + # Find the variant matching our device and OS version target_ipa_path = nil - + if plist_data && plist_data['variants'] plist_data['variants'].each do |variant| path = variant[0] @@ -256,45 +256,45 @@ platform :ios do end end end - + ipa_path = File.join('../', target_ipa_path) UI.message("Using IPA: #{ipa_path}") - + temp_dir = './build/temp_extraction' FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir) FileUtils.mkdir_p(temp_dir) - + # Extract the IPA sh("cd #{temp_dir} && unzip -q '#{ipa_path}'") payload_dir = File.join(temp_dir, 'Payload') app_files = Dir.glob(File.join(payload_dir, '*.app')) - + if app_files.empty? UI.error("No .app file found in extracted IPA") return end - + thinned_app_path = app_files.first # Find the original .app in the XCArchive original_app_pattern = File.join(xcarchive_path, 'Products', 'Applications', '*.app') original_app_files = Dir.glob(original_app_pattern) - + original_app_path = original_app_files.first - + FileUtils.rm_rf(original_app_path) FileUtils.cp_r(thinned_app_path, original_app_path) - + # Clean up temporary directory FileUtils.rm_rf(temp_dir) end - + UI.success("Successfully replaced app in XCArchive with thinned build for #{device_type}") end desc 'Upload SnapshotPreviews snapshots to Sentry' - lane :upload_sentry_preview_snapshots do + lane :upload_sentry_snapshots do |options| sentry_upload_snapshots( - path: 'snapshot-images', + path: options[:path], app_id: 'com.emergetools.hackernews', auth_token: ENV['SENTRY_SENTRY_AUTH_TOKEN'], org_slug: 'sentry', @@ -303,7 +303,7 @@ platform :ios do end desc 'Upload swift-snapshot-testing snapshots to Sentry' - lane :upload_sentry_snapshots do + lane :upload_sentry_snapshots_swift_snapshot_testing do sentry_upload_snapshots( path: 'HackerNewsTests/__Snapshots__/SwiftSnapshotTest', app_id: 'com.emergetools.hackernews', From 0e0dc8f777e2424c649401654a10180975f258a4 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Fri, 17 Apr 2026 12:35:22 +0100 Subject: [PATCH 21/23] ci(ios): Pin arm64 arch in iPad Sentry snapshot destination Add `arch=arm64` to the iPad Air simulator destination in the Sentry snapshot upload workflow to skip destination disambiguation, matching the existing pattern used for the iPhone destination. --- .github/workflows/ios_sentry_upload_snapshots.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 85087146..75132077 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -82,7 +82,7 @@ jobs: set -o pipefail && xcodebuild test \ -scheme HackerNews \ -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M4)' \ + -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M4),arch=arm64' \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ -resultBundlePath ../SnapshotResults-ipad.xcresult \ -skipPackagePluginValidation \ From a7053a15267cfd6cd09569f1b3344425a3964422 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Fri, 17 Apr 2026 12:35:46 +0100 Subject: [PATCH 22/23] ci(ios): Remove Sentry snapshot upload step Drop the fastlane upload_sentry_snapshots step from the Emerge snapshot workflow so snapshots are only uploaded to Emerge. --- .github/workflows/ios_emerge_upload_snapshots.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ios_emerge_upload_snapshots.yml b/.github/workflows/ios_emerge_upload_snapshots.yml index 9c00112c..5ec703a4 100644 --- a/.github/workflows/ios_emerge_upload_snapshots.yml +++ b/.github/workflows/ios_emerge_upload_snapshots.yml @@ -88,7 +88,3 @@ jobs: --client-library swift-snapshot-testing \ --project-root . \ --debug - - name: Upload snapshots to Sentry - env: - SENTRY_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_SENTRY_AUTH_TOKEN }} - run: bundle exec fastlane ios upload_sentry_snapshots From b8e01e3635a1e2fba6c63a7b8e30bfde101ae1f5 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Fri, 17 Apr 2026 13:30:51 +0100 Subject: [PATCH 23/23] ci(ios): Split snapshot tests into build-for-testing and test phases Build the snapshot test bundle once with build-for-testing targeting both iPhone and iPad device families, then run test-without-building for each simulator. This avoids rebuilding the test bundle per destination and keeps iPhone and iPad runs sharing the same compiled artifacts. Route DerivedData to a workspace-local path so the SPM cache key and subsequent test runs point at a stable location. --- .../workflows/ios_sentry_upload_snapshots.yml | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml index 75132077..65f92c62 100644 --- a/.github/workflows/ios_sentry_upload_snapshots.yml +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -18,6 +18,7 @@ jobs: env: SNAPSHOT_UPLOAD_BASE_DIR: "${{ github.workspace }}/ios/snapshot-images" + DERIVED_DATA_PATH: "${{ github.workspace }}/DerivedData-snapshot-upload" XCODE_RUNNING_FOR_PREVIEWS: 1 steps: @@ -43,7 +44,7 @@ jobs: with: path: | ~/Library/Caches/org.swift.swiftpm - ~/Library/Developer/Xcode/DerivedData/HackerNews-*/SourcePackages + ${{ env.DERIVED_DATA_PATH }}/SourcePackages key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: ${{ runner.os }}-spm- @@ -51,20 +52,20 @@ jobs: run: | mkdir -p "${SNAPSHOT_UPLOAD_BASE_DIR}/iphone" mkdir -p "${SNAPSHOT_UPLOAD_BASE_DIR}/ipad" + mkdir -p "${DERIVED_DATA_PATH}" - - name: Generate snapshot images (iPhone) - env: - TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ env.SNAPSHOT_UPLOAD_BASE_DIR }}/iphone" + - name: Build snapshot tests run: | - set -o pipefail && xcodebuild test \ + set -o pipefail && xcodebuild build-for-testing \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max,arch=arm64' \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ - -resultBundlePath ../SnapshotResults-iphone.xcresult \ + -resultBundlePath ../SnapshotBuildForTesting.xcresult \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ -skipPackagePluginValidation \ ONLY_ACTIVE_ARCH=YES \ - TARGETED_DEVICE_FAMILY=1 \ + TARGETED_DEVICE_FAMILY="1,2" \ SUPPORTS_MACCATALYST=NO \ CODE_SIGNING_ALLOWED=NO \ COMPILATION_CACHING=YES \ @@ -75,24 +76,30 @@ jobs: - name: Boot iPad simulator run: xcrun simctl boot "iPad Air 11-inch (M4)" || true + - name: Generate snapshot images (iPhone) + env: + TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ env.SNAPSHOT_UPLOAD_BASE_DIR }}/iphone" + run: | + set -o pipefail && xcodebuild test-without-building \ + -scheme HackerNews \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max,arch=arm64' \ + -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ + -resultBundlePath ../SnapshotResults-iphone.xcresult \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ + | xcpretty + - name: Generate snapshot images (iPad) env: TEST_RUNNER_SNAPSHOTS_EXPORT_DIR: "${{ env.SNAPSHOT_UPLOAD_BASE_DIR }}/ipad" run: | - set -o pipefail && xcodebuild test \ + set -o pipefail && xcodebuild test-without-building \ -scheme HackerNews \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPad Air 11-inch (M4),arch=arm64' \ -only-testing:HackerNewsTests/HackerNewsSnapshotTest \ -resultBundlePath ../SnapshotResults-ipad.xcresult \ - -skipPackagePluginValidation \ - ONLY_ACTIVE_ARCH=YES \ - TARGETED_DEVICE_FAMILY="1,2" \ - SUPPORTS_MACCATALYST=NO \ - CODE_SIGNING_ALLOWED=NO \ - COMPILATION_CACHING=YES \ - EAGER_LINKING=YES \ - FUSE_BUILD_SCRIPT_PHASES=YES \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ | xcpretty - name: List generated snapshot files