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 diff --git a/.github/workflows/ios_sentry_upload_snapshots.yml b/.github/workflows/ios_sentry_upload_snapshots.yml new file mode 100644 index 00000000..65f92c62 --- /dev/null +++ b/.github/workflows/ios_sentry_upload_snapshots.yml @@ -0,0 +1,115 @@ +name: Sentry iOS Upload (Snapshots) + +on: + push: + branches: [main] + paths: [ios/**, .github/workflows/ios*] + pull_request: + branches: [main] + paths: [ios/**, .github/workflows/ios*] + +jobs: + upload_sentry_snapshots: + runs-on: macos-26 + + defaults: + run: + working-directory: ./ios + + env: + SNAPSHOT_UPLOAD_BASE_DIR: "${{ github.workspace }}/ios/snapshot-images" + DERIVED_DATA_PATH: "${{ github.workspace }}/DerivedData-snapshot-upload" + XCODE_RUNNING_FOR_PREVIEWS: 1 + + 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 + ${{ env.DERIVED_DATA_PATH }}/SourcePackages + 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" + mkdir -p "${DERIVED_DATA_PATH}" + + - name: Build snapshot tests + run: | + 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 ../SnapshotBuildForTesting.xcresult \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ + -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: 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-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 \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ + | xcpretty + + - name: List generated snapshot files + run: | + 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_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 new file mode 100644 index 00000000..d9d93c28 --- /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_swift_snapshot_testing diff --git a/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/HackerNews.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f74453c2..84fc15e4 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", @@ -78,7 +78,7 @@ "location" : "https://github.com/EmergeTools/SnapshotPreviews", "state" : { "branch" : "main", - "revision" : "1b3bd9bb341de85d927a78ad8874a35d081a8a66" + "revision" : "c04636cedbdbdf3b22181b522ad4025f63b78126" } }, { 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(), diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index cfcccf66..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,43 +256,54 @@ 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_snapshots do |options| + sentry_upload_snapshots( + path: options[:path], + 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 + lane :upload_sentry_snapshots_swift_snapshot_testing do sentry_upload_snapshots( path: 'HackerNewsTests/__Snapshots__/SwiftSnapshotTest', app_id: 'com.emergetools.hackernews',