Skip to content

refactor(ci): standardize workflows and add retry mechanisms and centralized config #186

refactor(ci): standardize workflows and add retry mechanisms and centralized config

refactor(ci): standardize workflows and add retry mechanisms and centralized config #186

Workflow file for this run

#
# .github/workflows/android-cpp-ci.yml
# Workflow for building and testing android-cpp with BrowserStack integration
#
---
name: Android C++ CI
on:
pull_request:
paths:
- "android-cpp/**"
- ".github/workflows/android-cpp-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: Install clang-format
run: |
sudo apt-get update
sudo apt-get install -y clang-format
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
android-cpp/.gradle
key: gradle-${{ runner.os }}-${{ hashFiles('android-cpp/QuickStartTasksCPP/gradle/wrapper/gradle-wrapper.properties', 'android-cpp/QuickStartTasksCPP/**/*.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-cpp/QuickStartTasksCPP
run: ./gradlew lint
- name: Check C++ formatting
working-directory: android-cpp/QuickStartTasksCPP/app/src/main/cpp
run: |
echo "Checking C++ code formatting with clang-format..."
find . -name "*.cpp" -o -name "*.h" | xargs clang-format --dry-run --Werror
if [ $? -eq 0 ]; then
echo "✅ C++ code formatting check passed"
else
echo "❌ C++ code formatting issues found. Run clang-format to fix."
exit 1
fi
build:
name: Build APK
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 APK
working-directory: android-cpp/QuickStartTasksCPP
run: ./gradlew assembleRelease
- name: Upload APK artifacts
uses: actions/upload-artifact@v4
with:
name: android-cpp-apks
path: |
android-cpp/QuickStartTasksCPP/app/build/outputs/apk/release/app-release-unsigned.apk
retention-days: 1
browserstack-android:
name: BrowserStack Android Testing
runs-on: ubuntu-latest
needs: [build]
if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
timeout-minutes: 150
outputs:
build_id: ${{ steps.test.outputs.build_id }}
steps:
- uses: actions/checkout@v4
- name: Download APK artifacts
uses: actions/download-artifact@v4
with:
name: android-cpp-apks
path: android-cpp/QuickStartTasksCPP/app/build/outputs/apk/release/
- name: Setup Java for Appium test
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
- name: Upload APK to BrowserStack
id: upload
run: |
echo "Uploading APK to BrowserStack..."
CREDS="${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}"
APP_RESPONSE=$(curl -u "$CREDS" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "file=@android-cpp/QuickStartTasksCPP/app/build/outputs/apk/release/app-release-unsigned.apk" \
-F "custom_id=ditto-android-cpp-appium-app")
APP_URL=$(echo "$APP_RESPONSE" | yq eval -p=json .app_url)
if [ "$APP_URL" = "null" ] || [ -z "$APP_URL" ]; then
echo "Error: Failed to upload APK"
exit 1
fi
echo "app_url=$APP_URL" >> "$GITHUB_OUTPUT"
echo "APK uploaded: $APP_URL"
- name: Get BrowserStack build info
id: build-info
uses: ./.github/actions/get-browserstack-build-info
with:
project-name: 'quickstart - Android C++'
- name: Seed and execute Appium tests on BrowserStack
id: test
uses: nick-fields/retry@v3
with:
max_attempts: 5
timeout_minutes: 20
retry_wait_seconds: 900
command: |
# Seed test task to Ditto Cloud
echo "Seeding test task to Ditto Cloud..."
TIMESTAMP=$(date +%s)
INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP))
SEED_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
-H 'Content-type: application/json' \
-H "Authorization: Bearer ${{ secrets.DITTO_API_KEY }}" \
-d "{
\"statement\": \"INSERT INTO tasks DOCUMENTS (:newTask) ON ID CONFLICT DO UPDATE\",
\"args\": {
\"newTask\": {
\"_id\": \"${INVERTED_TIMESTAMP}_android-cpp_ci_test_${{ github.run_id }}_${{ github.run_number }}\",
\"title\": \"${INVERTED_TIMESTAMP}_android-cpp_ci_test_${{ github.run_id }}_${{ github.run_number }}\",
\"done\": false,
\"deleted\": false
}
}
}" \
"https://${{ secrets.DITTO_API_URL }}/api/v4/store/execute")
HTTP_CODE=$(echo "$SEED_RESPONSE" | tail -n1)
BODY=$(echo "$SEED_RESPONSE" | sed '$d')
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then
TASK_TITLE="${INVERTED_TIMESTAMP}_android-cpp_ci_test_${{ github.run_id }}_${{ github.run_number }}"
echo "Seeded task: $TASK_TITLE"
else
echo "Error: Failed to seed task. HTTP Status: $HTTP_CODE"
echo "Response: $BODY"
exit 1
fi
# Load device from centralized config
DEVICE=$(yq eval '.["android-cpp"].devices[0]' .github/browserstack-devices.yml)
echo "Using device: $DEVICE"
# Execute tests
cd android-cpp/QuickStartTasksCPP/appium-test
export BROWSERSTACK_USERNAME="${{ secrets.BROWSERSTACK_USERNAME }}"
export BROWSERSTACK_ACCESS_KEY="${{ secrets.BROWSERSTACK_ACCESS_KEY }}"
export BROWSERSTACK_APP_URL="${{ steps.upload.outputs.app_url }}"
export BROWSERSTACK_DEVICE="$DEVICE"
export DITTO_CLOUD_TASK_TITLE="$TASK_TITLE"
export BROWSERSTACK_PROJECT="${{ steps.build-info.outputs.project-name }}"
export BROWSERSTACK_BUILD="${{ steps.build-info.outputs.build-name }}"
../gradlew test --console=plain --no-daemon
# Query BrowserStack API to get the build ID
echo "Fetching build ID from BrowserStack..."
BUILDS_RESPONSE=$(curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
"https://api-cloud.browserstack.com/app-automate/builds.json?limit=1")
BUILD_ID=$(echo "$BUILDS_RESPONSE" | yq eval '.[0].hashed_id' -)
if [ "$BUILD_ID" != "null" ] && [ -n "$BUILD_ID" ]; then
echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT
echo "✅ Build ID: $BUILD_ID"
else
echo "⚠️ Could not retrieve build ID from BrowserStack API"
fi
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
android-cpp/QuickStartTasksCPP/app/build/outputs/apk/
android-cpp/QuickStartTasksCPP/appium-test/build/reports/
summary:
name: CI Report
runs-on: ubuntu-latest
needs: [lint, build, browserstack-android]
if: always()
steps:
- name: Report Results
run: |
echo "## 📱 Android C++ CI" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Overall status
if [[ "${{ needs.lint.result }}" == "success" && \
"${{ needs.build.result }}" == "success" && \
"${{ needs.browserstack-android.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-android.result == 'success' && '✅ Passed' || (needs.browserstack-android.result == 'skipped' && '⏭️ Skipped') || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# BrowserStack link
if [[ "${{ needs.browserstack-android.result }}" != "skipped" ]]; then
echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -n "${{ needs.browserstack-android.outputs.build_id }}" ]; then
echo "🤖 [View Test Results](https://app-automate.browserstack.com/dashboard/v2/builds/${{ needs.browserstack-android.outputs.build_id }})" >> $GITHUB_STEP_SUMMARY
else
echo "🤖 [View Test Results](https://app-automate.browserstack.com/builds?project=quickstart+-+Android+CPP)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Tested Device:**" >> $GITHUB_STEP_SUMMARY
echo "- Google Pixel 7 (Android 13.0)" >> $GITHUB_STEP_SUMMARY
fi