bump to 4.13.2-rc.1
#549
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
| name: Swift CI | |
| on: | |
| pull_request: | |
| paths: | |
| - "swift/**" | |
| - ".github/workflows/swift-ci.yml" | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint: | |
| name: Lint | |
| runs-on: macos-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: "latest-stable" | |
| - name: Create .env file (test credentials for linting) | |
| run: | | |
| echo "DITTO_APP_ID=test_app_id" > .env | |
| echo "DITTO_PLAYGROUND_TOKEN=test_token" >> .env | |
| echo "DITTO_AUTH_URL=https://test.com" >> .env | |
| echo "DITTO_WEBSOCKET_URL=wss://test.com" >> .env | |
| - name: Generate Env.swift (test) | |
| working-directory: swift | |
| run: | | |
| chmod +x buildEnv.sh | |
| ./buildEnv.sh ../.env Tasks/ | |
| - name: Install SwiftLint | |
| run: | | |
| if ! command -v swiftlint &> /dev/null; then | |
| echo "Installing SwiftLint..." | |
| brew install swiftlint | |
| fi | |
| swiftlint version | |
| - name: Run SwiftLint | |
| working-directory: swift | |
| run: | | |
| echo "🔍 Running SwiftLint analysis..." | |
| swiftlint lint --config .swiftlint.yml | |
| build-macos: | |
| name: Build macOS | |
| runs-on: macos-latest | |
| needs: lint | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: "latest-stable" | |
| - name: Create .env file (production credentials) | |
| 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: Generate Env.swift (production) | |
| working-directory: swift | |
| run: | | |
| chmod +x buildEnv.sh | |
| ./buildEnv.sh ../.env Tasks/ | |
| - name: Resolve Package Dependencies | |
| working-directory: swift | |
| run: | | |
| echo "📦 Resolving Swift Package dependencies..." | |
| xcodebuild -resolvePackageDependencies \ | |
| -project Tasks.xcodeproj \ | |
| -scheme Tasks | |
| - name: Build macOS App | |
| working-directory: swift | |
| run: | | |
| echo "🍎 Building macOS app..." | |
| xcodebuild build \ | |
| -project Tasks.xcodeproj \ | |
| -scheme Tasks \ | |
| -configuration Debug \ | |
| -destination 'platform=macOS' \ | |
| -allowProvisioningUpdates \ | |
| SWIFT_TREAT_WARNINGS_AS_ERRORS=NO \ | |
| CODE_SIGN_IDENTITY="" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO | |
| build-ios: | |
| name: Build iOS | |
| runs-on: macos-latest | |
| needs: lint | |
| timeout-minutes: 20 | |
| outputs: | |
| ios-build-success: ${{ steps.build-status.outputs.success }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: "latest-stable" | |
| - name: Create .env file (production credentials) | |
| 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: Generate Env.swift (production) | |
| working-directory: swift | |
| run: | | |
| chmod +x buildEnv.sh | |
| ./buildEnv.sh ../.env Tasks/ | |
| - name: Resolve Package Dependencies | |
| working-directory: swift | |
| run: | | |
| echo "📦 Resolving Swift Package dependencies..." | |
| xcodebuild -resolvePackageDependencies \ | |
| -project Tasks.xcodeproj \ | |
| -scheme Tasks | |
| - name: Build iOS Simulator | |
| working-directory: swift | |
| run: | | |
| echo "🔨 Building iOS app for simulator..." | |
| xcodebuild build \ | |
| -project Tasks.xcodeproj \ | |
| -scheme Tasks \ | |
| -configuration Debug \ | |
| -sdk iphonesimulator \ | |
| -destination 'generic/platform=iOS Simulator' \ | |
| -allowProvisioningUpdates \ | |
| SWIFT_TREAT_WARNINGS_AS_ERRORS=NO \ | |
| CODE_SIGN_IDENTITY="" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| ONLY_ACTIVE_ARCH=NO | |
| - name: Build iOS Device Archive and IPA (for BrowserStack) | |
| working-directory: swift | |
| run: | | |
| echo "🍎 Building iOS device .ipa for BrowserStack..." | |
| # Build and archive iOS app for real device | |
| xcodebuild -project Tasks.xcodeproj \ | |
| -scheme Tasks \ | |
| -configuration Debug \ | |
| -destination 'generic/platform=iOS' \ | |
| -archivePath build/Tasks.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 build/Tasks.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 build/Payload | |
| cp -R "$APP_BUNDLE_PATH" build/Payload/ | |
| (cd build && zip -qry Tasks-unsigned.ipa Payload && rm -rf Payload) | |
| if [ -f "build/Tasks-unsigned.ipa" ]; then | |
| echo "✅ Unsigned .ipa created successfully" | |
| ls -la build/Tasks-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_id }} | |
| path: swift/build/Tasks-unsigned.ipa | |
| retention-days: 1 | |
| - name: Set build status | |
| id: build-status | |
| run: | | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| browserstack: | |
| name: BrowserStack iOS Testing | |
| runs-on: macos-latest | |
| needs: [build-ios] | |
| if: needs.build-ios.outputs.ios-build-success == 'true' | |
| timeout-minutes: 150 | |
| outputs: | |
| build_id: ${{ steps.test.outputs.build_id }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: "latest-stable" | |
| - name: Create .env file (production credentials for BrowserStack API) | |
| 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: Generate Env.swift (production for XCUITest build) | |
| working-directory: swift | |
| run: | | |
| chmod +x buildEnv.sh | |
| ./buildEnv.sh ../.env Tasks/ | |
| - name: Download iOS IPA Artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ios-ipa-${{ github.run_id }} | |
| path: ./ | |
| - name: Build XCUITest Bundle for BrowserStack | |
| id: build_test_bundle | |
| working-directory: swift | |
| run: | | |
| set -euo pipefail | |
| echo "🧪 build-for-testing (device) with GitHub run info injected..." | |
| DERIVED="$PWD/build/DerivedData" | |
| PRODUCTS_DIR="$DERIVED/Build/Products" | |
| # Inject GitHub run info as build settings for the test | |
| xcodebuild build-for-testing \ | |
| -project Tasks.xcodeproj \ | |
| -scheme Tasks \ | |
| -configuration Debug \ | |
| -destination 'generic/platform=iOS' \ | |
| -derivedDataPath "$DERIVED" \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| GITHUB_RUN_ID=${{ github.run_id }} \ | |
| GITHUB_RUN_NUMBER=${{ github.run_number }} | |
| echo "🔍 Looking for Runner.app (device) and .xctestrun…" | |
| # Runner.app must come from Debug-iphoneos (device), not simulator | |
| RUNNER_APP=$(find "$PRODUCTS_DIR" -type d -path "*/Debug-iphoneos/*-Runner.app" -print -quit || true) | |
| XCTESTRUN=$(find "$PRODUCTS_DIR" -maxdepth 1 -type f -name "*.xctestrun" -print -quit || true) | |
| if [[ -z "${RUNNER_APP:-}" || ! -d "$RUNNER_APP" ]]; then | |
| echo "❌ Runner.app not found under $PRODUCTS_DIR" | |
| find "$PRODUCTS_DIR" -type d -name "*-Runner.app" || true | |
| exit 1 | |
| fi | |
| if [[ -z "${XCTESTRUN:-}" || ! -f "$XCTESTRUN" ]]; then | |
| echo "❌ .xctestrun not found at $PRODUCTS_DIR root" | |
| ls -la "$PRODUCTS_DIR" || true | |
| exit 1 | |
| fi | |
| echo "✅ Runner: $RUNNER_APP" | |
| echo "✅ xctestrun: $XCTESTRUN" | |
| # Create ZIP with BOTH files at ZIP ROOT (exactly as BrowserStack expects) | |
| OUT_DIR="$PWD/../build" | |
| mkdir -p "$OUT_DIR" | |
| OUT_ZIP="$OUT_DIR/TasksUITests.zip" | |
| rm -f "$OUT_ZIP" | |
| echo "📦 Creating $OUT_ZIP with root: [$(basename "$RUNNER_APP")]" | |
| echo "ℹ️ BrowserStack error requested removing .xctestrun file - trying Runner.app only" | |
| ( cd "$(dirname "$RUNNER_APP")" && zip -qry "$OUT_ZIP" "$(basename "$RUNNER_APP")" ) | |
| echo "🔍 ZIP contents:" | |
| unzip -l "$OUT_ZIP" | sed -n '1,120p' | |
| echo "test_bundle_path=$OUT_ZIP" >> "$GITHUB_OUTPUT" | |
| echo "✅ XCUITest test-suite zip ready: $OUT_ZIP" | |
| - name: Upload App and Test Bundle to BrowserStack | |
| id: upload | |
| run: | | |
| echo "📤 Uploading iOS app and XCUITest bundle to BrowserStack..." | |
| IPA_FILE="$(pwd)/Tasks-unsigned.ipa" | |
| TEST_BUNDLE="${{ steps.build_test_bundle.outputs.test_bundle_path }}" | |
| echo "📱 App file: $IPA_FILE" | |
| echo "🧪 Test bundle: $TEST_BUNDLE" | |
| # Validate both files exist | |
| if [ ! -f "$IPA_FILE" ]; then | |
| echo "❌ IPA file not found: $IPA_FILE" | |
| exit 1 | |
| fi | |
| if [ ! -f "$TEST_BUNDLE" ]; then | |
| echo "❌ Test bundle not found: $TEST_BUNDLE" | |
| exit 1 | |
| fi | |
| # Upload app to BrowserStack XCUITest v2 API | |
| echo "📱 Uploading app..." | |
| APP_UPLOAD_RESPONSE=$(curl --fail --silent --show-error -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ | |
| -X POST "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/app" \ | |
| -F "file=@$IPA_FILE" \ | |
| -F "custom_id=ditto-swift-app") | |
| echo "App upload response: $APP_UPLOAD_RESPONSE" | |
| APP_URL=$(echo "$APP_UPLOAD_RESPONSE" | yq eval -p=json .app_url) | |
| if [ "$APP_URL" = "null" ] || [ -z "$APP_URL" ]; then | |
| echo "❌ Failed to upload app" | |
| echo "Response: $APP_UPLOAD_RESPONSE" | |
| exit 1 | |
| fi | |
| # Upload test bundle to XCUITest v2 API | |
| echo "🧪 Uploading test bundle..." | |
| echo "Debug: Test bundle path: $TEST_BUNDLE" | |
| ls -la "$TEST_BUNDLE" || echo "Test bundle file not found!" | |
| # Upload with better error handling to see 422 details | |
| TEST_UPLOAD_RESPONSE=$(curl -s -w "\n%{http_code}" -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ | |
| -X POST "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/test-suite" \ | |
| -F "file=@$TEST_BUNDLE") | |
| HTTP_CODE=$(echo "$TEST_UPLOAD_RESPONSE" | tail -n1) | |
| BODY=$(echo "$TEST_UPLOAD_RESPONSE" | sed '$d') | |
| echo "Test upload HTTP $HTTP_CODE" | |
| echo "Response body: $BODY" | |
| if [ "$HTTP_CODE" -ne 200 ] && [ "$HTTP_CODE" -ne 201 ]; then | |
| echo "❌ Upload failed"; exit 1 | |
| fi | |
| # Prefer test_suite_url, fallback to test_url | |
| TEST_URL=$(echo "$BODY" | yq eval -p=json '.test_suite_url // .test_url // ""') | |
| if [ -z "$TEST_URL" ]; then | |
| echo "❌ Failed to upload test bundle - no test suite URL in response" | |
| echo "Response: $BODY" | |
| exit 1 | |
| fi | |
| echo "app_url=$APP_URL" >> $GITHUB_OUTPUT | |
| echo "test_url=$TEST_URL" >> $GITHUB_OUTPUT | |
| echo "✅ App and test bundle uploaded successfully" | |
| echo "📱 App URL: $APP_URL" | |
| echo "🧪 Test URL: $TEST_URL" | |
| - name: Get BrowserStack build info | |
| id: build-info | |
| uses: ./.github/actions/generate-browserstack-names | |
| - name: Seed and execute XCUITests 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}_swift_ci_test_${{ github.run_id }}_${{ github.run_number }}\", | |
| \"title\": \"${INVERTED_TIMESTAMP}_swift_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}_swift_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 | |
| # Validate inputs | |
| 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 "⚠️ No test bundle available - skipping automated testing" | |
| echo "📱 App is available for manual testing in BrowserStack dashboard" | |
| exit 0 | |
| fi | |
| # Execute tests | |
| # Load devices from centralized config | |
| DEVICES=$(yq eval -o=json -I=0 '.swift.devices' .github/browserstack-devices.yml) | |
| BUILD_RESPONSE=$(curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ | |
| -X POST "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/build" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"app\": \"$APP_URL\", | |
| \"testSuite\": \"$TEST_URL\", | |
| \"devices\": $DEVICES, | |
| \"project\": \"${{ steps.build-info.outputs.project-name }}\", | |
| \"buildName\": \"${{ steps.build-info.outputs.build-name }}\", | |
| \"buildTag\": \"${{ github.head_ref || github.ref_name }}\", | |
| \"deviceLogs\": true, | |
| \"video\": true, | |
| \"networkLogs\": true, | |
| \"setEnvVariables\": { | |
| \"GITHUB_RUN_ID\": \"${{ github.run_id }}\", | |
| \"GITHUB_RUN_NUMBER\": \"${{ github.run_number }}\", | |
| \"DITTO_CLOUD_TASK_TITLE\": \"$TASK_TITLE\" | |
| } | |
| }") | |
| echo "BrowserStack API Response:" | |
| echo "$BUILD_RESPONSE" | |
| BUILD_ID=$(echo "$BUILD_RESPONSE" | yq eval .build_id) | |
| if [ "$BUILD_ID" = "null" ] || [ -z "$BUILD_ID" ]; then | |
| echo "Error: Failed to create BrowserStack build" | |
| echo "Response: $BUILD_RESPONSE" | |
| exit 1 | |
| fi | |
| echo "Build started with ID: $BUILD_ID" | |
| echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT" | |
| # Wait for XCUITest to complete | |
| MAX_WAIT_TIME=1080 # 18 minutes | |
| CHECK_INTERVAL=30 # Check every 30 seconds | |
| ELAPSED=0 | |
| while [ $ELAPSED -lt $MAX_WAIT_TIME ]; do | |
| BUILD_STATUS_RESPONSE=$(curl -s -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ | |
| "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/builds/$BUILD_ID") | |
| BUILD_STATUS=$(echo "$BUILD_STATUS_RESPONSE" | yq eval .status) | |
| if [ "$BUILD_STATUS" = "null" ] || [ -z "$BUILD_STATUS" ]; then | |
| echo "Error getting build status. Response: $BUILD_STATUS_RESPONSE" | |
| sleep $CHECK_INTERVAL | |
| ELAPSED=$((ELAPSED + CHECK_INTERVAL)) | |
| continue | |
| fi | |
| echo "Build status: $BUILD_STATUS (elapsed: ${ELAPSED}s)" | |
| if [ "$BUILD_STATUS" = "done" ] || [ "$BUILD_STATUS" = "failed" ] || [ "$BUILD_STATUS" = "error" ] || [ "$BUILD_STATUS" = "passed" ] || [ "$BUILD_STATUS" = "completed" ]; then | |
| echo "Build completed with status: $BUILD_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/xcuitest/v2/builds/$BUILD_ID") | |
| echo "Final build result:" | |
| echo "$FINAL_RESULT" | jq . | |
| # Check final status | |
| BUILD_STATUS=$(echo "$FINAL_RESULT" | yq eval .status) | |
| if [ "$BUILD_STATUS" != "passed" ]; then | |
| echo "Build failed with status: $BUILD_STATUS" | |
| exit 1 | |
| else | |
| echo "All tests passed successfully!" | |
| fi | |
| summary: | |
| name: CI Report | |
| runs-on: ubuntu-latest | |
| needs: [lint, build-macos, build-ios, browserstack] | |
| if: always() | |
| steps: | |
| - name: Report Results | |
| run: | | |
| echo "## 📱 Swift CI" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Overall status | |
| if [[ "${{ needs.lint.result }}" == "success" && \ | |
| "${{ needs.build-macos.result }}" == "success" && \ | |
| "${{ needs.build-ios.result }}" == "success" && \ | |
| "${{ needs.browserstack.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 macOS | ${{ needs.build-macos.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Build iOS | ${{ needs.build-ios.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| BrowserStack Tests | ${{ needs.browserstack.result == 'success' && '✅ Passed' || (needs.browserstack.result == 'skipped' && '⏭️ Skipped') || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # BrowserStack link | |
| if [[ "${{ needs.browserstack.result }}" != "skipped" ]]; then | |
| echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "🍎 [View Test Results](https://app-automate.browserstack.com/dashboard/v2/builds/${{ needs.browserstack.outputs.build_id }}/)" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tested Device:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- iPhone 15 Pro (iOS 17)" >> $GITHUB_STEP_SUMMARY | |
| fi |