diff --git a/.github/actions/run-automation-tests/action.yml b/.github/actions/run-automation-tests/action.yml new file mode 100644 index 0000000000..deffe41d99 --- /dev/null +++ b/.github/actions/run-automation-tests/action.yml @@ -0,0 +1,126 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: Run all automated tests in the e2eTeest module + +inputs: + android-repository: + description: 'ground-android repository under test' + default: google/ground-android + + upload-artifacts: + description: 'Whether to upload the final emulator data artifacts' + default: 'false' + + google-maps-key: + description: 'A Google Maps API key' + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + repository: ${{ inputs.android-repository }} + + - name: Check Firebase emulator connection + shell: bash + run: | + echo "Checking connection to Firebase emulator..." + if curl -v http://localhost:4000; then + echo "Successfully connected to Firebase emulator!" + else + echo "Failed to connect to Firebase emulator (http://localhost:4000)" + exit 1 + fi + + - name: Validate MAPS_API_KEY + env: + MAPS_API_KEY: ${{ inputs.google-maps-key }} + shell: bash + run: | + if [ -z "${MAPS_API_KEY}" ]; then + echo "MAPS_API_KEY is missing from GitHub secrets" + exit 1 + fi + echo "MAPS_API_KEY is set" + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Enable KVM group perms + shell: bash + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Gradle cache + uses: gradle/actions/setup-gradle@v3 + + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-35-google_apis_playstore-x86_64-v1 + restore-keys: avd-35-google_apis_playstore-x86_64- + + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + target: google_apis_playstore + arch: x86_64 + force-avd-creation: true + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back emulated -logcat '*:e' + disable-animations: true + script: | + echo "Generated AVD snapshot for caching." + + - name: Run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + target: google_apis_playstore + arch: x86_64 + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back emulated -logcat '*:e' + disable-animations: true + script: | + echo "MAPS_API_KEY=${{ inputs.google-maps-key }}" > secrets.properties + ./gradlew :e2eTest:connectedLocalDebugAndroidTest --stacktrace + + - name: Upload test reports + if: ${{ inputs.upload-artifacts == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: '**/build/reports/androidTests' + retention-days: 7 + if-no-files-found: warn + + - name: Upload test screenshots + if: ${{ inputs.upload-artifacts == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: test-screenshots + path: '**/build/outputs/connected_android_test_additional_output' + retention-days: 7 + if-no-files-found: warn \ No newline at end of file diff --git a/.github/actions/submit-test/action.yml b/.github/actions/submit-test/action.yml deleted file mode 100644 index 4145499adb..0000000000 --- a/.github/actions/submit-test/action.yml +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -name: Submit to survey - -inputs: - android-repository: - description: 'ground-android repository under test' - default: google/ground-android - - platform-repository: - description: 'ground-platform repository under test (if applicable)' - default: google/ground-platform - - use-repo-data: - description: 'Whether to use the local repository emulator data or not' - default: 'true' - - upload-artifacts: - description: 'Whether to upload the final emulator data artifacts' - default: 'false' - - google-maps-key: - description: 'A Google Maps API key' - -runs: - using: composite - steps: - - name: Enable KVM group perms - shell: bash - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - ls /dev/kvm - - - name: Gradle cache - uses: gradle/actions/setup-gradle@v3 - - - name: AVD cache - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-24 - - - name: Checkout - uses: actions/checkout@v4 - with: - repository: ${{ inputs.android-repository }} - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 17 - - - name: Setup Gradle - uses: gradle/gradle-build-action@v3 - - - name: Set up Node.js 18 - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: Checkout ground-platform - uses: actions/checkout@v4 - with: - repository: ${{ inputs.platform-repository }} - path: ground-platform - - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - name: Build ground functions - shell: bash - run: | - cd ground-platform - npm run build:local - cd ../ - - - name: Install firebase-tools - shell: bash - run: | - npm install -g firebase-tools - - - name: Cache Firebase emulator - uses: actions/cache@v4 - with: - path: ~/.cache/firebase/emulators - key: ${{ runner.os }}-firebase-emulators-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-firebase-emulators- - - - name: Copy Firebase emulator data - uses: actions/download-artifact@v4 - if: inputs.use-repo-data != 'true' - with: - name: data-create - path: data/ - - - name: Copy the local repo data - if: inputs.use-repo-data == 'true' - shell: bash - run: cp -r ground-platform/data/test-create ground-platform/data/test - - - name: Replace Google Maps API key - shell: bash - env: - GOOGLE_MAPS_KEY: ${{ inputs.google-maps-key }} - run: | - sed -E -i 's/("current_key": ")[[:alnum:]_-]+(")/\1'"$GOOGLE_MAPS_KEY"'\2/' app/src/debug/local/google-services.json - - - name: Move the local google-services.json - shell: bash - run: | - cp -r app/src/debug/local/google-services.json app/src/debug/ - - - name: Build projects and run instrumentation tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 24 - target: google_apis_playstore - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back emulated -logcat '*:e' - disable-animations: true - script: | - firebase emulators:exec './gradlew :e2eTest:connectedLocalDebugAndroidTest --stacktrace' --config ground-platform/firebase.local.json --project local --import data/test --export-on-exit data/test - - - name: Upload test reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-reports - path: '**/build/reports/androidTests' - - - name: Upload screenshots - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-screenshots - path: '**/build/outputs/connected_android_test_additional_output' - - - name: Move Firebase emulator data (avoids .gitignore) - shell: bash - run: mv data/test/ ./test - - - name: Copy Firebase emulator data - if: inputs.upload-artifacts == 'true' - uses: actions/upload-artifact@v4 - with: - name: data-submit - path: '**/test' - retention-days: 7 - overwrite: true - if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 565a7775be..99730633a0 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -14,53 +14,27 @@ name: End to End Test on: - issue_comment: - types: [created] + push: + branches: + - master +# See docs/e2e-testing-doc.md for details jobs: e2eTest: runs-on: ubuntu-latest - timeout-minutes: 15 - if: github.event.issue.pull_request && contains(github.event.comment.body, '/e2eTest') + timeout-minutes: 45 + env: + FIREBASE_CLI_EXPERIMENTS: webframeworks steps: - - name: Start test - run: | - echo "Begin end to end test" + - name: Checkout repository + uses: actions/checkout@v6 + - name: Start Firebase emulator + uses: google/ground-platform/.github/actions/start-emulator@master - createTest: - needs: e2eTest - name: Create a new survey - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Run create-test - uses: google/ground-platform/.github/actions/create-test@master - with: - upload-artifacts: true - - submitTest: - needs: createTest - name: Submit to survey - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Run submit-test - uses: ./.github/actions/submit-test + - name: Run E2E Tests + uses: google/ground-android/.github/actions/run-automation-tests@master with: android-repository: ${{ github.repository }} - google-maps-key: ${{ secrets.GOOGLE_MAPS_KEY }} - use-repo-data: false + google-maps-key: ${{ secrets.MAPS_API_KEY }} upload-artifacts: true - - - verifyTest: - needs: submitTest - name: Verify survey submissions - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Run verify-test - uses: google/ground-platform/.github/actions/verify-test@master - with: - use-repo-data: false diff --git a/.github/workflows/test-submit.yml b/.github/workflows/test-submit.yml deleted file mode 100644 index 90a329a918..0000000000 --- a/.github/workflows/test-submit.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2024 The Ground Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -name: Submission Test - -on: - issue_comment: - types: [created] - -jobs: - submitTest: - if: github.event.issue.pull_request && contains(github.event.comment.body, '/submitTest') - name: Submit to survey - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout code - uses: actions/checkout@v6 - - name: Run submit-test - uses: ./.github/actions/submit-test - with: - android-repository: ${{ github.repository }} - google-maps-key: ${{ secrets.GOOGLE_MAPS_KEY }} - use-repo-data: true - upload-artifacts: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d9736a185..e6dda87116 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ secrets.properties !app/src/debug/local/google-services.json !.gitignore !gradle/ +!.github/ # Taken from Android.gitignore https://github.com/github/gitignore/blob/master/Android.gitignore # diff --git a/docs/e2e-testing-doc.md b/docs/e2e-testing-doc.md new file mode 100644 index 0000000000..d69c72907b --- /dev/null +++ b/docs/e2e-testing-doc.md @@ -0,0 +1,35 @@ +# End to end testing on Android + +## Current setup + +The Android E2E tests rely on a predefined test survey created by the web application and stored in the Firebase emulator data + +### 1. Predefined test survey +The test data was originally generated by running the web application locally against the Firebase emulator and exporting the emulator state + +### 2. Running the E2E tests +The CI workflow performs the following steps: +1. Start the Firebase emulator using the `start-emulator` action (which loads the predefined data) +2. Run the Android instrumentation tests in the `e2eTest` module against the emulator + +## Managing test data + +Since the Android app relies on specific survey structures, changes to the survey schema may require updating the test data + +1. Clone `ground-platform` and set up the project (see [documentation](https://github.com/google/ground-platform)) +2. Run the following command to launch the local Firebase emulator with test data: + ```bash + nx start-android-test-data + ``` +3. Manually set up or update the survey (via the web UI) to match the scenarios expected by the Android `e2eTest` module +4. Verify that all tests pass locally using the `localDebug` build variant +5. Export the updated emulator data to persist changes: + ```bash + firebase emulators:export data/test-android --project demo-local + ``` + *Note: This overwrites the data in `data/test-android`.* + +## Limitations +This setup does not fully prevent data drift between the web and Android apps. Any structural changes in surveys require manual updates to the predefined test data. + +This could be improved by implementing web E2E tests that generate Firestore test data on each run and trigger the Android E2E tests. Optionally, the web tests could also verify the final persisted state. \ No newline at end of file