mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-03 16:10:21 +00:00
## 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>
96 lines
3.8 KiB
JavaScript
96 lines
3.8 KiB
JavaScript
// Reusable "core RUM" suite for one mobile platform. Drives the app (Maestro), asserts the
|
|
// telemetry landed (API), asserts the crashed session is viewable (UI), and documents the
|
|
// known Error-Tracking outage as a skipped test that flips green when it's fixed.
|
|
const { test, expect } = require('@playwright/test');
|
|
const { runFlow } = require('./maestro');
|
|
const { search, pollUntil } = require('./ooClient');
|
|
const { RumDashboardPage } = require('../pages/rumDashboardPage');
|
|
const cfg = require('./config');
|
|
|
|
function coreRumSuite({ name, tags, flows, service, expectedSource, viewSubstring, device = '' }) {
|
|
test.describe.configure({ mode: 'serial' });
|
|
test.describe(name, () => {
|
|
let start;
|
|
let sessionId;
|
|
let crashMessage;
|
|
|
|
test.beforeAll(() => {
|
|
start = Date.now() - 30000;
|
|
for (const f of flows) runFlow(f, { device });
|
|
});
|
|
|
|
test(
|
|
'telemetry: views, actions, handled error and native crash land in _rumdata',
|
|
{ tag: [...tags, '@P0'] },
|
|
async () => {
|
|
// Poll until the crash (last event) lands, then assert the whole surface.
|
|
const rows = await pollUntil(
|
|
() =>
|
|
search(
|
|
`SELECT session_id, type, source, view_name, error_message, error_is_crash ` +
|
|
`FROM ${cfg.RUM_STREAM} WHERE service='${service}' ORDER BY _timestamp DESC`,
|
|
start,
|
|
),
|
|
(r) => r.some((x) => x.error_is_crash === true),
|
|
{ tries: 24, delayMs: 5000 },
|
|
);
|
|
|
|
expect(rows.length, 'events ingested').toBeGreaterThan(0);
|
|
expect([...new Set(rows.map((r) => r.source))], 'source tag').toContain(expectedSource);
|
|
|
|
const views = [...new Set(rows.filter((r) => r.type === 'view').map((r) => r.view_name))];
|
|
expect(
|
|
views.some((v) => (v || '').includes(viewSubstring)),
|
|
`a view containing "${viewSubstring}" was recorded`,
|
|
).toBeTruthy();
|
|
|
|
expect(rows.some((r) => r.type === 'action'), 'an action was recorded').toBeTruthy();
|
|
expect(
|
|
rows.some(
|
|
// !== true (not === false) so an SDK that emits null/undefined for a handled error's
|
|
// is_crash still counts as "not a crash".
|
|
(r) => (r.error_message || '').toLowerCase().includes('handled') && r.error_is_crash !== true,
|
|
),
|
|
'handled error recorded (is_crash not true)',
|
|
).toBeTruthy();
|
|
|
|
const crash = rows.find((r) => r.error_is_crash === true);
|
|
expect(crash, 'native crash flagged is_crash=true').toBeTruthy();
|
|
sessionId = crash.session_id;
|
|
crashMessage = crash.error_message;
|
|
},
|
|
);
|
|
|
|
test(
|
|
'the crashed session is viewable in the dashboard',
|
|
{ tag: [...tags, '@ui'] },
|
|
async ({ page }) => {
|
|
test.skip(!sessionId, 'no session id from the telemetry test');
|
|
const dash = new RumDashboardPage(page);
|
|
await dash.ensureServedOrSkip(test);
|
|
await dash.login();
|
|
await dash.openSession(sessionId);
|
|
await dash.expectSessionViewable();
|
|
},
|
|
);
|
|
|
|
// Known bug o2-enterprise#2289 (cross-platform): Error Tracking can't display errors
|
|
// (placeholder query + HTTP 429). Skipped until fixed — but a REAL body so removing `.fixme`
|
|
// runs an actual assertion (not a green no-op): the ingested crash must appear in Error Tracking.
|
|
test.fixme(
|
|
'crash is inspectable in the Error Tracking tab (o2-enterprise#2289)',
|
|
{ tag: [...tags, '@known-bug'] },
|
|
async ({ page }) => {
|
|
test.skip(!crashMessage, 'no crash message from the telemetry test');
|
|
const dash = new RumDashboardPage(page);
|
|
await dash.ensureServedOrSkip(test);
|
|
await dash.login();
|
|
await dash.openErrorTracking();
|
|
await dash.expectErrorListed(crashMessage);
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
module.exports = { coreRumSuite };
|