#!/usr/bin/env bash set -Eeuo pipefail PACKAGE_NAME='com.d3ro.voice' APK_PATH="${1:-}" OUTPUT_ROOT="${2:-apps/mobile-rn/.maestro-output}" MAESTRO_BIN="${MAESTRO_BIN:-maestro}" if [[ -z "$APK_PATH" || ! -f "$APK_PATH" ]]; then echo 'A bundled E2E APK path is required.' >&2 exit 2 fi if ! command -v adb >/dev/null 2>&1; then echo 'adb is required.' >&2 exit 2 fi if ! command -v "$MAESTRO_BIN" >/dev/null 2>&1; then echo 'Maestro CLI is required.' >&2 exit 2 fi mkdir -p "$OUTPUT_ROOT" display_override_active='false' connected_devices="$(adb devices | awk '$2 == "device" { count += 1 } END { print count + 0 }')" if [[ "$connected_devices" -ne 1 && -z "${ANDROID_SERIAL:-}" ]]; then echo "Expected exactly one Android device, found $connected_devices. Set ANDROID_SERIAL explicitly." >&2 exit 2 fi adb get-state | grep -Fx 'device' >/dev/null collect_evidence() { local gate_status="$1" set +e adb exec-out screencap -p > "$OUTPUT_ROOT/final-screen.png" adb logcat -d > "$OUTPUT_ROOT/logcat.txt" adb shell dumpsys package "$PACKAGE_NAME" > "$OUTPUT_ROOT/package.txt" adb shell dumpsys activity activities > "$OUTPUT_ROOT/activities.txt" adb shell getprop > "$OUTPUT_ROOT/device-properties.txt" adb shell wm size > "$OUTPUT_ROOT/display-size.txt" adb shell wm density > "$OUTPUT_ROOT/display-density.txt" printf 'gate_status=%s\n' "$gate_status" > "$OUTPUT_ROOT/gate-status.txt" set -e } on_exit() { local exit_code="$?" if [[ "$exit_code" -ne 0 ]]; then collect_evidence "FAILED:$exit_code" fi if [[ "$display_override_active" == 'true' ]]; then set +e adb shell wm size reset >/dev/null set -e fi exit "$exit_code" } trap on_exit EXIT if adb shell pm path "$PACKAGE_NAME" | grep -F "package:" >/dev/null 2>&1; then adb uninstall "$PACKAGE_NAME" >/dev/null fi if adb shell pm path "$PACKAGE_NAME" | grep -F "package:" >/dev/null 2>&1; then echo 'Fresh-install precondition failed: package is still installed.' >&2 exit 1 fi adb logcat -c adb install --no-streaming "$APK_PATH" adb shell pm path "$PACKAGE_NAME" | grep -F "package:" >/dev/null run_flow() { local flow_name="$1" local flow_path="$2" "$MAESTRO_BIN" test "$flow_path" \ --format JUNIT \ --output "apps/mobile-rn/.maestro/${flow_name}.junit.xml" \ --test-output-dir "$OUTPUT_ROOT/$flow_name" } run_flow 'fresh-install-auth' 'apps/mobile-rn/.maestro/fresh-install-auth.yaml' run_flow 'fresh-install-signup' 'apps/mobile-rn/.maestro/fresh-install-signup.yaml' run_flow 'invite-deep-link' 'apps/mobile-rn/.maestro/invite-deep-link.yaml' adb shell wm size 840x1490 >/dev/null display_override_active='true' run_flow 'small-screen-signup' 'apps/mobile-rn/.maestro/fresh-install-signup.yaml' adb shell wm size reset >/dev/null display_override_active='false' if [[ -n "${MOBILE_E2E_EMAIL:-}" && -n "${MOBILE_E2E_PASSWORD:-}" ]]; then "$MAESTRO_BIN" test 'apps/mobile-rn/.maestro/authenticated-parity.yaml' \ -e MOBILE_E2E_EMAIL="$MOBILE_E2E_EMAIL" \ -e MOBILE_E2E_PASSWORD="$MOBILE_E2E_PASSWORD" \ --format JUNIT \ --output 'apps/mobile-rn/.maestro/authenticated-parity.junit.xml' \ --test-output-dir "$OUTPUT_ROOT/authenticated-parity" printf 'RUN:authenticated-parity\n' > "$OUTPUT_ROOT/external-auth-status.txt" authenticated_status='PASS' elif [[ -n "${MOBILE_E2E_EMAIL:-}" || -n "${MOBILE_E2E_PASSWORD:-}" ]]; then echo 'MOBILE_E2E_EMAIL and MOBILE_E2E_PASSWORD must be configured together.' >&2 exit 2 else printf 'NOT_RUN:missing_MOBILE_E2E_EMAIL_and_MOBILE_E2E_PASSWORD\n' > "$OUTPUT_ROOT/external-auth-status.txt" authenticated_status='NOT_RUN_MISSING_CREDENTIALS' fi printf '%s\n' \ '{' \ " \"authenticatedParity\": \"$authenticated_status\"," \ ' "canonicalHttpsAppLink": "NOT_RUN_RELEASE_SIGNED_ARTIFACT_REQUIRED",' \ ' "googleOAuthConsentCallback": "NOT_RUN_PROVIDER_ACCOUNT_REQUIRED",' \ ' "googlePlayPurchaseAndRestore": "NOT_RUN_PLAY_DISTRIBUTION_REQUIRED",' \ ' "adMobSsvCreditSettlement": "NOT_RUN_PRODUCTION_SSV_REQUIRED",' \ ' "externalShareTargetReceipt": "NOT_RUN_TARGET_APP_REQUIRED"' \ '}' > "$OUTPUT_ROOT/external-gates.json" collect_evidence 'COLLECTED' fatal_pattern='AndroidRuntime: Process: com\.d3ro\.voice|ActivityManager: ANR in com\.d3ro\.voice|>>> com\.d3ro\.voice <<<|Unable to load script|Could not connect to development server|ReactNativeJS:.*(TypeError|ReferenceError|Invariant Violation|Unhandled promise rejection)' fatal_count="$(grep -Ec "$fatal_pattern" "$OUTPUT_ROOT/logcat.txt" || true)" printf 'fatal_or_metro_error_matches=%s\n' "$fatal_count" > "$OUTPUT_ROOT/fatal-scan.txt" if [[ "$fatal_count" -ne 0 ]]; then echo 'Fatal Android, React Native, or Metro dependency error found during clean-room E2E.' >&2 exit 1 fi printf 'PASS\n' > "$OUTPUT_ROOT/gate-status.txt" trap - EXIT