Files
Kordant/.github/workflows/firebase-test-lab.yml

380 lines
16 KiB
YAML

name: Firebase Test Lab
on:
push:
branches: [main]
paths:
- 'android/**'
- '.github/workflows/firebase-test-lab.yml'
pull_request:
branches: [main]
paths:
- 'android/**'
- '.github/workflows/firebase-test-lab.yml'
# Allow manual trigger for release verification
workflow_dispatch:
inputs:
build_type:
description: 'Build type to test'
required: true
default: 'release'
type: choice
options:
- release
- debug
skip_robo:
description: 'Skip Robo tests'
required: false
default: false
type: boolean
skip_instrumentation:
description: 'Skip instrumentation tests'
required: false
default: false
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# ============================================================================
# Build Job: Compile the Android app and test APK
# ============================================================================
build:
name: Build APKs
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: wrapper
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
android/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/*.gradle.kts', 'android/gradle/libs.versions.toml') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build release APK
run: |
cd android
./gradlew :app:assembleProdRelease :app:assembleProdDebugAndroidTest --no-daemon
- name: Upload app APK
uses: actions/upload-artifact@v4
with:
name: app-release-apk
path: android/app/build/outputs/apk/prod/release/*.apk
retention-days: 7
- name: Upload test APK
uses: actions/upload-artifact@v4
with:
name: app-test-apk
path: android/app/build/outputs/apk/androidTest/prod/debug/*.apk
retention-days: 7
- name: Upload AAB (for Robo tests)
uses: actions/upload-artifact@v4
with:
name: app-release-aab
path: android/app/build/outputs/bundle/prodRelease/*.aab
retention-days: 7
# ============================================================================
# Robo Tests Job: Crash/ANR detection via autonomous crawl
# ============================================================================
robo-tests:
name: Robo Tests
needs: build
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY_TEST_LAB }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ secrets.FIREBASE_PROJECT_ID || 'kordant-android' }}
- name: Download AAB
uses: actions/download-artifact@v4
with:
name: app-release-aab
path: build/
- name: Download APK (fallback)
uses: actions/download-artifact@v4
with:
name: app-release-apk
path: build/
- name: Run Robo tests
id: robo
run: |
# Check which file type is available (prefer AAB)
AAB_FILE=$(find build -name "*.aab" -type f 2>/dev/null | head -1)
APK_FILE=$(find build -name "*prod-release.apk" -type f 2>/dev/null | head -1)
SCRIPT_DIR="android/firebase-test-lab"
if [ -n "$AAB_FILE" ]; then
echo "Using AAB: $AAB_FILE"
gcloud firebase test android run \
--type robo \
--project "${{ secrets.FIREBASE_PROJECT_ID || 'kordant-android' }}" \
--app-package "com.kordant.android" \
--aab "$AAB_FILE" \
--robo-script "$SCRIPT_DIR/robo_script.json" \
--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 \
--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 \
--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 \
--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 \
--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 \
--timeout 60m \
--max-crawl-time 600 \
--record-video \
--performance-metrics \
--results-history-name "Kordant Android Robo CI" \
--fail-fast \
|| ROBO_EXIT=$?
elif [ -n "$APK_FILE" ]; then
echo "Using APK: $APK_FILE"
gcloud firebase test android run \
--type robo \
--project "${{ secrets.FIREBASE_PROJECT_ID || 'kordant-android' }}" \
--app "$APK_FILE" \
--robo-script "$SCRIPT_DIR/robo_script.json" \
--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 \
--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 \
--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 \
--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 \
--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 \
--timeout 60m \
--max-crawl-time 600 \
--record-video \
--performance-metrics \
--results-history-name "Kordant Android Robo CI" \
--fail-fast \
|| ROBO_EXIT=$?
else
echo "No APK or AAB found!"
exit 1
fi
echo "ROBO_EXIT_CODE=${ROBO_EXIT:-0}" >> $GITHUB_OUTPUT
- name: Upload Robo test results
if: always()
uses: actions/upload-artifact@v4
with:
name: robo-test-results
path: |
android/firebase-test-lab/robo_script.json
build/*.aab
build/*prod-release*.apk
retention-days: 14
- name: Mark build as failed if Robo tests failed
if: steps.robo.outputs.ROBO_EXIT_CODE != '0'
run: |
echo "❌ Robo tests failed with exit code ${{ steps.robo.outputs.ROBO_EXIT_CODE }}"
echo "Review results in Firebase Console"
exit 1
# ============================================================================
# Instrumentation Tests Job: UI tests with assertions
# ============================================================================
instrumentation-tests:
name: Instrumentation Tests
needs: build
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY_TEST_LAB }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ secrets.FIREBASE_PROJECT_ID || 'kordant-android' }}
- name: Download APKs
uses: actions/download-artifact@v4
with:
name: app-release-apk
path: build/apk/
- uses: actions/download-artifact@v4
with:
name: app-test-apk
path: build/apk/
- name: Run Instrumentation tests
id: instrumentation
run: |
APP_APK=$(find build/apk -name "*prod-release.apk" -type f 2>/dev/null | head -1)
TEST_APK=$(find build/apk -name "*androidTest*.apk" -type f 2>/dev/null | head -1)
if [ -z "$APP_APK" ] || [ -z "$TEST_APK" ]; then
echo "Error: Could not find APK files."
echo "App APK: ${APP_APK:-not found}"
echo "Test APK: ${TEST_APK:-not found}"
exit 1
fi
echo "App APK: $APP_APK"
echo "Test APK: $TEST_APK"
gcloud firebase test android run \
--type instrumentation \
--project "${{ secrets.FIREBASE_PROJECT_ID || 'kordant-android' }}" \
--app "$APP_APK" \
--test "$TEST_APK" \
--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 \
--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 \
--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 \
--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 \
--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 \
--timeout 60m \
--num-flaky-test-attempts 2 \
--record-video \
--performance-metrics \
--results-history-name "Kordant Android Instrumentation CI" \
--fail-fast \
|| INSTR_EXIT=$?
echo "INSTR_EXIT_CODE=${INSTR_EXIT:-0}" >> $GITHUB_OUTPUT
- name: Upload instrumentation test results
if: always()
uses: actions/upload-artifact@v4
with:
name: instrumentation-test-results
path: build/apk/
retention-days: 14
- name: Mark build as failed if instrumentation tests failed
if: steps.instrumentation.outputs.INSTR_EXIT_CODE != '0'
run: |
echo "❌ Instrumentation tests failed with exit code ${{ steps.instrumentation.outputs.INSTR_EXIT_CODE }}"
echo "Review results in Firebase Console"
exit 1
# ============================================================================
# Summary Job: Collect all test results
# ============================================================================
test-summary:
name: Test Summary
needs: [robo-tests, instrumentation-tests]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check test results
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 Firebase Test Lab - CI Results Summary"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "View detailed results in Firebase Console:"
echo " https://console.firebase.google.com/project/${{ secrets.FIREBASE_PROJECT_ID || 'kordant-android' }}/testlab"
echo ""
echo "Devices tested:"
echo " ✅ Pixel 6 (API 33) - primary target"
echo " ✅ Pixel 4 (API 30) - older device"
echo " ✅ Galaxy S21 (API 31) - Samsung"
echo " ✅ Redmi Note 8 (API 29) - Xiaomi"
echo " ✅ Aquest M2 (API 28) - low-end device"
echo ""
echo "Orientations: portrait, landscape"
echo "Locales: en_US, es_ES"
echo ""
- name: Send notification on failure
if: failure()
run: |
echo "::warning::Firebase Test Lab tests failed. Check the Firebase Console for details."
- name: Determine overall status
run: |
if [ "${{ needs.robo-tests.result }}" = "failure" ] || [ "${{ needs.instrumentation-tests.result }}" = "failure" ]; then
echo "❌ Firebase Test Lab: FAILED"
exit 1
else
echo "✅ Firebase Test Lab: PASSED"
fi