Skip to content

Per-test trace slices carry no DOM on an Appium mobile-web session #391

Closed

Description

@vishnuv688

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.network is 0 bytes on mobile web for every adapter. Appium emits no BiDi network events, and the Chrome perf-log fallback needs goog: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

  1. changed the title [-]Selenium adapter captures no DOM on an Appium mobile-web session[/-] [+]Per-test trace slices carry no DOM on an Appium mobile-web session[/+] on Sep 28, 2026
  2. vishnuv688 commented on Sep 28, 2026

    @vishnuv688
    MemberAuthor

    Retitled and rewrote the description above. The original root-cause analysis was wrong in three ways, all mine:

    • The four-adapter comparison was confounded — Selenium's mobile example is the only one on traceGranularity: 'test', so it was the only one taking the eager flush path. Not an adapter difference.
    • "Selenium's injection is reactive and too slow to start" — it starts promptly on the navigation, like the others. What is slow is the four sequential round trips it needs against Appium.
    • The jwproxy TypeErrors are teardown noise, not a cause: every one lands after the final test, against a chromedriver session quit() 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions