diff --git a/.github/actions/seed-ditto-document/action.yml b/.github/actions/seed-ditto-document/action.yml new file mode 100644 index 000000000..cad09b91d --- /dev/null +++ b/.github/actions/seed-ditto-document/action.yml @@ -0,0 +1,78 @@ +name: 'Seed Ditto Test Document' +description: 'Creates a test document in Ditto Cloud with inverted timestamp for CI testing' +author: 'Ditto' + +inputs: + ditto-api-key: + description: 'Ditto API key for authentication' + required: true + ditto-api-url: + description: 'Ditto API URL (e.g., cloud.ditto.live)' + required: true + app-name: + description: 'Name of the app/platform being tested (e.g., android-kotlin, swift, etc.)' + required: true + +outputs: + document-id: + description: 'The generated document ID' + value: ${{ steps.seed.outputs.document_id }} + document-title: + description: 'The generated document title (same as ID)' + value: ${{ steps.seed.outputs.document_title }} + inverted-timestamp: + description: 'The inverted timestamp used for ordering' + value: ${{ steps.seed.outputs.inverted_timestamp }} + +runs: + using: 'composite' + steps: + - name: Generate and insert test document + id: seed + shell: bash + run: | + # Generate inverted timestamp for top position in list + TIMESTAMP=$(date +%s) + INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) + + # Create unique document ID and title + DOC_ID="${INVERTED_TIMESTAMP}_${{ inputs.app-name }}_ci_test_${{ github.run_id }}_${{ github.run_number }}" + DOC_TITLE="$DOC_ID" + + echo "๐Ÿ“ Seeding test document to Ditto Cloud" + echo "๐Ÿ“ App: ${{ inputs.app-name }}" + echo "๐Ÿ“ ID: ${DOC_ID}" + echo "๐Ÿ“ Timestamp: ${TIMESTAMP} โ†’ Inverted: ${INVERTED_TIMESTAMP}" + + # Insert document using Ditto API v4 + RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + -H 'Content-type: application/json' \ + -H "Authorization: Bearer ${{ inputs.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://${{ inputs.ditto-api-url }}/api/v4/store/execute") + + # Extract HTTP status code and response body (portable version) + 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" + echo "document_id=${DOC_ID}" >> $GITHUB_OUTPUT + echo "document_title=${DOC_TITLE}" >> $GITHUB_OUTPUT + echo "inverted_timestamp=${INVERTED_TIMESTAMP}" >> $GITHUB_OUTPUT + else + echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" + echo "Response: $BODY" + exit 1 + fi diff --git a/.github/scripts/browserstack-test.py b/.github/scripts/browserstack-test.py index d469499d8..774348941 100644 --- a/.github/scripts/browserstack-test.py +++ b/.github/scripts/browserstack-test.py @@ -21,9 +21,7 @@ def wait_for_sync_document(driver, doc_id, max_wait=30): """Wait for a specific document to appear in the task list.""" print(f"Waiting for document '{doc_id}' to sync...") - # Extract the run ID from the document ID (format: github_test_RUNID_RUNNUMBER) - run_id = doc_id.split("_")[2] if len(doc_id.split("_")) > 2 else doc_id - print(f"Looking for GitHub Run ID: {run_id}") + print(f"Looking for exact match on document ID/title: {doc_id}") start_time = time.time() @@ -33,13 +31,13 @@ def wait_for_sync_document(driver, doc_id, max_wait=30): # Use the most specific selector first task_elements = driver.find_elements(By.CSS_SELECTOR, "div.group span") - # Check each element for our GitHub run ID + # Check each element for exact document ID match for element in task_elements: try: element_text = element.text.strip() - # Check if the run ID appears in the text and it's our GitHub test task - if run_id in element_text and "GitHub Test Task" in element_text: - print(f"โœ“ Found synced document: {element_text}") + # Exact match on the document ID (which is also the title) + if element_text == doc_id: + print(f"โœ“ Found synced document with exact match: {element_text}") return True except: continue diff --git a/.github/scripts/retry-browserstack.sh b/.github/scripts/retry-browserstack.sh new file mode 100755 index 000000000..4aaed4e94 --- /dev/null +++ b/.github/scripts/retry-browserstack.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# +# Generic retry wrapper for BrowserStack API calls +# Retries on BROWSERSTACK_ALL_PARALLELS_IN_USE errors with exponential backoff +# +# Usage: .github/scripts/retry-browserstack.sh '' +# + +set -e + +# Configuration +MAX_ATTEMPTS=${MAX_ATTEMPTS:-5} +INITIAL_WAIT=${INITIAL_WAIT:-60} # Start with 1 minute +MAX_WAIT=${MAX_WAIT:-300} # Max 5 minutes between retries + +# Function to run command with retry logic +retry_on_queue_full() { + local attempt=1 + local wait_time=$INITIAL_WAIT + + while [ $attempt -le $MAX_ATTEMPTS ]; do + echo "๐Ÿ”„ Attempt $attempt/$MAX_ATTEMPTS..." + + # Run the command and capture output + set +e + OUTPUT=$(eval "$@" 2>&1) + EXIT_CODE=$? + set -e + + echo "$OUTPUT" + + # Check if it's a BrowserStack queue error + if echo "$OUTPUT" | grep -q "BROWSERSTACK_ALL_PARALLELS_IN_USE"; then + if [ $attempt -lt $MAX_ATTEMPTS ]; then + echo "โณ BrowserStack queue is full. Waiting ${wait_time}s before retry (attempt $attempt/$MAX_ATTEMPTS)..." + sleep $wait_time + + # Exponential backoff with max cap + wait_time=$((wait_time * 2)) + if [ $wait_time -gt $MAX_WAIT ]; then + wait_time=$MAX_WAIT + fi + + attempt=$((attempt + 1)) + else + echo "โŒ Max attempts ($MAX_ATTEMPTS) reached. BrowserStack queue still full." + return 1 + fi + else + # Not a queue error - either success or fail immediately + if [ $EXIT_CODE -eq 0 ]; then + echo "โœ… Command succeeded!" + else + echo "โŒ Command failed with non-queue error" + fi + return $EXIT_CODE + fi + done + + return 1 +} + +# Check if command was provided +if [ $# -eq 0 ]; then + echo "Usage: $0 ''" + echo "" + echo "Examples:" + echo " $0 'curl -u \$USER:\$KEY -X POST https://api.browserstack.com/...'" + echo " $0 './gradlew test'" + echo "" + echo "Environment variables:" + echo " MAX_ATTEMPTS=$MAX_ATTEMPTS (default: 5)" + echo " INITIAL_WAIT=$INITIAL_WAIT (default: 60 seconds)" + echo " MAX_WAIT=$MAX_WAIT (default: 300 seconds)" + exit 1 +fi + +# Run with retry logic +retry_on_queue_full "$@" diff --git a/.github/workflows/android-cpp-ci.yml b/.github/workflows/android-cpp-ci.yml index 76208511b..3cc56b452 100644 --- a/.github/workflows/android-cpp-ci.yml +++ b/.github/workflows/android-cpp-ci.yml @@ -133,50 +133,26 @@ jobs: browserstack-appium-test: name: BrowserStack Device Testing runs-on: ubuntu-latest - needs: build + 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: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'android-cpp' + - name: Download APK artifacts uses: actions/download-artifact@v4 with: name: android-cpp-apks path: android-cpp/QuickStartTasksCPP/app/build/outputs/apk/release/ - - - name: Insert test document into Ditto Cloud - id: seed_doc - run: | - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_TITLE="${INVERTED_TIMESTAMP}_android_cpp_ci_test_${{ github.run_id }}_${{ github.run_number }}" - - echo "doc_title=${DOC_TITLE}" >> $GITHUB_OUTPUT - - # Insert document using Ditto API v4 (same as other Android workflows) - 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_TITLE}\", - \"title\": \"${DOC_TITLE}\", - \"done\": false, - \"deleted\": false - } - } - }" \ - "https://${{ secrets.DITTO_API_URL }}/api/v4/store/execute") - - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - if [ "$HTTP_CODE" -ne 200 ] && [ "$HTTP_CODE" -ne 201 ]; then - echo "Failed to insert document. HTTP Status: $HTTP_CODE" - exit 1 - fi - name: Setup Java for Appium test uses: actions/setup-java@v4 @@ -210,7 +186,7 @@ jobs: export BROWSERSTACK_USERNAME="${{ secrets.BROWSERSTACK_USERNAME }}" export BROWSERSTACK_ACCESS_KEY="${{ secrets.BROWSERSTACK_ACCESS_KEY }}" export BROWSERSTACK_APP_URL="${{ steps.upload.outputs.app_url }}" - export GITHUB_TEST_DOC_ID="${{ steps.seed_doc.outputs.doc_title }}" + export GITHUB_TEST_DOC_ID="${{ steps.seed_task.outputs.document-title }}" ../gradlew test --console=plain --no-daemon @@ -223,34 +199,41 @@ jobs: android-cpp/QuickStartTasksCPP/app/build/outputs/apk/ android-cpp/QuickStartTasksCPP/appium-test/build/reports/ - - name: Comment PR with results - if: github.event_name == 'pull_request' && always() - uses: actions/github-script@v7 - with: - script: | - const testDocId = '${{ steps.seed_doc.outputs.doc_title }}'; - const status = '${{ job.status }}'; - const runUrl = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'; - - const body = `## ๐Ÿ“ฑ BrowserStack Test Results (Android C++) + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [browserstack-appium-test] + if: always() - **Status:** ${status === 'success' ? 'โœ… Passed' : 'โŒ Failed'} - **Build:** [#${{ github.run_number }}](${runUrl}) - **Test Document ID:** ${testDocId || 'Not generated'} - - ### Tested Devices: - - Google Pixel 7 (Android 13.0) - - ### Test Verification: - - โœ… Lint check completed - - โœ… APK build successful - - โœ… 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 - }); + steps: + - name: Report Results + run: | + echo "## ๐Ÿ“ฑ Android C++ CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.browserstack-appium-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| BrowserStack Tests | ${{ needs.browserstack-appium-test.result == 'success' && 'โœ… Passed' || (needs.browserstack-appium-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # BrowserStack link + if [[ "${{ needs.browserstack-appium-test.result }}" != "skipped" ]]; then + echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿค– [View Test Results](https://app-automate.browserstack.com/builds?project=Ditto+Android+C%2B%2B&build=Build+%23${{ github.run_number }})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tested Device:**" >> $GITHUB_STEP_SUMMARY + echo "- Google Pixel 7 (Android 13.0)" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/android-java-ci.yml b/.github/workflows/android-java-ci.yml index 1251de558..dc5e9c87d 100644 --- a/.github/workflows/android-java-ci.yml +++ b/.github/workflows/android-java-ci.yml @@ -108,65 +108,27 @@ jobs: browserstack-test: name: BrowserStack Device Testing runs-on: ubuntu-latest - needs: build + 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: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'android-java' + - 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: | @@ -212,60 +174,56 @@ jobs: # 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\" + + # Create test execution request + TITLE="${{ steps.seed_task.outputs.document-title }}" + + BUILD_RESPONSE=$(curl -s -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\" + "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" @@ -343,38 +301,44 @@ jobs: 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 - }); \ No newline at end of file + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [browserstack-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## ๐Ÿ“ฑ Android Java CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.browserstack-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| BrowserStack Tests | ${{ needs.browserstack-test.result == 'success' && 'โœ… Passed' || (needs.browserstack-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # BrowserStack link + if [[ "${{ needs.browserstack-test.result }}" != "skipped" ]]; then + echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿค– [View Test Results](https://app-automate.browserstack.com/builds?project=Ditto+Android+Java&build=Build+%23${{ github.run_number }})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tested Devices:**" >> $GITHUB_STEP_SUMMARY + echo "- Google Pixel 8 (Android 14)" >> $GITHUB_STEP_SUMMARY + echo "- Samsung Galaxy S23 (Android 13)" >> $GITHUB_STEP_SUMMARY + echo "- Google Pixel 6 (Android 12)" >> $GITHUB_STEP_SUMMARY + echo "- OnePlus 9 (Android 11)" >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/.github/workflows/android-kotlin-ci.yml b/.github/workflows/android-kotlin-ci.yml index 7d9b033c4..5a721579d 100644 --- a/.github/workflows/android-kotlin-ci.yml +++ b/.github/workflows/android-kotlin-ci.yml @@ -54,29 +54,27 @@ jobs: working-directory: android-kotlin/QuickStartTasks run: ./gradlew lint - build-and-test: + build: name: Build and Test runs-on: ubuntu-latest needs: lint timeout-minutes: 30 - outputs: - test_doc_title: ${{ steps.test_doc.outputs.test_doc_title }} - + 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: @@ -86,23 +84,7 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- - - - name: Generate test document title - id: test_doc - run: | - # Create a unique GitHub test document with inverted timestamp to appear at top - 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 "test_doc_id=$DOC_ID" >> $GITHUB_OUTPUT - echo "test_doc_title=$DOC_TITLE" >> $GITHUB_OUTPUT - echo "๐Ÿ“ Generated test document (inverted timestamp for top position)" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - echo "๐Ÿ“ Timestamp: ${TIMESTAMP} โ†’ Inverted: ${INVERTED_TIMESTAMP}" - + - name: Build APKs working-directory: android-kotlin/QuickStartTasks env: @@ -110,9 +92,8 @@ jobs: DITTO_PLAYGROUND_TOKEN: ${{ secrets.DITTO_PLAYGROUND_TOKEN }} DITTO_AUTH_URL: ${{ secrets.DITTO_AUTH_URL }} DITTO_WEBSOCKET_URL: ${{ secrets.DITTO_WEBSOCKET_URL }} - TEST_DOCUMENT_TITLE: ${{ steps.test_doc.outputs.test_doc_title }} run: ./gradlew assembleDebug assembleDebugAndroidTest - + - name: Run unit tests working-directory: android-kotlin/QuickStartTasks env: @@ -121,7 +102,7 @@ jobs: DITTO_AUTH_URL: ${{ secrets.DITTO_AUTH_URL }} DITTO_WEBSOCKET_URL: ${{ secrets.DITTO_WEBSOCKET_URL }} run: ./gradlew test - + - name: Upload APK artifacts uses: actions/upload-artifact@v4 with: @@ -130,7 +111,7 @@ jobs: android-kotlin/QuickStartTasks/app/build/outputs/apk/debug/app-debug.apk android-kotlin/QuickStartTasks/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk retention-days: 1 - + - name: Upload test reports if: always() uses: actions/upload-artifact@v4 @@ -138,64 +119,31 @@ jobs: name: test-reports-${{ github.run_number }} path: android-kotlin/QuickStartTasks/app/build/reports/ retention-days: 1 - + browserstack-test: name: BrowserStack Device Testing runs-on: ubuntu-latest - needs: build-and-test + 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: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'android-kotlin' + - name: Download APK artifacts uses: actions/download-artifact@v4 with: name: android-apks-${{ github.run_number }} path: android-kotlin/QuickStartTasks/app/build/outputs/apk/ - - name: Insert test document into Ditto Cloud - run: | - # Use the same document title that was built into the APK - DOC_TITLE="${{ needs.build-and-test.outputs.test_doc_title }}" - DOC_ID="$DOC_TITLE" - - echo "๐Ÿ“ Inserting test document that matches build-time configuration" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - - # Insert document using Ditto API v4 (same as Swift 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: | @@ -238,8 +186,8 @@ jobs: fi # Create test execution request with instrumentationOptions (correct approach for Android) - TITLE="${{ needs.build-and-test.outputs.test_doc_title }}" - + TITLE="${{ steps.seed_task.outputs.document-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" \ @@ -349,3 +297,45 @@ jobs: echo "Warning: Could not parse final results" echo "Raw response: $FINAL_RESULT" fi + + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [browserstack-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## ๐Ÿ“ฑ Android Kotlin CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.browserstack-test.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 and Test | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| BrowserStack Tests | ${{ needs.browserstack-test.result == 'success' && 'โœ… Passed' || (needs.browserstack-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # BrowserStack link + if [[ "${{ needs.browserstack-test.result }}" != "skipped" ]]; then + echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿค– [View Test Results](https://app-automate.browserstack.com/builds?project=Ditto+Android+Kotlin&build=Build+%23${{ github.run_number }})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tested Devices:**" >> $GITHUB_STEP_SUMMARY + echo "- Google Pixel 8 (Android 14)" >> $GITHUB_STEP_SUMMARY + echo "- Samsung Galaxy S23 (Android 13)" >> $GITHUB_STEP_SUMMARY + echo "- Google Pixel 6 (Android 12)" >> $GITHUB_STEP_SUMMARY + echo "- OnePlus 9 (Android 11)" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/cpp-tui-ci.yml b/.github/workflows/cpp-tui-ci.yml index 5eb409a2d..866966b23 100644 --- a/.github/workflows/cpp-tui-ci.yml +++ b/.github/workflows/cpp-tui-ci.yml @@ -93,39 +93,13 @@ jobs: echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env - - name: Insert test document into Ditto Cloud - run: | - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - 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") - - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | head -n-1) - - if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then - echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + - name: Seed test document to Ditto Cloud + id: seed + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'cpp-tui' - name: Download and setup Ditto C++ SDK working-directory: cpp-tui/taskscpp @@ -134,7 +108,7 @@ jobs: - name: Build and run integration test working-directory: cpp-tui/taskscpp env: - GITHUB_TEST_DOC_TITLE: ${{ env.GITHUB_TEST_DOC_TITLE }} + GITHUB_TEST_DOC_TITLE: ${{ steps.seed.outputs.document-title }} GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_NUMBER: ${{ github.run_number }} run: | @@ -145,4 +119,32 @@ jobs: src/task.cpp src/tasks_peer.cpp src/tasks_log.cpp \ -L./sdk -lditto -ldl -lrt -pthread \ -o build/integration_test - ./build/integration_test \ No newline at end of file + ./build/integration_test + + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [lint, build, integration-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## ๐Ÿ”ง C++ TUI CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.integration-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Integration Test | ${{ needs.integration-test.result == 'success' && 'โœ… Passed' || (needs.integration-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/dotnet-maui-ci.yml b/.github/workflows/dotnet-maui-ci.yml index b3562fe26..65638a202 100644 --- a/.github/workflows/dotnet-maui-ci.yml +++ b/.github/workflows/dotnet-maui-ci.yml @@ -149,69 +149,10 @@ jobs: path: dotnet-maui/DittoMauiTasksApp/bin/Release/net9.0-ios/ios-arm64/DittoMauiTasksApp-device.ipa retention-days: 1 - seed-task-data: - name: Seed Task Data - runs-on: ubuntu-latest - needs: [build-android, build-ios] - timeout-minutes: 5 - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - outputs: - test_task_title: ${{ steps.seed_task.outputs.test_task_title }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Seed test task in Ditto Cloud - id: seed_task - run: | - # Create a unique test document with inverted timestamp to appear at top - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - echo "๐Ÿ“ Inserting test document (inverted timestamp for top position)" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - echo "๐Ÿ“ Timestamp: ${TIMESTAMP} โ†’ Inverted: ${INVERTED_TIMESTAMP}" - - # Insert document using Ditto API v4 - 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}" - echo "test_task_title=${DOC_TITLE}" >> $GITHUB_OUTPUT - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi - browserstack-android: name: BrowserStack Android Tests runs-on: ubuntu-latest - needs: [build-android, seed-task-data] + needs: [build-android] timeout-minutes: 30 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') @@ -219,6 +160,14 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'dotnet-maui' + - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -265,14 +214,14 @@ jobs: BROWSERSTACK_USERNAME="${{ secrets.BROWSERSTACK_USERNAME }}" \ BROWSERSTACK_ACCESS_KEY="${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ BROWSERSTACK_APP_ID="${{ steps.upload-android.outputs.app_url }}" \ - EXPECTED_TASK_TITLE="${{ needs.seed-task-data.outputs.test_task_title }}" \ + EXPECTED_TASK_TITLE="${{ steps.seed_task.outputs.document-title }}" \ BUILD_NAME="CI Build #${{ github.run_number }}" \ dotnet run browserstack-ios: name: BrowserStack iOS Tests runs-on: macos-latest - needs: [build-ios, seed-task-data] + needs: [build-ios] timeout-minutes: 60 if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') @@ -280,6 +229,14 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'dotnet-maui' + - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -326,14 +283,14 @@ jobs: BROWSERSTACK_USERNAME="${{ secrets.BROWSERSTACK_USERNAME }}" \ BROWSERSTACK_ACCESS_KEY="${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ BROWSERSTACK_APP_ID="${{ steps.upload-ios.outputs.app_url }}" \ - EXPECTED_TASK_TITLE="${{ needs.seed-task-data.outputs.test_task_title }}" \ + EXPECTED_TASK_TITLE="${{ steps.seed_task.outputs.document-title }}" \ BUILD_NAME="CI Build #${{ github.run_number }}" \ dotnet run summary: name: CI Report runs-on: ubuntu-latest - needs: [lint, build-android, build-ios, browserstack-android, browserstack-ios] + needs: [browserstack-android, browserstack-ios] if: always() steps: diff --git a/.github/workflows/dotnet-tui-ci.yml b/.github/workflows/dotnet-tui-ci.yml index 01e05076b..0cfc9758b 100644 --- a/.github/workflows/dotnet-tui-ci.yml +++ b/.github/workflows/dotnet-tui-ci.yml @@ -97,39 +97,13 @@ jobs: echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env - - name: Insert test document into Ditto Cloud - run: | - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - 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") - - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | head -n-1) - - if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then - echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + - name: Seed test document to Ditto Cloud + id: seed + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'dotnet-tui' - name: Build .NET application working-directory: dotnet-tui @@ -138,7 +112,35 @@ jobs: - name: Run .NET integration tests working-directory: dotnet-tui env: - TASK_TO_FIND: ${{ env.GITHUB_TEST_DOC_TITLE }} + TASK_TO_FIND: ${{ steps.seed.outputs.document-title }} GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_NUMBER: ${{ github.run_number }} - run: dotnet test DittoDotNetTasksConsole.Tests --configuration Release --logger "console;verbosity=detailed" \ No newline at end of file + run: dotnet test DittoDotNetTasksConsole.Tests --configuration Release --logger "console;verbosity=detailed" + + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [lint, build, integration-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## ๐Ÿ”ท .NET TUI CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.integration-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Integration Test | ${{ needs.integration-test.result == 'success' && 'โœ… Passed' || (needs.integration-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/dotnet-winforms-ci.yml b/.github/workflows/dotnet-winforms-ci.yml index bfc7bcd73..995291a3b 100644 --- a/.github/workflows/dotnet-winforms-ci.yml +++ b/.github/workflows/dotnet-winforms-ci.yml @@ -93,58 +93,47 @@ jobs: echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env - - name: Insert test document into Ditto Cloud - run: | - $timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $invertedTimestamp = 9999999999 - $timestamp - $docId = "${invertedTimestamp}_winforms_ci_test_${{ github.run_id }}_${{ github.run_number }}" - $docTitle = "${invertedTimestamp}_winforms_ci_test_${{ github.run_id }}_${{ github.run_number }}" - - Write-Host "๐Ÿ“ Inserting test document for integration testing" - Write-Host "๐Ÿ“ ID: '$docId'" - Write-Host "๐Ÿ“ Title: '$docTitle'" - - # Store title for later use in integration test - echo "GITHUB_TEST_DOC_TITLE=$docTitle" >> $env:GITHUB_ENV - - # Prepare JSON body - $body = @{ - statement = "INSERT INTO tasks DOCUMENTS (:newTask) ON ID CONFLICT DO UPDATE" - args = @{ - newTask = @{ - _id = $docId - title = $docTitle - done = $false - deleted = $false - } - } - } | ConvertTo-Json -Depth 10 - - # Insert document using Ditto API - $headers = @{ - "Content-Type" = "application/json" - "Authorization" = "Bearer ${{ secrets.DITTO_API_KEY }}" - } - - try { - $response = Invoke-RestMethod -Uri "https://${{ secrets.DITTO_API_URL }}/api/v4/store/execute" ` - -Method POST ` - -Headers $headers ` - -Body $body ` - -ErrorAction Stop - - Write-Host "โœ… Document inserted successfully" - } catch { - Write-Host "โŒ Failed to insert document" - Write-Host "Error: $_" - exit 1 - } + - name: Seed test document to Ditto Cloud + id: seed + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'dotnet-winforms' - name: Build and run integration test working-directory: dotnet-winforms/IntegrationTest env: - GITHUB_TEST_DOC_TITLE: ${{ env.GITHUB_TEST_DOC_TITLE }} + GITHUB_TEST_DOC_TITLE: ${{ steps.seed.outputs.document-title }} run: | dotnet restore dotnet build --configuration Release --no-restore - dotnet run --configuration Release --no-build \ No newline at end of file + dotnet run --configuration Release --no-build + + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [lint, build, integration-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## ๐ŸชŸ .NET WinForms CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.integration-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Integration Test | ${{ needs.integration-test.result == 'success' && 'โœ… Passed' || (needs.integration-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 2a350b71a..ee30c3faa 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -373,67 +373,10 @@ jobs: path: flutter_app/coverage/ retention-days: 1 - seed-data: - name: Seed Test Data - runs-on: ubuntu-latest - needs: [build-android, build-ios, build-web, build-macos, build-windows] - if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - outputs: - test_title: ${{ steps.seed.outputs.test_title }} - timeout-minutes: 5 - - steps: - - name: Insert test document into Ditto Cloud - id: seed - run: | - # Generate test document for BrowserStack testing - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_flutter_ci_test_${{ github.run_id }}_${{ github.run_number }}" - DOC_TITLE="${INVERTED_TIMESTAMP}_flutter_ci_test_${{ github.run_id }}_${{ github.run_number }}" - - echo "๐Ÿ“ Inserting test document for BrowserStack testing" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - - # Output title for other jobs to use - echo "test_title=${DOC_TITLE}" >> $GITHUB_OUTPUT - - # Insert document using Ditto API v4 - Flutter app uses: title, done, deleted fields - 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') - - echo "Response: $BODY" - echo "HTTP Status: $HTTP_CODE" - - if [ "$HTTP_CODE" -ne 200 ] && [ "$HTTP_CODE" -ne 201 ]; then - echo "โŒ Failed to insert document. HTTP status: $HTTP_CODE" - exit 1 - fi - - echo "โœ… Document inserted successfully" - browserstack-android: name: BrowserStack Android Testing runs-on: ubuntu-latest - needs: [build-android, seed-data] + needs: [build-android] if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' timeout-minutes: 45 outputs: @@ -443,6 +386,14 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'flutter' + - name: Setup Java uses: actions/setup-java@v4 with: @@ -480,8 +431,8 @@ jobs: - name: Build Android debug APKs with seeded task title working-directory: flutter_app/android run: | - # Use the task title from seed-data job - TITLE="${{ needs.seed-data.outputs.test_title }}" + # Use the task title from seed job + TITLE="${{ steps.seed_task.outputs.document-title }}" ENCODED_TASK=$(echo -n "TASK_TO_FIND=$TITLE" | base64) TARGET_PATH="$(pwd)/../integration_test/app_test.dart" @@ -666,7 +617,7 @@ jobs: echo "โŒ **Status:** Failed" >> $GITHUB_STEP_SUMMARY fi - echo "- **Test Task:** ${{ needs.seed-data.outputs.test_title }}" >> $GITHUB_STEP_SUMMARY + echo "- **Test Task:** ${{ steps.seed_task.outputs.document-title }}" >> $GITHUB_STEP_SUMMARY echo "- **Build ID:** $BUILD_ID" >> $GITHUB_STEP_SUMMARY echo "- **Dashboard:** [View Results](https://app-automate.browserstack.com/dashboard/v2/builds/$BUILD_ID)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -674,7 +625,7 @@ jobs: browserstack-ios: name: BrowserStack iOS Testing runs-on: macos-latest - needs: [build-ios, seed-data] + needs: [build-ios] if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' timeout-minutes: 45 @@ -682,6 +633,14 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'flutter' + - name: Download iOS IPA uses: actions/download-artifact@v4 with: @@ -712,8 +671,8 @@ jobs: - name: Build iOS integration test package with seeded task title working-directory: flutter_app run: | - # Use the task title from seed-data job - TITLE="${{ needs.seed-data.outputs.test_title }}" + # Use the task title from seed job + TITLE="${{ steps.seed_task.outputs.document-title }}" echo "๐Ÿงช Building iOS integration test package with task title: $TITLE" flutter build ios integration_test/app_test.dart --release --no-codesign \ @@ -903,7 +862,7 @@ jobs: echo "โŒ **Status:** Failed" >> $GITHUB_STEP_SUMMARY fi - echo "- **Test Task:** ${{ needs.seed-data.outputs.test_title }}" >> $GITHUB_STEP_SUMMARY + echo "- **Test Task:** ${{ steps.seed_task.outputs.document-title }}" >> $GITHUB_STEP_SUMMARY echo "- **Build ID:** $BUILD_ID" >> $GITHUB_STEP_SUMMARY echo "- **Dashboard:** [View Results](https://app-automate.browserstack.com/dashboard/v2/builds/$BUILD_ID)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/java-spring-ci.yml b/.github/workflows/java-spring-ci.yml index 96d19be7c..5398a3030 100644 --- a/.github/workflows/java-spring-ci.yml +++ b/.github/workflows/java-spring-ci.yml @@ -56,8 +56,6 @@ jobs: runs-on: ubuntu-latest needs: lint timeout-minutes: 20 - outputs: - test_doc_title: ${{ steps.test_doc.outputs.test_doc_title }} steps: - uses: actions/checkout@v4 @@ -71,23 +69,6 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - - - name: Generate test document title - id: test_doc - run: | - # Create a unique GitHub test document with inverted timestamp to appear at top - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_java_spring_ci_test_${{ github.run_id }}_${{ github.run_number }}" - DOC_TITLE="${INVERTED_TIMESTAMP}_java_spring_ci_test_${{ github.run_id }}_${{ github.run_number }}" - - echo "test_doc_id=$DOC_ID" >> $GITHUB_OUTPUT - echo "test_doc_title=$DOC_TITLE" >> $GITHUB_OUTPUT - echo "๐Ÿ“ Generated test document (inverted timestamp for top position)" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - echo "๐Ÿ“ Timestamp: ${TIMESTAMP} โ†’ Inverted: ${INVERTED_TIMESTAMP}" - - name: Create .env file (root) run: | echo "DITTO_APP_ID=${{ secrets.DITTO_APP_ID }}" > .env @@ -116,60 +97,27 @@ jobs: browserstack-test: name: BrowserStack Device Testing (macos-latest) runs-on: macos-latest - needs: build + 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: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'java-spring' + - name: Download JAR artifacts uses: actions/download-artifact@v4 with: name: java-spring-jar-${{ github.run_number }} path: java-spring/build/libs/ - - - name: Insert test document into Ditto Cloud - run: | - # Use the same document title that was generated in build job - DOC_TITLE="${{ needs.build.outputs.test_doc_title }}" - DOC_ID="$DOC_TITLE" - - echo "๐Ÿ“ Inserting test document that matches build-time configuration" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - - # Insert document using Ditto API v4 (same as Android 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: Setup Java uses: actions/setup-java@v4 with: @@ -241,12 +189,18 @@ jobs: - name: Start Spring Boot app in background working-directory: java-spring + env: + DITTO_APP_ID: ${{ secrets.DITTO_APP_ID }} + DITTO_PLAYGROUND_TOKEN: ${{ secrets.DITTO_PLAYGROUND_TOKEN }} + DITTO_AUTH_URL: ${{ secrets.DITTO_AUTH_URL }} + DITTO_WEBSOCKET_URL: ${{ secrets.DITTO_WEBSOCKET_URL }} + DITTO_ENABLE_CLOUD_SYNC: true run: | nohup java -jar build/libs/*.jar \ --server.port=8080 \ --spring.profiles.active=test > app.log 2>&1 & echo $! > app.pid - + # Wait for application to be ready echo "Waiting for Spring Boot app to start..." for i in {1..30}; do @@ -257,16 +211,16 @@ jobs: sleep 2 done - - name: Execute Selenium tests on BrowserStack cloud browsers + - name: Execute Selenium tests on BrowserStack cloud browsers working-directory: java-spring env: BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} BROWSERSTACK_LOCAL: true - TEST_TASK_TITLE: ${{ needs.build.outputs.test_doc_title }} - GITHUB_TEST_DOC_ID: ${{ needs.build.outputs.test_doc_title }} + TEST_TASK_TITLE: ${{ steps.seed_task.outputs.document-title }} + GITHUB_TEST_DOC_ID: ${{ steps.seed_task.outputs.document-title }} run: | - TITLE="${{ needs.build.outputs.test_doc_title }}" + TITLE="${{ steps.seed_task.outputs.document-title }}" # Run only the BrowserStack test method, not all test methods ./gradlew test --tests "*TaskVisibilityIntegrationTest.shouldPassWithExistingTask" \ @@ -308,3 +262,42 @@ jobs: java-spring/app.log browserstack-local.log retention-days: 1 + + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [browserstack-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## โ˜• Java Spring CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.browserstack-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| BrowserStack Tests | ${{ needs.browserstack-test.result == 'success' && 'โœ… Passed' || (needs.browserstack-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # BrowserStack link + if [[ "${{ needs.browserstack-test.result }}" != "skipped" ]]; then + echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿ”— [View Test Results](https://automate.browserstack.com/builds?project=Ditto+Java+Spring+Tasks&build=Java+Spring+Selenium+Tests+%23${{ github.run_number }})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tested Browser:**" >> $GITHUB_STEP_SUMMARY + echo "- Chrome Latest (Windows 11)" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/javascript-tui-ci.yml b/.github/workflows/javascript-tui-ci.yml index c32a45d08..2074359cd 100644 --- a/.github/workflows/javascript-tui-ci.yml +++ b/.github/workflows/javascript-tui-ci.yml @@ -100,47 +100,46 @@ jobs: echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env - - name: Insert test document into Ditto Cloud - run: | - TIMESTAMP=$(date +%s) - # Invert the timestamp to allow reverse-chronological sorting of documents by ID. - # 9999999999 is chosen as a far-future Unix timestamp (Sat Nov 20 2286 17:46:39 UTC), - # ensuring all current timestamps are less than this value. - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - 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") - - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | head -n-1) - - if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then - echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + - name: Seed test document to Ditto Cloud + id: seed + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'javascript-tui' - name: Run integration test working-directory: javascript-tui env: - GITHUB_TEST_DOC_TITLE: ${{ env.GITHUB_TEST_DOC_TITLE }} + GITHUB_TEST_DOC_TITLE: ${{ steps.seed.outputs.document-title }} GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_NUMBER: ${{ github.run_number }} - run: npm run build && node tests/integration_test.js \ No newline at end of file + run: npm run build && node tests/integration_test.js + + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [lint, build, integration-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## ๐Ÿ“œ JavaScript TUI CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.integration-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Integration Test | ${{ needs.integration-test.result == 'success' && 'โœ… Passed' || (needs.integration-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/javascript-web-browserstack.yml b/.github/workflows/javascript-web-ci.yml similarity index 69% rename from .github/workflows/javascript-web-browserstack.yml rename to .github/workflows/javascript-web-ci.yml index fc77be8cd..649096226 100644 --- a/.github/workflows/javascript-web-browserstack.yml +++ b/.github/workflows/javascript-web-ci.yml @@ -45,41 +45,16 @@ jobs: echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env - 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: 'javascript-web' + + - name: Set document ID for tests run: | - # Use GitHub run ID to create deterministic document ID - DOC_ID="github_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC") - - # Insert document using curl with correct JSON structure - 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\": \"GitHub Test Task ${GITHUB_RUN_ID}\", - \"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" | head -n-1) - - # 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 "GITHUB_TEST_DOC_ID=${DOC_ID}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + echo "GITHUB_TEST_DOC_ID=${{ steps.seed_document.outputs.document-title }}" >> $GITHUB_ENV - name: Install dependencies working-directory: javascript-web @@ -161,3 +136,35 @@ jobs: test-report.md *screenshot*.png .github/scripts/browserstack-test.py + + - name: Generate test summary + if: always() + run: | + echo "## ๐ŸŒ JavaScript Web CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [ "${{ job.status }}" = "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 "| Check | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Lint | โœ… Passed |" >> $GITHUB_STEP_SUMMARY + echo "| Build | โœ… Passed |" >> $GITHUB_STEP_SUMMARY + echo "| BrowserStack Tests | ${{ job.status == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # BrowserStack info + echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿ”— [View Dashboard](https://automate.browserstack.com/dashboard/v2)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tested Browsers:**" >> $GITHUB_STEP_SUMMARY + echo "- Chrome 120.0 (Windows 11)" >> $GITHUB_STEP_SUMMARY + echo "- Firefox 121.0 (Windows 11)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Test Document ID:** \`${GITHUB_TEST_DOC_ID:-Not generated}\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/kotlin-multiplatform-ci.yml b/.github/workflows/kotlin-multiplatform-ci.yml index 7c6189736..845e1456b 100644 --- a/.github/workflows/kotlin-multiplatform-ci.yml +++ b/.github/workflows/kotlin-multiplatform-ci.yml @@ -72,25 +72,23 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 needs: lint - outputs: - test_doc_title: ${{ steps.test_doc.outputs.test_doc_title }} defaults: run: working-directory: kotlin-multiplatform - + steps: - uses: actions/checkout@v4 - + - name: Setup Java uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' cache: 'gradle' - + - name: Setup Android SDK uses: android-actions/setup-android@v3 - + - name: Cache Gradle dependencies uses: actions/cache@v4 with: @@ -101,38 +99,20 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('kotlin-multiplatform/gradle/wrapper/gradle-wrapper.properties', 'kotlin-multiplatform/**/*.gradle*', 'kotlin-multiplatform/gradle/libs.versions.toml') }} restore-keys: | gradle-${{ runner.os }}- - - - name: Generate test document title - id: test_doc - run: | - # Create a unique GitHub test document with inverted timestamp to appear at top - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_kmp_android_ci_test_${{ github.run_id }}_${{ github.run_number }}" - DOC_TITLE="${INVERTED_TIMESTAMP}_kmp_android_ci_test_${{ github.run_id }}_${{ github.run_number }}" - - echo "test_doc_id=$DOC_ID" >> $GITHUB_OUTPUT - echo "test_doc_title=$DOC_TITLE" >> $GITHUB_OUTPUT - echo "๐Ÿ“ Generated test document (inverted timestamp for top position)" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - echo "๐Ÿ“ Timestamp: ${TIMESTAMP} โ†’ Inverted: ${INVERTED_TIMESTAMP}" - + - name: Create production .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: Make gradlew executable run: chmod +x gradlew - + - name: Build Android APKs - env: - TEST_DOCUMENT_TITLE: ${{ steps.test_doc.outputs.test_doc_title }} run: ./gradlew :composeApp:assembleDebug :composeApp:assembleDebugAndroidTest --stacktrace - + - name: Upload APK artifacts uses: actions/upload-artifact@v4 with: @@ -148,27 +128,26 @@ jobs: timeout-minutes: 30 needs: lint outputs: - test_doc_title: ${{ steps.test_doc.outputs.test_doc_title }} ios-build-success: ${{ steps.build-status.outputs.success }} defaults: run: working-directory: kotlin-multiplatform - + steps: - uses: actions/checkout@v4 - + - name: Setup Java uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' cache: 'gradle' - + - name: Setup Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: 'latest-stable' - + - name: Cache Gradle dependencies uses: actions/cache@v4 with: @@ -179,17 +158,7 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('kotlin-multiplatform/gradle/wrapper/gradle-wrapper.properties', 'kotlin-multiplatform/**/*.gradle*', 'kotlin-multiplatform/gradle/libs.versions.toml') }} restore-keys: | gradle-${{ runner.os }}- - - - name: Generate test document title - id: test_doc - run: | - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_kmp_ios_ci_test_${{ github.run_id }}_${{ github.run_number }}" - DOC_TITLE="${INVERTED_TIMESTAMP}_kmp_ios_ci_test_${{ github.run_id }}_${{ github.run_number }}" - echo "test_doc_id=$DOC_ID" >> $GITHUB_OUTPUT - echo "test_doc_title=$DOC_TITLE" >> $GITHUB_OUTPUT - + - name: Write production .env run: | echo "DITTO_APP_ID=${{ secrets.DITTO_APP_ID }}" > ../.env @@ -335,60 +304,27 @@ jobs: browserstack-android: name: BrowserStack Android Testing runs-on: ubuntu-latest - needs: build-android + needs: [build-android] if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' timeout-minutes: 45 steps: - uses: actions/checkout@v4 - + + - name: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'kotlin-multiplatform' + - name: Download Android APK artifacts uses: actions/download-artifact@v4 with: name: android-kmp-apks-${{ github.run_number }} path: kotlin-multiplatform/composeApp/build/outputs/apk/ - - - name: Insert test document into Ditto Cloud - run: | - # Use the same document title that was built into the APK - DOC_TITLE="${{ needs.build-android.outputs.test_doc_title }}" - DOC_ID="$DOC_TITLE" - - echo "๐Ÿ“ Inserting test document that matches build-time configuration" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - - # Insert document using Ditto API v4 (use "title" field to match KMP app) - 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: | @@ -431,8 +367,8 @@ jobs: fi # Create test execution request with instrumentationOptions - TITLE="${{ needs.build-android.outputs.test_doc_title }}" - + TITLE="${{ steps.seed_task.outputs.document-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" \ @@ -577,4 +513,12 @@ jobs: else echo "โš ๏ธ Core builds passed but testing had issues. Check testing logs above." >> $GITHUB_STEP_SUMMARY fi + fi + + # BrowserStack link + if [[ "${{ needs.browserstack-android.result }}" != "skipped" ]]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "### BrowserStack Session" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿค– [View Android Test Results](https://app-automate.browserstack.com/builds?project=Ditto+Kotlin+Multiplatform&build=Build+%23${{ github.run_number }})" >> $GITHUB_STEP_SUMMARY fi \ No newline at end of file diff --git a/.github/workflows/react-native-ci.yml b/.github/workflows/react-native-ci.yml index d9878fe12..0a951c010 100644 --- a/.github/workflows/react-native-ci.yml +++ b/.github/workflows/react-native-ci.yml @@ -210,59 +210,18 @@ jobs: needs: [build-android, build-ios] timeout-minutes: 10 outputs: - test_task_title: ${{ steps.seed-document.outputs.test_task_title }} + test_task_title: ${{ steps.seed-document.outputs.document-title }} steps: - uses: actions/checkout@v4 - name: Insert test document into Ditto Cloud id: seed-document - run: | - # Create a unique GitHub test document with inverted timestamp to appear at top - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - echo "๐Ÿ“ Inserting common test document for all RN apps" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - - # Output the title for use in test steps - echo "test_task_title=${DOC_TITLE}" >> $GITHUB_OUTPUT - - # Insert document using Ditto API v4 (same as Swift 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}" - echo "GITHUB_TEST_DOC_ID=${DOC_ID}" >> $GITHUB_ENV - echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + 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 diff --git a/.github/workflows/react-native-expo-ci.yml b/.github/workflows/react-native-expo-ci.yml index 310df34da..0b4d5a8b1 100644 --- a/.github/workflows/react-native-expo-ci.yml +++ b/.github/workflows/react-native-expo-ci.yml @@ -213,59 +213,18 @@ jobs: needs: [build-android, build-ios] timeout-minutes: 10 outputs: - test_task_title: ${{ steps.seed-document.outputs.test_task_title }} + test_task_title: ${{ steps.seed-document.outputs.document-title }} steps: - uses: actions/checkout@v4 - name: Insert test document into Ditto Cloud id: seed-document - run: | - # Create a unique GitHub test document with inverted timestamp to appear at top - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - echo "๐Ÿ“ Inserting common test document for all RN apps" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - - # Output the title for use in test steps - echo "test_task_title=${DOC_TITLE}" >> $GITHUB_OUTPUT - - # Insert document using Ditto API v4 (same as Swift 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}" - echo "GITHUB_TEST_DOC_ID=${DOC_ID}" >> $GITHUB_ENV - echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + 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-expo' test-android-maestro: name: Test Android - BrowserStack Maestro diff --git a/.github/workflows/rust-tui-ci.yml b/.github/workflows/rust-tui-ci.yml index ae33b6222..a32978e8e 100644 --- a/.github/workflows/rust-tui-ci.yml +++ b/.github/workflows/rust-tui-ci.yml @@ -113,48 +113,52 @@ jobs: echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env - - name: Insert test document into Ditto Cloud - run: | - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - 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") - - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | head -n-1) - - if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then - echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + - name: Seed test document to Ditto Cloud + id: seed + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'rust-tui' - name: Build and run integration test working-directory: rust-tui env: - TASK_TO_FIND: ${{ env.GITHUB_TEST_DOC_TITLE }} + TASK_TO_FIND: ${{ steps.seed.outputs.document-title }} GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_NUMBER: ${{ github.run_number }} run: | # Build integration test cargo build --bin integration_test # Run integration test - cargo run --bin integration_test \ No newline at end of file + cargo run --bin integration_test + + summary: + name: CI Report + runs-on: ubuntu-latest + needs: [lint, build, test, integration-test] + if: always() + + steps: + - name: Report Results + run: | + echo "## ๐Ÿฆ€ Rust TUI CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Overall status + if [[ "${{ needs.lint.result }}" == "success" && \ + "${{ needs.build.result }}" == "success" && \ + "${{ needs.test.result }}" == "success" && \ + "${{ needs.integration-test.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 | ${{ needs.build.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Test | ${{ needs.test.result == 'success' && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Integration Test | ${{ needs.integration-test.result == 'success' && 'โœ… Passed' || (needs.integration-test.result == 'skipped' && 'โญ๏ธ Skipped') || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index 6eee7de6a..8bdb4532f 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -5,8 +5,12 @@ on: branches: [main, 'sdk-*'] paths: - 'swift/**' + - '.github/workflows/swift-ci.yml' push: branches: [main, 'sdk-*'] + paths: + - 'swift/**' + - '.github/workflows/swift-ci.yml' workflow_dispatch: concurrency: @@ -220,25 +224,33 @@ jobs: steps: - uses: actions/checkout@v4 - + + - name: Seed test task to Ditto Cloud + id: seed_task + uses: ./.github/actions/seed-ditto-document + with: + ditto-api-key: ${{ secrets.DITTO_API_KEY }} + ditto-api-url: ${{ secrets.DITTO_API_URL }} + app-name: 'swift' + - 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: @@ -302,52 +314,11 @@ jobs: echo "test_bundle_path=$OUT_ZIP" >> "$GITHUB_OUTPUT" echo "โœ… XCUITest test-suite zip ready: $OUT_ZIP" - - - name: Insert test document into Ditto Cloud + + - name: Set environment variables for tests run: | - # Create a unique GitHub test document with inverted timestamp to appear at top - TIMESTAMP=$(date +%s) - INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) - DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" - - echo "๐Ÿ“ Inserting GitHub test document (inverted timestamp for top position)" - echo "๐Ÿ“ ID: '${DOC_ID}'" - echo "๐Ÿ“ Title: '${DOC_TITLE}'" - echo "๐Ÿ“ Timestamp: ${TIMESTAMP} โ†’ Inverted: ${INVERTED_TIMESTAMP}" - - # Insert document using Ditto API v4 (same as JavaScript) - 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}" - echo "GITHUB_TEST_DOC_ID=${DOC_ID}" >> $GITHUB_ENV - echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV - else - echo "โŒ Failed to insert document. HTTP Status: $HTTP_CODE" - echo "Response: $BODY" - exit 1 - fi + echo "GITHUB_TEST_DOC_ID=${{ steps.seed_task.outputs.document-title }}" >> $GITHUB_ENV + echo "GITHUB_TEST_DOC_TITLE=${{ steps.seed_task.outputs.document-title }}" >> $GITHUB_ENV - name: Upload App and Test Bundle to BrowserStack id: upload @@ -534,60 +505,42 @@ jobs: fi summary: - name: Summary - runs-on: macos-latest - needs: [lint, build-macos, build-ios, browserstack] + name: CI Report + runs-on: ubuntu-latest + needs: [browserstack] if: always() - + steps: - name: Report Results run: | - echo "## ๐Ÿ“ฑ Swift Build and Test Results" - echo "" - echo "### Job Results:" - echo "- **Lint**: ${{ needs.lint.result }}" - echo "- **Build macOS**: ${{ needs.build-macos.result }}" - echo "- **Build iOS**: ${{ needs.build-ios.result }}" - echo "- **BrowserStack Test**: ${{ needs.browserstack.result }}" - echo "" - - # Check results - if [[ "${{ needs.lint.result }}" != "success" ]]; then - echo "โŒ **Linting failed** - Code quality issues found" - fi - - if [[ "${{ needs.build-macos.result }}" != "success" ]]; then - echo "โš ๏ธ **macOS build failed** - Platform compatibility issues" - fi - - if [[ "${{ needs.build-ios.result }}" != "success" ]]; then - echo "โŒ **iOS build failed** - BrowserStack testing was skipped" - elif [[ "${{ needs.browserstack.result }}" == "success" ]]; then - echo "โœ… **Full pipeline success!**" - echo " - โœ… Linting passed" - echo " - โœ… macOS build succeeded" - echo " - โœ… iOS build succeeded" - echo " - โœ… BrowserStack real device testing passed" - echo " - โœ… End-to-end GitHub Actions โ†’ Ditto Cloud โ†’ BrowserStack sync validated" - echo " - ๐Ÿš€ **Efficient parallel builds**: macOS & iOS built simultaneously" - elif [[ "${{ needs.browserstack.result }}" == "failure" ]]; then - echo "โŒ **BrowserStack testing failed** - iOS builds but cloud sync may have issues" - elif [[ "${{ needs.browserstack.result }}" == "skipped" ]]; then - echo "โš ๏ธ **BrowserStack testing skipped** - iOS build failed" - fi - + echo "## ๐Ÿ“ฑ Swift CI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + # Overall status - echo "" if [[ "${{ needs.lint.result }}" == "success" && \ - "${{ needs.build-ios.result }}" == "success" ]]; then - echo "๐ŸŽ‰ **Core pipeline successful!** iOS app builds and passes quality checks." - if [[ "${{ needs.build-macos.result }}" == "success" ]]; then - echo "๐ŸŽ **macOS compatibility confirmed!** Cross-platform builds working." - fi - if [[ "${{ needs.browserstack.result }}" == "success" ]]; then - echo "๐Ÿš€ **BrowserStack integration validated!** Real device testing works end-to-end." - fi + "${{ 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 "โš ๏ธ Core pipeline has issues - check failed jobs above." - exit 1 + 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/builds?project=Ditto+Swift&build=Build+%23${{ github.run_number }})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tested Device:**" >> $GITHUB_STEP_SUMMARY + echo "- iPhone 15 Pro (iOS 17)" >> $GITHUB_STEP_SUMMARY fi \ No newline at end of file diff --git a/javascript-web/src/App.tsx b/javascript-web/src/App.tsx index e13679fa4..4ae976d16 100644 --- a/javascript-web/src/App.tsx +++ b/javascript-web/src/App.tsx @@ -86,7 +86,7 @@ const App = () => { // Register observer, which runs against the local database on this peer // https://docs.ditto.live/sdk/latest/crud/observing-data-changes#setting-up-store-observers tasksObserver.current = ditto.current.store.registerObserver( - 'SELECT * FROM tasks WHERE deleted=false ORDER BY done', + 'SELECT * FROM tasks WHERE deleted=false ORDER BY title ASC', (results) => { console.log('Observer', results); const tasks = results.items.map((item) => item.value); diff --git a/rust-tui/src/tui/todolist.rs b/rust-tui/src/tui/todolist.rs index 2d2bf65b8..aab54e1c3 100644 --- a/rust-tui/src/tui/todolist.rs +++ b/rust-tui/src/tui/todolist.rs @@ -94,7 +94,7 @@ impl Todolist { // register observer for live query // Register observer, which runs against the local database on this peer let tasks_observer = ditto.store().register_observer_v2( - "SELECT * FROM tasks WHERE deleted=false ORDER BY _id", + "SELECT * FROM tasks WHERE deleted=false ORDER BY title ASC", move |query_result| { let docs = query_result .into_iter()