chahinebrini 63fae25531 fix(android-protection): explicit specialUse FGS type — Samsung/Android 16 crash loop
RebreakVpnService.onStartCommand crashed with SecurityException because Android 16's validateForegroundServiceType rejects the implicit 2-arg startForeground(). Now passes FOREGROUND_SERVICE_TYPE_SPECIAL_USE explicitly (Google's documented best practice) and guards the call so a failed foreground promotion stops the service cleanly instead of crashing the app. Verified vs reported Galaxy A54 / Android 16 signature (97% of crash events, 1-user crash loop).

Bundles pending working-tree work across native/marketing/locales/mac + graphify-out rebuild. gitignore: google-services.json + /screenshots/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 22:33:28 +02:00

184 lines
7.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# capture-marketing.sh
#
# Erzeugt alle 9 Marketing-Screenshots und legt sie unter
# apps/marketing/public/preview/ mit den exakten Dateinamen ab.
#
# Voraussetzungen:
# 1. Maestro CLI installiert: curl -Ls "https://get.maestro.mobile.dev" | bash
# 2. iOS Simulator läuft mit der rebreak-native App (org.rebreak.app)
# z.B.: cd apps/rebreak-native && pnpm exec expo run:ios --device "iPhone 16 Pro"
# 3. E2E_TEST_USER und E2E_TEST_PASSWORD gesetzt (via Infisical oder manuell)
# 4. Staging-Backend erreichbar
#
# Aufruf:
# bash apps/rebreak-native/.maestro/screens/capture-marketing.sh
#
# Mit Credentials:
# E2E_TEST_USER=admin E2E_TEST_PASSWORD=<pw> \
# bash apps/rebreak-native/.maestro/screens/capture-marketing.sh
#
# Oder via Infisical:
# infisical run -- bash apps/rebreak-native/.maestro/screens/capture-marketing.sh
set -euo pipefail
# ─── Pfade ────────────────────────────────────────────────────────────────────
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
MAESTRO_DIR="$REPO_ROOT/apps/rebreak-native/.maestro"
TOUR_FLOW="$MAESTRO_DIR/screens/marketing-tour.yaml"
PREVIEW_DIR="$REPO_ROOT/apps/marketing/public/preview"
# ─── Credentials-Check ────────────────────────────────────────────────────────
if [[ -z "${E2E_TEST_USER:-}" || -z "${E2E_TEST_PASSWORD:-}" ]]; then
echo "FEHLER: E2E_TEST_USER und E2E_TEST_PASSWORD müssen gesetzt sein."
echo " Variante A: E2E_TEST_USER=admin E2E_TEST_PASSWORD=<pw> bash $0"
echo " Variante B: infisical run -- bash $0"
exit 1
fi
# ─── Maestro-Check ────────────────────────────────────────────────────────────
if ! command -v maestro &>/dev/null; then
echo "FEHLER: Maestro CLI nicht gefunden."
echo " Installieren: curl -Ls 'https://get.maestro.mobile.dev' | bash"
echo " Dann: export PATH=\"\$PATH:\$HOME/.maestro/bin\""
exit 1
fi
# ─── Zielverzeichnis sicherstellen ────────────────────────────────────────────
mkdir -p "$PREVIEW_DIR"
# ─── iOS Simulator: Status-Bar überschreiben ──────────────────────────────────
# Zeigt 9:41, volles Signal/WLAN/Akku — klassischer Apple-Screenshot-State
# Erfordert: xcrun simctl status_bar <UDID> override ...
# UDID des laufenden Simulators automatisch ermitteln
BOOTED_UDID=""
if command -v xcrun &>/dev/null; then
BOOTED_UDID=$(xcrun simctl list devices booted --json \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
for runtime, devices in data.get('devices', {}).items():
for d in devices:
if d.get('state') == 'Booted':
print(d['udid'])
exit()
" 2>/dev/null || true)
fi
if [[ -n "$BOOTED_UDID" ]]; then
echo "Status-Bar für Simulator $BOOTED_UDID auf 9:41 setzen..."
xcrun simctl status_bar "$BOOTED_UDID" override \
--time "9:41" \
--wifiMode "active" \
--wifiBars 3 \
--cellularMode "active" \
--cellularBars 4 \
--batteryState "charged" \
--batteryLevel 100 \
2>/dev/null || echo " Status-Bar-Override fehlgeschlagen (nicht kritisch — Flow läuft weiter)"
else
echo "Kein gestarteter iOS-Simulator gefunden — Status-Bar-Override übersprungen."
echo " Starte den Simulator zuerst: pnpm exec expo run:ios --device 'iPhone 16 Pro'"
fi
# ─── Maestro-Flow ausführen ────────────────────────────────────────────────────
echo ""
echo "Starte Marketing-Tour-Flow..."
echo " Flow: $TOUR_FLOW"
echo " User: $E2E_TEST_USER@rebreak.internal"
echo ""
# Maestro schreibt Screenshots nach ~/.maestro/tests/<timestamp>/screenshots/
# --output schreibt JUnit-Report, Screenshot-Pfad ist immer im Default-Verzeichnis
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
MAESTRO_SCREENSHOT_BASE="$HOME/.maestro/tests"
maestro test \
--env="E2E_TEST_USER=$E2E_TEST_USER" \
--env="E2E_TEST_PASSWORD=$E2E_TEST_PASSWORD" \
"$TOUR_FLOW"
echo ""
echo "Flow abgeschlossen. Screenshots suchen..."
# ─── Screenshot-Verzeichnis finden ────────────────────────────────────────────
# Maestro legt Screenshots im neuesten Testlauf-Verzeichnis ab
LATEST_RUN=$(ls -td "$MAESTRO_SCREENSHOT_BASE"/*/ 2>/dev/null | head -1 || true)
if [[ -z "$LATEST_RUN" ]]; then
echo "FEHLER: Kein Maestro-Testlauf-Verzeichnis gefunden unter $MAESTRO_SCREENSHOT_BASE"
echo " Manuell suchen: ls ~/.maestro/tests/"
exit 1
fi
SCREENSHOTS_DIR="$LATEST_RUN/screenshots"
if [[ ! -d "$SCREENSHOTS_DIR" ]]; then
# Maestro 1.38+ legt Screenshots direkt im Lauf-Verzeichnis ab
SCREENSHOTS_DIR="$LATEST_RUN"
fi
echo "Screenshots-Quellverzeichnis: $SCREENSHOTS_DIR"
echo ""
# ─── Mapping: Maestro-Name → Marketing-Dateiname ──────────────────────────────
# takeScreenshot-Wert in marketing-tour.yaml → Zieldateiname
declare -A MAPPING=(
["screenshots/01-onboarding"]="01-onboarding.png"
["screenshots/02-blocker"]="02-blocker.png"
["screenshots/03-blocked"]="03-blocked.png"
["screenshots/04-sos-lyra"]="04-sos-lyra.png"
["screenshots/05-breathing"]="05-breathing.png"
["screenshots/06-mail-schutz"]="06-mail-schutz.png"
["screenshots/07-community"]="07-community.png"
["screenshots/08-streak"]="08-streak.png"
["screenshots/09-geraete"]="09-geraete.png"
)
# Maestro benennt Screenshots nach dem letzten Teil des takeScreenshot-Werts
# "screenshots/01-onboarding" → Datei heißt "01-onboarding.png" im Maestro-Output
COPIED=0
MISSING=0
for MAESTRO_KEY in "${!MAPPING[@]}"; do
TARGET_NAME="${MAPPING[$MAESTRO_KEY]}"
# Maestro schreibt nur den Basename ohne Verzeichnis
BASENAME=$(basename "$MAESTRO_KEY")
SOURCE_FILE="$SCREENSHOTS_DIR/${BASENAME}.png"
if [[ -f "$SOURCE_FILE" ]]; then
cp "$SOURCE_FILE" "$PREVIEW_DIR/$TARGET_NAME"
echo " OK $TARGET_NAME$SOURCE_FILE"
((COPIED++))
else
echo " FEHLT $TARGET_NAME (Quelle nicht gefunden: $SOURCE_FILE)"
((MISSING++))
fi
done
# ─── Status-Bar zurücksetzen ──────────────────────────────────────────────────
if [[ -n "$BOOTED_UDID" ]]; then
xcrun simctl status_bar "$BOOTED_UDID" clear 2>/dev/null || true
fi
# ─── Zusammenfassung ──────────────────────────────────────────────────────────
echo ""
echo "─────────────────────────────────────────"
echo " Screenshots kopiert: $COPIED / 9"
if [[ $MISSING -gt 0 ]]; then
echo " Fehlend: $MISSING"
echo ""
echo " Für fehlende Screenshots:"
echo " - 03-blocked.png: manuell via Safari + aktivem VPN erstellen"
echo " Dann: cp /path/to/screenshot.png $PREVIEW_DIR/03-blocked.png"
echo ""
echo " Alle Screenshots unter: $SCREENSHOTS_DIR"
fi
echo " Zielverzeichnis: $PREVIEW_DIR"
echo "─────────────────────────────────────────"
if [[ $MISSING -gt 0 ]]; then
exit 1
fi