Skip to content

iOS Mac Mini POC ​

Objective ​

Evaluate the capacity of a self-hosted Mac Mini for running iOS simulators and GitHub Actions runners for E2E testing, with the goal of running multiple test suites in parallel to reduce PR feedback time.

Simulator capacity on the current machine ​

iOS simulators are fundamentally different from Android emulators — they run native ARM code directly on the host CPU with no virtualisation layer (no QEMU, no KVM). There's no guest OS kernel, no software GPU rendering, and no separate ADB server. Compared to Android:

  • Minimal idle CPU — only spikes during active test interaction (vs Android's constant QEMU overhead)
  • No software rendering tax — uses the host GPU directly via Metal

Actual iOS simulator capacity on the current Mac Mini (10 cores, 32GB):

RunnersAvg RAMViability
385%Passes but slower — same memory pressure as 4 runners, 4-6 min longer
480%Optimal — all suites pass, 13 min, stable
5—Not viable — OOM kills during startup

Each runner slot carries a simulator, Appium server, and WDA instance. The combined overhead of 4 runners pushes the machine to 99% during startup; this drops quickly once simulators are booted and settled.

The same machine that can't sustain 3 Android emulators long-term can run 4 iOS runners — though not without memory pressure at startup.

What Was Tried ​

Memory consistently hits 99% during simulator launch regardless of runner count — the combined overhead of simulators, Appium, and WDA processes saturates 32GB at startup. The key variable is whether the machine recovers and stays stable once the sims are booted.

Metric5 runners3 runners (unoptimised)4 runners (optimised)
Suites passed3/55/55/5
Avg memory80%85%80%
Avg CPU62%56%52%
Pageouts66k23k15k
OOM kills200
Test duration~12 min~16 min~13 min

5 runners — 2 simulators OOM-killed during build download. Not viable on 32GB.

4 runners — all 5 suites passed in 13 min.

3 runners — was showing same memory behaviour as 4 runners (before optimisation). Fewer runners just means fewer parallel jobs, so tests take longer without meaningfully reducing the startup memory spike. Since we were optimising for the most runners as possible, I did not take the time to optimised further since 4 seemed feasible.

Observed Resource Usage ​

Final config (Run #10) ​

MetricValueNotes
Test duration~13 min
Peak memory99% (31G/31G)~2 min only, during startup
Avg memory80%Stable throughout run
Avg CPU52%No WDA compilation overhead
Pageouts15k
OOM kills0All 4 simulators stable throughout
WDA peak CPU16.6%Fully cached — no compilation
WDA processes4No duplicates (WDA kill working)
Post-run memory40% (13G/32G)Clean recovery

Full regression (21 suites, 2026-03-21) ​

21 suites run across 7 waves of 4 parallel runners. All 21 suites passed in ~83 min.

MetricValue
Avg memory78%
Peak memory99% (7% of the time — mostly wave transitions)
Avg CPU58%
Total pageouts~90k (~13k per wave, consistent with single-run rate)
OOM kills0
Post-run memory35% (11G/31G) — lowest post-run baseline observed

The machine sustained 7 consecutive waves without degradation. Resource usage per wave was consistent with single-run numbers, confirming the config is stable under extended load.

Limitations Discovered ​

99% memory peak is unavoidable on 32GB ​

4 simulators + Appium + WDA + runner processes exceed 32GB during startup. The 99% peak lasts ~2 min in the optimised config and then drops — it's not a failure state, but there's no headroom for additional processes during that window. 64GB RAM could potentially eliminate the peak and support 5-6 runners.

5 runners causes immediate OOM kills ​

5 concurrent simulators push the machine past its memory limit during the build download and extract phase. Simulators are OOM-killed before tests can start. Not viable on 32GB.

WDA derived data paths must not be shared ​

Sharing a single derived data path across runners causes WDA to rebuild from source for each simulator (different UDIDs/architectures invalidate the cache). CPU spikes return to 87%+. Each runner must have its own derived data path.

WDA duplicate processes accumulate without intervention ​

Without a kill step, each run can accumulate WDA process duplicates (8 observed vs expected 4), consuming ~1.5GB extra RAM at peak. A pkill -f WebDriverAgent before each run is required to keep the count clean.

Things to Try ​

  • Smaller simulator device — switching to a smaller iPhone model (e.g. iPhone SE) could reduce the memory footprint per simulator and lower the startup spike. Not tried — the E2E tests are written against a specific device size and would need updating first.

Maintenance ​

  • EAS build cache is cleaned up nightly at 3 AM — files older than 30 days are removed automatically.
  • Runner logs are stored on the Desktop and retained for 30 days.

Final Recommendation ​

4 parallel iOS runners on the Mac Mini M4 (32GB), with the following optimisations — all are required for stable operation:

OptimisationWhat it solved
Build download cachingEliminated redundant downloads; prevented simultaneous extract from exhausting memory
Staggered simulator bootsSmoothed the memory spike at startup; 4th sim boots ~1 min after first 3
usePrebuiltWDAEliminated 84-89% CPU compilation spikes
Per-simulator derived dataKeeps WDA cache valid per sim — sharing paths breaks the cache
WDA kill before startPrevents duplicate WDA processes accumulating; keeps count at 4 instead of 8