Skip to content

Reduce screenshot workflow runtime on hosted macOS runners #167

Description

@philprime

Context

The Screenshots GitHub Actions job is reliable but remains slow and highly variable on hosted macOS runners.

Initial evidence came from PR #159 job 99810822044, which completed in approximately 23m50s:

  • Simulator readiness: 7m07s
  • Screenshot capture and upload: 15m47s
  • Everything else: approximately 56s

PR #166 implements the first optimization pass.

Improvements validated in PR #166

  • Build the screenshot test bundle once with build-for-testing.
  • Run retries with test-without-building instead of rebuilding.
  • Build only for the resolved arm64 simulator in single-device CI.
  • Reuse the same derived-data path for build and test.
  • Pass the simulator action's resolved UDID into Fastlane.
  • Replace flaky context-menu long presses with exact swipe actions.
  • Preserve in-process retries and simulator reboot recovery.
  • Validate four screenshots for the single-device CI run.

Three consecutive runs of commit ce9ad40 succeeded without a screenshot test retry or simulator reboot:

Run Duration Result
Attempt 1 20m32s Success
Attempt 2 14m38s Success
Attempt 3 19m28s Success

Average job duration was 18m13s, approximately 5m37s faster than the original job. The optimized screenshot capture step averaged 10m39s instead of 15m47s.

A later restored-prewarming run also succeeded in 17m39s.

Remaining bottlenecks

Simulator readiness and Xcode prewarming

The complete simulator readiness step varied from 5m20s to 7m24s.

Runtime resolution and simulator boot typically consumed 1m37s to 2m22s. The remaining 3m43s to 5m17s was xcodebuild -showBuildSettings prewarming. It commonly required three or four attempts and sometimes exhausted its 300-second timeout.

The restored-prewarming run spent:

  • 1m53s resolving and booting the simulator
  • 5m02s prewarming Xcode
  • 3m32s building the screenshot bundle
  • 5m25s running XCTest and collecting screenshots

Cold builds

Each GitHub-hosted macOS job and rerun receives a new ephemeral VM. /tmp/screenshot_derived_data and Xcode DerivedData therefore start empty. The workflow currently restores Ruby dependencies but does not restore compiled Xcode products.

The single-device architecture-specific build still takes roughly 2m28s to 4m15s depending on runner performance.

XCTest overhead

Successful testScreenshots executions varied from approximately 172 to 212 seconds. XCTest startup and cleanup add another one to two minutes. This is acceptable for the current end-to-end screenshot flow.

Runner queue time

Some reruns waited roughly ten minutes for a hosted macOS runner. Queue time is separate from job duration and cannot be addressed by workflow code.

Failed no-prewarming experiment

Commit 20a4dfa disabled warm-xcodebuild-settings only in the PR Screenshots workflow.

The first fresh-runner experiment failed after 8m04s:

  1. Runtime resolution, simulator boot, and bootstatus succeeded in 3m07s.
  2. Fastlane resolved Swift packages in approximately 2m02s.
  3. Fastlane then became the first caller of xcodebuild -showBuildSettings.
  4. Its attempts timed out after 3, 6, 12, and 24 seconds.
  5. build-for-testing never started.

This shows that the action prewarm currently protects the workflow from Fastlane's short build-settings timeout. Simply disabling prewarming is not stable. Prewarming was restored in commit 8fdfa56.

Recommended next experiment

Replace Fastlane's run_tests(build_for_testing: true) implementation in build_screenshots with a direct xcodebuild build-for-testing invocation.

The direct build should:

  • Use the resolved simulator destination.
  • Use /tmp/screenshot_derived_data.
  • Use the existing Debug configuration and SWIFT_TREAT_WARNINGS_AS_ERRORS=NO setting.
  • Allow the real build to wait for Xcode initialization without a separate short showBuildSettings preflight.
  • Preserve the generated .xctestrun discovery and the existing Fastlane test-without-building retry flow.

After that change, disable the standalone action prewarm in the PR Screenshots workflow and run the same commit three times.

Acceptance criteria:

  • Three consecutive successful Screenshots jobs on fresh hosted runners.
  • Four screenshots generated and uploaded in every run.
  • No simulator readiness regression.
  • Screenshot test retries and simulator reboot recovery remain available.
  • Median job duration improves materially over the 18m13s prewarmed baseline.
  • Inspect logs to confirm that build work starts instead of failing in a Fastlane build-settings probe.

If this succeeds, consider applying the same approach to the beta screenshot workflow. Keep release workflows unchanged until separately validated.

Later options

Only investigate DerivedData caching after the direct-build experiment. Any cache must be keyed tightly to the Xcode version, SDK, Package.resolved, project configuration, and relevant sources. Stale Xcode caches are a stability risk and should not be the first optimization.

It may also be useful to propose upstream improvements:

  • Add a separate warm-up timeout to kula-app/wait-for-services-action.
  • Surface whether prewarming succeeded as an action output.
  • Avoid or configure Fastlane's short showBuildSettings timeout when an explicit destination and derived-data path are already known.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions