Skip to content

feat: upgrade react-native quickstart to 0.82.1 with ditto 4.13.0 #361

feat: upgrade react-native quickstart to 0.82.1 with ditto 4.13.0

feat: upgrade react-native quickstart to 0.82.1 with ditto 4.13.0 #361

Workflow file for this run

name: React Native CI
on:
pull_request:
paths:
- "react-native/**"
- ".github/workflows/react-native-ci.yml"
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: 150
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: 150
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: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
working-directory: react-native
- 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
browserstack-android:
name: Test Android - BrowserStack Maestro
runs-on: ubuntu-latest
needs: [build-android]
timeout-minutes: 150
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_DITTO_CLOUD_TASK_TITLE} 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: Get BrowserStack build info (Android)
id: build-info-android
uses: ./.github/actions/generate-browserstack-names
with:
platform-suffix: ' (Android)'
title-max-length: '90'
commit-max-length: '130'
- name: Seed, create test suite, upload, and execute Android tests on BrowserStack
id: execute-tests
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}_react-native_ci_test_${{ github.run_id }}_${{ github.run_number }}\",
\"title\": \"${INVERTED_TIMESTAMP}_react-native_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}_react-native_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
# Upload APK to BrowserStack
echo "Uploading APK to BrowserStack..."
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 | yq eval -p=json '.app_url')
if [ -z "$APP_URL" ] || [ "$APP_URL" = "null" ]; then
echo "Error: Failed to upload APK"
echo "Response: $response"
exit 1
fi
echo "APK uploaded: $APP_URL"
# Create Maestro test suite ZIP with seeded task title
cd react-native
rm -rf maestro-tests
mkdir maestro-tests
# Replace MAESTRO_DITTO_CLOUD_TASK_TITLE with actual seeded task name
sed "s/\${MAESTRO_DITTO_CLOUD_TASK_TITLE}/$TASK_TITLE/g" \
.maestro/flows/01-app-launch-and-seeded-tasks-android.yaml > maestro-tests/01-app-launch-and-seeded-tasks-android.yaml
mkdir maestro-tests/flows
cp maestro-tests/01-app-launch-and-seeded-tasks-android.yaml maestro-tests/flows/
zip -r maestro-tests.zip maestro-tests -x "*.DS_Store"
cd ..
# Upload Maestro test suite to BrowserStack
echo "Uploading test suite to BrowserStack..."
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-Tests-${{ github.run_id }}")
TEST_SUITE_URL=$(echo "$response" | yq eval -p=json '.test_suite_url')
if [ -z "$TEST_SUITE_URL" ] || [ "$TEST_SUITE_URL" = "null" ]; then
echo "Error: Failed to upload test suite"
echo "Response: $response"
exit 1
fi
echo "Test suite uploaded: $TEST_SUITE_URL"
# Execute tests
# Load devices from centralized config
DEVICES=$(yq eval -o=json -I=0 '.["react-native"].android.devices' .github/browserstack-devices.yml)
echo "Loaded devices: $DEVICES"
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\": \"$APP_URL\",
\"testSuite\": \"$TEST_SUITE_URL\",
\"project\": \"${{ steps.build-info-android.outputs.project-name }}\",
\"buildName\": \"${{ steps.build-info-android.outputs.build-name }}\",
\"buildTag\": \"${{ github.head_ref || github.ref_name }}\",
\"devices\": $DEVICES,
\"execute\": [\"01-app-launch-and-seeded-tasks-android.yaml\"],
\"deviceLogs\": true,
\"networkLogs\": true,
\"video\": true
}")
echo "Execution response: $response"
build_id=$(echo $response | yq eval '.build_id')
if [ "$build_id" = "null" ] || [ -z "$build_id" ]; then
echo "❌ Failed to create BrowserStack build"
echo "Response: $response"
exit 1
fi
echo "BrowserStack Build ID: $build_id"
# Wait for test completion and get results
MAX_WAIT_TIME=1080 # 18 minutes
CHECK_INTERVAL=10 # Check every 10 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/maestro/v2/builds/$build_id")
status=$(echo $response | yq eval '.status')
echo "Test status: $status (${ELAPSED}s elapsed)"
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 | yq eval '.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 $CHECK_INTERVAL
ELAPSED=$((ELAPSED + CHECK_INTERVAL))
done
if [ $ELAPSED -ge $MAX_WAIT_TIME ]; then
echo "❌ Tests timed out after ${MAX_WAIT_TIME}s (18 minutes)"
exit 1
fi
browserstack-ios:
name: Test iOS - BrowserStack Maestro
runs-on: ubuntu-latest
needs: [build-ios]
timeout-minutes: 150
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: Get BrowserStack build info (iOS)
id: build-info-ios
uses: ./.github/actions/generate-browserstack-names
with:
platform-suffix: ' (iOS)'
title-max-length: '90'
commit-max-length: '130'
- name: Seed, upload, and execute iOS Maestro tests on BrowserStack
id: execute-tests
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}_react-native_ci_test_${{ github.run_id }}_${{ github.run_number }}\",
\"title\": \"${INVERTED_TIMESTAMP}_react-native_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}_react-native_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
# Upload IPA to BrowserStack
echo "Uploading IPA to BrowserStack..."
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 | yq eval -p=json '.app_url')
if [ -z "$APP_URL" ] || [ "$APP_URL" = "null" ]; then
echo "Error: Failed to upload IPA"
echo "Response: $response"
exit 1
fi
echo "IPA uploaded: $APP_URL"
# Create Maestro test suite ZIP
cd react-native
rm -rf maestro-tests
mkdir maestro-tests
# Copy iOS-specific test
cp .maestro/flows/01-app-launch-and-seeded-tasks-ios.yaml maestro-tests/
# Create flows/ subfolder
mkdir maestro-tests/flows
cp .maestro/flows/01-app-launch-and-seeded-tasks-ios.yaml maestro-tests/flows/
# Create ZIP
zip -r maestro-tests.zip maestro-tests -x "*.DS_Store"
cd ..
# Upload Maestro test suite to BrowserStack
echo "Uploading test suite to BrowserStack..."
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 | yq eval '.test_suite_url')
if [ -z "$TEST_SUITE_URL" ] || [ "$TEST_SUITE_URL" = "null" ]; then
echo "Error: Failed to upload test suite"
echo "Response: $response"
exit 1
fi
echo "Test suite uploaded: $TEST_SUITE_URL"
# Execute tests
# Load devices from centralized config
DEVICES=$(yq eval -o=json -I=0 '.["react-native"].ios.devices' .github/browserstack-devices.yml)
echo "Loaded devices: $DEVICES"
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\": \"$APP_URL\",
\"testSuite\": \"$TEST_SUITE_URL\",
\"project\": \"${{ steps.build-info-ios.outputs.project-name }}\",
\"buildName\": \"${{ steps.build-info-ios.outputs.build-name }}\",
\"buildTag\": \"${{ github.head_ref || github.ref_name }}\",
\"devices\": $DEVICES,
\"execute\": [\"01-app-launch-and-seeded-tasks-ios.yaml\"],
\"setEnvVariables\": {
\"MAESTRO_DITTO_CLOUD_TASK_TITLE\": \"$TASK_TITLE\"
},
\"deviceLogs\": true,
\"networkLogs\": true,
\"video\": true
}")
echo "Execution response: $response"
build_id=$(echo $response | yq eval '.build_id')
if [ "$build_id" = "null" ] || [ -z "$build_id" ]; then
echo "❌ Failed to create BrowserStack build"
echo "Response: $response"
exit 1
fi
echo "BrowserStack Build ID: $build_id"
# Wait for test completion and get results
MAX_WAIT_TIME=1080 # 18 minutes
CHECK_INTERVAL=10 # Check every 10 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/maestro/v2/builds/$build_id")
status=$(echo $response | yq eval '.status')
echo "Test status: $status (${ELAPSED}s elapsed)"
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 | yq eval '.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 $CHECK_INTERVAL
ELAPSED=$((ELAPSED + CHECK_INTERVAL))
done
if [ $ELAPSED -ge $MAX_WAIT_TIME ]; then
echo "❌ Tests timed out after ${MAX_WAIT_TIME}s (18 minutes)"
exit 1
fi
summary:
name: CI Report
runs-on: ubuntu-latest
needs: [browserstack-android, browserstack-ios]
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.browserstack-android.outputs.build_id }}"
ANDROID_STATUS="${{ needs.browserstack-android.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.browserstack-ios.outputs.build_id }}"
IOS_STATUS="${{ needs.browserstack-ios.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