#!/bin/bash # run_ui_tests.sh — Phased test runner for the HoneyDue iOS suites. # # Architecture: every UI test mints its OWN isolated Kratos account (see # Core/Fixtures/TestAccount.swift + AuthenticatedUITestCase), so suites are # fully independent and the parallel phase scales to many workers with no # cross-suite data races. Pure-API tests live in a separate standalone target # (HoneyDueAPITests) that runs in seconds without launching the app. # # Phases: # 0. Smoke gate — fast launch/login sanity. Abort the run if it fails. # 1. Seed — ensure baseline accounts exist (AAA_SeedTests). # 1b. API — standalone HoneyDueAPITests (no app launch; ~seconds). # 2. Parallel — the WHOLE UI target minus the four phase-managed suites, # via -skip-testing. New suites are auto-included (no hand- # maintained list to drift), run at $WORKERS workers. # 3. Sweep — clear-all-data + delete leaked uit_* Kratos identities # (SuiteZZ_CleanupTests). Non-blocking. # # Usage: # ./run_ui_tests.sh # iPhone 17 Pro, 8 workers # ./run_ui_tests.sh "iPhone Air" 6 # custom device + worker count # ./run_ui_tests.sh --skip-seed # skip phase 1 # ./run_ui_tests.sh --skip-cleanup # skip phase 3 # ./run_ui_tests.sh --only-parallel # only phase 2 # ./run_ui_tests.sh --smoke # only phase 0 # ./run_ui_tests.sh --only-api # only phase 1b set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT="$SCRIPT_DIR/honeyDue.xcodeproj" SCHEME="HoneyDueUITests" API_SCHEME="HoneyDueAPITests" TARGET="HoneyDueUITests" DESTINATION="platform=iOS Simulator,name=iPhone 17 Pro" WORKERS=8 # Suites that run in their own phases — excluded from the parallel phase. PHASE_MANAGED=( "$TARGET/AAA_SeedTests" "$TARGET/SuiteZZ_CleanupTests" "$TARGET/SmokeUITests" "$TARGET/AppLaunchUITests" ) SKIP_SEED=false; SKIP_CLEANUP=false; ONLY_PARALLEL=false; ONLY_SMOKE=false; ONLY_API=false POSITIONAL_ARGS=() for arg in "$@"; do case $arg in --skip-seed) SKIP_SEED=true ;; --skip-cleanup) SKIP_CLEANUP=true ;; --only-parallel) ONLY_PARALLEL=true; SKIP_SEED=true; SKIP_CLEANUP=true ;; --smoke) ONLY_SMOKE=true ;; --only-api) ONLY_API=true ;; *) POSITIONAL_ARGS+=("$arg") ;; esac done [ ${#POSITIONAL_ARGS[@]} -ge 1 ] && DESTINATION="platform=iOS Simulator,name=${POSITIONAL_ARGS[0]}" [ ${#POSITIONAL_ARGS[@]} -ge 2 ] && WORKERS="${POSITIONAL_ARGS[1]}" RESULTS_DIR="$SCRIPT_DIR/build/test-results" DERIVED_DATA="$SCRIPT_DIR/build/DerivedData" mkdir -p "$RESULTS_DIR" "$DERIVED_DATA" BOLD='\033[1m'; GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[0;33m'; RESET='\033[0m' phase_header() { echo ""; echo -e "${BOLD}════════════════════════════════════════════════════${RESET}"; echo -e "${BOLD} $1${RESET}"; echo -e "${BOLD}════════════════════════════════════════════════════${RESET}"; echo ""; } # run_xcodebuild [extra xcodebuild args...] run_xcodebuild() { local result_path="$RESULTS_DIR/$1.xcresult"; local scheme="$2"; shift 2 rm -rf "$result_path" xcodebuild test \ -project "$PROJECT" -scheme "$scheme" -destination "$DESTINATION" \ -derivedDataPath "$DERIVED_DATA" -resultBundlePath "$result_path" \ "$@" 2>&1 | tail -40 return ${PIPESTATUS[0]} } OVERALL_START=$(date +%s) # ── Phase 1b only ────────────────────────────────────────────── if [ "$ONLY_API" = true ]; then phase_header "API tests (standalone)" run_xcodebuild "API" "$API_SCHEME" && exit 0 || exit 1 fi # ── Phase 0: Smoke gate ──────────────────────────────────────── if [ "$ONLY_PARALLEL" = false ]; then phase_header "Phase 0: Smoke gate" if run_xcodebuild "Smoke" "$SCHEME" \ -only-testing:"$TARGET/SmokeUITests" -only-testing:"$TARGET/AppLaunchUITests"; then echo -e "${GREEN}✓ Smoke passed${RESET}" else echo -e "${RED}✗ Smoke FAILED — aborting (app can't launch/log in).${RESET}"; exit 1 fi [ "$ONLY_SMOKE" = true ] && exit 0 fi # ── Phase 1: Seed ────────────────────────────────────────────── if [ "$SKIP_SEED" = false ]; then phase_header "Phase 1: Seed baseline accounts" if run_xcodebuild "Seed" "$SCHEME" -only-testing:"$TARGET/AAA_SeedTests"; then echo -e "${GREEN}✓ Seed passed${RESET}" else echo -e "${RED}✗ Seed FAILED — aborting.${RESET}"; exit 1 fi fi # ── Phase 1b: API contract tests (fast, standalone) ──────────── API_PASSED=true if [ "$ONLY_PARALLEL" = false ]; then phase_header "Phase 1b: API tests (standalone bundle, no app launch)" if run_xcodebuild "API" "$API_SCHEME"; then echo -e "${GREEN}✓ API tests passed${RESET}" else API_PASSED=false; echo -e "${RED}✗ API tests FAILED${RESET}" fi fi # ── Phase 2: Parallel (whole UI target minus phase-managed) ──── phase_header "Phase 2: Parallel UI suite ($WORKERS workers)" SKIP_ARGS=() for t in "${PHASE_MANAGED[@]}"; do SKIP_ARGS+=( -skip-testing:"$t" ); done PARALLEL_START=$(date +%s) if run_xcodebuild "Parallel" "$SCHEME" \ -only-testing:"$TARGET" "${SKIP_ARGS[@]}" \ -parallel-testing-enabled YES -parallel-testing-worker-count "$WORKERS"; then PARALLEL_PASSED=true; echo -e "${GREEN}✓ Parallel phase passed${RESET}" else PARALLEL_PASSED=false; echo -e "${RED}✗ Parallel phase FAILED${RESET}" fi PARALLEL_END=$(date +%s) # ── Phase 3: Sweep ───────────────────────────────────────────── if [ "$SKIP_CLEANUP" = false ]; then phase_header "Phase 3: Sweep leaked accounts + data" if run_xcodebuild "Sweep" "$SCHEME" -only-testing:"$TARGET/SuiteZZ_CleanupTests"; then echo -e "${GREEN}✓ Sweep passed${RESET}" else echo -e "${YELLOW}⚠ Sweep failed (non-blocking)${RESET}" fi fi # ── Summary ──────────────────────────────────────────────────── phase_header "Summary" echo " Total time: $(( $(date +%s) - OVERALL_START ))s" echo " Parallel: $(( PARALLEL_END - PARALLEL_START ))s @ $WORKERS workers" echo " API tests: $([ "$API_PASSED" = true ] && echo passed || echo FAILED)" echo " Results: $RESULTS_DIR/" echo "" if [ "${PARALLEL_PASSED:-false}" = true ] && [ "$API_PASSED" = true ]; then echo -e " ${GREEN}${BOLD}ALL TESTS PASSED${RESET}"; exit 0 else echo -e " ${RED}${BOLD}TESTS FAILED${RESET} — open $RESULTS_DIR/"; exit 1 fi