diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index e16441e..9ec53f7 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -62,6 +62,7 @@ jobs: GITHUB_REPOSITORY: ${{ github.repository }} - name: Wait for iOS Simulator + id: simulator uses: kula-app/wait-for-services-action/ios-simulator@v1.3.0 with: device: iPhone 17 Pro @@ -71,7 +72,7 @@ jobs: scheme: ScreenshotUITests - name: Generate and Upload Screenshots to Sentry - run: bundle exec fastlane generate_and_upload_screenshots_ci + run: bundle exec fastlane generate_and_upload_screenshots_ci "simulator_udid:${{ steps.simulator.outputs.udid }}" env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} LICENSE_PLIST_GITHUB_TOKEN: ${{ steps.github_app_token.outputs.token }} diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml index dd65676..5653aa3 100644 --- a/.github/workflows/screenshots.yml +++ b/.github/workflows/screenshots.yml @@ -32,6 +32,7 @@ jobs: bundler-cache: true - name: Wait for iOS Simulator + id: simulator uses: kula-app/wait-for-services-action/ios-simulator@v1.3.0 with: device: iPhone 17 Pro @@ -41,7 +42,7 @@ jobs: scheme: ScreenshotUITests - name: Capture and Upload Screenshots - run: bundle exec fastlane generate_and_upload_screenshots_ci + run: bundle exec fastlane generate_and_upload_screenshots_ci "simulator_udid:${{ steps.simulator.outputs.udid }}" env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} LICENSE_PLIST_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Targets/ScreenshotUITests/Sources/ScreenshotUITests.swift b/Targets/ScreenshotUITests/Sources/ScreenshotUITests.swift index 6c46424..d6d2eb2 100644 --- a/Targets/ScreenshotUITests/Sources/ScreenshotUITests.swift +++ b/Targets/ScreenshotUITests/Sources/ScreenshotUITests.swift @@ -5,6 +5,18 @@ final class ScreenshotUITests: XCTestCase { continueAfterFailure = false } + @MainActor + private func revealEditButton(for item: XCUIElement, named name: String, in app: XCUIApplication) -> XCUIElement { + let editButton = app.buttons["Edit \(name)"] + + item.swipeLeft() + if !editButton.waitForExistence(timeout: 5) { + item.swipeLeft() + } + + return editButton + } + @MainActor func testScreenshots() throws { // swiftlint:disable:this function_body_length let app = XCUIApplication() @@ -59,11 +71,8 @@ final class ScreenshotUITests: XCTestCase { let createdLinkButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'techprimate.com'")).firstMatch XCTAssert(createdLinkButton.waitForExistence(timeout: 10), "Newly created link not found after creation") - // Long press to bring up context menu for edit - createdLinkButton.press(forDuration: 1.0) - - let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Edit'")).firstMatch - XCTAssert(editButton.waitForExistence(timeout: 10), "Edit button not found in context menu") + let editButton = revealEditButton(for: createdLinkButton, named: "techprimate.com", in: app) + XCTAssert(editButton.waitForExistence(timeout: 10), "Edit button not found in swipe actions") editButton.tap() // Wait for edit form to appear @@ -147,10 +156,8 @@ final class ScreenshotUITests: XCTestCase { // Open the edit list editor let createdListButton = app.buttons.containing(NSPredicate(format: "label CONTAINS '\(lists[idx].name)'")).firstMatch XCTAssert(createdListButton.waitForExistence(timeout: 10), "Created list button not found for list \(idx)") - createdListButton.press(forDuration: 1.0) - - let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Edit \(lists[idx].name)'")).firstMatch - XCTAssert(editButton.waitForExistence(timeout: 10), "Edit button not found in context menu") + let editButton = revealEditButton(for: createdListButton, named: lists[idx].name, in: app) + XCTAssert(editButton.waitForExistence(timeout: 10), "Edit button not found in swipe actions") editButton.tap() // Wait for the edit list form to appear diff --git a/fastlane/lanes/screenshots.rb b/fastlane/lanes/screenshots.rb index a3ada65..5b8d619 100644 --- a/fastlane/lanes/screenshots.rb +++ b/fastlane/lanes/screenshots.rb @@ -38,9 +38,11 @@ The output can then be used by run_screenshot_on_device for each device. Options: derived_data_path: path for build products (default: /tmp/screenshot_derived_data) + destination: xcodebuild destination (default: generic iOS Simulator) DESC lane :build_screenshots do |options| derived_data_path = options[:derived_data_path] || "/tmp/screenshot_derived_data" + destination = options[:destination] || "generic/platform=iOS Simulator" UI.message "Building screenshot test bundle..." @@ -49,7 +51,7 @@ scheme: "ScreenshotUITests", configuration: "Debug", derived_data_path: derived_data_path, - destination: "generic/platform=iOS Simulator", + destination: destination, build_for_testing: true, xcargs: "SWIFT_TREAT_WARNINGS_AS_ERRORS=NO" ) @@ -134,6 +136,7 @@ result = run_tests( project: "Flinky.xcodeproj", scheme: "ScreenshotUITests", + derived_data_path: derived_data_path, xctestrun: xctestrun_path, test_without_building: true, destination: "platform=iOS Simulator,id=#{simulator_udid}", @@ -184,6 +187,7 @@ Options: language: language code (default: en-US) output_dir: output directory (default: fastlane/screenshots) + expected_count: expected screenshot count (default: all configured devices) DESC lane :collect_screenshots do |options| language = options[:language] || SCREENSHOT_LANGUAGE @@ -206,7 +210,7 @@ UI.message " ✅ #{File.basename(file)}" end - expected = SCREENSHOT_DEVICES.length * 4 # 4 screenshots per device + expected = options[:expected_count]&.to_i || SCREENSHOT_DEVICES.length * 4 if screenshots.length == expected UI.success "✅ All #{screenshots.length} screenshots collected!" else diff --git a/fastlane/lanes/utilities.rb b/fastlane/lanes/utilities.rb index 0af4ede..34af874 100644 --- a/fastlane/lanes/utilities.rb +++ b/fastlane/lanes/utilities.rb @@ -134,32 +134,20 @@ Captures screenshots on a single iPhone device for faster CI builds Use generate_screenshots for full multi-device App Store screenshots DESC -lane :generate_screenshots_ci do +lane :generate_screenshots_ci do |options| UI.message "Generating screenshots for CI (single device)" - capture_screenshots( - scheme: "ScreenshotUITests", - devices: [ - "iPhone 17 Pro" # iPhone 6.3" display - ], - languages: ["en-US"], - configuration: "Debug", # Use Debug to reduce build time (~5-6 min faster than Release) - - clear_previous_screenshots: true, - concurrent_simulators: false, - skip_open_summary: true, - - reinstall_app: true, - override_status_bar: true, - localize_simulator: true, - disable_slide_to_type: true, + derived_data_path = options[:derived_data_path] || "/tmp/screenshot_derived_data" + simulator_udid = options[:simulator_udid] + destination = simulator_udid ? "platform=iOS Simulator,id=#{simulator_udid}" : nil - skip_helper_version_check: true, - - # See generate_screenshots: retry flakes, then fail loudly. - number_of_retries: 3, - stop_after_first_error: true + build_screenshots(derived_data_path: derived_data_path, destination: destination) + run_screenshot_on_device( + device: "iPhone 17 Pro", + simulator_udid: simulator_udid, + derived_data_path: derived_data_path ) + collect_screenshots(expected_count: 4) UI.success "✅ CI screenshots generated successfully!" UI.message "Screenshots generated in: fastlane/screenshots/" @@ -206,8 +194,11 @@ Fast single-device screenshot generation for CI builds Combines generate_screenshots_ci and upload_screenshots_to_sentry lanes DESC -lane :generate_and_upload_screenshots_ci do - generate_screenshots_ci +lane :generate_and_upload_screenshots_ci do |options| + generate_screenshots_ci( + simulator_udid: options[:simulator_udid], + derived_data_path: options[:derived_data_path] + ) upload_screenshots_to_sentry end