fluttersdk_dusk 0.0.6 copy "fluttersdk_dusk: ^0.0.6" to clipboard
fluttersdk_dusk: ^0.0.6 copied to clipboard

Flutter E2E driver for LLM agents and CI. 32 CLI commands and 31 MCP tools drive a running app over VM Service extensions; no flutter_test harness needed.

Changelog #

All notable changes to this project will be documented in this file.

This project follows Semantic Versioning 2.0.0. Entries follow the Keep a Changelog shape.


Unreleased #

No unreleased changes yet.


0.0.6 - 2026-06-09 #

Added #

  • dusk:screenshot web CDP fallback via Page.captureScreenshot. When ~/.artisan/state.json carries a cdpPort (a web target), the CLI command sends Page.enable + Page.captureScreenshot (format, quality, fromSurface: true) over the Chrome DevTools Protocol and writes the decoded bytes directly, bypassing the in-isolate ext.dusk.screenshot extension that hangs under CanvasKit+DWDS (issue #13). Native targets (no cdpPort) keep using ext.dusk.screenshot. The command captures the full app frame. This CDP fallback is CLI-only; the dusk_screenshot MCP tool still dispatches ext.dusk.screenshot in-isolate, so web agents should use the CLI for screenshots. Region (ref/rect) capture remains deferred.
  • Non-fatal FlutterError capture surfaced by dusk:exceptions. DuskPlugin.install() now chains a FlutterError.onError handler that records every non-fatal error (including RenderFlex overflow, tagged type: "overflow") into a bounded in-package ring buffer (cap 50, dedup by message + stackHead, newest-first). ext.dusk.exceptions merges this buffer with the existing telescope reader output, so overflow and other non-fatal rendering errors appear in dusk:exceptions results even when fluttersdk_telescope is absent (issue #14).
  • Per-ref overflow: annotation in dusk:snap output. Interactive nodes inside a currently-overflowing render ancestor now carry an additive overflow: true sub-line in the snapshot YAML. The check is a live renderObject.toStringShort().contains(' OVERFLOWING') call (the Flutter debug-mode convention from RenderFlex.toStringShort); no retained state, no Expando. Non-overflowing layouts produce no annotation. The annotation silently drops if a future Flutter version renames the suffix; dusk:exceptions remains the authoritative overflow signal.

Changed #

  • fluttersdk_artisan constraint bumped from ^0.0.6 to ^0.0.7 (Dart pre-1.0 caret rule: ^0.0.7 resolves to >=0.0.7 <0.0.8). Consumers now pull in artisan 0.0.7 which hardens start --cdp-port with busy-port fast-fail and Chrome/FIFO/profile cleanup (issue #25). All dusk-consumed artisan surfaces (CommandBoot, ArtisanCommand, ArtisanContext.callExtension, McpToolDescriptor, registerExtensionIdempotent, StateFile.read/write) are signature-identical to 0.0.6; the bump is non-breaking.

0.0.5 - 2026-05-28 #

Changed #

  • fluttersdk_artisan constraint bumped from ^0.0.5 to ^0.0.6 (Dart pre-1.0 caret rule: ^0.0.6 resolves to >=0.0.6 <0.0.7). Consumers now pull in artisan 0.0.6 which ships the substrate mcp:install --invocation=<exec> flag this release depends on for the fallback behavior below.
  • mcp:install fallback when bin/fsa is absent now writes dart run fluttersdk_dusk mcp:serve. The dusk wrapper auto-injects --invocation=fluttersdk_dusk when forwarding mcp:install to the substrate, so the substrate's .mcp.json writer picks the plugin-aware payload instead of the legacy dart run :dispatcher mcp:serve fallback. No change in behavior when fastcli is present; the ./bin/fsa mcp:serve payload is unchanged.
  • Renamed every dart run fluttersdk_artisan reference inside the dusk package to dart run fluttersdk_dusk (33 docs/code occurrences). The dusk wrapper proxies the full artisan command surface; the package-local invocation is now canonical inside dusk's own docs, error messages, dartdocs, and chained subprocess calls. Substrate package:fluttersdk_artisan/ Dart imports unchanged.

Fixed #

  • bin/fluttersdk_dusk.dart now forces collectMcpTools: true when dispatching mcp:serve, so dart run fluttersdk_dusk mcp:serve surfaces all 31 dusk_* MCP tools even without the fastcli scaffold. Previously returned 0 plugin tools (only the 10 substrate tools). Verified end-to-end on a fresh flutter create consumer with path-linked dusk + artisan 0.0.6 against a running Flutter app on Chrome (real counter increments visible via dusk:tap + subsequent dusk:snap).

0.0.4 - 2026-05-27 #

Added #

  • README.md ## AI Coding Assistants section + llms.txt ## AI & Tooling section + 📡 AI-first Distribution feature-table row. Aligns dusk's surface with the cross-package fluttersdk pattern (already shipped on fluttersdk_wind): the canonical fluttersdk-dusk skill at skills/fluttersdk-dusk/ is distributed through fluttersdk/ai to 8 agents (Claude Code, Cursor, OpenCode, Gemini CLI, VS Code Copilot, Codex CLI, Cline, Roo Code) via npx skills add fluttersdk/ai --skill fluttersdk-dusk. The hosted docs MCP at mcp.fluttersdk.com exposes a search-docs tool over Streamable HTTP for direct docs-corpus queries, with an npx @fluttersdk/mcp stdio bridge for clients without HTTP MCP transport. The README copy is explicit that this is independent of dusk's own runtime MCP (./bin/fsa mcp:serve): the docs MCP teaches the agent ABOUT dusk; the runtime MCP gives the agent eyes and hands on a running Flutter app.

Changed #

  • Hero logo (.github/dusk-logo.svg) realigned to fluttersdk_magic 1:1. The previous logo had drifted toward indigo (#3730A3, #4338CA, #6366F1, #818CF8) which is not in the magic palette the sibling packages share, and its custom wavy shimmer accents diverged from the family line work. The new SVG is a verbatim copy of magic-logo.svg: same 4-layer 3D chevron geometry, same three tilted orbit rings (rotated -12°, 25°, 60° around the same center), same rx / ry / stroke-width / stop-opacity tokens, same 7-color violet palette (#4C1D95 through #DDD6FE). The only change is the gradient ID prefix (m* -> d*, plus orbit-N -> d-orbit-N) so both logos can render on the same page without DOM-level ID collisions. Verification: diff <(grep colors dusk) <(grep colors magic) is empty (set-equal); the same diff over rotate() transforms, ellipse params, chevron paths, and stroke / opacity tokens is also empty.

Fixed #

  • README + CI workflow stale develop references. README.md hero logo URL, CI badge ?branch=, and contributor-section CI sentence pointed at the retired develop branch (404 after the GitHub Flow migration in 0.0.3). All three now point at master. .github/workflows/ci.yml push + pull_request triggers reduced from [main, master, develop] to [master] (single long-lived branch per the new flow; main was never used, develop is retired). Pub.dev's frozen 0.0.3 archive still carries the broken logo URL; this 0.0.4 docs-only release ships the fix to pub.dev.

0.0.3 - 2026-05-26 #

Added #

  • skills/fluttersdk-dusk/ Section 7 + references/community.md. Opt-in star and issue-report CTAs for the LLM-agent skill, bumped to skill version: 0.0.3. Section 7 carries the trigger matrix only (star = task verified end-to-end; issue = dusk-side bug, explicitly excluding all six Core Law 3 actionability substrings since those are app-state signals). Executable detail (preflight command -v gh && gh auth status, gh api --method PUT /user/starred/fluttersdk/dusk --silent, gh issue create -R fluttersdk/dusk --body-file - heredoc, dusk:doctor + dusk_console + dusk_exceptions diagnostic gather, prefill URL fallback under 6KB, spam brakes) lives in references/community.md so the always-loaded SKILL.md body stays compact. Both flows are prose-permission only, maximum once per session, never auto-executed; on gh absence the agent prints the URL but does not invoke open / xdg-open / start.

Changed #

  • Skill bundle decontaminated from consumer-specific identifiers. dusk_evaluate examples in references/mcp-tools.md, references/cli-commands.md, and references/workflows.md now use generic placeholders (MyService.instance.state, MyService.instance.state.toString()) instead of consumer-private symbols (Magic.find<MonitorController>(), Magic.find<MagicApplication>()). Route-discovery hint switched from grep -r 'MagicRoute.page' to portable grep -rEn 'GoRoute|MaterialPage|name:' lib/.

  • Tinker REPL guidance unified on the concrete command ./bin/fsa tinker across the published skill bundle. Package-name attribution (magic_tinker, artisan_tinker) dropped from SKILL.md, references/mcp-tools.md, references/workflows.md, references/cli-commands.md since users only ever need the command they run. Code-side magic_tinker references in lib/src/dusk_artisan_provider.dart, lib/src/extensions/ext_evaluate.dart, ARCHITECTURE.md, and doc/mcp/tool-reference.md are unchanged and tracked for a separate follow-up.

  • Three Copilot review findings on closed PR #5. references/mcp-tools.md IIFE closure now returns state.toString() so the placeholder API stays consistent with the surrounding MyService.instance.state examples. references/workflows.md route-discovery grep uses portable grep -rEn extended-regex syntax instead of the BSD-incompatible basic-regex \| alternation. skills/fluttersdk-dusk/SKILL.md stale REPL attribution rewritten.

  • Two Copilot review findings on PR #6. skills/fluttersdk-dusk/SKILL.md CLI output description rewritten to match references/cli-commands.md truth: 9 read / query verbs emit JSON, the 18 side-effect verbs print a one-line success summary by default and only emit JSON when --includeSnapshot is passed. references/community.md star-flow note drops the spurious HTTP 304 reference; GitHub's PUT /user/starred/{owner}/{repo} is idempotent and returns 204 whether the star was new or already set.

Docs #

  • CLAUDE.md adopts GitHub Flow (Golden Rule 5 + Branching section). One long-lived branch (master); task branches cut from master, PR back into master; releases bump pubspec.yaml + promote [Unreleased] then tag (git tag X.Y.Z && git push origin X.Y.Z triggers publish.yml). Matches flutter/flutter, dart-lang/sdk, dart-lang/pub, and the modern OSS ecosystem (react, vscode, rust, node, kubernetes, go, angular). The repo's develop branch is retired after this release PR merges.

0.0.2 - 2026-05-24 #

Added #

  • skills/fluttersdk-dusk/ LLM-agent skill bundle. Ships an Anthropic-shape skill that teaches an LLM agent (Claude Code or any MCP client) how to drive a Flutter app where fluttersdk_dusk is installed. Mirrors the fluttersdk_telescope skill layout. Five files: SKILL.md (frontmatter + 6 core laws + 3 agent loops + tool families + install snippet), references/mcp-tools.md (per-tool input schema / return shape / when-to-use / pitfalls across all 31 dusk_* tools), references/cli-commands.md (CLI mirror via ./bin/fsa dusk:*, pipeline patterns, exit codes), references/actionability-and-refs.md (6-step gate detail + e<N> / q<N> ref recovery matrix), references/workflows.md (8 concrete agent playbooks: form fill, scroll-to-tap, modal flow, navigation verify, hot-reload-after-edit, pull-to-refresh, log tail, before/after diff). Frontmatter front-loads TRIGGER when: / DO NOT TRIGGER when: vocabulary so the model auto-loads the skill on any dusk_* MCP call, dusk:* CLI invocation, or E2E-driver task on a running Flutter app.

Docs #

  • README demo.gif placeholder removed. The <p align="center"><img src=".../screenshots/demo.gif"></p> block plus its TODO(v0.0.2-followup) recording-instructions comment are dropped until the actual asset ships. The hero logo, badges, and below-the-fold content are unchanged.
  • example showroom em-dash sweep. example/lib/main.dart section headers and inline comments are normalised to commas / colons / parentheses, aligning the example with the global no-em-dash rule applied across the rest of the repo.

0.0.1 - 2026-05-23 #

Initial public release of fluttersdk_dusk. E2E driver for Flutter apps. Snapshot, tap, type, drag, scroll, screenshot, wait, find via VM Service extensions (ext.dusk.*). Framework-agnostic (vanilla Flutter friendly); Magic / Wind integrations ship inside those packages via DuskPlugin.enrichers extension point. Plugin of fluttersdk_artisan ^0.0.5 (hosted-only; no path overrides). Wind diagnostics flow through the neutral fluttersdk_wind_diagnostics_contracts bridge (WindDebugRegistry) rather than through the enricher list, so wind alpha-10 needs no dusk-side install wiring.

Added #

  • 32 CLI commands via DuskArtisanProvider.commands() (live count from ls lib/src/commands/*_command.dart): dusk:install, dusk:snap, dusk:tap, dusk:screenshot, dusk:type, dusk:scroll, dusk:wait, dusk:wait_for_network_idle, dusk:hover, dusk:drag, dusk:modal, dusk:doctor, dusk:navigate, dusk:navigate_back, dusk:get_routes, dusk:press_key, dusk:select_option, dusk:close_app, dusk:find, dusk:focus, dusk:blur, dusk:clear, dusk:right_click, dusk:dblclick, dusk:triple_click, dusk:set_checkbox, dusk:console, dusk:exceptions, dusk:observe, dusk:resize, dusk:device, dusk:hot_reload_and_snap. dusk:install is the one-shot bootstrap; the rest wrap a matching VM Service extension or substrate-routed action.
  • 31 MCP tool descriptors via DuskArtisanProvider.mcpTools() (live count from grep "name: 'dusk_" lib/src/dusk_artisan_provider.dart | sort -u): dusk_blur, dusk_clear, dusk_close_app, dusk_console, dusk_dblclick, dusk_device_profile, dusk_dismiss_modals, dusk_drag, dusk_evaluate, dusk_exceptions, dusk_find, dusk_focus, dusk_get_routes, dusk_hot_reload_and_snap, dusk_hover, dusk_navigate, dusk_navigate_back, dusk_observe, dusk_press_key, dusk_resize_viewport, dusk_right_click, dusk_screenshot, dusk_scroll, dusk_select_option, dusk_set_checkbox, dusk_snap, dusk_tap, dusk_triple_click, dusk_type, dusk_wait_for, dusk_wait_for_network_idle. All McpToolDescriptor const instances with Claude Code canonical descriptions (imperative opener + context paragraph + Usage: bullets).
  • 28 ext.dusk. VM Service extensions + 3 artisan:dusk: substrate-routed tools** (live count from grep "extensionMethod:" lib/src/dusk_artisan_provider.dart | sort -u). Direct ext.dusk.: snap, screenshot, tap, hover, drag, type, scroll, wait_for, wait_for_network_idle, dismiss_modals, press_key, select_option, navigate, navigate_back, get_routes, evaluate, close_app, find, focus, blur, clear, right_click, dblclick, triple_click, set_checkbox, console, exceptions, observe. Substrate-routed via artisan:dusk:*: resize, device, hot_reload_and_snap (in-isolate hot-reload deadlock avoidance). All ext.dusk. extensions register through registerExtensionIdempotent for hot-restart safety.
  • DuskPlugin.install(); idempotent host-side install entry. Wraps the app widget root in a RepaintBoundary (no GlobalKey) so ext.dusk.screenshot can find it via render-tree walk. Hot-restart safe via static _installCount guard. Honors DUSK_DISABLE env var (1 / true / yes, case-insensitive) as kill switch.
  • DuskSnapshotEnricher typedef; snapshot-enricher extension point. String? Function(Element, RefRegistry). Magic ships its enrichers via MagicDuskIntegration. Wind no longer ships an enricher as of wind alpha-10: wind state is read through the neutral fluttersdk_wind_diagnostics_contracts.WindDebugRegistry.current?.resolve(element) bridge inside ext_snapshot.dart and ext_observe.dart ahead of the enricher loop, so the 6 core wind fields (breakpoint, brightness, platform, states, bgColor, textColor) survive without an enricher registration. Contract: synchronous, stateless w.r.t. call ordering, may return null to skip, multi-line fragments split + indented under the ref entry by the dispatcher.
  • fluttersdk_wind_diagnostics_contracts integration: new production dep fluttersdk_wind_diagnostics_contracts: ^1.0.0. ext.dusk.snap and ext.dusk.observe read wind state via WindDebugRegistry.current?.resolve(element) in addition to the existing enricher list dispatch; the wind: block (filtered by _kDefaultWindKeys in defaults mode) is emitted directly by dusk. Magic enricher contract UNCHANGED.
  • RefRegistry; stable e<N> (snapshot-frozen) and q<N> (re-resolvable Playwright-Locator) token systems. e<N> refs are minted at dusk_snap time and consumed by every action tool; q<N> refs are minted by dusk:find and re-execute their stored predicates against the live tree on every action call (resilient to widget rebuild + route push).
  • Actionability gate (lib/src/utils/actionability_gate.dart); tap / hover / drag / type resolve through a single gate that verifies the target's enabled flag (Tristate.isFalse fails; Tristate.none and Tristate.isTrue pass), zero-area rect, and viewport overlap BEFORE synthesising the pointer / key event. Failures surface ServiceExtensionResponse.error(extensionError, "Widget ref=$ref is not actionable: $reason") with $reason ∈ {"not enabled", "zero rect", "off-viewport (rect=..., viewport=...)"}. scroll, select_option, and press_key intentionally skip the gate (see Known gaps).
  • dusk:install one-shot bootstrap; minimal install. Edits the consumer's lib/main.dart only (no bin/artisan.dart or lib/app/ scaffolding for vanilla Flutter apps). Detects Magic-stack apps via the await Magic.init( anchor and injects DuskPlugin.install() BEFORE Magic.init (then MagicDuskIntegration.install() AFTER), falling back to the runApp( anchor for vanilla Flutter apps. Wind alpha-10 needs no install-time wiring from dusk: the consumer calls Wind.installDebugResolver() directly, and dusk reads wind state through WindDebugRegistry at snap time. Vanilla consumers access dusk via dart run fluttersdk_dusk <cmd>. Idempotent; safe to re-run.
  • Flutter-free CLI wrapper; bin/fluttersdk_dusk.dart + executables: fluttersdk_dusk pubspec entry. dart run fluttersdk_dusk <cmd> proxies the full artisan CLI surface and exposes the dusk commands without dragging dart:ui into pure-Dart contexts.
  • install.yaml plugin manifest; V1 manifest at the package root makes plugin:install fluttersdk_dusk work end-to-end via the artisan PluginInstaller.
  • lib/cli.dart codegen barrel; Flutter-free typedef alias FluttersdkDuskArtisanProvider. Consumed by consumer-side lib/app/_plugins.g.dart auto-discovery without pulling Flutter symbols into the pure-Dart artisan codegen path.
  • dusk:find Playwright-Locator pattern; mints q<N> query handles backed by text / semanticsLabel / key predicates. Unlike e<N> refs (frozen at snap time), q-handles re-execute the Semantics + Element walk on every action call, so they survive widget rebuilds and route pushes as long as the predicates still match. Stale match returns an explicit stale-handle error; the agent re-finds, never silently retries.
  • dusk:doctor; diagnostic command that checks ~/.artisan/state.json Chrome PID staleness, DUSK_DISABLE env-var value, registered enricher count, Semantics-tree-forced flag, and Magic-init wiring in one pass. Emits a categorised report (OK / WARN / ERROR per check); exit code 0 when every check passes.
  • Chrome reaper (lib/src/utils/chrome_reaper.dart); graceful Chromium subprocess teardown between dusk:* runs so leftover headless tabs no longer accumulate. Detects orphans by VM Service URI, exits cleanly via SystemNavigator.pop first, falls back to SIGTERM.
  • Example apps: example/ (vanilla Flutter, 7 scenario screens: home menu + buttons / inputs / scroll / modals / drawer / forms) for live e2e validation against the 31 MCP tools + 32 CLI commands.
  • CDP driver (lib/src/cdp/): CdpClient, DevicePresets (8 curated device presets with explicit DPR values: iphone-x, iphone-13, iphone-15-pro, pixel-5, pixel-8, ipad-pro-12.9, desktop-1440, desktop-1920), ChromeFinder. Minimal in-house Chrome DevTools Protocol client (~110 LoC, dart:io WebSocket + dart:convert; no pub.dev deps).
  • dusk:resize CLI (lib/src/commands/dusk_resize_command.dart): dart run fluttersdk_dusk dusk:resize --width=375 --height=812 [--dpr=3] [--mobile] [--touch]. Reads cdpPort from state.json, opens CdpClient, sends Emulation.setDeviceMetricsOverride (+ optional setTouchEmulationEnabled). --reset sends 3-call clear chain. Fails loudly when CDP not enabled.
  • dusk:device CLI (lib/src/commands/dusk_device_command.dart): dart run fluttersdk_dusk dusk:device --preset=iphone-x. Applies the full emulation chain (metrics + conditional touch + UA) from the curated preset database. --list prints all 8 preset entries; --reset mirrors dusk:resize --reset.
  • 2 CDP MCP tools (dusk_resize_viewport + dusk_device_profile): both dispatch via the existing artisan: substrate prefix (no mcp_server.dart changes).
  • FakeCdpServer test harness (test/src/cdp/fake_cdp_server.dart): dart:io HttpServer + WebSocketTransformer.upgrade on an ephemeral loopback port. Configurable failure modes (failOnJsonVersion, dropWebSocket, delayResponseMs). Used by cdp_client_test.dart, dusk_resize_command_test.dart, dusk_device_command_test.dart.
  • Integration smoke test (test/integration/cdp_smoke_test.dart): tagged @Skip so default flutter test skips it; run manually via flutter test test/integration --tags integration to validate dart-lang/webdev#2642 regression status.
  • dusk:install magic-detect branch: now injects import 'package:magic/dusk_integration.dart'; instead of import 'package:magic/magic.dart';. Pairs with magic 1.0.0-alpha.15 which extracts the integration class into a dedicated sub-barrel.
  • 6-step actionability gate (Wave 3): Step 0 defunct preflight + Stable + Receives-Events gates round out ensureActionable (now async). Total preconditions in evaluation order: defunct (preflight), enabled, zero-rect, off-viewport, stable (rect unchanged across 2 consecutive frames; Playwright auto-waiting), receives-events (hit-test confirms ref is the front-most pointer target). Opt-out via checkStable=false / checkReceivesEvents=false (both default true). Failure-reason substrings extended: "defunct", "not stable", "obscured by" join the existing agent branch surface.
  • Snapshot-in-action-response (Wave 3, Playwright setIncludeSnapshot pattern): 8 action handlers (tap, hover, drag, type, press_key, scroll, navigate, navigate_back) accept includeSnapshot=true and append the post-action snapshot YAML to the success response. The agent no longer needs a mandatory follow-up dusk_snap call. duskSnapBuild widened from @visibleForTesting to public (legitimate production reuse). press_key handler endOfFrame omission fixed in passing.
  • Structured error envelope + fuzzy-match suggestions (Wave 3): lib/src/utils/error_envelope.dart with DuskErrorEnvelope carrying type + widget_path + suggestions[]. 10 type values: timeout, not_found, obscured, disabled, stale, zero_rect, off_viewport, not_stable, missing_param, unexpected. 6 factories. Dual-write into errorDetail (JSON envelope alongside the free-form message) preserves backward compat for substring-matching agents. Levenshtein with prefix-bonus drives the suggestions list for not_found. RefRegistry.activeRefs() added to support candidate collection.
  • ext.dusk.wait_for_network_idle (Wave 3): polls TelescopeStore.pendingHttpCount until the count hits zero for a configurable idleMs window. Params timeoutMs (5000), idleMs (500), pollIntervalMs (200). Function-pointer indirection (pendingHttpCountReader exported from dusk.dart) keeps dusk free of a hard telescope dependency; magic-side wires the real reader at install time. New CLI command dusk:wait_for_network_idle.
  • 4 utility tools (Wave 3): dusk_console (telescope log reader, function-pointer indirection via recentLogsReader), dusk_exceptions (telescope exception reader via recentExceptionsReader), dusk_dblclick (two synthesised taps with 100ms inter-tap delay, shared 6-step actionability gate + snapshot embed), dusk_set_checkbox (idempotent Checkbox / Switch toggle via element walk; no-op when current value matches target).
  • ext.dusk.observe (Wave 4): Stagehand-style observe-once-act-many pattern. Walks every active PipelineOwner semantics tree, filters interactive nodes (buttons / textfields / links / checkboxes / dropdowns via _roleFor / _isInteractive), mints a re-resolvable q<N> ref per candidate (Playwright Locator pattern; never e<N>), and returns a structured JSON list {candidates: [...], count: N}. Each candidate carries ref, role, label, value, bounds, isEnabled, isVisible, plus enricher-projected fields. Params: intent (caller hint, echoed only), limit (default 50), roles (comma-separated filter), includeEnrichers.
  • dusk:hot_reload_and_snap (Wave 4): CLI-side orchestration via VmServiceClient.reloadSources (in-isolate handler cannot reload its own isolate; deadlock avoidance). Sequence: reload -> wait -> snap -> screenshot -> exceptions -> bundle. Success envelope {reloaded, durationMs, snapshot, screenshot, recentExceptions}; compile-error envelope skips snap/screenshot but still gathers exceptions. Screenshot failure surfaces as partial-result screenshotError rather than aborting the round-trip. MCP descriptor uses the artisan: substrate routing prefix (extensionMethod: 'artisan:dusk:hot_reload_and_snap').
  • dusk:install is now self-sufficient (Wave 5 pre-publish). Phase 1 patches lib/main.dart (unchanged contract). Phase 2 chains dart run fluttersdk_dusk install (scaffolds bin/dispatcher.dart + ./bin/fsa AOT wrapper) followed by dart run fluttersdk_dusk plugin:install fluttersdk_dusk (registers DuskArtisanProvider; artisan 0.0.5 auto-purges the AOT bundle cache). Both Phase 2 sub-process calls are file-marker-guarded (bin/dispatcher.dart, .artisan/installed/fluttersdk_dusk.json) so re-runs are fast no-ops; failures swallow with a warning so Phase 1's lib/main.dart inject remains the guaranteed contract regardless of the consumer's dart PATH / sandbox state. Net effect: a fresh consumer needs only flutter pub add fluttersdk_dusk + dart run fluttersdk_dusk dusk:install to reach a working ./bin/fsa list + MCP tools/list surface.
  • ext.dusk.find substring predicate + dusk:find --contains=<substring> CLI flag (Wave 5; pre-publish E2E pass). Existing --text=<exact> semantics unchanged; agents now have a brittle / dynamic-label fallback. DuskQuery.containsText field is the carrier; matching walks Semantics labels first, then Text.data, mirroring the text path.
  • dusk:drag --fromRef=<eN> --toRef=<eN> flag aliases parallel to the --ref shape used by dusk:tap / dusk:hover (Wave 5). Legacy --startRef / --endRef flags retained for back-compat.
  • dusk:scroll --direction=<up|down|left|right> --pixels=<N> convenience flags that translate to signed --dy / --dx (Wave 5). Explicit --dy / --dx still win when both forms supplied.
  • Surface deltas (live counts): CLI commands: 32 (lib/src/commands/*_command.dart); MCP tool descriptors: 31 (dusk_artisan_provider.dart); VM Service extensions: 28 ext.dusk.* + 3 artisan:dusk:* substrate-routed.

Fixed (pre-publish macOS + web E2E pass, Wave 5) #

  • dusk_resize_viewport MCP arg parsing (GAP I): handler cast ctx.input.option('width') as String? which failed when MCP tools/call delivers {"width":390} as a native JSON int rather than a stringified arg. Resize command now defensively reads int / double / bool from either type via _readInt / _readDouble / _readBool helpers. CLI invocations still work unchanged (ArgParser-emitted strings).

Fixed #

  • ext.dusk.focus on TextField + EditableText (GAP C): handler walked UP from the snap-captured Semantics element looking for a Focus ancestor; for TextField the FocusNode sits BELOW the captured element (inside EditableText / FocusableActionDetector). Now falls back to a descendant walk that picks the first EditableText.focusNode or Focus.focusNode it finds. Reproducer: dusk:focus --ref=<textbox-eN> previously returned no Focus ancestor; now returns focused: true.
  • ext.dusk.scroll with ref pointing at the Scrollable itself (GAP D): Scrollable.maybeOf(context) walks UP, so passing the ListView's own ref (e.g. from dusk:find --key=my-list) returned null. Handler now resolves in three stages: (1) target element IS a Scrollable, use its state; (2) Scrollable ancestor (legacy); (3) descendant Scrollable walk (when ref is a parent like a Scaffold wrapping a list).
  • dusk:press_key --key= case-sensitivity (NIT 5): agents calling --key=TAB or --key=enter hit unknown key even though the supported set covered the intent. Lookup now does a case-insensitive fallback over _kKeyMap.keys when the direct hit misses; canonical PascalCase keys (Tab, Enter, ArrowUp) remain documented.
  • dusk:screenshot success message now reports decoded byte count + KB + format, e.g. Wrote 239456 bytes (233.8 KB, jpeg) to ./shot.jpg (NIT 1). Previously the line referenced the base64 character count which misled agents parsing for byte size.
  • dusk:screenshot missing-output error now suggests the canonical invocation dusk:screenshot --output=./shot.jpg --format=jpeg (NIT 8).
  • README + installation.md document the full 3-step install flow: flutter pub add fluttersdk_dusk + dart run fluttersdk_dusk dusk:install + dart run fluttersdk_dusk install && dart run fluttersdk_dusk plugin:install fluttersdk_dusk (GAP B). Previously the plugin:install step was missing, leaving consumers with ./bin/fsa list showing 0 dusk:* commands. installation.md carries a new ## Register with artisan section explaining the fastcli scaffold + plugin registration.

Test coverage #

  • 678 tests passing (2026-05-23 pre-publish, flutter test --exclude-tags=integration --timeout=30s). Scope covers handler entry points (params + error paths + happy paths where reachable under flutter_test), 32 CLI commands (name / boot / description / configure / handle / missing-arg validation), DuskArtisanProvider.commands() / mcpTools() shape, DuskPlugin.install() idempotency + DUSK_DISABLE env-var kill switch, RefRegistry mint / lookup / disposeGroup / disposeAll / refsForGroup / registerQuery / lookupQuery, actionability gate (6-step: defunct / enabled / zero-rect / off-viewport / not-stable / obscured), encodeToJpeg PNG-to-JPEG roundtrip + quality boundaries (1, 100, error), modal-route classification, dispatcher contract, CDP client + device presets + resize/device commands, Wave 3 structured error envelopes, Wave 4 observe + hot-reload-and-snap, Wave 5 find-contains substring + descendant focus walk + Scrollable-own-ref scroll. Pre-publish E2E pass against a fresh vanilla Flutter consumer (/tmp/dusk_e2e) verified 27 of 32 CLI commands + MCP initialize + tools/list (41 tools = 31 dusk_* + 10 artisan_*) + tools/call dusk_snap (identical to CLI) + tools/call dusk_evaluate (actual evaluation via artisan 0.0.5 substrate routing).
  • Coverage: dusk ~79% line coverage via flutter test --coverage. The remaining gap covers engine-dependent paths that hang the flutter_test fake-clock harness: handler endOfFrame waits, Future.delayed poll loops in wait_for, real toImage() rasterisation in screenshot success paths, and private _defaultProcessStartTime / _parsePsLstart doctor seam defaults. End-to-end coverage for those paths is captured by the example/ playground sweep.

Known gaps #

  • dusk:doctor runs in pure-Dart CLI context and cannot import package:flutter/rendering.dart without dragging dart:ui (breaks dart run invocation). Two checks defang gracefully as a result: semanticsEnabledProbe defaults to true (the only ERROR-class check, so doctor cannot ERROR from CLI) and enrichersProbe defaults to 0 (always WARNs on Check 3). The real probes belong to a future VM-Service-attached doctor invocation that calls into the running app.
  • scroll, select_option, and press_key intentionally skip the actionability gate: scroll targets the parent scrollable not the ref, select_option dispatches through Material/Cupertino popup machinery that owns its own enabled check, and press_key targets the focused widget rather than a ref. Adding the gate to these three handlers is V1.x candidate work.
  • RefRegistry._queries (q-handle store) is monotonically growing within a debug session; only RefRegistry.disposeAll() clears it. Worst-case memory bounded by debug-session lifetime; per-handle eviction is V1.x candidate work.

Risks Accepted #

  • dart-lang/webdev#2642 live regression: "Hot restart broken when running DWDS without Chrome Debug Port". Integration smoke test (test/integration/cdp_smoke_test.dart) surfaces this if active. Mitigation lives in the user's pinned Flutter SDK; plan does not block on regression resolution.
  • Flutter SDK >= 3.30.0 required for --cdp-port (per flutter/flutter#170612). Lower versions get an actionable error from both artisan doctor (advisory) and artisan start --cdp-port (fail-fast).
  • GAP E (drag synthesis vs Flutter Draggable): dusk:drag returns success but Flutter's DragTarget.onAcceptWithDetails does not fire on synthesised events in some configurations (Pointer Down + 5x Move + Up sequence may not match Draggable's gesture recognizer expectations on certain platforms / dwell times). Verified via E2E showroom (2026-05-23). Tracked for a 0.0.2 follow-up; agents needing drag should fall back to a pair of dusk:tap + manual scroll for now.
  • GAP G (advisory): receives-events check + q-refs on widgets with deep render subtrees: when dusk:find --key=<name-field> resolves to a TextField (or any widget whose findRenderObject() returns a top-level RenderObject), the actionability gate's receives-events check sees a hit-test path topped by a deeper descendant (e.g. RenderEditable) and trips obscured by other widget. The _isDescendantOf walk does not catch this case consistently. Workarounds: (1) use the e<N> ref from a prior dusk:snap rather than a q<N> from --key; (2) pass --no-checkReceivesEvents on the action. Tracked for a 0.0.2 follow-up; deeper investigation needed in the gate's hit-test path traversal.
  • GAP H (web): dusk:screenshot + dusk:close_app timeout on Chrome (DWDS): 10s timeout. macOS desktop works fine. The web path likely needs special handling for RepaintBoundary.toImage() under DWDS pixel pipeline + the platform-close semantics of SystemNavigator.pop() (which closes the tab, so the response can't return). Workaround for close: rely on ./bin/fsa stop SIGTERM (works). Workaround for screenshot on web: use the browser DevTools snapshot. Tracked for a 0.0.2 follow-up.

Backward compat #

DuskSnapshotEnricher typedef, DuskPlugin.install / DuskPlugin.enrichers / DuskPlugin.registerNavigateAdapter, RefRegistry public methods (register, lookup, registerQuery, lookupQuery, disposeAll, resetForTesting), and every MCP tool name / ext.dusk.* extension name are part of the public 0.0.1 contract. Future releases keep these stable across the 0.x line; any change requires a coordinated bump with magic + wind.

1
likes
160
points
4.04k
downloads

Documentation

Documentation
API reference

Publisher

verified publisherfluttersdk.com

Weekly Downloads

Flutter E2E driver for LLM agents and CI. 32 CLI commands and 31 MCP tools drive a running app over VM Service extensions; no flutter_test harness needed.

Homepage
Repository (GitHub)
View/report issues

Topics

#mcp-server #e2e-testing #ai-agents #testing #flutter

License

MIT (license)

Dependencies

flutter, fluttersdk_artisan, fluttersdk_wind_diagnostics_contracts, image, meta

More

Packages that depend on fluttersdk_dusk