refactor(ci): inline seed steps and add BrowserStack summaries (#181) #237
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Android Java CI | |
| on: | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - 'android-java/**' | |
| - '.github/workflows/android-java-ci.yml' | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - 'android-java/**' | |
| - '.github/workflows/android-java-ci.yml' | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint: | |
| name: Lint (ubuntu-latest) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| android-java/.gradle | |
| key: gradle-${{ runner.os }}-${{ hashFiles('android-java/gradle/wrapper/gradle-wrapper.properties', 'android-java/**/*.gradle*') }} | |
| restore-keys: | | |
| gradle-${{ runner.os }}- | |
| - name: Create test .env file | |
| run: | | |
| echo "DITTO_APP_ID=test" > .env | |
| echo "DITTO_PLAYGROUND_TOKEN=test" >> .env | |
| echo "DITTO_AUTH_URL=test" >> .env | |
| echo "DITTO_WEBSOCKET_URL=test" >> .env | |
| - name: Run Android linting | |
| working-directory: android-java | |
| run: ./gradlew lint | |
| build: | |
| name: Build APKs | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| - name: Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Create .env file | |
| run: | | |
| echo "DITTO_APP_ID=${{ secrets.DITTO_APP_ID }}" > .env | |
| echo "DITTO_PLAYGROUND_TOKEN=${{ secrets.DITTO_PLAYGROUND_TOKEN }}" >> .env | |
| echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env | |
| echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env | |
| - name: Build APKs | |
| working-directory: android-java | |
| run: ./gradlew assembleDebug assembleDebugAndroidTest | |
| - name: Upload APK artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-apks-${{ github.run_number }} | |
| path: | | |
| android-java/app/build/outputs/apk/debug/app-debug.apk | |
| android-java/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk | |
| retention-days: 1 | |
| browserstack-test: | |
| name: BrowserStack Device Testing | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Seed test task to Ditto Cloud | |
| id: seed_task | |
| uses: ./.github/actions/seed-ditto-document | |
| with: | |
| ditto-api-key: ${{ secrets.DITTO_API_KEY }} | |
| ditto-api-url: ${{ secrets.DITTO_API_URL }} | |
| app-name: 'android-java' | |
| - name: Download APK artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: android-apks-${{ github.run_number }} | |
| path: android-java/app/build/outputs/apk/ | |
| - name: Upload APKs to BrowserStack | |
| id: upload | |
| run: | | |
| CREDS="${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" | |
| # Upload app APK | |
| echo "π± Uploading app APK to BrowserStack..." | |
| APP_RESPONSE=$(curl -u "$CREDS" \ | |
| -X POST "https://api-cloud.browserstack.com/app-automate/espresso/v2/app" \ | |
| -F "file=@android-java/app/build/outputs/apk/debug/app-debug.apk" \ | |
| -F "custom_id=ditto-android-java-app") | |
| APP_URL=$(echo "$APP_RESPONSE" | jq -r .app_url) | |
| echo "app_url=$APP_URL" >> "$GITHUB_OUTPUT" | |
| if [ "$APP_URL" = "null" ] || [ -z "$APP_URL" ]; then | |
| echo "β Failed to upload app APK" | |
| echo "Response: $APP_RESPONSE" | |
| exit 1 | |
| fi | |
| echo "β App APK uploaded: $APP_URL" | |
| # Upload test APK | |
| echo "π§ͺ Uploading test APK to BrowserStack..." | |
| TEST_RESPONSE=$(curl -u "$CREDS" \ | |
| -X POST "https://api-cloud.browserstack.com/app-automate/espresso/v2/test-suite" \ | |
| -F "file=@android-java/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk" \ | |
| -F "custom_id=ditto-android-java-test") | |
| TEST_URL=$(echo "$TEST_RESPONSE" | jq -r .test_suite_url) | |
| echo "test_url=$TEST_URL" >> "$GITHUB_OUTPUT" | |
| if [ "$TEST_URL" = "null" ] || [ -z "$TEST_URL" ]; then | |
| echo "β Failed to upload test APK" | |
| echo "Response: $TEST_RESPONSE" | |
| exit 1 | |
| fi | |
| echo "β Test APK uploaded: $TEST_URL" | |
| - name: Execute tests on BrowserStack | |
| id: test | |
| run: | | |
| # Validate inputs before creating test execution request | |
| APP_URL="${{ steps.upload.outputs.app_url }}" | |
| TEST_URL="${{ steps.upload.outputs.test_url }}" | |
| echo "App URL: $APP_URL" | |
| echo "Test URL: $TEST_URL" | |
| if [ -z "$APP_URL" ] || [ "$APP_URL" = "null" ]; then | |
| echo "Error: No valid app URL available" | |
| exit 1 | |
| fi | |
| if [ -z "$TEST_URL" ] || [ "$TEST_URL" = "null" ]; then | |
| echo "Error: No valid test URL available" | |
| exit 1 | |
| fi | |
| # Create test execution request | |
| TITLE="${{ steps.seed_task.outputs.document-title }}" | |
| BUILD_RESPONSE=$(curl -s -u '${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}' \ | |
| -X POST 'https://api-cloud.browserstack.com/app-automate/espresso/v2/build' \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{ | |
| "app": "'"$APP_URL"'", | |
| "testSuite": "'"$TEST_URL"'", | |
| "devices": [ | |
| "Google Pixel 8-14.0", | |
| "Samsung Galaxy S23-13.0", | |
| "Google Pixel 6-12.0", | |
| "OnePlus 9-11.0" | |
| ], | |
| "project": "Ditto Android Java", | |
| "buildName": "Build #${{ github.run_number }}", | |
| "buildTag": "${{ github.ref_name }}", | |
| "deviceLogs": true, | |
| "video": true, | |
| "networkLogs": true, | |
| "autoGrantPermissions": true, | |
| "instrumentationLogs": true, | |
| "instrumentationOptions": { | |
| "github_test_doc_id": "'"$TITLE"'" | |
| } | |
| }') | |
| BUILD_ID=$(echo "$BUILD_RESPONSE" | jq -r .build_id) | |
| if [ "$BUILD_ID" = "null" ] || [ -z "$BUILD_ID" ]; then | |
| echo "Error: Failed to create BrowserStack build" | |
| echo "Response: $BUILD_RESPONSE" | |
| exit 1 | |
| fi | |
| echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT | |
| echo "Build started with ID: $BUILD_ID" | |
| - name: Wait for BrowserStack tests to complete | |
| run: | | |
| BUILD_ID="${{ steps.test.outputs.build_id }}" | |
| if [ "$BUILD_ID" = "null" ] || [ -z "$BUILD_ID" ]; then | |
| echo "β No valid BUILD_ID available" | |
| exit 1 | |
| fi | |
| MAX_WAIT_TIME=1200 # 20 minutes | |
| CHECK_INTERVAL=30 # Check every 30 seconds | |
| ELAPSED=0 | |
| echo "β³ Waiting for test execution to complete..." | |
| while [ $ELAPSED -lt $MAX_WAIT_TIME ]; do | |
| RESPONSE=$(curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ | |
| "https://api-cloud.browserstack.com/app-automate/espresso/v2/builds/$BUILD_ID") | |
| STATUS=$(echo "$RESPONSE" | jq -r .status) | |
| if [ "$STATUS" = "null" ] || [ -z "$STATUS" ]; then | |
| echo "β οΈ API error, retrying... (${ELAPSED}s elapsed)" | |
| sleep $CHECK_INTERVAL | |
| ELAPSED=$((ELAPSED + CHECK_INTERVAL)) | |
| continue | |
| fi | |
| echo "π Status: $STATUS (${ELAPSED}s elapsed)" | |
| # Check for completion | |
| if [[ "$STATUS" =~ ^(done|failed|error|passed|completed)$ ]]; then | |
| echo "β Build completed with status: $STATUS" | |
| break | |
| fi | |
| sleep $CHECK_INTERVAL | |
| ELAPSED=$((ELAPSED + CHECK_INTERVAL)) | |
| done | |
| # Get final results | |
| FINAL_RESULT=$(curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ | |
| "https://api-cloud.browserstack.com/app-automate/espresso/v2/builds/$BUILD_ID") | |
| echo "π Final results:" | |
| echo "$FINAL_RESULT" | jq . | |
| # Validate and check results | |
| if echo "$FINAL_RESULT" | jq -e .devices > /dev/null 2>&1; then | |
| BUILD_STATUS=$(echo "$FINAL_RESULT" | jq -r .status) | |
| if [ "$BUILD_STATUS" != "passed" ]; then | |
| echo "β Tests failed with status: $BUILD_STATUS" | |
| FAILED_DEVICES=$(echo "$FINAL_RESULT" | jq -r '.devices[] | select(.sessions[].status != "passed") | .device') | |
| if [ -n "$FAILED_DEVICES" ]; then | |
| echo "Failed on devices: $FAILED_DEVICES" | |
| fi | |
| exit 1 | |
| else | |
| echo "π All tests passed successfully!" | |
| fi | |
| else | |
| echo "β οΈ Could not parse final results" | |
| exit 1 | |
| fi | |
| - name: Upload test artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results | |
| path: | | |
| android-java/app/build/outputs/apk/ | |
| android-java/app/build/reports/ | |
| summary: | |
| name: CI Report | |
| runs-on: ubuntu-latest | |
| needs: [browserstack-test] | |
| if: always() | |
| steps: | |
| - name: Report Results | |
| run: | | |
| echo "## π± Android Java CI" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Overall status | |
| if [[ "${{ needs.lint.result }}" == "success" && \ | |
| "${{ needs.build.result }}" == "success" && \ | |
| "${{ needs.browserstack-test.result }}" == "success" ]]; then | |
| echo "**Overall Status:** β All checks passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "**Overall Status:** β Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Lint | ${{ needs.lint.result == 'success' && 'β Passed' || 'β Failed' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Build | ${{ needs.build.result == 'success' && 'β Passed' || 'β Failed' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| BrowserStack Tests | ${{ needs.browserstack-test.result == 'success' && 'β Passed' || (needs.browserstack-test.result == 'skipped' && 'βοΈ Skipped') || 'β Failed' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # BrowserStack link | |
| if [[ "${{ needs.browserstack-test.result }}" != "skipped" ]]; then | |
| echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "π€ [View Test Results](https://app-automate.browserstack.com/builds?project=Ditto+Android+Java&build=Build+%23${{ github.run_number }})" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tested Devices:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Google Pixel 8 (Android 14)" >> $GITHUB_STEP_SUMMARY | |
| echo "- Samsung Galaxy S23 (Android 13)" >> $GITHUB_STEP_SUMMARY | |
| echo "- Google Pixel 6 (Android 12)" >> $GITHUB_STEP_SUMMARY | |
| echo "- OnePlus 9 (Android 11)" >> $GITHUB_STEP_SUMMARY | |
| fi |