Skip to content

Android snapshot stays stale after the second Navigation 3 navigation (in-ComposeView content swap) #1254

Description

@antFrancon

We ran into this in a Compose app that uses Jetpack Navigation 3. After navigating twice, agent-device snapshot keeps returning the screen from the first navigation. The first navigation reports fine; the second one (and anything after) doesn't. From that point every selector on the new screen fails to match (click, fill, is, wait, find), even though the app has actually moved on.

adb shell uiautomator dump at the same moment shows the correct screen, so it's not the app or the device — the helper's snapshot just isn't refreshing. Looks like the same family as #564 and #850, but the recovery from #861 only kicks in for system-only captures, so this case (a stale-but-plausible app screen) slips past it.

Repro

Minimal project here: https://github.com/antFrancon/nav3-agent-device-snapshot-repro

It's a bare ComponentActivity with three trivial Nav3 destinations, ~110 lines. ./repro.sh builds, installs, runs the two navigations and checks the mismatch, but by hand it's:

agent-device open com.example.nav3repro --relaunch
agent-device snapshot              # Home       ok
agent-device click id=to-screen2
agent-device snapshot              # Screen 2   ok    (first swap refreshes)
agent-device click id=to-screen3
agent-device snapshot              # Screen 2   <-- wrong, the app is on Screen 3

At that last step the app really is on Screen 3:

adb shell uiautomator dump /dev/tty   # contains screen3-title and screen3-field

Navigation 3 renders every destination inside one AndroidComposeView and swaps the content in place — there's no new Activity or window, dumpsys window | grep mCurrentFocus stays on the same MainActivity the whole time.

Environment

  • agent-device 0.19.2 (also saw it on 0.19.3)
  • Android emulator, Pixel 8, API 35
  • androidx.navigation3 1.0.1 (pulls in navigationevent-compose 1.0.2, activity 1.13.0, compose-ui 1.11.4), Compose BOM 2026.06.01, Kotlin 2.4.0, compileSdk 36 / targetSdk 35 / minSdk 26

Things I checked while cutting it down

  • Activity type doesn't matter — happens with both ComponentActivity and AppCompatActivity.
  • Doesn't depend on entryDecorators, enableEdgeToEdge, ViewModels, a custom theme, or whether you snapshot between the two clicks.
  • snapshot --force-full doesn't help — it re-emits the cached (stale) tree instead of recapturing.
  • Coordinate taps still land, so the app is fine; it's only the helper's tree that's stuck.
  • Reproduces every run.

Guess at the cause

The helper's accessibility cache doesn't seem to get invalidated after an in-place content swap that keeps the same window/Activity. uiautomator dump does a fresh getRootInActiveWindow() after waitForIdle, which is why it's unaffected — same cache angle discussed in #429. Resetting the cache before capture, or extending the #861 recovery to also catch a stale/divergent capture (not just system-only) and fall back to uiautomator, would both fix it.

Happy to adjust the repro if that helps.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions