Skip to content

Bump Ditto dependency to 4.12.3 #335

Bump Ditto dependency to 4.12.3

Bump Ditto dependency to 4.12.3 #335

Workflow file for this run

name: React Native CI
on:
pull_request:
branches:
- main
- 'sdk-*'
paths:
- 'react-native/**'
push:
branches:
- main
- 'sdk-*'
paths:
- 'react-native/**'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
cache-dependency-path: react-native/yarn.lock
- name: Install dependencies
working-directory: react-native
run: yarn install --frozen-lockfile
- name: Run linting
working-directory: react-native
run: yarn lint
build-android:
name: Build Android
runs-on: ubuntu-latest
needs: lint
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
cache-dependency-path: react-native/yarn.lock
- name: Install dependencies
working-directory: react-native
run: yarn install --frozen-lockfile
- 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: Create .env file (production credentials for builds)
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: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
react-native/android/.gradle
key: gradle-${{ runner.os }}-${{ hashFiles('react-native/android/gradle/wrapper/gradle-wrapper.properties', 'react-native/android/**/*.gradle*') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Build Android APK
working-directory: react-native/android
# Use direct gradlew instead of yarn android to generate unsigned APK required by BrowserStack
# yarn android creates signed builds that don't work with BrowserStack real device testing
run: NODE_ENV=production ./gradlew assembleRelease --no-daemon --console=plain
- name: Upload Android APK artifact
uses: actions/upload-artifact@v4
with:
name: android-apk-${{ github.run_number }}
path: react-native/android/app/build/outputs/apk/release/app-release.apk
retention-days: 1
build-ios:
name: Build iOS
runs-on: macos-latest
needs: lint
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
cache-dependency-path: react-native/yarn.lock
- name: Install dependencies
working-directory: react-native
run: yarn install --frozen-lockfile
- name: Create .env file (production credentials for builds)
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: Cache CocoaPods dependencies
uses: actions/cache@v4
with:
path: |
react-native/ios/Pods
~/Library/Caches/CocoaPods
~/.cocoapods
key: cocoapods-${{ runner.os }}-${{ hashFiles('react-native/ios/Podfile.lock') }}
restore-keys: |
cocoapods-${{ runner.os }}-
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.4'
- name: Install Ruby gems
working-directory: react-native
run: bundle install
- name: Install CocoaPods
working-directory: react-native/ios
run: bundle exec pod install
- name: Build iOS Archive and IPA (for BrowserStack)
working-directory: react-native
run: |
echo "🍎 Building iOS device .ipa for BrowserStack..."
# Use direct xcodebuild instead of yarn ios to generate unsigned IPA required by BrowserStack
# yarn ios creates signed builds for simulators that don't work with BrowserStack real device testing
# Build and archive iOS app for real device
xcodebuild -workspace ios/DittoReactNativeSampleApp.xcworkspace \
-scheme DittoReactNativeSampleApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath ios/build/DittoReactNativeSampleApp.xcarchive \
archive \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
echo "📦 Creating unsigned .ipa for BrowserStack..."
# Find the .app bundle from the archive
APP_BUNDLE_PATH=$(find ios/build/DittoReactNativeSampleApp.xcarchive/Products/Applications -maxdepth 1 -name "*.app" -type d | head -1)
if [ -d "$APP_BUNDLE_PATH" ]; then
echo "✅ iOS app bundle found: $APP_BUNDLE_PATH"
# Create unsigned IPA: Payload/<App>.app zipped as .ipa
mkdir -p ios/build/Payload
cp -R "$APP_BUNDLE_PATH" ios/build/Payload/
(cd ios/build && zip -qry DittoReactNativeSampleApp-unsigned.ipa Payload && rm -rf Payload)
if [ -f "ios/build/DittoReactNativeSampleApp-unsigned.ipa" ]; then
echo "✅ Unsigned .ipa created successfully"
ls -la ios/build/DittoReactNativeSampleApp-unsigned.ipa
else
echo "❌ Failed to create .ipa file"
exit 1
fi
else
echo "❌ iOS app bundle not found in archive"
exit 1
fi
- name: Upload iOS IPA artifact
uses: actions/upload-artifact@v4
with:
name: ios-ipa-${{ github.run_number }}
path: react-native/ios/build/DittoReactNativeSampleApp-unsigned.ipa
retention-days: 1
seed-ditto-cloud:
name: Create test documents in Big Peer
runs-on: ubuntu-latest
needs: [build-android, build-ios]
timeout-minutes: 10
outputs:
test_task_title: ${{ steps.seed-document.outputs.document-title }}
steps:
- uses: actions/checkout@v4
- name: Insert test document into Ditto Cloud
id: seed-document
uses: ./.github/actions/seed-ditto-document
with:
ditto-api-key: ${{ secrets.DITTO_API_KEY }}
ditto-api-url: ${{ secrets.DITTO_API_URL }}
app-name: 'react-native'
test-android-maestro:
name: Test Android - BrowserStack Maestro
runs-on: ubuntu-latest
needs: [build-android, seed-ditto-cloud]
timeout-minutes: 30
outputs:
build_id: ${{ steps.execute-tests.outputs.build_id }}
status: ${{ job.status }}
# TODO: BrowserStack Android Maestro API environment variables don't work (confirmed by BS staff)
# On their roadmap with estimate: Oct 2025. Will revert to setEnvVariables when ready.
# Using sed workaround to replace ${MAESTRO_TASK_TO_FIND} with actual task name in test files
steps:
- uses: actions/checkout@v4
- name: Download Android APK
uses: actions/download-artifact@v4
with:
name: android-apk-${{ github.run_number }}
path: ./artifacts/
- name: Upload APK to BrowserStack
id: upload-apk
run: |
response=$(curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/app" \
-F "file=@./artifacts/app-release.apk" \
-F "custom_id=ReactNative-Android-${{ github.run_id }}")
app_url=$(echo $response | jq -r '.app_url')
# Validate API response
if [ -z "$app_url" ] || [ "$app_url" = "null" ]; then
echo "❌ Failed to get app_url from upload response"
echo "Response: $response"
exit 1
fi
echo "✅ APK uploaded successfully: $app_url"
echo "app_url=$app_url" >> $GITHUB_OUTPUT
- name: Create Maestro test suite ZIP
run: |
cd react-native
# Create proper BrowserStack folder structure (normal parent folder, not hidden)
rm -rf maestro-tests
mkdir maestro-tests
# Replace MAESTRO_TASK_TO_FIND with actual seeded task name (BrowserStack Android API bug workaround)
sed 's/\${MAESTRO_TASK_TO_FIND}/${{ needs.seed-ditto-cloud.outputs.test_task_title }}/g' \
.maestro/flows/01-app-launch-and-seeded-tasks-android.yaml > maestro-tests/01-app-launch-and-seeded-tasks-android.yaml
# Also create flows/ subfolder for organization
mkdir maestro-tests/flows
cp .maestro/flows/01-app-launch-and-seeded-tasks-android.yaml maestro-tests/flows/
# Create ZIP with proper structure
zip -r maestro-tests.zip maestro-tests -x "*.DS_Store"
- name: Upload Maestro test suite to BrowserStack
id: upload-tests
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
run: |
set -euo pipefail
test -f react-native/maestro-tests.zip
response=$(curl --fail-with-body -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \
-F "file=@react-native/maestro-tests.zip" \
-F "custom_id=ReactNative-Tests-${{ github.run_id }}")
echo "test_suite_url=$(echo "$response" | jq -r '.test_suite_url')" >> $GITHUB_OUTPUT
- name: Execute Maestro tests on BrowserStack (Parallel)
id: execute-tests
run: |
response=$(curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/android/build" \
-H "Content-Type: application/json" \
-d '{
"app": "${{ steps.upload-apk.outputs.app_url }}",
"testSuite": "${{ steps.upload-tests.outputs.test_suite_url }}",
"project": "Ditto React Native",
"buildName": "Build #${{ github.run_number }}",
"buildTag": "${{ github.ref_name }}",
"devices": [
"Samsung Galaxy S22-12.0",
"Google Pixel 7-13.0"
],
"execute": ["01-app-launch-and-seeded-tasks-android.yaml"],
"deviceLogs": true,
"networkLogs": true,
"video": true
}')
echo "Execution response: $response"
build_id=$(echo $response | jq -r '.build_id')
echo "build_id=$build_id" >> $GITHUB_OUTPUT
echo "BrowserStack Build ID: $build_id"
- name: Wait for test completion and get results
run: |
build_id="${{ steps.execute-tests.outputs.build_id }}"
# Wait for tests to complete (max 20 minutes)
for i in {1..120}; do
response=$(curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
"https://api-cloud.browserstack.com/app-automate/maestro/v2/builds/$build_id")
status=$(echo $response | jq -r '.status')
echo "Test status: $status (attempt $i/120)"
if [ "$status" = "passed" ] || [ "$status" = "failed" ]; then
echo "Tests completed with status: $status"
echo $response | jq '.'
if [ "$status" = "failed" ]; then
echo "❌ Android Maestro tests failed!"
exit 1
else
echo "✅ Android Maestro tests passed!"
fi
break
elif [ "$status" = "error" ]; then
echo "❌ BrowserStack reported build error. Fetching session details..."
echo $response | jq '.'
echo $response | jq -r '.devices[]?.sessions[]?.id' 2>/dev/null | while read sid; do
if [ -n "$sid" ]; then
echo "Session $sid details:"
curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
"https://api-cloud.browserstack.com/app-automate/maestro/v2/builds/$build_id/sessions/$sid" | jq '.'
fi
done
exit 1
fi
sleep 10
done
if [ $i -eq 120 ]; then
echo "❌ Tests timed out after 20 minutes"
exit 1
fi
test-ios-maestro:
name: Test iOS - BrowserStack Maestro
runs-on: ubuntu-latest
needs: [build-ios, seed-ditto-cloud]
timeout-minutes: 30
outputs:
build_id: ${{ steps.execute-tests.outputs.build_id }}
status: ${{ job.status }}
steps:
- uses: actions/checkout@v4
- name: Download iOS IPA
uses: actions/download-artifact@v4
with:
name: ios-ipa-${{ github.run_number }}
path: ./artifacts/
- name: Upload IPA to BrowserStack
id: upload-ipa
run: |
response=$(curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/app" \
-F "file=@./artifacts/DittoReactNativeSampleApp-unsigned.ipa" \
-F "custom_id=ReactNative-iOS-${{ github.run_id }}")
app_url=$(echo $response | jq -r '.app_url')
# Validate API response
if [ -z "$app_url" ] || [ "$app_url" = "null" ]; then
echo "❌ Failed to get app_url from upload response"
echo "Response: $response"
exit 1
fi
echo "✅ IPA uploaded successfully: $app_url"
echo "app_url=$app_url" >> $GITHUB_OUTPUT
- name: Create Maestro test suite ZIP
run: |
cd react-native
# Create proper BrowserStack folder structure (normal parent folder, not hidden)
rm -rf maestro-tests
mkdir maestro-tests
# Copy only iOS-specific tests to root level (avoid conflicting appIds)
cp .maestro/flows/01-app-launch-and-seeded-tasks-ios.yaml maestro-tests/
# Also create flows/ subfolder for organization
mkdir maestro-tests/flows
cp .maestro/flows/01-app-launch-and-seeded-tasks-ios.yaml maestro-tests/flows/
# Create ZIP with proper structure
zip -r maestro-tests.zip maestro-tests -x "*.DS_Store"
- name: Upload Maestro test suite to BrowserStack
id: upload-tests
run: |
response=$(curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \
-F "file=@react-native/maestro-tests.zip" \
-F "custom_id=ReactNative-iOS-Tests-${{ github.run_id }}")
test_suite_url=$(echo $response | jq -r '.test_suite_url')
echo "test_suite_url=$test_suite_url" >> $GITHUB_OUTPUT
- name: Execute Maestro tests on BrowserStack (Parallel)
id: execute-tests
run: |
response=$(curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/ios/build" \
-H "Content-Type: application/json" \
-d '{
"app": "${{ steps.upload-ipa.outputs.app_url }}",
"testSuite": "${{ steps.upload-tests.outputs.test_suite_url }}",
"project": "Ditto React Native",
"buildName": "Build #${{ github.run_number }}",
"buildTag": "${{ github.ref_name }}",
"devices": [
"iPhone 15-17.0",
"iPhone 14-16.0"
],
"execute": ["01-app-launch-and-seeded-tasks-ios.yaml"],
"setEnvVariables": {
"MAESTRO_TASK_TO_FIND": "${{ needs.seed-ditto-cloud.outputs.test_task_title }}"
},
"deviceLogs": true,
"networkLogs": true,
"video": true
}')
echo "Execution response: $response"
build_id=$(echo $response | jq -r '.build_id')
echo "build_id=$build_id" >> $GITHUB_OUTPUT
echo "BrowserStack Build ID: $build_id"
- name: Wait for test completion and get results
run: |
build_id="${{ steps.execute-tests.outputs.build_id }}"
# Wait for tests to complete (max 20 minutes)
for i in {1..120}; do
response=$(curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
"https://api-cloud.browserstack.com/app-automate/maestro/v2/builds/$build_id")
status=$(echo $response | jq -r '.status')
echo "Test status: $status (attempt $i/120)"
if [ "$status" = "passed" ] || [ "$status" = "failed" ]; then
echo "Tests completed with status: $status"
echo $response | jq '.'
if [ "$status" = "failed" ]; then
echo "❌ iOS Maestro tests failed!"
exit 1
else
echo "✅ iOS Maestro tests passed!"
fi
break
elif [ "$status" = "error" ]; then
echo "❌ BrowserStack reported build error. Fetching session details..."
echo $response | jq '.'
echo $response | jq -r '.devices[]?.sessions[]?.id' 2>/dev/null | while read sid; do
if [ -n "$sid" ]; then
echo "Session $sid details:"
curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
"https://api-cloud.browserstack.com/app-automate/maestro/v2/builds/$build_id/sessions/$sid" | jq '.'
fi
done
exit 1
fi
sleep 10
done
if [ $i -eq 120 ]; then
echo "❌ Tests timed out after 20 minutes"
exit 1
fi
report-results:
name: Report BrowserStack Results
runs-on: ubuntu-latest
needs: [test-android-maestro, test-ios-maestro]
if: always()
steps:
- name: Generate BrowserStack Summary
run: |
echo "## 📱 React Native - BrowserStack Maestro Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Android results
echo "### Android Tests" >> $GITHUB_STEP_SUMMARY
ANDROID_BUILD_ID="${{ needs.test-android-maestro.outputs.build_id }}"
ANDROID_STATUS="${{ needs.test-android-maestro.outputs.status }}"
if [ -n "$ANDROID_BUILD_ID" ] && [ "$ANDROID_BUILD_ID" != "null" ]; then
if [ "$ANDROID_STATUS" = "success" ]; then
echo "✅ **Status:** Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Status:** Failed" >> $GITHUB_STEP_SUMMARY
fi
echo "🔗 **[View Dashboard](https://app-automate.browserstack.com/dashboard/v2/builds/$ANDROID_BUILD_ID)**" >> $GITHUB_STEP_SUMMARY
echo "Build ID: \`$ANDROID_BUILD_ID\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Status:** Failed (No build created)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# iOS results
echo "### iOS Tests" >> $GITHUB_STEP_SUMMARY
IOS_BUILD_ID="${{ needs.test-ios-maestro.outputs.build_id }}"
IOS_STATUS="${{ needs.test-ios-maestro.outputs.status }}"
if [ -n "$IOS_BUILD_ID" ] && [ "$IOS_BUILD_ID" != "null" ]; then
if [ "$IOS_STATUS" = "success" ]; then
echo "✅ **Status:** Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Status:** Failed" >> $GITHUB_STEP_SUMMARY
fi
echo "🔗 **[View Dashboard](https://app-automate.browserstack.com/dashboard/v2/builds/$IOS_BUILD_ID)**" >> $GITHUB_STEP_SUMMARY
echo "Build ID: \`$IOS_BUILD_ID\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Status:** Failed (No build created)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# Summary
echo "---" >> $GITHUB_STEP_SUMMARY
if [ "$ANDROID_STATUS" = "success" ] && [ "$IOS_STATUS" = "success" ]; then
echo "✅ **Overall Result:** All tests passed" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ **Overall Result:** Some tests failed" >> $GITHUB_STEP_SUMMARY
fi