Files
Kordant/android/firebase-test-lab/run_robo_tests.sh

240 lines
7.9 KiB
Bash
Executable File

#!/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