search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs
You are viewing an older version (0.0.7). Go to the latest.

Driving real apps: gotchas for agents

A long real-app E2E session surfaces a recurring set of traps that every agent re-discovers the hard way. This page collects them, with the workaround each one now has built into fluttersdk_dusk. Read it once before driving a non-trivial app; it will save you a dozen dead-end round trips.


Table of contents


1. Refs go stale on rebuild

e refs from dusk:snap freeze the widget at snapshot time. A navigation, a modal open/close, a setState, or any significant rebuild invalidates them: the next action on a stale e fails with a not-found or stale envelope.

Workaround:

  • Re-snap (dusk:snap) after any action that changes the screen, and use the fresh refs.
  • For a target that survives re-renders (a stable Text, accessibility label, or Key), prefer dusk:find / dusk:observe, which mint a re-resolvable q handle that re-walks the live tree on every action call. q handles survive rebuild, route push, and snapshot disposal as long as the predicates still match.
  • Pointer verbs (dusk:tap, dusk:hover, dusk:drag, ...) now dispatch at the element's LIVE rect, not the cached snapshot rect, so a target that merely shifted slots (same Element/RenderObject) is still hit correctly. The false-success class (gate passes, pointer lands on the stale position, onTap never fires) is gone for slot shifts; genuine rebuilds still need a re-snap.

2. Text fields may snapshot nested

A wind WInput (and any TextField wrapped in Semantics(textField: true)) historically snapshotted as TWO nested textbox nodes, because RenderEditable unconditionally owns its own textField Semantics node and MergeSemantics cannot absorb it. Agents naturally targeted the inner leaf, where dusk:type threw a -32000.

Workaround:

  • dusk:snap now collapses the nested pair (by render-object containment, never label/value equality, so two sibling fields sharing a label stay distinct) and emits a single ref for the outer node, marked typeable: true. Target the node carrying typeable: true.
  • Better: use dusk:fill --ref= --text=, which focuses, clears, types, and settles in one call (and retries once on a stale handle). It resolves the right editable for you and composes the gated focus/clear/type handlers, so you do not re-build the focus + clear + type + settle dance.

3. dusk:console captures debugPrint in-package now

dusk:console historically surfaced full structured logs only when fluttersdk_telescope was installed and wired.

Workaround:

  • DuskPlugin.install() now chains a debugPrint override that records every debugPrint(...) / print(...) call into a bounded in-package ring buffer, so those entries appear in dusk:console even without telescope.
  • When telescope IS installed it enriches the output with Logger.root.onRecord entries and its other watchers. Direct dart:developer log() calls that bypass debugPrint still require telescope's LogWatcher.

4. dusk:exceptions is cumulative

dusk:exceptions returns the full exception history by default, so a single pre-existing error keeps re-appearing after every action and produces false positives when you are checking whether YOUR action raised something new.

Workaround:

  • Record the current time before the action, then pass dusk:exceptions --since= afterwards to get only exceptions raised strictly after that timestamp. Unparseable since values are treated as absent (full list).

5. restart preserves the CDP port

When artisan was started with --cdp-port (the web path that backs dusk:screenshot CDP fallback, dusk:resize, dusk:device), a naive restart used to drop the port, breaking those CDP-routed tools until you restarted with the flag again.

Workaround:

  • artisan restart (and bare start) now re-read the prior cdpPort from ~/.artisan/state.json and reuse it as the default, so CDP stays wired across restarts. An explicit --cdp-port still wins.

6. Overlays get stuck

A left-over dialog, bottom sheet, dropdown menu, or barrier modal blocks the next screen from rendering, and a single dusk:modal (dismiss-modals) does not clear overlays that are not PopupRoutes.

Workaround:

  • Use dusk:reset_overlays, which runs three idempotent layers: dismiss every PopupRoute, press Escape (for shortcut-driven overlays), then tap a Cancel/Dismiss/Close/OK/Done affordance as a last resort. Safe to call speculatively between flows; the response (popped, escaped, dismissTapped) tells you which layer cleared the screen.

See also