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>
184 lines
7.7 KiB
Bash
Executable File
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
|