# 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@` before build | `O2_RN_SDK_VERSION` | | Android-native | `build.gradle.kts`: `System.getenv("O2_ANDROID_SDK_VERSION") ?: ""` | `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: ```yaml - 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 downloaded and run **natively** by the device jobs so the app posts real RUM to it. The Linux side first looks for the exact merge-SHA `release-ci` binary + frontend already produced by `playwright.yml`; when both artifacts exist it reuses them instead of compiling OpenObserve again. Mobile-only/manual/SDK-release runs may not have matching Playwright artifacts, so the workflow keeps an exact-source local build fallback: - **`resolve_oo_linux`** finds an exact-SHA Playwright artifact; **`build_oo_linux`** (`ubuntu-latest`) builds the Linux binary + UI only when none exists. - **`build_oo_macos`** (`macos-15`) still compiles the macOS binary + UI: a Linux binary cannot run on the iOS simulator host, and no other PR workflow produces an exact-SHA Darwin artifact. - **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. The resolver therefore requires the matching Playwright `frontend-dist-` alongside `release-ci-binary-`; fallback builds upload `oo--frontend`. Device jobs extract either source 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.