Skip to content

refactor(ci): inline seed steps and add BrowserStack summaries (#181) #237

refactor(ci): inline seed steps and add BrowserStack summaries (#181)

refactor(ci): inline seed steps and add BrowserStack summaries (#181) #237

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