Files
openobserve/tests/mobile-testing/docs/CI-NOTES.md
T
Vamsi Karthik RandShrinath Rao 6930a9b724 test(mobile): RUM SDK end-to-end suite across 4 platforms + self-contained CI (#13628)
## What this PR adds

end-to-end test suite for the **Mobile RUM SDK** under
`tests/mobile-testing/`, plus a **self-contained CI workflow** that runs
it on real emulators/simulators against an OpenObserve **built from this
PR's source**. Test / fixture / CI only — no product-code/existing test
changes

## How each test works (3 layers)

Every scenario is a real end-to-end journey, so a failure points at the
actual broken layer:

1. **Drive** — a Maestro flow taps through the app like a user
(`maestro/<track>/*.yaml`).
2. **API** — query the `_rumdata` stream and assert the telemetry is
correct (`utils/ooClient.js`, poll-and-retry for async ingestion).
3. **UI** — open the RUM dashboard and assert the sessions

## Coverage (42 tests across 4 platforms)

The **native platforms (android-native, ios-native) are now at parity
with React Native** — each new
native test (masking, network, bg/fg, user-identity, no-phone-home) was
built + validated on a real
emulator/simulator before landing.

| Capability | Layer | RN-Android | Android-native | RN-iOS | iOS-native
|
|---|---|:-:|:-:|:-:|:-:|
| Core telemetry (views/actions/handled-error/crash → `_rumdata`) | API
| ✅ | ✅ | ✅ | ✅ |
| Crash session viewable in dashboard | UI | ✅ | ✅ | ✅ | ✅ |
| Attributes: env / service / version tagging | API | ✅ | ✅ | ✅ | ✅ |
| User identity (`setUser`) | API | ✅ | ✅ | ✅ | ✅ |
| Network: fetch → resource + 4xx/5xx | API | ✅ | ✅ | ✅ | ✅ |
| Session-replay PII masking | UI | ✅ | ✅ | ✅ | ✅ |
| Background / foreground view transitions | API | ✅ | ✅ | — | ✅ |
| No-phone-home (no egress to Datadog hosts) | security | ✅ | ✅ | — | ✅
|

- Shared per-capability factories keep the specs DRY
(`utils/rumChecks.js`, `utils/coreRumSpec.js`).
- Negatives are covered: 4xx capture, PII-must-not-leak (masking), no
phone-home, handled-error ≠ crash.
- `o2-enterprise#2289` dashboard bugs are kept as inline skips that flip
green once fixed.
- Full capability × platform detail + on-device status:
`docs/COVERAGE-MATRIX.md`.

## Fixture apps + build-time config

- Four vendored apps under `apps/`: `o2-rum-tester` (React Native —
drives both RN platforms), `o2-native-android` (Kotlin), `o2-native-ios`
(Swift / xcodegen).
- Each reads its RUM target (endpoint / org / token) and SDK version
from a **build-time-overridable** config. The committed default is the
current pin, so local dev is unchanged; CI regenerates it to point at
the runner's OpenObserve and can pin the latest SDK on release.

## CI — `.github/workflows/mobile-sdk-e2e.yml` (self-contained, no
secrets)

- **Build once per OS from source.** `build_oo_linux` / `build_oo_macos`
compile OpenObserve (binary + embedded web UI) from this PR's code and
cache it. Device jobs download and run it **natively**, and the app
posts real RUM to it (Android reaches the host at `10.0.2.2`, the iOS
simulator via `localhost`). No Docker, no shared cluster, no secrets.
- **Device jobs run in parallel.** `android` (Linux + KVM emulator →
rn-android + android-native) and `ios` (macOS simulator, pinned Xcode
16.4 → rn-ios + ios-native).
- **`merge_reports`** combines both platforms into one browsable HTML
report — even when a device job fails.
- **`decide` picks which platform runs, to keep scarce macOS runners in
check:**
- PR touching Android or shared mobile-RUM code → **Android auto-runs**;
iOS is gated behind the `mobile-e2e` label.
  - `mobile-e2e` label → both platforms (full pre-merge gate).
- `repository_dispatch(mobile-sdk-release)` from an SDK repo → runs that
SDK's platform (companion PRs wire this up:
openobserve-react-native-rum#3, openobserve-sdk-android#1,
openobserve-sdk-ios#1).
  - `workflow_dispatch` → manual, choose platforms.

*Separate workflow :* it needs macOS **and** Android emulators, builds 4
phone apps, and is dispatch-driven — matching the repo's existing
per-suite split

## Status

Green end-to-end on GitHub runners (Android + iOS device jobs + merge) :
https://github.com/openobserve/openobserve/actions/runs/31733717758/job/94565520891

---------

Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
2026-08-20 08:13:35 +00:00

6.6 KiB

Mobile RUM SDK — CI notes

How .github/workflows/mobile-sdk-e2e.yml runs. Model B: self-contained, no secrets — each device job stands up its own OpenObserve on the runner and points the app at it, exactly like playwright.yml does for the web tests.

Why self-contained (and why it's possible)

  • RUM ingestion is OSS — a plain OpenObserve accepts the data, so no special/enterprise cluster.
  • The other test workflows already run a throwaway OpenObserve on the runner with the well-known defaults (root@example.com / Complexpass#123 / org default / localhost:5080) as plain values — no secrets. This workflow does the same.
  • Result: every run is isolated (its own instance), so the Android and iOS jobs run in parallel with no collision — this removes the shared-service problem entirely.

How the app gets pointed at the local instance (build-time config injection)

Each app reads its RUM target from a build-time-overridable config. The committed values are placeholders (REPLACE_ME / openobserve.example.com); CI regenerates them to point at the local instance, and mints its own RUM token per run:

App Mechanism Regenerated by
RN (o2-rum-tester) inline @gen:* consts in App.tsx (NOT a JSON import — that breaks the Hermes release upload) scripts/gen-config.js (postinstall, seds App.tsx from O2_RUM_*)
Android-native BuildConfig.OO_* fields build.gradle.kts reads O2_RUM_* via System.getenv
iOS-native Sources/GeneratedConfig.swift gen-config.sh (reads O2_RUM_*)

Env the CI job sets before building: O2_RUM_HOST, O2_RUM_ORG, O2_RUM_TOKEN, O2_RUM_ENV. With none set, every generator keeps the committed placeholders — so set these (or .env) for a real local target.

Networking

  • Android emulator can't see the host's localhost; it reaches the host at 10.0.2.2. So the app posts to http://10.0.2.2:5080 while the suite's API/UI checks (on the host) use localhost:5080.
  • iOS simulator shares the host network → app and suite both use localhost:5080.
  • Cleartext HTTP to the local instance: Android manifests set usesCleartextTraffic="true"; the RN iOS build adds an ATS NSAllowsLocalNetworking key at build time (committed source stays HTTPS).

Testing the latest SDK (version injection)

The fixture apps pin the SDK version (RN @openobserve/mobile-react-native 0.1.1; the Android and iOS native SDKs 0.1.0 — separate packages, independently versioned) so ordinary runs are reproducible. But on a release we want to test the new version, not the frozen pin. So the SDK version is a build input, defaulting to the pin:

App Override mechanism Env
RN workflow npm install @openobserve/mobile-react-native@<ver> before build O2_RN_SDK_VERSION
Android-native build.gradle.kts: System.getenv("O2_ANDROID_SDK_VERSION") ?: "<pin>" O2_ANDROID_SDK_VERSION
iOS-native workflow sed of project.yml exactVersion before xcodegen O2_IOS_SDK_VERSION

The mobile-sdk-release dispatch carries {sdk, version}; the "Resolve SDK version override" step maps it to the right env so a release builds + tests against the latest SDK. A manual run can do the same via the sdk + sdk_version inputs. Unset → the committed pin (reproducible).

Triggers

A decide job turns each event into run_android / run_ios, so device jobs run only where relevant (macOS runners are scarce):

Trigger Fires when Runs
repository_dispatch (mobile-sdk-release) an SDK repo publishes a release (cross-repo) the SDK's platform (sdk = ios→iOS, android→Android, react-native→both)
pull_request (paths) a PR touches Android/shared mobile-RUM code preflight + Android device jobs (iOS is label-gated)
pull_request + mobile-e2e label full pre-merge gate preflight + both platforms
workflow_dispatch manual; run_full_suite=true + platforms preflight + the chosen platforms

The cross-repo trigger

The SDK source lives in other repos, so an SDK change never appears as a PR here. Each SDK repo sends a dispatch on release. The dispatch workflows are already opened as PRs on the three SDK repos (openobserve-react-native-rum, openobserve-sdk-android, openobserve-sdk-ios); each needs a repo secret OO_E2E_DISPATCH_TOKEN = a token that can dispatch on openobserve/openobserve (classic PAT with repo, or fine-grained with Contents:read + Actions:write). The call they make:

- name: Dispatch mobile-sdk-release
  env:
    GH_TOKEN: ${{ secrets.OO_E2E_DISPATCH_TOKEN }}
  run: |
    echo "{\"event_type\":\"mobile-sdk-release\",\"client_payload\":{\"sdk\":\"ios\",\"version\":\"$VER\"}}" \
      | gh api repos/openobserve/openobserve/dispatches --input -

sdk is react-native / android / ios per repo; the decide job maps it to the platform(s) to run.

Runners & the OpenObserve build

OpenObserve is built from this repo's source per-OS (not Docker, not a prebuilt binary), cached, then downloaded and run natively by the device jobs so the app posts real RUM to it:

  • build_oo_linux (ubuntu-latest) + build_oo_macos (macos-15) compile the binary + web UI.
  • Android → ubuntu-latest + KVM emulator, runs the Linux binary; app reaches it at 10.0.2.2.
  • iOS → macos-15 + simulator (pinned Xcode 16.4), runs the macOS binary; app uses localhost.
  • merge_reports combines both platforms into one HTML report (survives a failed device job).

release-ci sets debug-assertions=true, which puts rust-embed in disk-read mode — so each build job also uploads the built web UI (oo-<os>-frontend) and the device job extracts it to /tmp/dist so /web serves and the dashboard-UI tests run.

Status

Green end-to-end on GitHub runners (Android + iOS device jobs + merge). Known SDK build fixes and the cleartext-HTTP handling are recorded below / in the git history.

Validated locally

  • Suite parses (playwright --list): 42 tests, 4 platforms (+ sdk-drift project).
  • gen-config.js / gen-config.sh keep the committed placeholders when no env is set, and substitute O2_RUM_* when set.
  • Android/iOS native config wiring is in place (BuildConfig.OO_*, GeneratedConfig).

Optional later

  • Host next to the enterprise/alpha pipeline (mobile RUM is enterprise-flavored) — but note macOS is free here (public) and billed there (private).
  • A per-run RUM run-id attribute is no longer needed for isolation (each run has its own instance); only relevant if you ever move back to a shared cluster.