Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
97f45ce
feat(ci): add GitHub Step Summary to all workflows and standardize na…
teodorciuraru Oct 2, 2025
41fb8b6
feat(ci): extract seed document logic into reusable composite action
teodorciuraru Oct 2, 2025
66ffba3
fix(javascript-web): use ORDER BY _id ASC instead of ORDER BY done
teodorciuraru Oct 2, 2025
6bec5bf
fix: use ORDER BY title ASC for consistency across all apps
teodorciuraru Oct 2, 2025
1be6a8c
fix(javascript-web): correct document ID matching in BrowserStack test
teodorciuraru Oct 2, 2025
b49e294
fix: portable head command and exact doc ID matching in tests
teodorciuraru Oct 2, 2025
9daf1c6
refactor(ci): standardize KMP workflow to use single seed job after b…
teodorciuraru Oct 2, 2025
1a444ed
refactor(ci): standardize seed job name to seed-ditto-cloud
teodorciuraru Oct 2, 2025
32d3110
refactor(ci): extract inline seed to seed-ditto-cloud job in android-…
teodorciuraru Oct 2, 2025
88b3184
refactor(ci): extract inline seed to seed-ditto-cloud job in android-…
teodorciuraru Oct 2, 2025
8d999e3
refactor(ci): standardize seed-ditto-cloud job in swift and flutter
teodorciuraru Oct 2, 2025
efec197
refactor(ci): extract inline seed to seed-ditto-cloud job in java-spring
teodorciuraru Oct 2, 2025
75e718e
fix: remove redundant job dependencies from summary jobs
teodorciuraru Oct 2, 2025
b52d6e7
fix(java-spring): ensure Ditto cloud sync is enabled at runtime
teodorciuraru Oct 2, 2025
57c7d96
fix(ci): remove retry logic from android-java and fix kotlin-multipla…
teodorciuraru Oct 2, 2025
b4a63aa
refactor(ci): move seed steps inline to BrowserStack test jobs
teodorciuraru Oct 2, 2025
a899b26
fix(ci): use specific BrowserStack links in android-cpp and java-spri…
teodorciuraru Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/actions/seed-ditto-document/action.yml
Original file line number Diff line number Diff line change
@@ -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
12 changes: 5 additions & 7 deletions .github/scripts/browserstack-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
Expand Down
79 changes: 79 additions & 0 deletions .github/scripts/retry-browserstack.sh
Original file line number Diff line number Diff line change
@@ -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 '<your curl command>'
#

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 '<command>'"
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 "$@"
115 changes: 49 additions & 66 deletions .github/workflows/android-cpp-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Loading
Loading