name: Android CI on: [push, pull_request, workflow_dispatch] permissions: pull-requests: write contents: read actions: read concurrency: group: build-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: build: name: Run tests and generate APK runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up JDK uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' - name: Cache packages id: cache-packages uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-packages-${{ runner.os }}-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', 'gradle.properties') }} restore-keys: gradle-packages-${{ runner.os }} - name: Access test login credentials run: | echo "TEST_USER_NAME=${{ secrets.TEST_USER_NAME }}" >> local.properties echo "TEST_USER_PASSWORD=${{ secrets.TEST_USER_PASSWORD }}" >> local.properties - name: AVD cache if: github.event_name != 'pull_request' uses: actions/cache@v3 id: avd-cache with: path: | ~/.android/avd/* ~/.android/adb* key: avd-tablet-api-24 - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' && github.event_name != 'pull_request' uses: reactivecircus/android-emulator-runner@v2 with: api-level: 24 force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: echo "Generated AVD snapshot for caching." - name: Run Instrumentation tests if: github.event_name != 'pull_request' uses: reactivecircus/android-emulator-runner@v2 with: api-level: 24 force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true profile: Nexus 10 script: | adb shell content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0 adb shell content insert --uri content://settings/system --bind name:s:user_rotation --bind value:i:0 adb emu geo fix 37.422131 -122.084801 ./gradlew connectedBetaDebugAndroidTest --stacktrace - name: Run Unit tests with unified coverage if: github.event_name != 'pull_request' run: ./gradlew -Pcoverage testBetaDebugUnitTestUnifiedCoverage --stacktrace - name: Run Unit tests without unified coverage if: github.event_name == 'pull_request' run: ./gradlew -Pcoverage testBetaDebugUnitTestCoverage --stacktrace - name: Upload Test Report to Codecov if: github.event_name != 'pull_request' run: | curl -Os https://uploader.codecov.io/latest/linux/codecov chmod +x codecov ./codecov -f "app/build/reports/jacoco/testBetaDebugUnitTestUnifiedCoverage/testBetaDebugUnitTestUnifiedCoverage.xml" -Z - name: Generate betaDebug APK run: bash ./gradlew assembleBetaDebug --stacktrace - name: Upload betaDebug APK uses: actions/upload-artifact@v4 with: name: betaDebugAPK path: app/build/outputs/apk/beta/debug/app-*.apk - name: Generate prodDebug APK run: bash ./gradlew assembleProdDebug --stacktrace - name: Upload prodDebug APK uses: actions/upload-artifact@v4 with: name: prodDebugAPK path: app/build/outputs/apk/prod/debug/app-*.apk - name: Comment on PR with APK download links if: github.event_name == 'pull_request' uses: actions/github-script@v6 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | try { const token = process.env.GITHUB_TOKEN; if (!token) { throw new Error('GITHUB_TOKEN is not set. Please check workflow permissions.'); } const { data: { artifacts } } = await github.rest.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: context.runId }); if (!artifacts || artifacts.length === 0) { console.log('No artifacts found for this workflow run.'); return; } const betaArtifact = artifacts.find(artifact => artifact.name === "betaDebugAPK"); const prodArtifact = artifacts.find(artifact => artifact.name === "prodDebugAPK"); if (!betaArtifact || !prodArtifact) { console.log('Could not find both Beta and Prod APK artifacts.'); console.log('Available artifacts:', artifacts.map(a => a.name).join(', ')); return; } const betaDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${context.runId}/artifacts/${betaArtifact.id}`; const prodDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${context.runId}/artifacts/${prodArtifact.id}`; const commentBody = ` 📱 **APK for pull request is ready to see the changes** 📱 - [Download Beta APK](${betaDownloadUrl}) - [Download Prod APK](${prodDownloadUrl}) `; await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: commentBody }); console.log('Successfully posted comment with APK download links'); } catch (error) { console.error('Error in PR comment creation:', error); if (error.message.includes('GITHUB_TOKEN')) { core.setFailed('Missing or invalid GITHUB_TOKEN. Please check repository secrets configuration.'); } else if (error.status === 403) { core.setFailed('Permission denied. Please check workflow permissions in repository settings.'); } else { core.setFailed(`Workflow failed: ${error.message}`); } }