From 516c26f9149ed9e8a64e98230347d6c08131b9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Tue, 10 Jun 2025 17:57:21 +0200 Subject: [PATCH 1/9] GHA: Simplify CI flow --- .github/workflows/ci.yml | 89 +++++----------------------------------- 1 file changed, 10 insertions(+), 79 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a6d6553b37d..6db2beab6eb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,14 +14,10 @@ on: - '**.md' - '.github/**' -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: - code_formatting: - name: Code Formatting - runs-on: ubuntu-24.04 + ci: + name: CI + runs-on: ubuntu-latest steps: - name: Checkout repository @@ -29,6 +25,8 @@ jobs: with: fetch-depth: 0 # required due to setting Spotless ratchetFrom submodules: recursive + token: ${{ secrets.GT_DAXMOBILE }} + ref: ${{ github.event.inputs.ref }} - name: Set up JDK version uses: actions/setup-java@v4 @@ -39,27 +37,13 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - - name: Run Code Formatting Checks - run: ./gradlew spotlessCheck - - unit_tests: - name: Unit tests - runs-on: android-large-runner - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set up JDK version - uses: actions/setup-java@v4 + - name: Set up Go + uses: actions/setup-go@v5 with: - java-version-file: .github/.java-version - distribution: 'adopt' + go-version: '1.18.3' - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + - name: Run Code Formatting Checks + run: ./gradlew spotlessCheck - name: JVM tests run: ./gradlew jvm_tests @@ -75,30 +59,6 @@ jobs: name: unit-tests-report path: unit-tests-report.zip - lint: - name: Lint - runs-on: android-large-runner - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set up JDK version - uses: actions/setup-java@v4 - with: - java-version-file: .github/.java-version - distribution: 'adopt' - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.18.3' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 - - name: Lint run: ./gradlew lint @@ -113,40 +73,11 @@ jobs: name: lint-report path: lint-report.zip - android_tests: - runs-on: android-large-runner - name: Android CI tests - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup jq - uses: dcarbone/install-jq-action@v1.0.1 - with: - force: true - - - name: Set up JDK version - uses: actions/setup-java@v4 - with: - java-version-file: .github/.java-version - distribution: 'adopt' - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.18.3' - - name: Decode secret env: FLANK: ${{ secrets.FLANK }} run: echo "$FLANK" > flank.json - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 - - name: Build run: ./gradlew androidTestsBuild From 70d043f0e9503dfcf53c53fde634fc59d4028d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Wed, 19 Nov 2025 17:48:36 +0100 Subject: [PATCH 2/9] make it callable --- .github/workflows/release_create_tag.yml | 15 +++++ .github/workflows/release_create_task.yml | 17 ++++++ .github/workflows/release_unified.yml | 60 +++++++++++++++++++ .github/workflows/release_upload_internal.yml | 18 +++++- .../workflows/release_upload_play_store.yml | 18 ++++-- 5 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release_unified.yml diff --git a/.github/workflows/release_create_tag.yml b/.github/workflows/release_create_tag.yml index 56683af0e89e..14f4c9bcb2f2 100644 --- a/.github/workflows/release_create_tag.yml +++ b/.github/workflows/release_create_tag.yml @@ -7,6 +7,21 @@ on: description: 'App Version for Release' required: true default: 'PLACEHOLDER' + workflow_call: + inputs: + app-version: + description: 'App Version for Release' + required: true + type: string + secrets: + ASANA_ACCESS_TOKEN: + required: true + GT_DAXMOBILE: + required: true + MM_AUTH_TOKEN: + required: true + MM_TEAM_ID: + required: true env: ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} diff --git a/.github/workflows/release_create_task.yml b/.github/workflows/release_create_task.yml index fa096e555597..ab168dcd1495 100644 --- a/.github/workflows/release_create_task.yml +++ b/.github/workflows/release_create_task.yml @@ -7,6 +7,23 @@ on: description: 'App Version for Release' required: true default: 'PLACEHOLDER' + workflow_call: + inputs: + app-version: + description: 'App Version for Release' + required: true + type: string + outputs: + asana-task-url: + description: "The URL of the created Asana task" + value: ${{ jobs.create_release_task.outputs.asana_task_url }} + secrets: + ASANA_ACCESS_TOKEN: + required: true + MM_AUTH_TOKEN: + required: true + MM_TEAM_ID: + required: true env: ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} diff --git a/.github/workflows/release_unified.yml b/.github/workflows/release_unified.yml new file mode 100644 index 000000000000..ae69da2b7047 --- /dev/null +++ b/.github/workflows/release_unified.yml @@ -0,0 +1,60 @@ +name: Release - Full Release Process + +on: + workflow_dispatch: + inputs: + app-version: + description: 'App Version for Release' + required: true + default: 'PLACEHOLDER' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.inputs.app-version }} + cancel-in-progress: true + +jobs: + # ================================================================= + # Step 1: Create the Asana Task for the Release + # ================================================================= + create_task: + name: 'Step 1: Create Asana Task' + uses: ./.github/workflows/release_create_task.yml + with: + app-version: ${{ github.event.inputs.app-version }} + secrets: inherit # Pass all secrets from the caller + + # ================================================================= + # Step 2: Run Release Tests + # ================================================================= + run_tests: + name: 'Step 2: Run Release Tests' + needs: create_task # Ensures this runs after the task is created + uses: ./.github/workflows/release_tests.yml + with: + app-version: ${{ github.event.inputs.app-version }} + asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} + secrets: inherit + + # ================================================================= + # Step 3: Upload to Play Store + # ================================================================= + upload_play_store: + name: 'Step 3: Upload to Play Store' + needs: run_tests # Ensures this runs after tests pass + uses: ./.github/workflows/release_upload_play_store.yml + with: + app-version: ${{ github.event.inputs.app-version }} + asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} + secrets: inherit + + # ================================================================= + # Step 4: Upload Internal Build + # ================================================================= + upload_internal: + name: 'Step 4: Upload Internal Build' + needs: run_tests # Can run in parallel with Play Store upload + uses: ./.github/workflows/release_upload_internal.yml + with: + app-version: ${{ github.event.inputs.app-version }} + asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/release_upload_internal.yml b/.github/workflows/release_upload_internal.yml index 102d36f5cb7e..2028435f9d9a 100644 --- a/.github/workflows/release_upload_internal.yml +++ b/.github/workflows/release_upload_internal.yml @@ -3,8 +3,24 @@ name: Release to Internal and Firebase on: schedule: - cron: '0 2 * * *' # run at 3 AM UTC - workflow_call: workflow_dispatch: + workflow_call: + secrets: + ASANA_ACCESS_TOKEN: + required: true + GT_DAXMOBILE: + required: true + UPLOAD_PLAY_CREDENTIALS: + required: true + UPLOAD_FIREBASE_CREDENTIALS: + required: true + MALICIOUS_SITE_PROTECTION_AUTH_TOKEN: + required: true + MM_AUTH_TOKEN: + required: true + MM_TEAM_ID: + required: true + env: ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} diff --git a/.github/workflows/release_upload_play_store.yml b/.github/workflows/release_upload_play_store.yml index 3c95ea777f84..9909c5dcf26c 100644 --- a/.github/workflows/release_upload_play_store.yml +++ b/.github/workflows/release_upload_play_store.yml @@ -1,17 +1,27 @@ name: Production Release to Play Store and Github on: - workflow_call: + workflow_dispatch: inputs: ref: description: 'Ref to build APK from (branch, tag, commit)' - type: string required: true - - workflow_dispatch: + workflow_call: inputs: ref: description: 'Ref to build APK from (branch, tag, commit)' + type: string + required: true + secrets: + ASANA_ACCESS_TOKEN: + required: true + GT_DAXMOBILE: + required: true + UPLOAD_PLAY_CREDENTIALS: + required: true + MM_AUTH_TOKEN: + required: true + MM_TEAM_ID: required: true env: From 78a056a98018a93f5e596b7e7f4707ee62aa86a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Fri, 5 Dec 2025 21:32:26 +0100 Subject: [PATCH 3/9] test sequential release --- .github/workflows/notify_mattermost.yml | 53 +++++ .github/workflows/release_full_process.yml | 69 +++++++ .github/workflows/release_tests.yml | 218 +++------------------ .github/workflows/release_unified.yml | 60 ------ 4 files changed, 154 insertions(+), 246 deletions(-) create mode 100644 .github/workflows/notify_mattermost.yml create mode 100644 .github/workflows/release_full_process.yml delete mode 100644 .github/workflows/release_unified.yml diff --git a/.github/workflows/notify_mattermost.yml b/.github/workflows/notify_mattermost.yml new file mode 100644 index 000000000000..94bde89b8c8a --- /dev/null +++ b/.github/workflows/notify_mattermost.yml @@ -0,0 +1,53 @@ +name: Mattermost - Send Message + +on: + workflow_dispatch: + inputs: + mattermost-message: + description: 'Message to Send to Mattermost' + required: true + type: string + mattermost-channel-name: + description: 'Channel to Send the message to' + required: true + type: string + workflow_call: + inputs: + mattermost-message: + description: 'Message to Send to Mattermost' + required: true + type: string + mattermost-channel-name: + description: 'Channel to Send the message to' + required: true + type: string + secrets: + MM_AUTH_TOKEN: + required: true + MM_TEAM_ID: + required: true + +env: + ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} + emoji_start: ":flight_departure:" # 🛫 + emoji_info: ":information_source:" # ℹ️ + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + notify_mattermost: + name: Send Message to Mattermost + runs-on: macos-latest + + steps: + - name: Notify Mattermost of Release starting + id: send-mm-release-starting + uses: duckduckgo/native-github-asana-sync@v2.0 + with: + mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} + mattermost-team-id: ${{ secrets.MM_TEAM_ID }} + mattermost-channel-name: ${{ github.event.inputs.mattermost-channel }} + mattermost-message: ${{ github.event.inputs.mattermost-message }} + action: 'send-mattermost-message' \ No newline at end of file diff --git a/.github/workflows/release_full_process.yml b/.github/workflows/release_full_process.yml new file mode 100644 index 000000000000..788470ecf406 --- /dev/null +++ b/.github/workflows/release_full_process.yml @@ -0,0 +1,69 @@ +name: Release - Full Release Process + +on: + workflow_dispatch: + inputs: + app-version: + description: 'App Version for Release' + required: true + default: 'PLACEHOLDER' + +env: + emoji_start: ":flight_departure:" # 🛫 + emoji_info: ":information_source:" # ℹ️ + +concurrency: + group: ${{ github.workflow }}-${{ github.event.inputs.app-version }} + cancel-in-progress: true + +jobs: + notify_mattermost_start: + name: 'Step 1: Notify Mattermost of Release start' + uses: ./.github/workflows/notify_mattermost.yml + with: + mattermost-message: ${{env.emoji_start}} Starting release process for version ${{ github.event.inputs.app-version }} + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + secrets: inherit + + create_task: + name: 'Step 2: Create Asana Task' + uses: ./.github/workflows/release_create_task.yml + needs: notify_mattermost_start + with: + app-version: ${{ github.event.inputs.app-version }} + + run_release_tests: + name: 'Step 3: Run Release Tests' + needs: create_task + uses: ./.github/workflows/release_tests.yml + with: + app-version: ${{ github.event.inputs.app-version }} + test-tag: 'releaseTest' + secrets: inherit + + upload_play_store: + name: 'Step 4: Upload to Play Store' + needs: run_tests + uses: ./.github/workflows/release_upload_play_store.yml + with: + app-version: ${{ github.event.inputs.app-version }} + asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} + secrets: inherit + + upload_internal: + name: 'Step 5: Upload Internal Build' + needs: run_tests + uses: ./.github/workflows/release_upload_internal.yml + with: + app-version: ${{ github.event.inputs.app-version }} + asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} + secrets: inherit + + notify_mattermost_finish: + name: 'Step 1: Notify Mattermost of Release completed' + uses: ./.github/workflows/notify_mattermost.yml + needs: upload_play_store + with: + mattermost-message: ${{env.emoji_end}} Release ${{ github.event.inputs.app-version }} completed successfully and is now in review. + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/release_tests.yml b/.github/workflows/release_tests.yml index 1e952a54f4a0..a359ec0c3f2a 100644 --- a/.github/workflows/release_tests.yml +++ b/.github/workflows/release_tests.yml @@ -1,198 +1,44 @@ -name: Release - Run E2E Maestro Tests +name: Release - Full Release Process on: workflow_dispatch: inputs: app-version: - description: 'App Version for Testing' + description: 'App Version for Release' required: true default: 'PLACEHOLDER' - test-tag: - description: 'Maestro Tests tag to include' - required: true - default: 'releaseTest' env: - ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} + emoji_start: ":flight_departure:" # 🛫 emoji_info: ":information_source:" # ℹ️ - emoji_success: ":white_check_mark:" # ✅ - emoji_failure: ":x:" # ❌ - -jobs: - run-release-tests: - name: Create release APK and run E2E Maestro tests - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - ref: ${{ github.event.inputs.app-version }} - - - name: Set up JDK version - uses: actions/setup-java@v4 - with: - java-version-file: .github/.java-version - distribution: 'adopt' - - - name: Create folder - if: always() - run: mkdir apk - - - name: Decode keys - uses: davidSchuppa/base64Secret-toFile-action@v2 - with: - secret: ${{ secrets.FAKE_RELEASE_PROPERTIES }} - fileName: ddg_android_build.properties - destination-path: $HOME/jenkins_static/com.duckduckgo.mobile.android/ - - - name: Decode key file - uses: davidSchuppa/base64Secret-toFile-action@v2 - with: - secret: ${{ secrets.FAKE_RELEASE_KEY }} - fileName: android - destination-path: $HOME/jenkins_static/com.duckduckgo.mobile.android/ - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 - - - name: Assemble release APK - run: ./gradlew assemblePlayRelease -Pforce-default-variant -Pskip-onboarding - - - name: Move APK to new folder - if: always() - run: find . -name "*.apk" -exec mv '{}' apk/release.apk \; - - - name: Notify Mattermost of Maestro tests - id: send-mm-tests-started - uses: duckduckgo/native-github-asana-sync@v2.0 - with: - mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} - mattermost-team-id: ${{ secrets.MM_TEAM_ID }} - mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} - mattermost-message: ${{env.emoji_info}} Release ${{ github.event.inputs.app-version }}. Running Maestro tests for tag ${{ github.event.inputs.test-tag }} https://github.com/duckduckgo/Android/actions/runs/${{ github.run_id }} - action: 'send-mattermost-message' - - name: Maestro tests flows - id: release-tests - uses: mobile-dev-inc/action-maestro-cloud@v1.9.8 - timeout-minutes: 120 - with: - api-key: ${{ secrets.ROBIN_API_KEY }} - project-id: ${{ vars.ROBIN_ANDROID_PROJECT_ID }} - timeout: ${{ vars.ROBIN_TIMEOUT_MINUTES }} - app-file: apk/release.apk - android-api-level: 30 - workspace: .maestro - include-tags: ${{ github.event.inputs.test-tag }} +concurrency: + group: ${{ github.workflow }}-${{ github.event.inputs.app-version }} + cancel-in-progress: true - - name: Analyze Maestro Flow Results - id: analyze-flow-results - if: always() - run: | - echo "Console URL: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }}" - echo "Upload Status: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_UPLOAD_STATUS }}" - echo "App Binary ID: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_APP_BINARY_ID }}" - - flow_results_json='${{ steps.release-tests.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}' - echo "Raw Flow Results JSON: $flow_results_json" - - # Default to success, change if issues are found - final_status="success" - - # Check for empty or invalid JSON (though Maestro action should provide valid JSON) - if ! echo "$flow_results_json" | jq -e . > /dev/null 2>&1; then - echo "::warning::MAESTRO_CLOUD_FLOW_RESULTS is not valid JSON or is empty." - final_status="unknown_format" - else - # Check for any flow with status "ERROR" - if echo "$flow_results_json" | jq -e '.[] | select(.status=="ERROR")' > /dev/null; then - echo "::error::At least one Maestro flow has status: ERROR." - final_status="failure" - fi - - # Check for any flow with status "CANCELED" - # You might decide if CANCELED flows also mean the overall status is a failure for your release - if echo "$flow_results_json" | jq -e '.[] | select(.status=="CANCELED")' > /dev/null; then - echo "::warning::At least one Maestro flow has status: CANCELED." - # If any canceled flow makes the whole thing a failure: - if [ "$final_status" != "failure" ]; then # Don't override if already a critical failure - final_status="canceled_present" # Or treat as "failure" if preferred - fi - fi - - # If after all checks, final_status is still "success", it means no "ERROR" or "CANCELED" - if [ "$final_status" == "success" ]; then - # Additional check: ensure there's at least one flow and it's not empty array if that's a concern - if echo "$flow_results_json" | jq -e '. | length > 0' > /dev/null; then - echo "All flows appear to be successful (no ERROR or CANCELED statuses found that are treated as errors)." - else - echo "::warning::MAESTRO_CLOUD_FLOW_RESULTS is an empty array. No flows reported." - final_status="empty_results" # Or "success" if empty results are acceptable - fi - fi - fi - - echo "Final determined status: $final_status" - echo "flow_summary_status=$final_status" >> $GITHUB_OUTPUT - - - name: Access Outputs (for debugging) - if: always() - run: | - echo "Console URL: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }}" - echo 'Flow Results (JSON): ${{ steps.release-tests.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}' - echo "Release Tests Step Conclusion: ${{ steps.release-tests.conclusion }}" # From Maestro action itself - echo "Analyzed Flow Summary Status: ${{ steps.analyze-flow-results.outputs.flow_summary_status }}" # From our script - - - name: Notify Mattermost - Maestro Tests ALL SUCCEEDED - # Condition 1: Our script says success - # Condition 2: The Maestro action itself also reported overall success - if: always() && steps.analyze-flow-results.outputs.flow_summary_status == 'success' && steps.release-tests.conclusion == 'success' - uses: duckduckgo/native-github-asana-sync@v2.0 - with: - action: 'send-mattermost-message' - mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} - mattermost-team-id: ${{ secrets.MM_TEAM_ID }} - mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} - mattermost-message: | - ${{env.emoji_success}} Release ${{ github.event.inputs.app-version }}: Tests for tag ${{ github.event.inputs.test-tag }} PASSED successfully. - Console: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }} - - - name: Notify Mattermost - Maestro Tests FAILURES or ISSUES DETECTED - # Condition: Our script detected 'failure' OR the Maestro action itself reported failure - if: always() && (steps.analyze-flow-results.outputs.flow_summary_status == 'failure' || steps.release-tests.conclusion == 'failure') - uses: duckduckgo/native-github-asana-sync@v2.0 - with: - action: 'send-mattermost-message' - mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} - mattermost-team-id: ${{ secrets.MM_TEAM_ID }} - mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} - mattermost-message: | - ${{env.emoji_failure}} Release ${{ github.event.inputs.app-version }}: Tests for tag ${{ github.event.inputs.test-tag }} FAILED or encountered issues. - Console: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }} - - - name: Notify Mattermost - Maestro Tests CANCELED Flows Present (Informational or Warning) - # Condition: Our script detected 'canceled_present' AND no critical 'failure' was found - # AND Maestro action itself didn't mark the whole run as a 'failure' - if: always() && steps.analyze-flow-results.outputs.flow_summary_status == 'canceled_present' && steps.release-tests.conclusion != 'failure' - uses: duckduckgo/native-github-asana-sync@v2.0 - with: - action: 'send-mattermost-message' - mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} - mattermost-team-id: ${{ secrets.MM_TEAM_ID }} - mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} - mattermost-message: | - :warning: Release ${{ github.event.inputs.app-version }}: Some tests for tag ${{ github.event.inputs.test-tag }} were CANCELED. Please review. - Console: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }} - - - name: Create Asana task when workflow failed - if: ${{ failure() }} - id: create-failure-task - uses: duckduckgo/native-github-asana-sync@v2.0 - with: - asana-pat: ${{ secrets.ASANA_ACCESS_TOKEN }} - asana-project: ${{ vars.GH_ANDROID_APP_PROJECT_ID }} - asana-section: ${{ vars.GH_ANDROID_APP_INCOMING_SECTION_ID }} - asana-task-name: GH Workflow Failure - Tag Android Release (Robin) - asana-task-description: Run Release Tests in Maestro has failed. See https://github.com/duckduckgo/Android/actions/runs/${{ github.run_id }} - action: 'create-asana-task' +jobs: + notify_mattermost_start: + name: 'Step 1: Notify Mattermost of Release start' + uses: ./.github/workflows/notify_mattermost.yml + with: + mattermost-message: ${{env.emoji_start}} Starting release process for version ${{ github.event.inputs.app-version }} + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + secrets: inherit + + run_release_tests: + name: 'Step 3: Run Release Tests' + needs: notify_mattermost_start + uses: ./.github/workflows/release_tests.yml + with: + app-version: ${{ github.event.inputs.app-version }} + test-tag: 'omnibarTest' + secrets: inherit + + notify_mattermost_finish: + name: 'Step 1: Notify Mattermost of Release completed' + uses: ./.github/workflows/notify_mattermost.yml + needs: run_release_tests + with: + mattermost-message: ${{env.emoji_end}} Release ${{ github.event.inputs.app-version }} completed successfully and is now in review. + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/release_unified.yml b/.github/workflows/release_unified.yml deleted file mode 100644 index ae69da2b7047..000000000000 --- a/.github/workflows/release_unified.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Release - Full Release Process - -on: - workflow_dispatch: - inputs: - app-version: - description: 'App Version for Release' - required: true - default: 'PLACEHOLDER' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.inputs.app-version }} - cancel-in-progress: true - -jobs: - # ================================================================= - # Step 1: Create the Asana Task for the Release - # ================================================================= - create_task: - name: 'Step 1: Create Asana Task' - uses: ./.github/workflows/release_create_task.yml - with: - app-version: ${{ github.event.inputs.app-version }} - secrets: inherit # Pass all secrets from the caller - - # ================================================================= - # Step 2: Run Release Tests - # ================================================================= - run_tests: - name: 'Step 2: Run Release Tests' - needs: create_task # Ensures this runs after the task is created - uses: ./.github/workflows/release_tests.yml - with: - app-version: ${{ github.event.inputs.app-version }} - asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} - secrets: inherit - - # ================================================================= - # Step 3: Upload to Play Store - # ================================================================= - upload_play_store: - name: 'Step 3: Upload to Play Store' - needs: run_tests # Ensures this runs after tests pass - uses: ./.github/workflows/release_upload_play_store.yml - with: - app-version: ${{ github.event.inputs.app-version }} - asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} - secrets: inherit - - # ================================================================= - # Step 4: Upload Internal Build - # ================================================================= - upload_internal: - name: 'Step 4: Upload Internal Build' - needs: run_tests # Can run in parallel with Play Store upload - uses: ./.github/workflows/release_upload_internal.yml - with: - app-version: ${{ github.event.inputs.app-version }} - asana-task-url: ${{ needs.create_task.outputs.asana-task-url }} - secrets: inherit \ No newline at end of file From 932adb85e12a765b1af172afbe9ebef13795fdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Fri, 5 Dec 2025 21:45:53 +0100 Subject: [PATCH 4/9] update test worflow --- .github/workflows/release_tests.yml | 218 ++++++++++++++++++++++++---- .github/workflows/test_workflow.yml | 48 ++++-- 2 files changed, 219 insertions(+), 47 deletions(-) diff --git a/.github/workflows/release_tests.yml b/.github/workflows/release_tests.yml index a359ec0c3f2a..1e952a54f4a0 100644 --- a/.github/workflows/release_tests.yml +++ b/.github/workflows/release_tests.yml @@ -1,44 +1,198 @@ -name: Release - Full Release Process +name: Release - Run E2E Maestro Tests on: workflow_dispatch: inputs: app-version: - description: 'App Version for Release' + description: 'App Version for Testing' required: true default: 'PLACEHOLDER' + test-tag: + description: 'Maestro Tests tag to include' + required: true + default: 'releaseTest' env: - emoji_start: ":flight_departure:" # 🛫 + ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} emoji_info: ":information_source:" # ℹ️ - -concurrency: - group: ${{ github.workflow }}-${{ github.event.inputs.app-version }} - cancel-in-progress: true + emoji_success: ":white_check_mark:" # ✅ + emoji_failure: ":x:" # ❌ jobs: - notify_mattermost_start: - name: 'Step 1: Notify Mattermost of Release start' - uses: ./.github/workflows/notify_mattermost.yml - with: - mattermost-message: ${{env.emoji_start}} Starting release process for version ${{ github.event.inputs.app-version }} - mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} - secrets: inherit - - run_release_tests: - name: 'Step 3: Run Release Tests' - needs: notify_mattermost_start - uses: ./.github/workflows/release_tests.yml - with: - app-version: ${{ github.event.inputs.app-version }} - test-tag: 'omnibarTest' - secrets: inherit - - notify_mattermost_finish: - name: 'Step 1: Notify Mattermost of Release completed' - uses: ./.github/workflows/notify_mattermost.yml - needs: run_release_tests - with: - mattermost-message: ${{env.emoji_end}} Release ${{ github.event.inputs.app-version }} completed successfully and is now in review. - mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} - secrets: inherit \ No newline at end of file + run-release-tests: + name: Create release APK and run E2E Maestro tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{ github.event.inputs.app-version }} + + - name: Set up JDK version + uses: actions/setup-java@v4 + with: + java-version-file: .github/.java-version + distribution: 'adopt' + + - name: Create folder + if: always() + run: mkdir apk + + - name: Decode keys + uses: davidSchuppa/base64Secret-toFile-action@v2 + with: + secret: ${{ secrets.FAKE_RELEASE_PROPERTIES }} + fileName: ddg_android_build.properties + destination-path: $HOME/jenkins_static/com.duckduckgo.mobile.android/ + + - name: Decode key file + uses: davidSchuppa/base64Secret-toFile-action@v2 + with: + secret: ${{ secrets.FAKE_RELEASE_KEY }} + fileName: android + destination-path: $HOME/jenkins_static/com.duckduckgo.mobile.android/ + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Assemble release APK + run: ./gradlew assemblePlayRelease -Pforce-default-variant -Pskip-onboarding + + - name: Move APK to new folder + if: always() + run: find . -name "*.apk" -exec mv '{}' apk/release.apk \; + + - name: Notify Mattermost of Maestro tests + id: send-mm-tests-started + uses: duckduckgo/native-github-asana-sync@v2.0 + with: + mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} + mattermost-team-id: ${{ secrets.MM_TEAM_ID }} + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + mattermost-message: ${{env.emoji_info}} Release ${{ github.event.inputs.app-version }}. Running Maestro tests for tag ${{ github.event.inputs.test-tag }} https://github.com/duckduckgo/Android/actions/runs/${{ github.run_id }} + action: 'send-mattermost-message' + + - name: Maestro tests flows + id: release-tests + uses: mobile-dev-inc/action-maestro-cloud@v1.9.8 + timeout-minutes: 120 + with: + api-key: ${{ secrets.ROBIN_API_KEY }} + project-id: ${{ vars.ROBIN_ANDROID_PROJECT_ID }} + timeout: ${{ vars.ROBIN_TIMEOUT_MINUTES }} + app-file: apk/release.apk + android-api-level: 30 + workspace: .maestro + include-tags: ${{ github.event.inputs.test-tag }} + + - name: Analyze Maestro Flow Results + id: analyze-flow-results + if: always() + run: | + echo "Console URL: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }}" + echo "Upload Status: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_UPLOAD_STATUS }}" + echo "App Binary ID: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_APP_BINARY_ID }}" + + flow_results_json='${{ steps.release-tests.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}' + echo "Raw Flow Results JSON: $flow_results_json" + + # Default to success, change if issues are found + final_status="success" + + # Check for empty or invalid JSON (though Maestro action should provide valid JSON) + if ! echo "$flow_results_json" | jq -e . > /dev/null 2>&1; then + echo "::warning::MAESTRO_CLOUD_FLOW_RESULTS is not valid JSON or is empty." + final_status="unknown_format" + else + # Check for any flow with status "ERROR" + if echo "$flow_results_json" | jq -e '.[] | select(.status=="ERROR")' > /dev/null; then + echo "::error::At least one Maestro flow has status: ERROR." + final_status="failure" + fi + + # Check for any flow with status "CANCELED" + # You might decide if CANCELED flows also mean the overall status is a failure for your release + if echo "$flow_results_json" | jq -e '.[] | select(.status=="CANCELED")' > /dev/null; then + echo "::warning::At least one Maestro flow has status: CANCELED." + # If any canceled flow makes the whole thing a failure: + if [ "$final_status" != "failure" ]; then # Don't override if already a critical failure + final_status="canceled_present" # Or treat as "failure" if preferred + fi + fi + + # If after all checks, final_status is still "success", it means no "ERROR" or "CANCELED" + if [ "$final_status" == "success" ]; then + # Additional check: ensure there's at least one flow and it's not empty array if that's a concern + if echo "$flow_results_json" | jq -e '. | length > 0' > /dev/null; then + echo "All flows appear to be successful (no ERROR or CANCELED statuses found that are treated as errors)." + else + echo "::warning::MAESTRO_CLOUD_FLOW_RESULTS is an empty array. No flows reported." + final_status="empty_results" # Or "success" if empty results are acceptable + fi + fi + fi + + echo "Final determined status: $final_status" + echo "flow_summary_status=$final_status" >> $GITHUB_OUTPUT + + - name: Access Outputs (for debugging) + if: always() + run: | + echo "Console URL: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }}" + echo 'Flow Results (JSON): ${{ steps.release-tests.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}' + echo "Release Tests Step Conclusion: ${{ steps.release-tests.conclusion }}" # From Maestro action itself + echo "Analyzed Flow Summary Status: ${{ steps.analyze-flow-results.outputs.flow_summary_status }}" # From our script + + - name: Notify Mattermost - Maestro Tests ALL SUCCEEDED + # Condition 1: Our script says success + # Condition 2: The Maestro action itself also reported overall success + if: always() && steps.analyze-flow-results.outputs.flow_summary_status == 'success' && steps.release-tests.conclusion == 'success' + uses: duckduckgo/native-github-asana-sync@v2.0 + with: + action: 'send-mattermost-message' + mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} + mattermost-team-id: ${{ secrets.MM_TEAM_ID }} + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + mattermost-message: | + ${{env.emoji_success}} Release ${{ github.event.inputs.app-version }}: Tests for tag ${{ github.event.inputs.test-tag }} PASSED successfully. + Console: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }} + + - name: Notify Mattermost - Maestro Tests FAILURES or ISSUES DETECTED + # Condition: Our script detected 'failure' OR the Maestro action itself reported failure + if: always() && (steps.analyze-flow-results.outputs.flow_summary_status == 'failure' || steps.release-tests.conclusion == 'failure') + uses: duckduckgo/native-github-asana-sync@v2.0 + with: + action: 'send-mattermost-message' + mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} + mattermost-team-id: ${{ secrets.MM_TEAM_ID }} + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + mattermost-message: | + ${{env.emoji_failure}} Release ${{ github.event.inputs.app-version }}: Tests for tag ${{ github.event.inputs.test-tag }} FAILED or encountered issues. + Console: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }} + + - name: Notify Mattermost - Maestro Tests CANCELED Flows Present (Informational or Warning) + # Condition: Our script detected 'canceled_present' AND no critical 'failure' was found + # AND Maestro action itself didn't mark the whole run as a 'failure' + if: always() && steps.analyze-flow-results.outputs.flow_summary_status == 'canceled_present' && steps.release-tests.conclusion != 'failure' + uses: duckduckgo/native-github-asana-sync@v2.0 + with: + action: 'send-mattermost-message' + mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} + mattermost-team-id: ${{ secrets.MM_TEAM_ID }} + mattermost-channel-name: ${{ vars.MM_RELEASE_NOTIFY_CHANNEL }} + mattermost-message: | + :warning: Release ${{ github.event.inputs.app-version }}: Some tests for tag ${{ github.event.inputs.test-tag }} were CANCELED. Please review. + Console: ${{ steps.release-tests.outputs.MAESTRO_CLOUD_CONSOLE_URL }} + + - name: Create Asana task when workflow failed + if: ${{ failure() }} + id: create-failure-task + uses: duckduckgo/native-github-asana-sync@v2.0 + with: + asana-pat: ${{ secrets.ASANA_ACCESS_TOKEN }} + asana-project: ${{ vars.GH_ANDROID_APP_PROJECT_ID }} + asana-section: ${{ vars.GH_ANDROID_APP_INCOMING_SECTION_ID }} + asana-task-name: GH Workflow Failure - Tag Android Release (Robin) + asana-task-description: Run Release Tests in Maestro has failed. See https://github.com/duckduckgo/Android/actions/runs/${{ github.run_id }} + action: 'create-asana-task' diff --git a/.github/workflows/test_workflow.yml b/.github/workflows/test_workflow.yml index 73e4bfab55f8..28281ff42ab7 100644 --- a/.github/workflows/test_workflow.yml +++ b/.github/workflows/test_workflow.yml @@ -2,25 +2,43 @@ name: Test - Workflow for Testing on: workflow_dispatch: - inputs: - ref: - description: 'This is an example of an input' + app-version: + description: 'App Version for Release' required: true + default: 'PLACEHOLDER' env: - ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} - GH_TOKEN: ${{ secrets.GT_DAXMOBILE }} + emoji_start: ":flight_departure:" # 🛫 + emoji_info: ":information_source:" # ℹ️ + +concurrency: + group: ${{ github.workflow }}-${{ github.event.inputs.app-version }} + cancel-in-progress: true jobs: - test-workflow: - runs-on: ubuntu-latest - name: Add here whatever steps you want to test + notify_mattermost_start: + name: 'Step 1: Notify Mattermost of Release start' + uses: ./.github/workflows/notify_mattermost.yml + with: + mattermost-message: ${{env.emoji_start}} Starting release process for version ${{ github.event.inputs.app-version }} + mattermost-channel-name: ${{ vars.MM_TEST_NOTIFY_CHANNEL }} + secrets: inherit + + run_release_tests: + name: 'Step 3: Run Release Tests' + needs: notify_mattermost_start + uses: ./.github/workflows/release_tests.yml + with: + app-version: ${{ github.event.inputs.app-version }} + test-tag: 'omnibarTest' + secrets: inherit - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - token: ${{ secrets.GT_DAXMOBILE }} - ref: ${{ github.event.inputs.ref }} + notify_mattermost_finish: + name: 'Step 1: Notify Mattermost of Release completed' + uses: ./.github/workflows/notify_mattermost.yml + needs: run_release_tests + with: + mattermost-message: ${{env.emoji_end}} Release ${{ github.event.inputs.app-version }} completed successfully and is now in review. + mattermost-channel-name: ${{ vars.MM_TEST_NOTIFY_CHANNEL }} + secrets: inherit \ No newline at end of file From 2e4792ff7f4317d157184a11aba439f31064b8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Fri, 5 Dec 2025 22:04:29 +0100 Subject: [PATCH 5/9] pass emoki properly --- .github/workflows/test_workflow.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_workflow.yml b/.github/workflows/test_workflow.yml index 28281ff42ab7..5d12f09eaf97 100644 --- a/.github/workflows/test_workflow.yml +++ b/.github/workflows/test_workflow.yml @@ -8,10 +8,6 @@ on: required: true default: 'PLACEHOLDER' -env: - emoji_start: ":flight_departure:" # 🛫 - emoji_info: ":information_source:" # ℹ️ - concurrency: group: ${{ github.workflow }}-${{ github.event.inputs.app-version }} cancel-in-progress: true @@ -21,7 +17,7 @@ jobs: name: 'Step 1: Notify Mattermost of Release start' uses: ./.github/workflows/notify_mattermost.yml with: - mattermost-message: ${{env.emoji_start}} Starting release process for version ${{ github.event.inputs.app-version }} + mattermost-message: ':flight_departure:' Starting release process for version ${{ github.event.inputs.app-version }} mattermost-channel-name: ${{ vars.MM_TEST_NOTIFY_CHANNEL }} secrets: inherit @@ -39,6 +35,6 @@ jobs: uses: ./.github/workflows/notify_mattermost.yml needs: run_release_tests with: - mattermost-message: ${{env.emoji_end}} Release ${{ github.event.inputs.app-version }} completed successfully and is now in review. + mattermost-message: ':white_check_mark:' Release ${{ github.event.inputs.app-version }} completed successfully and is now in review. mattermost-channel-name: ${{ vars.MM_TEST_NOTIFY_CHANNEL }} secrets: inherit \ No newline at end of file From 62a2f6d24bca76009ac092269857b053af415f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Fri, 5 Dec 2025 22:08:05 +0100 Subject: [PATCH 6/9] standard emoji --- .github/workflows/test_workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_workflow.yml b/.github/workflows/test_workflow.yml index 5d12f09eaf97..a526f8141b4f 100644 --- a/.github/workflows/test_workflow.yml +++ b/.github/workflows/test_workflow.yml @@ -17,7 +17,7 @@ jobs: name: 'Step 1: Notify Mattermost of Release start' uses: ./.github/workflows/notify_mattermost.yml with: - mattermost-message: ':flight_departure:' Starting release process for version ${{ github.event.inputs.app-version }} + mattermost-message: ":flight_departure: Starting release process for version ${{ github.event.inputs.app-version }}" mattermost-channel-name: ${{ vars.MM_TEST_NOTIFY_CHANNEL }} secrets: inherit @@ -35,6 +35,6 @@ jobs: uses: ./.github/workflows/notify_mattermost.yml needs: run_release_tests with: - mattermost-message: ':white_check_mark:' Release ${{ github.event.inputs.app-version }} completed successfully and is now in review. + mattermost-message: ":white_check_mark: Release ${{ github.event.inputs.app-version }} completed successfully and is now in review." mattermost-channel-name: ${{ vars.MM_TEST_NOTIFY_CHANNEL }} secrets: inherit \ No newline at end of file From 51fd7c3c31f6aa0459986f9ab41f8397a350fa21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Fri, 5 Dec 2025 22:15:31 +0100 Subject: [PATCH 7/9] add on call secrets --- .github/workflows/release_tests.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/release_tests.yml b/.github/workflows/release_tests.yml index 1e952a54f4a0..d61a72045873 100644 --- a/.github/workflows/release_tests.yml +++ b/.github/workflows/release_tests.yml @@ -12,6 +12,32 @@ on: required: true default: 'releaseTest' + workflow_call: + inputs: + app-version: + description: 'App Version for Release' + required: true + type: string + test-tag: + description: 'Maestro Tests tag to include' + required: true + default: 'releaseTest' + type: string + secrets: + ASANA_ACCESS_TOKEN: + required: false + FAKE_RELEASE_PROPERTIES: + required: true + FAKE_RELEASE_KEY: + required: true + MM_AUTH_TOKEN: + required: true + MM_TEAM_ID: + required: true + ROBIN_API_KEY: + required: true + + env: ASANA_PAT: ${{ secrets.ASANA_ACCESS_TOKEN }} emoji_info: ":information_source:" # ℹ️ From 7de0104c0c1c62a792d840393f32cc64ed5e5e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Fri, 5 Dec 2025 22:20:35 +0100 Subject: [PATCH 8/9] proper spacing --- .github/workflows/release_tests.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release_tests.yml b/.github/workflows/release_tests.yml index d61a72045873..6d200ccf4a34 100644 --- a/.github/workflows/release_tests.yml +++ b/.github/workflows/release_tests.yml @@ -23,19 +23,19 @@ on: required: true default: 'releaseTest' type: string - secrets: - ASANA_ACCESS_TOKEN: - required: false - FAKE_RELEASE_PROPERTIES: - required: true - FAKE_RELEASE_KEY: - required: true - MM_AUTH_TOKEN: - required: true - MM_TEAM_ID: - required: true - ROBIN_API_KEY: - required: true + secrets: + ASANA_ACCESS_TOKEN: + required: false + FAKE_RELEASE_PROPERTIES: + required: true + FAKE_RELEASE_KEY: + required: true + MM_AUTH_TOKEN: + required: true + MM_TEAM_ID: + required: true + ROBIN_API_KEY: + required: true env: From 353735d8a0e2f96057002c5d3edb23a8cd3d96fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gonz=C3=A1lez?= Date: Fri, 5 Dec 2025 22:26:56 +0100 Subject: [PATCH 9/9] proper mm inputs --- .github/workflows/notify_mattermost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/notify_mattermost.yml b/.github/workflows/notify_mattermost.yml index 94bde89b8c8a..24c78f628c72 100644 --- a/.github/workflows/notify_mattermost.yml +++ b/.github/workflows/notify_mattermost.yml @@ -48,6 +48,6 @@ jobs: with: mattermost-token: ${{ secrets.MM_AUTH_TOKEN }} mattermost-team-id: ${{ secrets.MM_TEAM_ID }} - mattermost-channel-name: ${{ github.event.inputs.mattermost-channel }} + mattermost-channel-name: ${{ github.event.inputs.mattermost-channel-name }} mattermost-message: ${{ github.event.inputs.mattermost-message }} action: 'send-mattermost-message' \ No newline at end of file