Skip to content

ci: [SDK-4690] add Android E2E workflow#2652

Open
fadi-george wants to merge 1 commit into
mainfrom
fadi/sdk-4690
Open

ci: [SDK-4690] add Android E2E workflow#2652
fadi-george wants to merge 1 commit into
mainfrom
fadi/sdk-4690

Conversation

@fadi-george
Copy link
Copy Markdown
Contributor

Description

One Line Summary

Add an Android E2E GitHub Actions workflow that builds the demo APK and runs the shared Appium suite on BrowserStack, mirroring the Capacitor SDK setup.

Details

Motivation

The Capacitor, Flutter, and React Native wrappers already run the shared OneSignal/sdk-shared Appium suite on each release, but the native Android SDK had no equivalent. Without it there is no automated end-to-end signal that a freshly published com.onesignal:OneSignal artifact actually boots, registers a subscription, and exercises the Red App flows on a real device. This PR fills that gap so every release branch can produce a BrowserStack run alongside the Capacitor and other wrapper runs.

Scope

  • Adds .github/workflows/e2e.yml, which triggers on pushes to rel/** and on workflow_dispatch (with an optional sdk-version input). It resolves the SDK version from OneSignalSDK/gradle.properties, calls OneSignal/sdk-shared/.github/actions/wait-for-maven-artifact@main so we do not race the Maven publish, builds :app:assembleGmsDebug from examples/demo/ with -PSDK_VERSION=..., uploads app-gms-debug.apk as demo-apk, and then delegates to OneSignal/sdk-shared/.github/workflows/appium-e2e.yml@main with sdk-type: android.
  • Adds .github/actions/setup-demo/action.yml, a composite action that installs Java 17 + the Android cmdline-tools, caches Gradle, and writes examples/demo/local.properties with the OneSignal App ID, API key, and the ONESIGNAL_ANDROID_CHANNEL_ID used by the shared Appium suite. This is the Android counterpart to the Capacitor setup-demo action, with local.properties taking the place of Capacitor's .env.
  • No production SDK code, demo app code, or other workflows are changed. ci.yml, publish-release.yml, and create-release-pr.yml are untouched.

OPTIONAL - Other

A few Android-specific deviations from the Capacitor template are worth calling out:

  • Single build-android job (no iOS matrix) since this repo only ships Android.
  • Uses the gms flavor and the debug build type, because BrowserStack devices all run Google Play Services and isDebuggable=true is what Appium's UiAutomator2 driver needs to attach. The current demo build.gradle.kts does not configure ABI splits, so the unqualified app-gms-debug.apk is the correct artifact name.
  • Triggering on rel/** works the same way it does on Capacitor, but on this repo the rel branch bumps to an unpublished version, so wait-for-maven-artifact will keep polling (up to ~50 minutes) until publish-release.yml completes. If you would rather run the workflow strictly after a Maven publish, the workflow_dispatch trigger with the optional sdk-version input is the manual escape hatch.

Testing

Unit testing

N/A. Pure CI/workflow change.

Manual testing

  • Validated .github/workflows/e2e.yml with actionlint 1.7.12 (no findings).
  • Validated .github/actions/setup-demo/action.yml against the GitHub composite-action schema (action.yml files are not workflow files, so actionlint's workflow-schema errors there are expected and not a real issue).
  • Cross-checked the workflow against the Capacitor e2e.yml and against OneSignal/sdk-shared/.github/workflows/appium-e2e.yml@main to confirm the input contract (platform, app-artifact, app-filename, sdk-type, build-name) matches and that secrets: inherit covers the BrowserStack and Appium OneSignal secrets the shared workflow expects.
  • Confirmed the resolved APK path examples/demo/app/build/outputs/apk/gms/debug/app-gms-debug.apk matches AGP's default output for the gms flavor + debug build type given the current examples/demo/app/build.gradle.kts (no ABI splits, no APK rename).

Affected code checklist

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

(No production SDK surfaces are touched; this PR only adds CI plumbing.)

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

Made with Cursor

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

📊 Diff Coverage Report

✓ Coverage check passed (no source files changed)

📥 View workflow run

@fadi-george fadi-george marked this pull request as ready for review May 25, 2026 16:27
Comment on lines +45 to +53
env:
ONESIGNAL_APP_ID: ${{ inputs.onesignal-app-id }}
ONESIGNAL_API_KEY: ${{ inputs.onesignal-api-key }}
run: |
{
echo "ONESIGNAL_APP_ID=${ONESIGNAL_APP_ID}"
echo "ONESIGNAL_API_KEY=${ONESIGNAL_API_KEY}"
echo "ONESIGNAL_ANDROID_CHANNEL_ID=7ec2ece9-c538-4656-9516-1316f48a005c"
} > local.properties
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The composite action accepts a required onesignal-api-key input and writes ONESIGNAL_API_KEY=... to examples/demo/local.properties, but nothing ever reads it: examples/demo/app/build.gradle.kts only calls demoOverride() for ONESIGNAL_APP_ID and ONESIGNAL_ANDROID_CHANNEL_ID, and a repo-wide grep confirms ONESIGNAL_API_KEY appears only in the two new CI files. The required input therefore forces every caller to supply an unused REST API key and silently materializes it onto the runner disk for no benefit. Either drop the onesignal-api-key input and the corresponding echo line at action.yml:51 (plus the secrets.APPIUM_ONESIGNAL_API_KEY wiring at e2e.yml:32), or wire the value through build.gradle.kts to a real consumer (e.g., BuildConfig).

Extended reasoning...

What the bug is

.github/actions/setup-demo/action.yml declares onesignal-api-key as a required input (lines 7-9) and writes it into examples/demo/local.properties (line 51). The accompanying comment at lines 36-41 describes the file as containing only "the OneSignal App ID + channel id" — there is no mention of an API key, suggesting the API-key line was added by accident or as leftover boilerplate from the Capacitor template.

Why it's dead plumbing

examples/demo/app/build.gradle.kts is the only consumer of local.properties in this repo. Its demoOverride() helper is invoked exactly twice — for ONESIGNAL_APP_ID (line 57) and ONESIGNAL_ANDROID_CHANNEL_ID (line 64). There is no demoOverride("ONESIGNAL_API_KEY") call, and a repo-wide grep for ONESIGNAL_API_KEY returns only the two new CI files (.github/actions/setup-demo/action.yml and .github/workflows/e2e.yml). No Gradle script, Kotlin/Java source file, or AndroidManifest references the key.

Why the Appium suite can't pick it up either

local.properties is a Gradle-configuration-time file that lives on the CI runner's filesystem; it is not packaged into the APK. The APK uploaded to BrowserStack therefore never sees the key, and the shared OneSignal/sdk-shared/.github/workflows/appium-e2e.yml@main workflow obtains its OneSignal/BrowserStack secrets directly via secrets: inherit on e2e.yml:48 — not via local.properties. So there is no plausible runtime path by which the written value reaches any consumer.

Step-by-step proof

  1. .github/workflows/e2e.yml:32 passes secrets.APPIUM_ONESIGNAL_API_KEY to the composite action's onesignal-api-key input.
  2. .github/actions/setup-demo/action.yml:51 writes ONESIGNAL_API_KEY=$ONESIGNAL_API_KEY to examples/demo/local.properties.
  3. ./gradlew :app:assembleGmsDebug runs (e2e.yml:69). Gradle parses build.gradle.kts, which only calls demoOverride("ONESIGNAL_APP_ID") and demoOverride("ONESIGNAL_ANDROID_CHANNEL_ID"). The ONESIGNAL_API_KEY line in local.properties is ignored.
  4. The resulting app-gms-debug.apk is uploaded as demo-apk and consumed by the reusable Appium workflow. The APK contains no ONESIGNAL_API_KEY value, and local.properties is not shipped with it.
  5. The reusable workflow gets its own secrets via secrets: inherit, independently of anything in this repo's build.

The key is therefore written to a file nothing reads, on a runner that is torn down at the end of the job.

Impact

Severity is normal, not a runtime crash but a real correctness/cleanliness issue:

  • False signal: The workflow looks like it plumbs the REST API key into the build/test pipeline; it does not. A future contributor reading the action will reasonably believe BuildConfig.ONESIGNAL_API_KEY (or similar) is available — it isn't.
  • Hard-fail risk: Because the input is required: true, any caller without APPIUM_ONESIGNAL_API_KEY set will fail the workflow at the with: validation step for zero benefit.
  • Minor security smell: A REST API key is unnecessarily materialized onto the runner filesystem for a code path that never consumes it.

How to fix

Either:

  • Remove the onesignal-api-key input (action.yml:7-9), the ONESIGNAL_API_KEY env+echo lines (action.yml:48, 51), and the onesignal-api-key: line in e2e.yml:32; or
  • Wire it through examples/demo/app/build.gradle.kts by adding demoOverride("ONESIGNAL_API_KEY") and exposing it via buildConfigField/resValue so the demo app (or a runtime test hook) can actually consume it.

@fadi-george fadi-george force-pushed the fadi/sdk-4690 branch 2 times, most recently from 0755237 to 0d6ff30 Compare May 26, 2026 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant