#!/usr/bin/env bash # ============================================================================= # Run Robo Tests on Firebase Test Lab # ============================================================================= # This script runs Robo exploratory tests on Firebase Test Lab across the # configured device matrix. Robo tests automatically crawl the app UI to # find crashes and ANRs without requiring instrumented test code. # # Prerequisites: # 1. gcloud CLI installed and authenticated (gcloud auth login) # 2. Firebase project created and Blaze plan enabled # 3. Google Play Console linked to Firebase project (optional, for deep links) # 4. Service account with Firebase Test Lab admin role # # Usage: # ./run_robo_tests.sh [options] # # Options: # --project-id Firebase project ID (default: kordant-android) # --app-aab Path to app AAB (default: auto-detected) # --app-apk Path to app APK (default: auto-detected) # --robo-script Path to robo script JSON (default: robo_script.json) # --timeout Max robo crawl time in seconds (default: 600) # --dry-run Print gcloud command without executing # --help Show this help message # # Reference: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" # Default values PROJECT_ID="${FIREBASE_PROJECT_ID:-kordant-android}" APP_PATH="" ROBO_SCRIPT="${SCRIPT_DIR}/robo_script.json" MAX_CRAWL_TIME=600 DRY_RUN=false # Device matrix from test_matrix_config.yaml (generated via gcloud --device flags) # Each device runs with each orientation and locale combination declare -a DEVICE_ARGS=( # Pixel 6 - Primary target device (API 33) "--device model=Pixel6,version=33,locale=en_US,orientation=portrait" "--device model=Pixel6,version=33,locale=en_US,orientation=landscape" "--device model=Pixel6,version=33,locale=es_ES,orientation=portrait" "--device model=Pixel6,version=33,locale=es_ES,orientation=landscape" # Pixel 4 - Older Pixel device (API 30) "--device model=Pixel4,version=30,locale=en_US,orientation=portrait" "--device model=Pixel4,version=30,locale=en_US,orientation=landscape" "--device model=Pixel4,version=30,locale=es_ES,orientation=portrait" "--device model=Pixel4,version=30,locale=es_ES,orientation=landscape" # Samsung Galaxy S21 - Popular Samsung device (API 31) "--device model=GalaxyS21,version=31,locale=en_US,orientation=portrait" "--device model=GalaxyS21,version=31,locale=en_US,orientation=landscape" "--device model=GalaxyS21,version=31,locale=es_ES,orientation=portrait" "--device model=GalaxyS21,version=31,locale=es_ES,orientation=landscape" # Xiaomi Redmi Note 8 - Budget device (API 29) "--device model=RedmiNote8,version=29,locale=en_US,orientation=portrait" "--device model=RedmiNote8,version=29,locale=en_US,orientation=landscape" "--device model=RedmiNote8,version=29,locale=es_ES,orientation=portrait" "--device model=RedmiNote8,version=29,locale=es_ES,orientation=landscape" # Low-end device - Minimum spec target (API 28, 2GB RAM equivalent) "--device model=AquestM2,version=28,locale=en_US,orientation=portrait" "--device model=AquestM2,version=28,locale=en_US,orientation=landscape" "--device model=AquestM2,version=28,locale=es_ES,orientation=portrait" "--device model=AquestM2,version=28,locale=es_ES,orientation=landscape" ) # ============================================================ # Helper: Print usage # ============================================================ usage() { grep "^#" "$0" | grep -v "^#!/" | sed 's/^# //' exit 0 } # ============================================================ # Parse arguments # ============================================================ while [[ $# -gt 0 ]]; do case "$1" in --project-id) PROJECT_ID="$2" shift 2 ;; --app-aab) APP_PATH="$2" shift 2 ;; --app-apk) APP_PATH="$2" shift 2 ;; --robo-script) ROBO_SCRIPT="$2" shift 2 ;; --timeout) MAX_CRAWL_TIME="$2" shift 2 ;; --dry-run) DRY_RUN=true shift ;; --help) usage ;; *) echo "Unknown option: $1" echo "Usage: $0 --help" exit 1 ;; esac done # ============================================================ # Auto-detect APK/AAB path if not provided # ============================================================ if [ -z "$APP_PATH" ]; then # Prefer AAB for more accurate Play Store representation APP_PATH=$(find "$PROJECT_DIR/android/app/build/outputs/bundle" -name "*-release.aab" 2>/dev/null | head -1) # Fall back to APK if AAB not found if [ -z "$APP_PATH" ]; then APP_PATH=$(find "$PROJECT_DIR/android/app/build/outputs/apk" -name "*-release.apk" 2>/dev/null | head -1) fi # Last resort: any APK if [ -z "$APP_PATH" ]; then APP_PATH=$(find "$PROJECT_DIR/android/app/build/outputs/apk" -name "*.apk" 2>/dev/null | head -1) fi fi if [ -z "$APP_PATH" ]; then echo "Error: Could not find app APK or AAB." echo "" echo "Build the app first:" echo " ./gradlew :app:assembleProdRelease" echo " # or for AAB:" echo " ./gradlew :app:bundleProdRelease" exit 1 fi if [ ! -f "$ROBO_SCRIPT" ]; then echo "Warning: Robo script not found at $ROBO_SCRIPT" echo "Robo will run without guided script (fully autonomous crawl)." ROBO_SCRIPT="" fi # ============================================================ # Determine type flag based on file extension # ============================================================ if [[ "$APP_PATH" == *.aab ]]; then TYPE_FLAG="--type robo" APP_FLAG="--app-package com.kordant.android" AAB_FLAG="--aab \"$APP_PATH\"" APK_FLAG="" else TYPE_FLAG="--type robo" APP_FLAG="" AAB_FLAG="" APK_FLAG="--app \"$APP_PATH\"" fi # ============================================================ # Print configuration # ============================================================ echo "==========================================" echo "Firebase Test Lab - Robo Tests" echo "==========================================" echo "Project ID: $PROJECT_ID" echo "App: $APP_PATH" echo "Robo Script: ${ROBO_SCRIPT:-none (fully autonomous)}" echo "Max Crawl Time: ${MAX_CRAWL_TIME}s" echo "Devices: ${#DEVICE_ARGS[@]} configurations" echo "" # ============================================================ # Build gcloud command # ============================================================ GCLOUD_CMD="gcloud firebase test android run \ $TYPE_FLAG \ --project \"$PROJECT_ID\" \ $APP_FLAG \ $AAB_FLAG \ $APK_FLAG \ --timeout 60m \ --max-crawl-time $MAX_CRAWL_TIME \ --record-video \ --performance-metrics \ --results-history-name \"Kordant Android Robo Tests\"" # Add robo script if available if [ -n "$ROBO_SCRIPT" ]; then GCLOUD_CMD="$GCLOUD_CMD --robo-script \"$ROBO_SCRIPT\"" fi # Add device configurations for device in "${DEVICE_ARGS[@]}"; do GCLOUD_CMD="$GCLOUD_CMD $device" done echo "Command:" echo "$GCLOUD_CMD" echo "" if [ "$DRY_RUN" = true ]; then echo "DRY RUN - Command not executed." exit 0 fi # ============================================================ # Execute # ============================================================ echo "Starting Robo tests..." echo "" eval "$GCLOUD_CMD" EXIT_CODE=$? if [ $EXIT_CODE -eq 0 ]; then echo "" echo "✅ Robo tests completed successfully!" echo "View results in Firebase Console: https://console.firebase.google.com/project/$PROJECT_ID/testlab" echo "Review crawl maps, screenshots, and videos for each device." else echo "" echo "❌ Robo tests failed with exit code $EXIT_CODE" echo "View results in Firebase Console: https://console.firebase.google.com/project/$PROJECT_ID/testlab" fi exit $EXIT_CODE