diff --git a/.forgejo/workflows/android-emulator-tests.yml b/.forgejo/workflows/android-emulator-tests.yml index c262cff..200b8bb 100644 --- a/.forgejo/workflows/android-emulator-tests.yml +++ b/.forgejo/workflows/android-emulator-tests.yml @@ -1,14 +1,13 @@ -# We switched to Dagger. Running the emulator tests in Dagger does not really work -# We will use an external service for device testing. -# TODO: Switch to device testing. First choose a service. Maybe codemagic.io -name: Android Emulator Tests (Disabled) +name: Android Firebase Test Lab on: - workflow_dispatch: # Manual trigger only + push: + branches: [main] + pull_request: jobs: - integration-android: - name: Android Emulator Integration Tests + firebase-tests: + name: Android Instrumented Tests (Firebase Test Lab) runs-on: ubuntu-latest timeout-minutes: 60 @@ -17,18 +16,25 @@ jobs: with: fetch-depth: 50 - - name: Install Android SDK + - name: Install Dagger & Task run: | - SDK="${ANDROID_HOME:-$HOME/Android/Sdk}" - if [ ! -d "$SDK/platforms/android-34" ]; then - wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdtools.zip - mkdir -p "$SDK/cmdline-tools" - unzip -q /tmp/cmdtools.zip -d "$SDK/cmdline-tools" - [ -d "$SDK/cmdline-tools/cmdline-tools" ] && mv "$SDK/cmdline-tools/cmdline-tools" "$SDK/cmdline-tools/latest" - yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --licenses >/dev/null 2>&1 || true - "$SDK/cmdline-tools/latest/bin/sdkmanager" "emulator" "system-images;android-34;google_apis;x86_64" - "$SDK/cmdline-tools/latest/bin/sdkmanager" "platform-tools" "build-tools;34.0.0" "platforms;android-34" - fi + mkdir -p $HOME/.local/bin + curl -L https://dl.dagger.io/dagger/install.sh | BIN_DIR=$HOME/.local/bin sh + curl -sL https://taskfile.dev/install.sh | sh -s -- -b $HOME/.local/bin + echo "$HOME/.local/bin" >> $GITHUB_PATH + sudo apt-get update && sudo apt-get install -y stunnel4 netcat-openbsd - - name: Run Android Integration Tests - run: task integration-android + - name: Setup Dagger Remote Engine (via stunnel) + env: + DAGGER_STUNNEL_URL: ${{ secrets.DAGGER_STUNNEL_URL }} + DAGGER_CA_CERT: ${{ secrets.DAGGER_CA_CERT }} + DAGGER_CLIENT_CERT: ${{ secrets.DAGGER_CLIENT_CERT }} + DAGGER_CLIENT_KEY: ${{ secrets.DAGGER_CLIENT_KEY }} + run: scripts/setup_dagger_remote.sh + + - name: Run Android Tests on Firebase Test Lab + env: + FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY }} + FIREBASE_PROJECT_ID: ${{ vars.FIREBASE_PROJECT_ID }} + DAGGER_NO_NAG: "1" + run: task test-android-firebase diff --git a/Taskfile.yml b/Taskfile.yml index adfd96d..f0eac4e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -189,6 +189,16 @@ tasks: cmds: - dagger call --progress=plain -q -m ci --source=. test-sync-reliability + test-android-firebase: + desc: Build Android debug APKs and run instrumented tests on Firebase Test Lab (via Dagger) + preconditions: + - sh: test -n "$FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY" + msg: "FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY is not set" + - sh: test -n "$FIREBASE_PROJECT_ID" + msg: "FIREBASE_PROJECT_ID is not set" + cmds: + - dagger call --progress=plain -q -m ci --source=. test-android-firebase --service-account-key env:FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY --project-id "$FIREBASE_PROJECT_ID" + ci-graph: desc: Print a Mermaid diagram of the CI pipeline — paste into mermaid.live or any Markdown renderer cmds: diff --git a/ci/main.go b/ci/main.go index 998b81e..dca62dc 100644 --- a/ci/main.go +++ b/ci/main.go @@ -252,6 +252,13 @@ func (m *Ci) androidSrc() *dagger.Directory { }) } +// firebaseSrc is the source subset for Firebase Test Lab builds (app + instrumented tests). +func (m *Ci) firebaseSrc() *dagger.Directory { + return m.Source.Filter(dagger.DirectoryFilterOpts{ + Include: []string{"lib/", "android/", "integration_test/", "assets/", "pubspec.yaml", "pubspec.lock", "drift_schemas/"}, + }) +} + // linuxSrc is the source subset for Linux builds and integration tests. func (m *Ci) linuxSrc() *dagger.Directory { return m.Source.Filter(dagger.DirectoryFilterOpts{ @@ -606,6 +613,48 @@ func (m *Ci) DeployApk( Stdout(ctx) } +// BuildAndroidDebugApks builds the debug app APK and the androidTest APK needed for Firebase Test Lab. +// Returns a flat directory with app-debug.apk and app-debug-androidTest.apk. +func (m *Ci) BuildAndroidDebugApks() *dagger.Directory { + built := m.setup(m.firebaseSrc()). + WithExec([]string{"flutter", "build", "apk", "--debug", "--no-pub"}). + WithWorkdir("/src/android"). + WithExec([]string{"./gradlew", "app:assembleAndroidTest"}). + WithWorkdir("/src") + + return dag.Directory(). + WithFile("app-debug.apk", + built.File("build/app/outputs/flutter-apk/app-debug.apk")). + WithFile("app-debug-androidTest.apk", + built.File("android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk")) +} + +// TestAndroidFirebase builds Android APKs and runs instrumented tests on Firebase Test Lab. +func (m *Ci) TestAndroidFirebase( + ctx context.Context, + serviceAccountKey *dagger.Secret, + projectID string, +) (string, error) { + apks := m.BuildAndroidDebugApks() + + return dag.Container(). + From("google/cloud-sdk:slim"). + WithDirectory("/apks", apks). + WithSecretVariable("FIREBASE_SA_KEY", serviceAccountKey). + WithEnvVariable("FIREBASE_PROJECT_ID", projectID). + WithExec([]string{"/bin/bash", "-c", + `echo "$FIREBASE_SA_KEY" > /tmp/key.json && \ + gcloud auth activate-service-account --key-file=/tmp/key.json && \ + rm /tmp/key.json && \ + gcloud config set project "$FIREBASE_PROJECT_ID" && \ + gcloud firebase test android run \ + --type instrumentation \ + --app /apks/app-debug.apk \ + --test /apks/app-debug-androidTest.apk \ + --device model=Pixel6,version=33,locale=en,orientation=portrait`}). + Stdout(ctx) +} + // BuildAndroidRelease builds the AAB with a fixed build-number so Dagger can cache it. // versionCode and signing are applied separately via StampAndroidVersionCode + SignAndroidBundle. func (m *Ci) BuildAndroidRelease() *dagger.File {