Repository navigation
Per-test trace slices carry no DOM on an Appium mobile-web session #391
Description
What happens
On an Appium mobile-web session (Chrome on Android, Safari on iOS), a per-test trace slice carries no trace.mutations at all, so the player's replay pane has no DOM to show. Commands, console and assertions are captured normally, so the run looks healthy — only the page is missing.
Why
The slice is written from whatever the capturer holds at afterEach, and the adapters' DOM captures are fire-and-forget. finalizeTraceExport settles those before writing; the eager mid-run flush (flushRangeTrace) does not — it reaches the writer directly, by design, and never awaited them.
On a desktop driver that is invisible: a drain is milliseconds, so the promise has almost always resolved by the time the slice is written. Against Appium it is deterministic. Instrumented on a mobile-web run, with injection starting promptly on the navigation:
▶ test 1 starts
injectScript called (promptly, during test 1)
✓ test 1 ends (1368ms)
probe present=false <- first round trip returns AFTER the test ended
appending 213,826 bytes
▶ test 2 starts
appended ok <- append lands during test 2
The fallback injection needs four sequential round trips — collector probe, bundle load, append, readiness poll — and each costs ~1s against Appium rather than ~10ms. A 1.3s test is over before the collector exists.
Compounding it, the Selenium navigation capture (the path that instruments a freshly loaded document) was fired and forgotten rather than tracked, so even a flush that waited had nothing to wait for.
This is the race already recorded in CLAUDE.md as "eager per-test trace slice … can drop an action snapshot whose fire-and-forget capture hasn't resolved by afterEach". Appium's latency turns can into always.
Correction to the original report
The first version of this issue compared four adapters and concluded the defect was Selenium-specific. That comparison was confounded: Selenium's mobile example is the only one using traceGranularity: 'test'; the other three use session, which flushes at finalize and therefore goes through the gate that does settle captures. The adapters are not otherwise different here, and the "Selenium's injection is reactive/slower" reading was wrong — injection starts on the navigation in all of them.
Two further readings in the original report were also wrong and are withdrawn: BiDi being auto-enabled is not a factor (a run with webSocketUrl: false is byte-identical), and the jwproxy TypeErrors are teardown noise — every one is timestamped after the last test, hitting a chromedriver session that quit() had already closed.
Scope
The flush gap affects any adapter using the eager per-test path with fire-and-forget captures — Selenium and Nightwatch. The WebdriverIO service is immune because it awaits each capture inline before flushing. Python does not use this path.
Still open, separate
trace.networkis 0 bytes on mobile web for every adapter. Appium emits no BiDi network events, and the Chrome perf-log fallback needsgoog:loggingPrefs: { performance: 'ALL' }, which no mobile config sets. Worth deciding separately whether mobile web should request the capability or state the gap.- An inline event attribute blanks the trace player's replay pane #393 — an inline event attribute blanks the replay pane. Independent of capture, and not mobile-specific; it was hit while verifying this one, because a trace with DOM finally reached the renderer.
Verification
Measured per slice on an Android mobile-web run: trace.mutations absent → 234,023 bytes of real DOM. Confirmed across all four adapters on Android and iOS, native and web, using the mobile examples.
Environment: Appium 3.7.0, uiautomator2 7.6.1, emulator sdk_gphone64_arm64 Android 16 (API 36), Chrome 133.0.6943.137, chromedriver 133.0.6943.141, selenium-webdriver 4.46.0, iPhone 17 Pro simulator (iOS 26.5).
Activity
[-]Selenium adapter captures no DOM on an Appium mobile-web session[/-][+]Per-test trace slices carry no DOM on an Appium mobile-web session[/+]vishnuv688 commentedon Sep 28, 2026
Retitled and rewrote the description above. The original root-cause analysis was wrong in three ways, all mine:
traceGranularity: 'test', so it was the only one taking the eager flush path. Not an adapter difference.jwproxyTypeErrors are teardown noise, not a cause: every one lands after the final test, against a chromedriver sessionquit()had already closed.The real cause is the eager per-test flush not settling fire-and-forget captures, which Appium's latency makes deterministic rather than rare. Details and the instrumented timeline are in the body.
Also split out #393 — an inline event attribute (
<button onclick="…">) blanks the replay pane. It is independent of capture and not mobile-specific; it only surfaced here because fixing this issue finally got DOM as far as the renderer.