Skip to content

Releases: AlphaWaveSystems/flutter-probe

v0.10.4

Choose a tag to compare

@github-actions github-actions released this 07 Jul 03:04
33d4964

Fixed

  • A hung reconnect attempt could leave the CLI silently stuck with no way
    to recover other than manually interrupting it (part of PT-25).
    The
    auto-reconnect retry loop passed the outer, per-run context into each
    reconnect attempt — that context has no deadline of its own (only
    Ctrl-C/SIGTERM cancels it), unlike the per-step timeout the CLI otherwise
    honors. If a device call during reconnect (e.g. an adb command) ever
    hung, there was nothing to force it to give up. Each reconnect attempt is
    now bounded by the same step timeout the CLI already uses everywhere
    else. This addresses one confirmed, independently-reproducible
    contributing factor reported alongside PT-25's WebSocket-drop
    investigation; the drop itself is still open — see PT-25 in
    IMPROVEMENT_TASKS.md for what's resolved vs. still outstanding.
  • tap 1st #id (an ordinal modifier combined with an id-selector) always
    misparsed (PT-26).
    The parser's ordinal-selector branch only checked for
    quoted text or a bare identifier after the ordinal, never an #id token —
    so tap 1st #post_list_card left #post_list_card completely unconsumed,
    and it misparsed as a second, stray, unknown recipe call. Fixed by
    accepting an id-selector after an ordinal too. Since Flutter itself
    enforces unique Keys among direct siblings, disambiguating repeated
    same-id rows by position also required a matching fix on the Dart agent
    side: the ordinal selector kind was hardcoded to always match by
    displayed text, never by id — now it checks for the # prefix (matching
    the plain id selector kind's existing convention) and matches by key
    instead when present.

v0.10.3

Choose a tag to compare

@github-actions github-actions released this 06 Jul 15:47
63fb1b4

Fixed

  • A recipe named starting with the word "open" (e.g. open most recent post) always misparsed (PT-23). open is a reserved keyword for the
    built-in open the app/open link verbs, and the parser claimed any
    step starting with it unconditionally — an undocumented fallback then
    swallowed the next bare word as a selector and left the rest of the line
    to misparse as a second, stray, unknown recipe call. Fixed by only
    treating open as the built-in verb when what follows actually matches
    one of its two documented forms (the app/app, or a link) via a
    backtracking lookahead; anything else now falls through to a normal
    recipe call, preserving the whole phrase as the call's name.
  • A recipe with a hyphenated name (e.g. create looking-for post)
    always misparsed (PT-24).
    The lexer tokenized a word-internal hyphen
    as its own standalone token unconditionally — fine for the recipe
    definition (a quoted string, hyphen preserved as-is), but at the
    call site the recipe name is built by rejoining bare word tokens with
    spaces, so the hyphen came back out padded ("looking - for"), and the
    two representations could never match. Fixed by keeping a hyphen that's
    directly between two word characters part of the same identifier token.
    A standalone hyphen (e.g. the negative sign in set location -33.8, 151.2) is unaffected, since it's only ever reached preceded by a space,
    never mid-identifier.

v0.10.2

Choose a tag to compare

@github-actions github-actions released this 05 Jul 23:38
7e6c58b

A small release with no functional changes — a reported regression was
re-investigated and re-closed, with a regression test added to lock in the
verified-correct behavior going forward.

Testing

  • Locked in sequential tap+type behavior when one field auto-focuses on
    page load (PT-21, reopened and re-closed).
    A report claimed sequential
    multi-field text entry breaks specifically when a field requests focus
    during initState/first frame (e.g. a "remember last email" convenience
    feature) — text supposedly keeps landing in that auto-focused field
    regardless of which field is tapped afterward. Re-investigated with the
    exact discriminator described: a widget test with one field auto-focusing
    via a post-frame requestFocus(), plus real-device verification against
    the actual login screen with the same pattern temporarily added, in both
    tap orders. Both pass cleanly — focus correctly shifts to whichever field
    is tapped, and each field retains only its own typed value. Added a
    regression test locking this in.

v0.10.1

Choose a tag to compare

@github-actions github-actions released this 05 Jul 22:29
9a3ca48

A quick patch release for regressions surfaced during v0.10.0's own
verification pass, plus an unrelated CI-automation fix found along the way.

Fixed

  • The daily Dependabot auto-merge workflow never actually merged anything,
    for months.
    Its skip condition treated mergeStateStatus == "UNKNOWN"
    as "already in the merge queue" — but UNKNOWN just means GitHub hasn't
    finished computing mergeability yet (the common case on a fresh poll), not
    "already queued." Every eligible PR was silently skipped every single day
    since at least mid-May, despite the workflow itself reporting "success."
    Separately, even a PR that had passed that check would have hit a second,
    compounding bug: the merge command itself (gh pr merge --squash, no
    --auto) doesn't work for a merge-queue-gated branch — it just prints a
    warning and does nothing, the same issue found and fixed earlier this
    release for this repo's own CI (see PT-19-adjacent commit history). Fixed
    both: only skip on DIRTY/BLOCKED (states that definitively block a
    merge), and let a merge attempt happen and fail on its own terms
    otherwise; added --auto to the merge command. Verified by manually
    invoking the corrected logic against the real backlog — 9 previously
    stuck PRs (some open since May) are now genuinely in the merge queue.
  • wait for network idle/wait for the page to load/wait for page to load always misparsed, silently splitting into a no-op plus a stray,
    broken statement (PT-20).
    "for" (and "the") are both global filler
    words that parseWait's initial filler-strip consumes immediately after
    "wait" — by the time the code checked for "for" specifically to
    distinguish this verb family, it was already gone, so that check could
    never match. The parser fell through to a default case that produced a
    WaitPageLoad step (the right kind, by coincidence) but never consumed
    "network idle"/"page to load" — those leftover words became a second,
    separate statement that then failed at runtime as an unknown recipe
    call. Invisible to probe lint, since both halves parse as individually
    valid syntax; only surfaces when the test actually runs. This bug has
    existed since the project's very first commit — not a regression from
    any recent release, despite surfacing during this release's own
    verification pass. Fixed by detecting the network/page-load cases
    directly (mirroring the pattern wait for animations to end already
    used for the same underlying issue) and consuming the full phrase before
    the next token is checked.
  • .github/workflows/e2e.yml's CI E2E suite failed flutter pub get outright
    on every run
    ("name" field doesn't match expected name "probe_agent").
    The workflow's dependency_overrides block still referenced the package's
    old pre-rename name (probe_agent) instead of flutter_probe_agent — a
    stale reference to a rename that happened well before this release. Found
    when the v0.10.0 tag push triggered this workflow directly (it isn't
    gated on regular PRs, only tag pushes, which is why it went unnoticed
    through several prior releases). Unrelated to this release's own changes;
    fixed by updating the override key to the current package name.

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 05 Jul 17:12
bde43dc

A hardening release working through a backlog of real E2E test issues
surfaced by driver projects (IMPROVEMENT_TASKS.md, PT-01 through PT-19).
Every fix was reproduced and verified against a real device before shipping,
and several turned out to have a different (or larger) root cause than
originally reported — noted inline below.

Added

  • agent.launch_timeout config option (and --launch-timeout flag)
    (PT-10).
    restart the app/clear app data used to be bounded by a
    hardcoded, unconfigurable 90s step timeout — no amount of raising
    dial_timeout/token_read_timeout could help an app whose actual
    cold-launch path does non-trivial async work (e.g. Firebase App Check
    re-initialization costing 90-100s) before becoming interactive. Defaults
    to 120s; raise it for apps with an expensive startup path.
  • Verbose connect diagnostics (PT-01). -v/--verbose on probe test now
    traces every step of the connect handshake — ADB setup, port forward
    setup/teardown, each Android token-read source attempted (run-as,
    /data/local/tmp, logcat) with hit/miss detail, WebSocket dial attempts
    (including transient retries), and handshake accept/reject. Connect
    failures were previously a total black box with zero diagnostic output;
    see PT-01 in IMPROVEMENT_TASKS.md.
  • CLI↔agent version handshake (PT-07). probe.ping now carries
    client_version (CLI → agent) and agent_version (agent → CLI) alongside
    the existing {"ok":true} response. Every connect path (WebSocket dial,
    HTTP dial, relay, and reconnect-after-restart) now logs a warning when the
    CLI and agent versions differ, and hard-fails with a clear error when they
    have different major versions. Older CLIs/agents that don't send or
    recognize these fields degrade gracefully — an empty/missing version is
    always treated as "unknown," never as a mismatch. Addresses PT-07 in
    IMPROVEMENT_TASKS.md — CLI/agent version drift was a standing suspect in
    unexplained connection failures with zero signal from the tool itself
    until now.

Changed

  • ProbeScript now errors loudly instead of silently no-oping on several
    classes of malformed script (PT-02):
    • An unknown recipe call (typo, or the recipe was never defined/isn't
      loaded from recipes_folder/use) is now a runtime error, not a silent
      skip. This was the single highest-leverage fix in IMPROVEMENT_TASKS.md
      — a silently-skipped call could mask a completely broken flow (e.g. a
      sign-in recipe that never actually signs anyone in) indefinitely, with
      every downstream test still reporting green.
    • An unquoted <placeholder> (e.g. type <email> instead of
      type "<email>") is now a parse error. Angle brackets have no meaning
      outside a quoted string in ProbeScript's grammar; unquoted, both brackets
      were silently dropped by the lexer, leaving a bare identifier that gets
      typed/matched as literal text with no indication anything was wrong.
    • else is now accepted as an alias for otherwise. Previously else
      lexed as a plain identifier, was silently treated as an unknown recipe
      call (a sibling step of the if, not nested inside it), and its body ran
      unconditionally on every run regardless of the if condition —
      exactly the opposite of what the test author intended, with no error
      anywhere.
    • resolve()'s placeholder-substitution loop is now bounded. A variable
      bound to a value containing its own placeholder marker (e.g. passing the
      unquoted literal <email> as a recipe argument) previously looped
      forever substituting the same text for itself, hanging the CLI with no
      error; it now terminates, leaving the placeholder unresolved. (Full
      positional/named argument forwarding into nested recipe calls — a
      larger, separate redesign of how recipe calls are matched against
      definitions — is intentionally not part of this change; see PT-02's
      "Update" note in IMPROVEMENT_TASKS.md.)

Fixed

  • drag <selector> to <selector> — the documented syntax — always failed
    to parse (PT-19).
    "to" was lexed as its own token but was never
    actually consumable anywhere: it was missing from the parser's
    filler-word list and used nowhere else in the grammar. The parser choked
    on to where it expected the second selector to start, and the rest of
    the line got misparsed as an unrelated recipe call. Found during a final
    regression pass across the full e2e suite before this release — this bug
    has always existed; PT-02's error-loudly fix (above, same release) is what
    first surfaced it, since it previously failed silently instead.
  • kill the app followed by any step other than open the app/
    restart the app (a wait, tap, see, etc.) still hung and then
    permanently failed (PT-18, follow-on from PT-09's open the app fix).

    The generic step-level auto-reconnect only re-dials, assuming the app
    process is already running — after a genuine kill the app, nothing is
    listening at all, so re-dialing could never succeed regardless of
    remaining attempts. Detects a connection-refused dial failure
    specifically (as opposed to a timeout/reset on a still-alive process,
    which a transient network drop would produce) and relaunches the app
    before retrying, the same way the open the app fix does, but from the
    generic reconnect path so it now covers every verb, not just open.
  • open the app after kill the app never actually relaunched the app
    (PT-09).
    It always sent an RPC over the (now-closed) connection, which
    failed; the generic step-level auto-reconnect that kicked in afterward
    only re-dials assuming the app process is already running — it never
    relaunches one that was genuinely force-stopped. In practice this meant
    the documented kill the appopen the app pattern hung through the
    full reconnect-retry window and then failed with a connection-refused
    error. open the app now detects a dead connection and relaunches the
    app the same way restart the app does, before reconnecting. Found
    while documenting cross-test-block state behavior (see Documentation
    below) — not what that investigation originally set out to find, but
    blocking anyone who'd try to use kill the app/open the app for
    exactly the per-test isolation those docs describe as the supported way
    to opt out of the default shared-state behavior.
  • take screenshot could capture stale content from the previous route
    instead of the current screen (PT-16).
    Found while investigating a
    scroll bug (PT-03): a screenshot taken right after navigating sometimes
    looked unchanged, even though see/don't see assertions confirmed real
    navigation had happened. Two independent causes:
    • Unlike every other verb, screenshot never called
      _sync.waitForSettled() before capturing — a capture taken right after
      navigation could land mid-route-transition instead of waiting for the
      push/pop animation to finish.
    • _captureViaRepaintBoundary picked the largest RenderRepaintBoundary
      in the entire element tree with no route-awareness — since Navigator
      keeps the previous route mounted underneath the current one, and both
      routes typically produce a same-size, screen-sized boundary, the strict
      area > bestArea comparison kept whichever one was visited first (the
      previous route, in Overlay insertion order), silently capturing the
      old screen instead. Same class of bug as PT-03/PT-15, just in the
      screenshot path instead of ProbeFinder/scroll. Fixed by skipping
      boundaries belonging to a non-current route, mirroring ProbeFinder's
      existing fix.
  • scroll could lose the gesture arena to Dismissible-wrapped list rows
    and never actually scroll (PT-15).
    scroll was a thin delegate to
    swipe's pointer-gesture simulation, which has to win the gesture arena
    against any competing recognizer along the way — a Dismissible row's own
    HorizontalDragGestureRecognizer could still intercept it. Reproduced
    against a real iOS simulator: a 50-item list with Dismissible rows never
    scrolled past the first screen, while the identical verb worked fine on a
    plain list. scroll's job is "reveal more content," unlike swipe (which
    tests a real gesture interaction like swipe-to-dismiss) — it doesn't need
    to enter the gesture arena at all, so it now drives the nearest
    Scrollable's own ScrollPosition directly instead, sidestepping the
    competition entirely. When no selector is given, picks the Scrollable with
    the largest viewport (a TextField's own internal cursor-scrolling
    Scrollable could otherwise be found first in tree order and silently
    "scroll" nothing visible).
  • scroll/swipe could report success while producing zero visible
    movement (PT-03).
    Root-caused two independent bugs by reproducing
    against a real iOS simulator:
    • The synthetic drag gesture sent a single PointerMoveEvent covering the
      entire distance in one jump. Real touches (and Flutter's own gesture
      arena / scroll physics) expect a sequence of incremental moves — a
      single jump could fail to register as a scroll at all. Now split into
      10 incremental steps, matching a real drag.
    • The widget-tree finder had no concept of "which mounted route is
      actually the current one." Flutter's Navigator keeps previous routes
      mounted underneath the current one by default (no Offstage wrapper),
      so a screen reached via a stacked push could have several live
      Scrollables (and other matching widgets) simultaneously — one per
      mounted route — and every selector-based verb (not just scroll/swipe)
      could silently resolve to a widget on a route the user can no longer
      see. Fixed by checking ModalRoute.of(element)?.isCurrent in the
      finder's core visibility check, so this also fixes false-positive
      see/wait until matches against stale conte...
Read more

v0.9.9

Choose a tag to compare

@github-actions github-actions released this 13 May 06:48

Added

  • deliver signal "name" ["value"] — new ProbeScript step that resolves
    a pending awaitSignal(name) call in the Flutter app. Use to unblock any
    OS-level interaction that isn't in the Flutter widget tree: push permission
    dialogs, payment sheets, App Tracking Transparency, deep-link handlers, etc.
    The value defaults to "true" when omitted.
  • awaitSignal(String name) — new public function exported from
    flutter_probe_agent. Returns a Future<String> that resolves with the
    value sent by the CLI. Generalises the awaitBiometricResult() pattern to
    any named signal.
  • DeliverSignal(String name, {String value}) — new annotation step class
    in flutter_probe_annotation. Emits deliver signal "name" or
    deliver signal "name" "value".
  • Parser: TOKEN_DELIVER, TOKEN_SIGNAL, VerbDeliverSignal.
  • Agent: probe.signal JSON-RPC method, ProbeMethods.signal constant.

v0.9.8

Choose a tag to compare

@github-actions github-actions released this 13 May 02:45
88ee2da

Fixed

  • Biometric no-match on iOS 26+ simulatornotifyutil no-match notifications
    no longer resolve LAContext.evaluatePolicy on iOS 26 / Xcode 26.5. The CLI now
    sends a probe.biometric_signal {result: bool} JSON-RPC command to the agent
    after firing platform-level notifications. Test apps call awaitBiometricResult()
    from flutter_probe_agent instead of local_auth.authenticate() in PROBE_AGENT
    builds; the agent resolves a Dart Completer with the CLI-delivered result, making
    biometric no-match reliable on all iOS simulator versions.

Added

  • awaitBiometricResult() — new public function in flutter_probe_agent.
    Returns a Future<bool> that resolves when the CLI delivers
    probe.biometric_signal. Usage pattern in test apps:
    final ok = const bool.fromEnvironment('PROBE_AGENT')
        ? await awaitBiometricResult()
        : await localAuth.authenticate(...);
  • probe.biometric_signal — new JSON-RPC method. Sent by the CLI after
    the platform-level biometric simulation commands so the result is always
    delivered regardless of simulator version behavior.

v0.9.7

Choose a tag to compare

@github-actions github-actions released this 13 May 01:35
ab5e4b2

Added

  • Biometric authentication testing — three new ProbeScript steps that drive Face ID / Touch ID / fingerprint flows on iOS Simulator and Android emulator without real hardware. Skipped on physical devices with a warning (same pattern as set location and other simulator-only ops).
    • enroll biometric — marks the simulator/emulator as having an enrolled face or finger. iOS posts the com.apple.BiometricKit.enrollmentChanged Darwin notification via xcrun simctl spawn booted notifyutil. Android requires the fingerprint to be pre-enrolled in Settings.
    • biometric match — simulates a successful capture, satisfying any pending biometric prompt. iOS posts *_Sim.faceCapture.match AND *_Sim.fingerTouch.match so the same step works on Face ID and Touch ID devices. Android runs adb -s <serial> emu finger touch 1.
    • biometric no match — simulates a failed capture so the app's "authentication failed" path can be tested. iOS posts the .no-match variants; Android runs adb emu finger touch 9999 (an unregistered id).
    • Annotation DSL: matching EnrollBiometric(), BiometricMatch(), BiometricNoMatch() const Step classes in flutter_probe_annotation, with a new biometric_auth golden fixture in flutter_probe_gen/test/fixtures/ that round-trips through the Go parser via the cross-language integration test.
    • Parser: 2 new tokens (TOKEN_BIOMETRIC, TOKEN_ENROLL), 3 new ActionVerb constants, 2 new parser dispatch cases. 3 new unit tests in parser_test.go.
    • Runner: EnrollBiometric / BiometricMatch / BiometricNoMatch methods on DeviceContext, dispatch cases in Executor.runAction, and human-readable strings in stepDescription.
    • Docs: new section in annotations.md and syntax.md on the website. Per-package CHANGELOGs updated.

v0.9.6

Choose a tag to compare

@github-actions github-actions released this 13 May 00:46
61f0a17

Fixed

  • flutter_probe_gen: Mock path silently truncated. The emitter wrote the path unquoted (when the app calls GET /api/products), so the Go lexer split on / and the parser only recorded the first IDENT segment. Now emits the canonical quoted form. Caught by a new mock_and_call golden + the existing cross-language integration test.
  • flutter_probe_gen: See suffixes silently dropped. When state, containing, and matching were all set on a single assertion, only the last branch's text reached the output. Now composes all three suffixes additively: see "x" is enabled contains "y" matching "z". Caught by a new see_states golden covering the matrix.

Added

  • flutter_probe_annotation: @ProbeCompositeTest annotation. The flagship multi-device composite testing feature finally has a DSL surface. Pair with Device(alias, target: …), OnDevice(alias, steps: […]) per-device groups, and Sync(label) cross-device barriers. Emitter generates standard composite test / devices / <alias>: / sync blocks that the existing CLI runner picks up unchanged.
  • flutter_probe_annotation: See.id / See.selector factories — assertions can now target by ValueKey or any rich selector (Ordinal, Below/Above/LeftOf/RightOf, InContainer, TypeSel) rather than only by literal visible text. Same factories on DontSee. The Go parser always supported this; the DSL just didn't expose it.
  • flutter_probe_annotation: WaitUntil.idAppears / .idDisappears — emits unquoted #key selector form (Go parser's WaitSelector branch), which is more reliable than text matching for stable ValueKey-tagged widgets.
  • flutter_probe_gen: 6 new golden fixtures. mock_and_call, see_states, composite_chat, wait_variants, examples_inline, kitchen_sink. The kitchen sink fixture exercises one of every step, selector kind, and control-flow construct. Every fixture round-trips through internal/parser/golden_integration_test.go. Total golden coverage went from 4 → 10 fixtures, builder tests from 5 → 11.

Changed

  • flutter_probe_annotation: Press and Pinch are now @Deprecated. The Go parser has no press or pinch case, so emitted text fell through to parseRecipeCall and was misinterpreted. Marked deprecated until runtime support lands. Use GoBack() in place of Press('back').
  • flutter_probe_gen: emitter no longer coupled to enum declaration order. _direction, _httpMethod, and the See state lookup now read the enum constant identifier (_name field) instead of indexing a hard-coded array by .index. Reordering Direction, HttpMethod, or SeeState no longer silently corrupts emitted ProbeScript.

Docs

  • New website page: Annotation-driven Tests — full reference for the annotation DSL with every step class, selector kind, and the new composite test syntax.

v0.9.5

Choose a tag to compare

@github-actions github-actions released this 13 May 00:13
8187d54

Fixed

  • Dart agent: iOS / Impeller screenshotstake_screenshot previously called OffsetLayer.toImage() on the root render view. On iOS with the Impeller renderer (Flutter's default on iOS 17+), that returns a GPU-backed texture whose toByteData(ImageByteFormat.png) is null, so capture silently produced nothing. The agent now primarily captures via the largest visible RenderRepaintBoundary in the widget tree (Impeller-supported); the legacy OffsetLayer path is only used as a fallback when no boundary is found (Skia). Also awaits WidgetsBinding.instance.endOfFrame before capture so the latest frame is always in the image, and uses the actual View.devicePixelRatio rather than a hard-coded 2.0.