-
Notifications
You must be signed in to change notification settings - Fork 8
ci(android-java): add BrowserStack integration tests #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Teodor Ciuraru (teodorciuraru)
merged 43 commits into
main
from
teodor/add-android-java-ci
Sep 11, 2025
Merged
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
32c458b
chore(android-java): add BrowserStack integration tests
9e5ea1c
fix(android-java): improve UI test targeting for RecyclerView items
cc97248
fix(android-java): resolve NoActivityResumedException in UI tests
f7b07b0
fix(android-java): implement manual activity launch for UI tests
fad61fb
feat(android-java): enable cloud sync and upgrade Ditto SDK
2a9bb61
feat(android-java): add comprehensive document sync verification
729437e
fix(android-java): create robust test suite that works reliably
66efbc0
fix(android-java): create reliable test suite with proper verification
237da6a
revert: remove fake document test after verification
b8a2b1f
fix(android-java): make test same locally/CI with visual confirmation
67890b7
revert: switch back to working BrowserStack test behavior
148ac13
refactor: production-ready cleanup and improvements
83f0a6b
rename: workflow file to android-java-ci.yml
0cca9ff
feat(android-java): implement alphabetical CI test document verification
5a8be8f
fix(android-java): improve environment variable handling for BrowserS…
8398b27
fix(android-java): remove invalid otherApps field from BrowserStack A…
aa01cc4
feat(android-java): add intelligent CI document cleanup for consisten…
1a6bdc7
fix(android-java): resolve Ditto permission dialog blocking instrumen…
8529a38
fix(android-java): correct test architecture to use GHA-seeded documents
19a0ec9
fix(android-java): handle duplicate task titles in test gracefully
fca01b0
feat(android-java): improve CI document seeding with inverted timestamps
e540cfb
refactor(android-java): simplify test - no scrolling needed for top d…
1499b97
fix(android-java): correct BrowserStack API to use instrumentationOpt…
58c54b1
refactor(android-java): clean up test imports and remove cruft
020a3ce
feat(android-java): finalize production-ready CI configuration
0c3492d
fix(android-java): apply Copilot review suggestions
52f188a
refactor(android-java): clean up permission handling for tests
a7fb59e
fix(android-java): restore working permission handling for BrowserStack
2ce85e9
Revert "fix(android-java): restore working permission handling for Br…
e6e08d1
Revert "refactor(android-java): clean up permission handling for tests"
c545ee9
Revert "fix(android-java): apply Copilot review suggestions"
d7200d8
Revert "feat(android-java): finalize production-ready CI configuration"
c7db3e4
Merge branch 'main' of github.com:getditto/quickstart into teodor/add…
4cdefc5
refactor(android-java): restructure CI to match Kotlin workflow archi…
05eaede
fix(android-java): correct CI architecture to match Kotlin workflow e…
51e5771
simplify(android-java): remove unit tests and document seeding from b…
6b6be35
fix(android-java): add missing .env file creation in build job
683f69f
fix(android-java): resolve BrowserStack API validation error for test…
7848e14
refactor(android-java): simplify BrowserStack integration following K…
78cc037
refactor(android-java): clean up production-ready codebase
85e3474
test: temporarily break CI to demonstrate failure on BrowserStack
d1f07c4
fix: create actual mismatch to demonstrate BrowserStack failure
52a5b6d
revert: restore working BrowserStack test configuration
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,380 @@ | ||
| 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: Download APK artifacts | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: android-apks-${{ github.run_number }} | ||
| path: android-java/app/build/outputs/apk/ | ||
|
|
||
| - name: Insert test document into Ditto Cloud | ||
| run: | | ||
| # Generate test document for BrowserStack testing | ||
| TIMESTAMP=$(date +%s) | ||
| INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) | ||
| DOC_ID="${INVERTED_TIMESTAMP}_android_ci_test_${{ github.run_id }}_${{ github.run_number }}" | ||
| DOC_TITLE="${INVERTED_TIMESTAMP}_android_ci_test_${{ github.run_id }}_${{ github.run_number }}" | ||
|
|
||
| echo "📝 Inserting test document for BrowserStack testing" | ||
| echo "📝 ID: '${DOC_ID}'" | ||
| echo "📝 Title: '${DOC_TITLE}'" | ||
|
|
||
| # Store title for later use in BrowserStack step | ||
| echo "TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV | ||
|
|
||
| # Insert document using Ditto API v4 (same as Kotlin workflow) | ||
| 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\": \"${DOC_ID}\", | ||
| \"title\": \"${DOC_TITLE}\", | ||
| \"done\": false, | ||
| \"deleted\": false | ||
| } | ||
| } | ||
| }" \ | ||
| "https://${{ secrets.DITTO_API_URL }}/api/v4/store/execute") | ||
|
|
||
| # Extract HTTP status code and response body | ||
| HTTP_CODE=$(echo "$RESPONSE" | tail -n1) | ||
| BODY=$(echo "$RESPONSE" | sed '$d') | ||
|
|
||
| # Check if insertion was successful | ||
| if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then | ||
| echo "✓ Successfully inserted test document with ID: ${DOC_ID}" | ||
| echo "✓ Document title: ${DOC_TITLE}" | ||
| else | ||
| echo "❌ Failed to insert document. HTTP Status: $HTTP_CODE" | ||
| echo "Response: $BODY" | ||
| exit 1 | ||
| fi | ||
|
|
||
| - 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 with instrumentationOptions (same approach as Kotlin) | ||
| TITLE="${{ env.TEST_DOC_TITLE }}" | ||
|
|
||
| BUILD_RESPONSE=$(curl -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\" | ||
| } | ||
| }") | ||
|
|
||
| echo "BrowserStack API Response:" | ||
| echo "$BUILD_RESPONSE" | ||
|
|
||
| BUILD_ID=$(echo "$BUILD_RESPONSE" | jq -r .build_id) | ||
|
|
||
| # Check if BUILD_ID is null or empty | ||
| 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/ | ||
|
|
||
| - name: Comment PR with results | ||
| if: github.event_name == 'pull_request' && always() | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const testDocId = 'Generated during BrowserStack testing'; | ||
| const status = '${{ job.status }}'; | ||
| const runUrl = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'; | ||
|
|
||
| const body = `## 📱 BrowserStack Test Results (Android Java) | ||
|
|
||
| **Status:** ${status === 'success' ? '✅ Passed' : '❌ Failed'} | ||
| **Build:** [#${{ github.run_number }}](${runUrl}) | ||
| **Test Document ID:** ${testDocId || 'Not generated'} | ||
|
|
||
| ### Tested Devices: | ||
| - Google Pixel 8 (Android 14) | ||
| - Samsung Galaxy S23 (Android 13) | ||
| - Google Pixel 6 (Android 12) | ||
| - OnePlus 9 (Android 11) | ||
|
|
||
| ### Test Verification: | ||
| - ✅ Lint check completed | ||
| - ✅ APK build successful | ||
| - ✅ Unit tests passed | ||
| - ✅ Test document seeded to Ditto Cloud | ||
| - ${status === 'success' ? '✅' : '❌'} Integration test verification on BrowserStack | ||
| `; | ||
|
|
||
| github.rest.issues.createComment({ | ||
| issue_number: context.issue.number, | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| body: body | ||
| }); | ||
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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.