Skip to content

fix: tell a native session from a browser one in every adapter - #375

Merged
vishnuv688 merged 11 commits into
mainfrom
fix/372-native-detection-every-adapter
Sep 15, 2026
Merged

vishnuv688 merged 11 commits into
mainfrom
fix/372-native-detection-every-adapter

Conversation

@vishnuv688

@vishnuv688 vishnuv688 commented Sep 11, 2026

Copy link
Copy Markdown
Member

What & why

Every adapter can now tell a native session from a browser one. Until now only the WDIO service could: the predicate read browser.isMobile/isAndroid/isIOS, which are WDIO runtime flags that Selenium's WebDriver, Nightwatch's browser and the Python driver do not have. So Selenium and Nightwatch ran their DOM drain, their collector injection and their page-script probes against a native app anyway — the same wasted round trips and Method is not implemented errors the service stopped emitting in #371 — and the Python adapter read window.innerWidth on a session with no window.

The fact now has one reader, isNativeAppSession in shared, which asks the capabilities every adapter already publishes rather than a driver flag. It keys on whether the session named a browser, because a device alone does not answer the question — an Appium session driving Chrome or Safari runs on a phone and has a real page — and it reads both platformName and browserName one level into vendor options, since a device cloud commonly states them only inside its own bag.

Gated per adapter: Selenium's captureTrace, injectScript, reinjectIfNavigated and its performance read (whose 500 ms settle was being spent to reach a document that does not exist); Nightwatch's captureTrace, injectScript, anchorAfterNavigation and its own performance read; Python's collector, its performance read and its viewport, which now measures the device window rather than asking a page that isn't there.

The densest of these is the per-action snapshot, and all three adapters were paying it: two injected scripts plus url and title, on every action. Those four now drop out on a native session while the screenshot — the one probe a native app does serve — is still taken, so the trace keeps its per-action frames. Screenshots and manage().logs() are deliberately left alone: Appium serves both, and logcat arrives through the second, so gating them would lose data rather than save a failed call.

Two pre-existing bugs fell out of the work, both from one root: Selenium published its capabilities as selenium-webdriver's Capabilities instance, whose data lives in a private Map with serialize exposed only under a Symbol — so the string-keyed serialize?.() the adapter called returned undefined and the instance reached the dashboard as {"map_":{}}. Every Selenium trace therefore carried no device and a guessed browser name, and the capabilities pane was empty. It is now flattened through the class's own keys()/get(), which is also what makes the new guards work at all, since they read that bag. Reading the device out of vendor options fixes the same field for a cloud session, which previously read as desktop and reached the player framed as a browser window rather than a phone.

The player's mobile layout was also still WDIO-only in live mode: it gates on metadata.device, and only the service derived one before sending. The app now derives it from the capabilities when the adapter sent none, at the single ingestion point every live message passes through. A device the adapter did send wins. Trace mode was already correct — the exporter derives it on the way into the zip.

Advances #372. Not closing it: two checklist items remain, both below.

Type of change

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Polish (an improvement to an existing feature)
  • Breaking change (existing behavior changes for users)
  • Documentation
  • Internal (build, CI, dependencies, tooling)

Packages touched

  • shared (types and contracts)
  • core (framework-agnostic capture/reporting)
  • elements (published element/snapshot API — @wdio/elements)
  • service (WebdriverIO adapter)
  • nightwatch-devtools (Nightwatch adapter)
  • selenium-devtools (Selenium adapter)
  • selenium-devtools-py (Selenium Python adapter)
  • backend (server)
  • app (UI)
  • script (page-injected runtime)
  • trace (Trace mode)

Notes for reviewers

Why the shared logic is in shared and not core. #372 proposed core, and this lands in shared instead. The predicate is a pure read over a capability bag — no driver, no framework hook, no capture session — and shared already owns the neighbouring narrowing (isNativePlatform, deviceFromCapabilities) that the trace exporter calls. Putting it in core would have split one question across two packages and put it out of reach of anything that may not import core. It stays framework-agnostic either way; shared is the layer more consumers can reach.

Why the guards read metadata rather than the driver. SessionCapturerBase exposes it as isNativeAppSession, resolved from the metadata the adapter has already set. That indirection is not decoration: Selenium's own getCapabilities() is async, and a guard cannot await it at the point it has to decide.

Why the guards are inside the guarded methods, not at the call sites. This is #350's finding applied: two of four call sites asked and two forgot. Selenium's drain has three call sites and Nightwatch's has four.

Known gaps, both tracked, neither introduced here:

A native session also still gets no accessibility tree: deriving one from page source is a capture feature the service has and the other three do not.

Verification

CI cannot cover any of this — it needs a device. pnpm lint, pnpm build, pnpm test (2361 tests) and pnpm test:ui are green on CI; the native paths were exercised locally against a real Android emulator, on a native app session for each of the four adapters.

One finding from that exercise is worth flagging because it is a live blocker rather than a gap in this change: a mobile web Appium session deadlocks with the service attached (#374) — beforeCommand awaits page-side calls from inside the hook wrapping the command being issued, Appium serialises commands per session, so the wrapped command never reaches the browser. Measured at 6 m 13 s of timeouts against 1.6 s with the service removed. Native app sessions are unaffected, precisely because isNativeAppSession makes those same calls no-ops — which is the change in this PR.

Screenshots / recordings

n/a — no UI change beyond live-mode device derivation, which selects the existing mobile layout rather than adding one.

@vishnuv688 vishnuv688 linked an issue Sep 11, 2026 that may be closed by this pull request
7 tasks
@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The PR is not yet safe to merge because hybrid Appium sessions still lose DOM capture after switching from a native context into a webview.

Findings

  1. P1 Hybrid contexts lose DOM capture

Summary

  • Skips document-dependent collection for native sessions while preserving screenshot capture.
  • Derives device metadata for live sessions and normalizes Selenium capabilities.
  • Supports collector-bundle lookup from both built and source-resolved package entries.
  • Adds cross-adapter tests for native and mobile-browser sessions.
  • The previously reported hybrid-context limitation remains explicitly documented but unresolved.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  CAPS[Session capabilities] --> DETECT[Shared native-session detection]
  DETECT -->|Native app| NATIVE[Skip DOM and page-script probes]
  DETECT -->|Browser session| WEB[Inject and drain page collector]
  NATIVE --> SHOT[Retain screenshots]
  WEB --> DOM[Capture DOM and action snapshots]
  CAPS --> DEVICE[Derive device metadata]
  DEVICE --> APP[Live player layout]
Loading

Reviews (2) · Last reviewed commit: "style(shared): satisfy prettier in the a..."

Comment thread packages/shared/src/device.ts
@vishnuv688
vishnuv688 merged commit 0bf2dc4 into main Sep 15, 2026
11 checks passed
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.

Native mobile support is WDIO-only: no other adapter can detect a native session

1 participant