mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 08:09:27 +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>
76 lines
3.0 KiB
Swift
76 lines
3.0 KiB
Swift
import UIKit
|
|
import OpenObserveRUM
|
|
|
|
class MainViewController: UIViewController {
|
|
// A URLSession built with the instrumented delegate → its requests are tracked as RUM resources.
|
|
private let session = URLSession(configuration: .default, delegate: O2SessionDelegate(), delegateQueue: nil)
|
|
|
|
private func fetch(_ url: String) {
|
|
guard let u = URL(string: url) else { return }
|
|
session.dataTask(with: u).resume()
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
title = "O2 Native iOS"
|
|
view.backgroundColor = .systemBackground
|
|
|
|
let stack = UIStackView()
|
|
stack.axis = .vertical
|
|
stack.spacing = 16
|
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
view.addSubview(stack)
|
|
NSLayoutConstraint.activate([
|
|
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
|
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
|
|
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
|
|
])
|
|
|
|
func button(_ title: String, _ action: Selector) -> UIButton {
|
|
var cfg = UIButton.Configuration.filled()
|
|
cfg.title = title
|
|
let b = UIButton(configuration: cfg, primaryAction: nil)
|
|
b.addTarget(self, action: action, for: .touchUpInside)
|
|
b.accessibilityLabel = title
|
|
return b
|
|
}
|
|
|
|
stack.addArrangedSubview(button("Trigger handled error", #selector(handledError)))
|
|
stack.addArrangedSubview(button("Custom action", #selector(customAction)))
|
|
stack.addArrangedSubview(button("Go to Details", #selector(goToDetails)))
|
|
stack.addArrangedSubview(button("Go to Checkout", #selector(goToCheckout)))
|
|
stack.addArrangedSubview(button("Fetch resource 200", #selector(fetch200)))
|
|
stack.addArrangedSubview(button("Fetch resource 404", #selector(fetch404)))
|
|
stack.addArrangedSubview(button("Trigger native crash", #selector(triggerCrash)))
|
|
}
|
|
|
|
@objc func goToCheckout() {
|
|
navigationController?.pushViewController(CheckoutViewController(), animated: true)
|
|
}
|
|
|
|
@objc func fetch200() { fetch("https://jsonplaceholder.typicode.com/todos/1") }
|
|
@objc func fetch404() { fetch("https://jsonplaceholder.typicode.com/this-path-does-not-exist-404") }
|
|
|
|
@objc func handledError() {
|
|
RUMMonitor.shared().addError(
|
|
error: NSError(domain: "O2NativeIOS", code: 1,
|
|
userInfo: [NSLocalizedDescriptionKey: "O2 Native iOS — handled error from Main"]),
|
|
source: .source,
|
|
attributes: [:]
|
|
)
|
|
}
|
|
|
|
@objc func customAction() {
|
|
RUMMonitor.shared().addAction(type: .custom, name: "Native iOS custom action", attributes: [:])
|
|
}
|
|
|
|
@objc func goToDetails() {
|
|
navigationController?.pushViewController(DetailsViewController(), animated: true)
|
|
}
|
|
|
|
@objc func triggerCrash() {
|
|
let empty: [Int] = []
|
|
_ = empty[10] // index out of range -> crash
|
|
}
|
|
}
|