Skip to content
Merged
Show file tree
Hide file tree
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
Sep 3, 2025
9e5ea1c
fix(android-java): improve UI test targeting for RecyclerView items
Sep 3, 2025
cc97248
fix(android-java): resolve NoActivityResumedException in UI tests
Sep 3, 2025
f7b07b0
fix(android-java): implement manual activity launch for UI tests
Sep 3, 2025
fad61fb
feat(android-java): enable cloud sync and upgrade Ditto SDK
Sep 3, 2025
2a9bb61
feat(android-java): add comprehensive document sync verification
Sep 3, 2025
729437e
fix(android-java): create robust test suite that works reliably
Sep 3, 2025
66efbc0
fix(android-java): create reliable test suite with proper verification
Sep 3, 2025
237da6a
revert: remove fake document test after verification
Sep 3, 2025
b8a2b1f
fix(android-java): make test same locally/CI with visual confirmation
Sep 3, 2025
67890b7
revert: switch back to working BrowserStack test behavior
Sep 3, 2025
148ac13
refactor: production-ready cleanup and improvements
Sep 3, 2025
83f0a6b
rename: workflow file to android-java-ci.yml
Sep 3, 2025
0cca9ff
feat(android-java): implement alphabetical CI test document verification
Sep 4, 2025
5a8be8f
fix(android-java): improve environment variable handling for BrowserS…
Sep 4, 2025
8398b27
fix(android-java): remove invalid otherApps field from BrowserStack A…
Sep 4, 2025
aa01cc4
feat(android-java): add intelligent CI document cleanup for consisten…
Sep 4, 2025
1a6bdc7
fix(android-java): resolve Ditto permission dialog blocking instrumen…
Sep 4, 2025
8529a38
fix(android-java): correct test architecture to use GHA-seeded documents
Sep 4, 2025
19a0ec9
fix(android-java): handle duplicate task titles in test gracefully
Sep 4, 2025
fca01b0
feat(android-java): improve CI document seeding with inverted timestamps
Sep 4, 2025
e540cfb
refactor(android-java): simplify test - no scrolling needed for top d…
Sep 4, 2025
1499b97
fix(android-java): correct BrowserStack API to use instrumentationOpt…
Sep 4, 2025
58c54b1
refactor(android-java): clean up test imports and remove cruft
Sep 4, 2025
020a3ce
feat(android-java): finalize production-ready CI configuration
Sep 4, 2025
0c3492d
fix(android-java): apply Copilot review suggestions
Sep 4, 2025
52f188a
refactor(android-java): clean up permission handling for tests
Sep 4, 2025
a7fb59e
fix(android-java): restore working permission handling for BrowserStack
Sep 4, 2025
2ce85e9
Revert "fix(android-java): restore working permission handling for Br…
Sep 4, 2025
e6e08d1
Revert "refactor(android-java): clean up permission handling for tests"
Sep 4, 2025
c545ee9
Revert "fix(android-java): apply Copilot review suggestions"
Sep 4, 2025
d7200d8
Revert "feat(android-java): finalize production-ready CI configuration"
Sep 4, 2025
c7db3e4
Merge branch 'main' of github.com:getditto/quickstart into teodor/add…
Sep 8, 2025
4cdefc5
refactor(android-java): restructure CI to match Kotlin workflow archi…
Sep 8, 2025
05eaede
fix(android-java): correct CI architecture to match Kotlin workflow e…
Sep 8, 2025
51e5771
simplify(android-java): remove unit tests and document seeding from b…
Sep 8, 2025
6b6be35
fix(android-java): add missing .env file creation in build job
Sep 8, 2025
683f69f
fix(android-java): resolve BrowserStack API validation error for test…
Sep 8, 2025
7848e14
refactor(android-java): simplify BrowserStack integration following K…
Sep 8, 2025
78cc037
refactor(android-java): clean up production-ready codebase
Sep 8, 2025
85e3474
test: temporarily break CI to demonstrate failure on BrowserStack
Sep 8, 2025
d1f07c4
fix: create actual mismatch to demonstrate BrowserStack failure
Sep 8, 2025
52a5b6d
revert: restore working BrowserStack test configuration
Sep 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
380 changes: 380 additions & 0 deletions .github/workflows/android-java-ci.yml
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
});
4 changes: 4 additions & 0 deletions android-java/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

// Pass environment variables to instrumentation tests
testInstrumentationRunnerArguments["github_test_doc_id"] = System.getenv("GITHUB_TEST_DOC_ID") ?: ""
}

buildTypes {
Expand Down Expand Up @@ -127,6 +130,7 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.6.1")
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
Expand Down
Loading
Loading