mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-05 18:38:54 +00:00
Based on the later commits (after the earlier experimentation and reversions), the meaningful end state of the PR is: **Summary:** Improve E2E CI reliability, performance, and security by introducing robust build caching with safe fingerprinting and cache isolation, duration-based test sharding with graceful fallbacks, more reliable E2E impact detection and status handling, stronger cancellation/concurrency behavior, multiple iOS Detox stability fixes, and hardening GitHub Actions to avoid executing PR-authored code. Also includes CI fixes for iOS builds and a temporary Android E2E test skip for a known regression. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mattermost Build <build@mattermost.com>
251 lines
9.7 KiB
Bash
Executable File
251 lines
9.7 KiB
Bash
Executable File
#!/bin/bash
|
||
# Pre-boot an iOS simulator for Detox/Maestro CI.
|
||
#
|
||
# Optimized vs the former inline Detox workflow script:
|
||
# - One blocking bootstatus for sims that already have CoreSimulator dirs (typical CI).
|
||
# - Autofill plists written while shutdown — no "init boot just to mkdir" on warm sims.
|
||
# - Skips autofill re-configuration when already applied (marker + plist check).
|
||
# - Drops the redundant 8–10s bootstatus poll after bootstatus already completed.
|
||
# - Brand-new simulators (simctl create) still use boot → shutdown → configure → boot.
|
||
#
|
||
# Requires: DEVICE_NAME, DEVICE_OS_VERSION. Writes SIMULATOR_ID to GITHUB_ENV when set.
|
||
# Optional:
|
||
# PREBOOT_SKIP_PREWARM=1 — Maestro only (uses listapps readiness). Detox must pre-warm.
|
||
# PREBOOT_PREWARM_SECS — first pre-warm wait (default 15; iPad often needs 10–15s).
|
||
#
|
||
# Grants notifications only. Maestro's Calls flows need mic/camera and grant them
|
||
# themselves per batch (detox/maestro/scripts/run_ci_batches.sh
|
||
# grant_ios_calls_permissions), so this script does not — see grant_notifications
|
||
# for why extra privacy grants are avoided here.
|
||
|
||
set -euo pipefail
|
||
|
||
readonly BUNDLE_ID="com.mattermost.rnbeta"
|
||
readonly AUTOFILL_MARKER="mattermost-ci-autofill-v1"
|
||
readonly PREWARM_SECS="${PREBOOT_PREWARM_SECS:-15}"
|
||
readonly REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
|
||
log() {
|
||
echo "[preboot $(date +%H:%M:%S)] $*"
|
||
}
|
||
|
||
sim_state() {
|
||
xcrun simctl list devices 2>/dev/null | grep "$SIMULATOR_ID" | sed -E 's/.*\((Booted|Shutdown|Booting|Creating)\)$/\1/' || echo "Unknown"
|
||
}
|
||
|
||
shutdown_if_booted() {
|
||
if [ "$(sim_state)" = "Booted" ]; then
|
||
log "Shutting down simulator to edit autofill plists..."
|
||
xcrun simctl shutdown "$SIMULATOR_ID" || true
|
||
for _ in 1 2 3 4 5 6 7 8; do
|
||
[ "$(sim_state)" = "Shutdown" ] && return 0
|
||
sleep 0.5
|
||
done
|
||
log "Warning: simulator may not have reached Shutdown state"
|
||
fi
|
||
}
|
||
|
||
boot_and_wait() {
|
||
log "Booting simulator $SIMULATOR_ID..."
|
||
xcrun simctl boot "$SIMULATOR_ID" 2>/dev/null || true
|
||
log "Waiting for boot to complete (blocking bootstatus)..."
|
||
xcrun simctl bootstatus "$SIMULATOR_ID"
|
||
}
|
||
|
||
autofill_already_configured() {
|
||
[ -f "$AUTOFILL_STAMP" ] && return 0
|
||
[ -f "$SETTINGS_PLIST" ] || return 1
|
||
plutil -extract restrictedBool.allowPasswordAutoFill.value raw "$SETTINGS_PLIST" 2>/dev/null | grep -qi 'false'
|
||
}
|
||
|
||
configure_autofill_offline() {
|
||
log "Disabling password autofill (simulator shut down)..."
|
||
mkdir -p "$SETTINGS_DIR"
|
||
if ! (cd "$REPO_ROOT/detox" && node utils/disable_ios_autofill.js --simulator-id "$SIMULATOR_ID"); then
|
||
echo "Failed to disable password autofill"
|
||
exit 1
|
||
fi
|
||
touch "$AUTOFILL_STAMP"
|
||
}
|
||
|
||
seed_password_defaults() {
|
||
log "Seeding Passwords.app defaults (best-effort)..."
|
||
xcrun simctl spawn "$SIMULATOR_ID" defaults write com.apple.Passwords AutoFill -bool NO 2>/dev/null || true
|
||
xcrun simctl spawn "$SIMULATOR_ID" defaults write com.apple.Passwords AutoSave -bool NO 2>/dev/null || true
|
||
xcrun simctl spawn "$SIMULATOR_ID" defaults write com.apple.Passwords credentialSaveNotificationsEnabled -bool NO 2>/dev/null || true
|
||
xcrun simctl spawn "$SIMULATOR_ID" defaults write com.apple.springboard AutoFillPasswords -bool NO 2>/dev/null || true
|
||
}
|
||
|
||
install_app() {
|
||
local app_path
|
||
app_path=$(ls -d "$REPO_ROOT"/mobile-artifacts/*.app 2>/dev/null | head -1)
|
||
if [ -z "$app_path" ]; then
|
||
echo "No .app bundle found in mobile-artifacts/"
|
||
ls -la "$REPO_ROOT/mobile-artifacts/" || true
|
||
exit 1
|
||
fi
|
||
log "Installing $app_path..."
|
||
xcrun simctl install "$SIMULATOR_ID" "$app_path"
|
||
}
|
||
|
||
grant_notifications() {
|
||
# Match proven Detox CI: notifications only. Deny camera/photos corrupts TCC on iOS 26.x;
|
||
# keep grants minimal so a failed privacy call does not cascade into broken UI hit-testing.
|
||
log "Pre-granting notification permission..."
|
||
if ! xcrun simctl privacy "$SIMULATOR_ID" grant notifications "$BUNDLE_ID"; then
|
||
log "Warning: notification grant failed (continuing; Detox may re-request at launch)"
|
||
fi
|
||
}
|
||
|
||
kill_app_via_launchd() {
|
||
local app_pid
|
||
app_pid=$(xcrun simctl spawn "$SIMULATOR_ID" launchctl list 2>/dev/null | \
|
||
grep "$BUNDLE_ID" | awk '{print $1}' | grep -E '^[0-9]+$' || true)
|
||
if [ -z "$app_pid" ]; then
|
||
return 0
|
||
fi
|
||
xcrun simctl spawn "$SIMULATOR_ID" kill -9 "$app_pid" 2>/dev/null || true
|
||
# launchd teardown is asynchronous — poll until the bundle leaves the job list
|
||
# so a timeout is not reported as a successful kill.
|
||
for _ in $(seq 12); do
|
||
if ! xcrun simctl spawn "$SIMULATOR_ID" launchctl list 2>/dev/null | grep -q "$BUNDLE_ID"; then
|
||
log "Killed app via launchd (PID $app_pid)"
|
||
return 0
|
||
fi
|
||
sleep 0.25
|
||
done
|
||
log "Warning: app still listed in launchd 3s after kill -9 (PID $app_pid)"
|
||
return 1
|
||
}
|
||
|
||
prewarm_app() {
|
||
local sleep_time="${1:-$PREWARM_SECS}"
|
||
log "Pre-warming app (${sleep_time}s launch window)..."
|
||
xcrun simctl launch "$SIMULATOR_ID" "$BUNDLE_ID" 2>/dev/null &
|
||
local launch_pid=$!
|
||
sleep "$sleep_time"
|
||
kill "$launch_pid" 2>/dev/null || true
|
||
wait "$launch_pid" 2>/dev/null || true
|
||
if ! kill_app_via_launchd; then
|
||
log "Pre-warm cleanup did not complete — treating pre-warm as failed"
|
||
return 1
|
||
fi
|
||
if xcrun simctl get_app_container "$SIMULATOR_ID" "$BUNDLE_ID" data 2>/dev/null; then
|
||
log "Data container verified"
|
||
return 0
|
||
fi
|
||
log "Data container not found after pre-warm"
|
||
return 1
|
||
}
|
||
|
||
wait_ready_quick() {
|
||
# bootstatus already blocked until ready; one -b check avoids an extra fixed sleep.
|
||
if xcrun simctl bootstatus "$SIMULATOR_ID" -b 2>/dev/null; then
|
||
log "Simulator ready"
|
||
return 0
|
||
fi
|
||
log "Quick readiness check failed — waiting up to 5s..."
|
||
for _ in 1 2 3 4 5; do
|
||
sleep 1
|
||
xcrun simctl bootstatus "$SIMULATOR_ID" -b 2>/dev/null && return 0
|
||
done
|
||
log "Warning: simulator may not be fully ready"
|
||
return 0
|
||
}
|
||
|
||
verify_health() {
|
||
if ! xcrun simctl bootstatus "$SIMULATOR_ID" -b 2>/dev/null; then
|
||
log "Simulator unresponsive — rebooting..."
|
||
xcrun simctl shutdown "$SIMULATOR_ID" || true
|
||
sleep 2
|
||
boot_and_wait
|
||
open -a Simulator --args -CurrentDeviceUDID "$SIMULATOR_ID" || true
|
||
fi
|
||
if ! xcrun simctl get_app_container "$SIMULATOR_ID" "$BUNDLE_ID" 2>/dev/null; then
|
||
log "App missing — reinstalling..."
|
||
install_app
|
||
fi
|
||
sudo mdutil -a -i off 2>/dev/null || true
|
||
}
|
||
|
||
find_or_create_simulator() {
|
||
local device_name="${DEVICE_NAME:?DEVICE_NAME is required}"
|
||
local os_version="${DEVICE_OS_VERSION:?DEVICE_OS_VERSION is required}"
|
||
|
||
log "Looking for simulator: $device_name ($os_version)"
|
||
SIMULATOR_ID=$(xcrun simctl list devices | grep "$device_name" | grep "$os_version" | head -1 | grep -oE '([0-9A-F-]{36})' || true)
|
||
if [ -z "$SIMULATOR_ID" ]; then
|
||
SIMULATOR_ID=$(xcrun simctl list devices "$os_version" 2>/dev/null | grep "$device_name" | head -1 | grep -oE '([0-9A-F-]{36})' || true)
|
||
fi
|
||
|
||
if [ -n "$SIMULATOR_ID" ]; then
|
||
log "Found existing simulator: $SIMULATOR_ID"
|
||
return 0
|
||
fi
|
||
|
||
log "Creating simulator..."
|
||
local device_type runtime
|
||
device_type=$(xcrun simctl list devicetypes | grep "${device_name} (" | head -1 | awk -F'[()]' '{print $(NF-1)}')
|
||
runtime=$(xcrun simctl list runtimes | grep "$os_version" | head -1 | sed 's/.* - \(.*\)/\1/')
|
||
if [ -z "$device_type" ] || [ -z "$runtime" ]; then
|
||
echo "Could not resolve device type or runtime for $device_name / $os_version"
|
||
exit 1
|
||
fi
|
||
SIMULATOR_ID=$(xcrun simctl create "CI-${device_name}" "$device_type" "$runtime")
|
||
log "Created simulator: $SIMULATOR_ID"
|
||
export SIMULATOR_NEEDS_INIT_BOOT=1
|
||
}
|
||
|
||
# ─── Main ────────────────────────────────────────────────────────────────────
|
||
|
||
find_or_create_simulator
|
||
|
||
SETTINGS_DIR="$HOME/Library/Developer/CoreSimulator/Devices/$SIMULATOR_ID/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles"
|
||
SETTINGS_PLIST="$SETTINGS_DIR/UserSettings.plist"
|
||
AUTOFILL_STAMP="$SETTINGS_DIR/.$AUTOFILL_MARKER"
|
||
|
||
if autofill_already_configured; then
|
||
log "Autofill restrictions already configured — skipping plist edit"
|
||
if [ "$(sim_state)" != "Booted" ]; then
|
||
boot_and_wait
|
||
else
|
||
log "Simulator already booted"
|
||
fi
|
||
elif [ -n "${SIMULATOR_NEEDS_INIT_BOOT:-}" ] || [ ! -d "$SETTINGS_DIR" ]; then
|
||
# Fresh simulator: one boot creates system group dirs, then configure offline, then boot again.
|
||
log "New simulator — init boot to create CoreSimulator dirs..."
|
||
boot_and_wait
|
||
shutdown_if_booted
|
||
configure_autofill_offline
|
||
boot_and_wait
|
||
else
|
||
# Warm simulator on CI image: configure offline, single boot.
|
||
log "Warm simulator — applying autofill offline, then single boot"
|
||
shutdown_if_booted
|
||
configure_autofill_offline
|
||
boot_and_wait
|
||
fi
|
||
|
||
seed_password_defaults
|
||
install_app
|
||
grant_notifications
|
||
|
||
if [ "${PREBOOT_SKIP_PREWARM:-}" != "1" ]; then
|
||
if ! prewarm_app "$PREWARM_SECS"; then
|
||
log "Retrying pre-warm with 25s window..."
|
||
prewarm_app 25 || log "Pre-warm failed — first Detox launch may be slow"
|
||
fi
|
||
else
|
||
log "Skipping pre-warm (PREBOOT_SKIP_PREWARM=1)"
|
||
fi
|
||
|
||
open -a Simulator --args -CurrentDeviceUDID "$SIMULATOR_ID" || true
|
||
wait_ready_quick
|
||
verify_health
|
||
|
||
if [ -n "${GITHUB_ENV:-}" ]; then
|
||
echo "SIMULATOR_ID=$SIMULATOR_ID" >> "$GITHUB_ENV"
|
||
fi
|
||
|
||
log "Done. SIMULATOR_ID=$SIMULATOR_ID"
|