@swmansion/argent 0.20.1-next.7 → 0.20.1-next.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16776,10 +16776,10 @@ var require_browser = __commonJS({
16776
16776
  exports2.useColors = useColors;
16777
16777
  exports2.storage = localstorage();
16778
16778
  exports2.destroy = /* @__PURE__ */ (() => {
16779
- let warned = false;
16779
+ let warned2 = false;
16780
16780
  return () => {
16781
- if (!warned) {
16782
- warned = true;
16781
+ if (!warned2) {
16782
+ warned2 = true;
16783
16783
  console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
16784
16784
  }
16785
16785
  };
@@ -109158,12 +109158,16 @@ function settleWithin(p, ms, signal) {
109158
109158
  };
109159
109159
  p.then(
109160
109160
  (value) => finish({ type: "value", value }),
109161
- (err) => finish({ type: "error", error: err instanceof Error ? err.message : String(err) })
109161
+ (err) => {
109162
+ const cause = err instanceof Error ? err : new Error(String(err));
109163
+ finish({ type: "error", error: cause.message, cause });
109164
+ }
109162
109165
  );
109163
109166
  if (signal?.aborted) return finish({ type: "aborted" });
109164
109167
  const onAbort = () => finish({ type: "aborted" });
109165
109168
  signal?.addEventListener("abort", onAbort, { once: true });
109166
109169
  teardown.push(() => signal?.removeEventListener("abort", onAbort));
109170
+ if (ms === void 0) return;
109167
109171
  const timer = setTimeout(() => finish({ type: "timeout" }), Math.max(0, ms));
109168
109172
  teardown.push(() => clearTimeout(timer));
109169
109173
  });
@@ -145165,19 +145169,18 @@ async function queryVegaTree(device) {
145165
145169
 
145166
145170
  // ../tool-server/src/tools/flows/flow-tree.ts
145167
145171
  async function fetchFlowTree(registry2, device, launchedNativeApp) {
145168
- if (device.platform === "ios") {
145169
- return queryFullHierarchyTree(registry2, device, launchedNativeApp);
145170
- }
145171
- if (device.platform === "android") {
145172
- return queryAndroidFullHierarchy(registry2, device);
145173
- }
145174
- if (device.platform === "chromium") {
145175
- return queryChromiumTree(registry2, device);
145176
- }
145177
- if (device.platform === "vega") {
145178
- return queryVegaTree(device);
145179
- }
145180
- return fetchTree(registry2, device);
145172
+ const source = FLOW_TREE_SOURCES[device.platform];
145173
+ if (!source) return fetchTree(registry2, device);
145174
+ return source(registry2, device, launchedNativeApp);
145175
+ }
145176
+ var FLOW_TREE_SOURCES = {
145177
+ ios: (registry2, device, launchedNativeApp) => queryFullHierarchyTree(registry2, device, launchedNativeApp),
145178
+ android: (registry2, device) => queryAndroidFullHierarchy(registry2, device),
145179
+ chromium: (registry2, device) => queryChromiumTree(registry2, device),
145180
+ vega: (_registry, device) => queryVegaTree(device)
145181
+ };
145182
+ function supportsFlowTree(platform) {
145183
+ return FLOW_TREE_SOURCES[platform] !== void 0;
145181
145184
  }
145182
145185
 
145183
145186
  // ../tool-server/src/tools/flows/flow-add-step.ts
@@ -145511,11 +145514,16 @@ function runSequenceOutcome(result) {
145511
145514
  }
145512
145515
  return void 0;
145513
145516
  }
145517
+ var NESTED_ORCHESTRATORS = /* @__PURE__ */ new Map([
145518
+ [FLOW_EXECUTE_TOOL_ID, flowExecuteOutcome],
145519
+ [RUN_SEQUENCE_TOOL_ID, runSequenceOutcome]
145520
+ ]);
145521
+ function isNestedOrchestratorTool(tool) {
145522
+ return NESTED_ORCHESTRATORS.has(tool);
145523
+ }
145514
145524
  function nestedOrchestratorOutcome(tool, result) {
145515
145525
  if (!isRecord(result)) return void 0;
145516
- if (tool === FLOW_EXECUTE_TOOL_ID) return flowExecuteOutcome(result);
145517
- if (tool === RUN_SEQUENCE_TOOL_ID) return runSequenceOutcome(result);
145518
- return void 0;
145526
+ return NESTED_ORCHESTRATORS.get(tool)?.(result);
145519
145527
  }
145520
145528
 
145521
145529
  // ../tool-server/src/tools/flows/flow-pixels.ts
@@ -145659,6 +145667,7 @@ var FOCUS_REPORTING_SOURCES = /* @__PURE__ */ new Set([
145659
145667
  ]);
145660
145668
  var SETTLE_POLL_MS = 250;
145661
145669
  var SETTLE_TIMEOUT_MS = 3e3;
145670
+ var SETTLE_MIN_READS = 2;
145662
145671
  var MAX_SCROLL_ITERATIONS = 25;
145663
145672
  var SCROLL_INCREMENT = 0.5;
145664
145673
  var MIN_SCROLL_INCREMENT = 0.05;
@@ -145709,19 +145718,34 @@ function flowSelectorToFrame(tree, sel) {
145709
145718
  }
145710
145719
  return void 0;
145711
145720
  }
145712
- async function settleTree(env) {
145721
+ function provenTreeOutage(env) {
145722
+ const proven = env.treeOutage?.proven;
145723
+ return proven && proven.deviceId === env.device.id ? proven.error : void 0;
145724
+ }
145725
+ function readFlowTree(env) {
145726
+ return fetchFlowTree(env.registry, env.device, env.launchedNativeApp).then((data) => {
145727
+ if (env.treeOutage) env.treeOutage.proven = void 0;
145728
+ return data;
145729
+ });
145730
+ }
145731
+ async function settleTree(env, opts = {}) {
145713
145732
  const deadline = Date.now() + SETTLE_TIMEOUT_MS;
145714
145733
  let prevFp;
145715
145734
  let prevTree;
145716
145735
  let lastError;
145736
+ let reads = 0;
145717
145737
  for (; ; ) {
145718
145738
  if (env.signal?.aborted) return void 0;
145739
+ const proven = provenTreeOutage(env);
145740
+ if (opts.skipProvenOutage && proven) throw proven;
145741
+ const read = await settleWithin(readFlowTree(env), void 0, env.signal);
145719
145742
  let tree;
145720
- try {
145721
- ({ tree } = await fetchFlowTree(env.registry, env.device, env.launchedNativeApp));
145722
- } catch (err) {
145723
- lastError = err instanceof Error ? err : new Error(String(err));
145743
+ if (read.type === "value") {
145744
+ tree = read.value.tree;
145745
+ } else if (read.type === "error") {
145746
+ lastError = read.cause;
145724
145747
  }
145748
+ reads += 1;
145725
145749
  if (env.signal?.aborted) return void 0;
145726
145750
  if (tree !== void 0) {
145727
145751
  const fp = treeFingerprint(tree);
@@ -145730,7 +145754,11 @@ async function settleTree(env) {
145730
145754
  prevTree = tree;
145731
145755
  }
145732
145756
  if (Date.now() >= deadline) {
145733
- if (prevTree === void 0 && lastError !== void 0) throw lastError;
145757
+ if (reads < SETTLE_MIN_READS) continue;
145758
+ if (prevTree === void 0 && lastError !== void 0) {
145759
+ if (env.treeOutage) env.treeOutage.proven = { deviceId: env.device.id, error: lastError };
145760
+ throw lastError;
145761
+ }
145734
145762
  return prevTree;
145735
145763
  }
145736
145764
  if (!await sleepOrAbort(SETTLE_POLL_MS, env.signal)) return void 0;
@@ -145779,7 +145807,7 @@ async function waitForFocus(env, into, tappedFrame) {
145779
145807
  for (; ; ) {
145780
145808
  if (env.signal?.aborted) return;
145781
145809
  try {
145782
- const { tree, source } = await fetchFlowTree(env.registry, env.device, env.launchedNativeApp);
145810
+ const { tree, source } = await readFlowTree(env);
145783
145811
  if (!FOCUS_REPORTING_SOURCES.has(source)) return;
145784
145812
  const target = flowSelectorToFrame(tree, into) ?? tappedFrame;
145785
145813
  if (collectFocused(tree, []).some((n) => framesOverlap(n.frame, target))) return;
@@ -145892,6 +145920,25 @@ async function runDirective(env, step) {
145892
145920
  return runRotate(env, step);
145893
145921
  }
145894
145922
  }
145923
+ async function settleForGesture(env) {
145924
+ let warning;
145925
+ if (supportsFlowTree(env.device.platform)) {
145926
+ try {
145927
+ await settleTree(env, { skipProvenOutage: true });
145928
+ } catch (err) {
145929
+ warning = unsettledGestureWarning(err);
145930
+ }
145931
+ }
145932
+ if (env.signal?.aborted) return { aborted: true };
145933
+ return warning !== void 0 ? { warning } : {};
145934
+ }
145935
+ function warned(settle) {
145936
+ return settle.warning !== void 0 ? { warning: settle.warning } : {};
145937
+ }
145938
+ function unsettledGestureWarning(err) {
145939
+ const reason = err instanceof Error ? err.message : String(err);
145940
+ return `dispatched without settling the screen first: the UI tree could not be read (${reason}), so there was no way to tell whether anything was moving. The gesture went out against whatever was in flight, and one aimed at a moving element can miss it entirely - this step passing says it was sent, not that it landed. Restore the tree source, or put an explicit \`wait:\` in front of gestures that follow a transition.`;
145941
+ }
145895
145942
  async function resolveTargetPoint(env, target) {
145896
145943
  if (target.selector) {
145897
145944
  const frame = await waitForFrame(env, target.selector);
@@ -145899,26 +145946,29 @@ async function resolveTargetPoint(env, target) {
145899
145946
  if (!frame) {
145900
145947
  return { fail: { ok: false, reason: offscreenHint(target.selector) } };
145901
145948
  }
145902
- return getDescribeTapPoint(frame);
145949
+ return { point: getDescribeTapPoint(frame) };
145903
145950
  }
145904
145951
  if (typeof target.x === "number" && typeof target.y === "number") {
145905
- return { x: target.x, y: target.y };
145952
+ const settle = await settleForGesture(env);
145953
+ if (settle.aborted) return { fail: ABORTED_OUTCOME };
145954
+ return { point: { x: target.x, y: target.y }, ...warned(settle) };
145906
145955
  }
145907
145956
  return { fail: { ok: false, reason: "gesture needs a selector or x/y coordinates" } };
145908
145957
  }
145909
145958
  async function runTap(env, target) {
145910
- const point = await resolveTargetPoint(env, target);
145911
- if ("fail" in point) return point.fail;
145959
+ const resolved = await resolveTargetPoint(env, target);
145960
+ if ("fail" in resolved) return resolved.fail;
145912
145961
  await invokeOnDevice(env, "gesture-tap", {
145913
- ...point,
145962
+ ...resolved.point,
145914
145963
  ...target.times !== void 0 ? { clickCount: target.times } : {}
145915
145964
  });
145916
- return { ok: true };
145965
+ return { ok: true, ...warned(resolved) };
145917
145966
  }
145918
145967
  var DEFAULT_LONG_PRESS_MS = 800;
145919
145968
  async function runLongPress(env, step) {
145920
- const point = await resolveTargetPoint(env, step);
145921
- if ("fail" in point) return point.fail;
145969
+ const resolved = await resolveTargetPoint(env, step);
145970
+ if ("fail" in resolved) return resolved.fail;
145971
+ const point = resolved.point;
145922
145972
  const duration3 = step.duration ?? DEFAULT_LONG_PRESS_MS;
145923
145973
  if (env.device.platform === "chromium") {
145924
145974
  await invokeOnDevice(env, "gesture-drag", {
@@ -145936,17 +145986,21 @@ async function runLongPress(env, step) {
145936
145986
  ]
145937
145987
  });
145938
145988
  }
145939
- return { ok: true };
145989
+ return { ok: true, ...warned(resolved) };
145940
145990
  }
145941
145991
  async function runPinch(env, step) {
145942
145992
  let center = { x: 0.5, y: 0.5 };
145943
145993
  let frame;
145994
+ let settle = {};
145944
145995
  if (step.selector) {
145945
145996
  const resolved = await waitForFrame(env, step.selector);
145946
145997
  if (resolved === "aborted") return ABORTED_OUTCOME;
145947
145998
  if (!resolved) return { ok: false, reason: offscreenHint(step.selector) };
145948
145999
  frame = resolved;
145949
146000
  center = getDescribeTapPoint(resolved);
146001
+ } else {
146002
+ settle = await settleForGesture(env);
146003
+ if (settle.aborted) return ABORTED_OUTCOME;
145950
146004
  }
145951
146005
  const { n, per } = decomposePinch(step.scale);
145952
146006
  const guards = systemEdgeGuards(env.device);
@@ -145977,11 +146031,11 @@ async function runPinch(env, step) {
145977
146031
  await invokeOnDevice(env, "gesture-pinch", args);
145978
146032
  if (i < n - 1 && !await sleepOrAbort(PINCH_SETTLE_MS, env.signal)) return ABORTED_OUTCOME;
145979
146033
  }
145980
- return { ok: true };
146034
+ return { ok: true, ...warned(settle) };
145981
146035
  }
145982
146036
  async function fetchScreenAspect(env) {
145983
146037
  try {
145984
- const { screen } = await fetchFlowTree(env.registry, env.device, env.launchedNativeApp);
146038
+ const { screen } = await readFlowTree(env);
145985
146039
  return screen && screen.width > 0 && screen.height > 0 ? screen.width / screen.height : void 0;
145986
146040
  } catch {
145987
146041
  return void 0;
@@ -145990,12 +146044,16 @@ async function fetchScreenAspect(env) {
145990
146044
  async function runRotate(env, step) {
145991
146045
  let center = { x: 0.5, y: 0.5 };
145992
146046
  let frame;
146047
+ let settle = {};
145993
146048
  if (step.selector) {
145994
146049
  const resolved = await waitForFrame(env, step.selector);
145995
146050
  if (resolved === "aborted") return ABORTED_OUTCOME;
145996
146051
  if (!resolved) return { ok: false, reason: offscreenHint(step.selector) };
145997
146052
  frame = resolved;
145998
146053
  center = getDescribeTapPoint(resolved);
146054
+ } else {
146055
+ settle = await settleForGesture(env);
146056
+ if (settle.aborted) return ABORTED_OUTCOME;
145999
146057
  }
146000
146058
  const aspect = await fetchScreenAspect(env);
146001
146059
  const guards = systemEdgeGuards(env.device);
@@ -146037,7 +146095,7 @@ async function runRotate(env, step) {
146037
146095
  if (env.signal?.aborted) return ABORTED_OUTCOME;
146038
146096
  throw err;
146039
146097
  }
146040
- return { ok: true };
146098
+ return { ok: true, ...warned(settle) };
146041
146099
  }
146042
146100
  async function runType(env, step) {
146043
146101
  const frame = await waitForFrame(env, step.into);
@@ -146069,7 +146127,7 @@ async function waitForCondition(env, step, timeoutMs) {
146069
146127
  for (; ; ) {
146070
146128
  if (env.signal?.aborted) return ABORTED_OUTCOME;
146071
146129
  try {
146072
- const data = await fetchFlowTree(env.registry, env.device, env.launchedNativeApp);
146130
+ const data = await readFlowTree(env);
146073
146131
  lastMatches = flowFindAll(data.tree, step.selector);
146074
146132
  fetchError = void 0;
146075
146133
  everMatched ||= lastMatches.length > 0;
@@ -146164,11 +146222,7 @@ async function waitForIdle(env, step) {
146164
146222
  const roundStartedAt = Date.now();
146165
146223
  let answeredReadMs;
146166
146224
  const [read, frame] = await Promise.all([
146167
- settleWithin(
146168
- fetchFlowTree(env.registry, env.device, env.launchedNativeApp),
146169
- roundBudget,
146170
- env.signal
146171
- ).then((r) => {
146225
+ settleWithin(readFlowTree(env), roundBudget, env.signal).then((r) => {
146172
146226
  answeredReadMs = Date.now() - roundStartedAt;
146173
146227
  return r;
146174
146228
  }),
@@ -148807,6 +148861,7 @@ async function treeSourceGate(registry2, device, bundleId, signal) {
148807
148861
  async function runLaunch(state3, app) {
148808
148862
  const env = deviceEnv(state3);
148809
148863
  const { registry: registry2, device, signal } = env;
148864
+ if (state3.treeOutage) state3.treeOutage.proven = void 0;
148810
148865
  if (device.platform === "chromium") return runChromiumLaunch(state3, app);
148811
148866
  const bundleId = appIdForPlatform(app, device.platform);
148812
148867
  if (!bundleId) {
@@ -149028,8 +149083,8 @@ when \`on\` is omitted; distinct from the \`rotate\` tool, which changes device
149028
149083
  for a UI condition, and additionally takes the one condition that has no selector: \`idle: true\` waits
149029
149084
  until the screen has content and stops moving in BOTH the UI tree and the rendered pixels (it never
149030
149085
  fails a run \u2014 a screen that never settles passes carrying a \`warning\`, which is what makes it safe to
149031
- persist; the one outcome that does stop the run is an \`error\` for a tree source that could not be read
149032
- at all \u2014 a broken window rather than a verdict about the app, which leaves the run not-ok and skips
149086
+ persist; the one idle outcome that does stop the run is an \`error\` for a tree source THIS step could not
149087
+ read at all \u2014 a broken window rather than a verdict about the app, which leaves the run not-ok and skips
149033
149088
  every later step; it says nothing about WHICH screen settled \u2014 a dropped tap leaves the source screen
149034
149089
  perfectly idle \u2014 so pair it with the element check that names the destination); \`wait\` pauses for a fixed number of milliseconds; \`assert\` checks one now; \`snapshot\`
149035
149090
  diffs a screenshot \u2014 or, with \`cropOn: <selector>\`, one element's cropped region \u2014 against a stored
@@ -149037,6 +149092,14 @@ baseline (a missing baseline fails the step \u2014 set updateBaselines to adopt
149037
149092
  cropped element whose size drifted fails on dimensions); \`echo\` annotates; \`run\` executes another flow
149038
149093
  inline \u2014 a YAML path resolved against the directory of the flow file that references it (co-located
149039
149094
  runs only).
149095
+ A selector-less gesture \u2014 a coordinate \`tap\`/\`long-press\`, or a \`pinch\`/\`rotate\` with no \`on\` \u2014 resolves
149096
+ no frame out of the tree, so an unreadable tree source does NOT stop it the way it stops \`idle\`: it
149097
+ settles best-effort, dispatches anyway, and the step PASSES carrying a \`warning\` that quotes the source's
149098
+ own error. That green says the gesture was SENT, not that it landed. Restore the tree source (usually
149099
+ relaunch the app so the instrumentation loads), or accept the warning where the app can serve no tree;
149100
+ the first such gesture proves the outage and later ones spend that verdict without paying the settle
149101
+ window again. A tree read that comes back, or a relaunch, retires that verdict \u2014 which only makes the
149102
+ next gesture pay a fresh window, and it warns again if the source is still down.
149040
149103
  A \`when:\` block (condition + \`steps:\`, no else) runs its steps only if the condition holds \u2014
149041
149104
  checked once with the short assert grace \u2014 for one-sided divergences like interstitials and coach
149042
149105
  marks; a skipped block reports distinctly and failures inside an entered block are real failures.
@@ -149118,6 +149181,12 @@ Pass exactly one flow source: name for a saved flow under project_root, or flow_
149118
149181
  device,
149119
149182
  deviceIsExplicit: Boolean(params.device),
149120
149183
  signal,
149184
+ // One holder per ExecState, shared by nested `run:` flows: `deviceEnv`
149185
+ // spreads the reference, so what one step's settle learns about the
149186
+ // tree source the next one already has. A `tool: flow-execute` builds
149187
+ // its own instead, which is why that step spends this verdict rather
149188
+ // than inheriting whatever the sub-run proved.
149189
+ treeOutage: {},
149121
149190
  flowsDir,
149122
149191
  viaUpload,
149123
149192
  baselineKey: baselineKeyFor(canonicalPath, flowName),
@@ -149711,6 +149780,10 @@ async function execLeafStep(state3, step, index, scope) {
149711
149780
  try {
149712
149781
  if (FOREGROUND_CHANGING_TOOLS.has(step.name)) {
149713
149782
  state3.launchedNativeApp = void 0;
149783
+ if (state3.treeOutage) state3.treeOutage.proven = void 0;
149784
+ }
149785
+ if (isNestedOrchestratorTool(step.name) && state3.treeOutage) {
149786
+ state3.treeOutage.proven = void 0;
149714
149787
  }
149715
149788
  const result = await invokeSubTool(registry2, ctx, step.name, args);
149716
149789
  if (isUnmetUiWaitResult(step.name, result)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.20.1-next.7",
3
+ "version": "0.20.1-next.8",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",
@@ -112,6 +112,8 @@ In a `scroll-to` map, put the selector under `target:`. The map supports `up`, `
112
112
 
113
113
  `type` presses Enter unless `submit: false`. A polished focus tap plus keyboard call usually needs `submit: false`. Store external values as `{{secret:NAME}}`. The runner uses the first source that defines the name: environment `ARGENT_SECRET_NAME`; project `.argent/secrets.env`; project `.env.local`, then `.env`; then `~/.argent/secrets.env`. The two `secrets.env` files accept the bare `NAME`, but the shared dotenv files expose only `ARGENT_SECRET_`-prefixed keys, so a bare `NAME=…` in `.env` or `.env.local` stays unresolved. The runner redacts every resolved value, so do not use a placeholder for content a report must show.
114
114
 
115
+ A **selector-less gesture** — a coordinate `tap`/`long-press`, or a `pinch`/`rotate` with no `on:` — resolves no frame, so a tree source it cannot read does not fail it. It settles best effort, dispatches anyway, and the step **passes carrying a warning** that quotes the source's own error. That green says the gesture was sent, not that it landed: one aimed at a moving element can miss it entirely. Restore the tree source, usually by relaunching the app so the instrumentation loads. Accept the warning only where the app serves no tree at all, and put an explicit `wait:` before a gesture that follows a transition. The first such gesture proves the outage and later ones spend that verdict without paying the settle window again. A tree read that comes back, or a relaunch, retires that verdict — which only makes the next gesture pay a fresh window, and it warns again if the source is still down.
116
+
115
117
  ## Verification conditions
116
118
 
117
119
  ```yaml
@@ -164,7 +166,7 @@ It **never fails a run.** Every outcome short of a clean settle passes with a wa
164
166
  - **settled on the UI tree alone** — no screenshot pair could be read, so presentation-layer animation was never waited out.
165
167
  - **too few reads** — a settle needs three reads across two intervals and this step got fewer, so it ended with no evidence either way.
166
168
 
167
- Only a tree source that cannot be read stops the run, as an errored step — one still failing when the wait ends, one that wedges after answering, one that answers with an empty tree it flags as degraded (an unattached Vega toolkit, an AX service asking to be relaunched), or one that never answers (raise `timeout` before suspecting the app). The run is then not ok and every later step is skipped. A single failed read is not that: the hold restarts from the next good read.
169
+ Only a tree source this step could not read stops the run, as an errored step — one still failing when the wait ends, one that wedges after answering, one that answers with an empty tree it flags as degraded (an unattached Vega toolkit, an AX service asking to be relaunched), or one that never answers (raise `timeout` before suspecting the app). The run is then not ok and every later step is skipped. A single failed read is not that: the hold restarts from the next good read. The same outage stops no [selector-less gesture](#directives), which needs no frame and passes with its own warning instead.
168
170
 
169
171
  `idle` proves readiness only and never identifies the screen, so it cannot serve as acceptance evidence or replace the identity gate. Gate the next action on a stable element. Add `idle` during polish after each screen change, not after every step.
170
172
 
@@ -239,4 +239,6 @@ Manual rescue invalidates the pass. An `errored` step was never evaluated: an `i
239
239
 
240
240
  **A passing step that carries a `warning` is a finding, not noise.** `await: { idle: true }` raises [six different warnings](flow-yaml.md#idle-readiness) and they do not share one meaning. Two say the screen was moving; one says the wait ran out mid-hold and is repaired by raising the step's `timeout:`; one says the tree stayed empty; one says the tree did hold still and only the screenshot pairs were missing, so the capture path is what to check; one says the step ended with no evidence either way. No report separates intended motion from a load that never finished. Read which one it is, look at that screen, disclose what you found, and confirm the following step targets a stable element rather than stillness.
241
241
 
242
+ A [selector-less gesture](flow-yaml.md#directives) raises a warning of a different shape, not one of those six: a tree-source outage left it unsettled, so it dispatched blind and the green says only that the gesture was sent. Restore the source, usually by relaunching the app so the instrumentation loads. Accept it only where the app serves no tree at all, such as the [injection-free iOS form](reliability-and-recovery.md#terminally-non-injectable-ios-apps).
243
+
242
244
  One uninterrupted full pass completes a normal flow. `argent-qa-flows` requires two consecutive passes of unchanged YAML. For CI, use `argent flow run <name> [--platform ...]`; it exits non-zero on failure.
@@ -62,6 +62,8 @@ Apple system apps cannot load the instrumentation, and nothing in the launch pat
62
62
  - A point focus tap plus raw keyboard with `delayMs: 500`.
63
63
  - Raw swipes with `settle: true` because `scroll-to` needs the missing flow tree. Momentum-free scrolling keeps later coordinate taps valid.
64
64
 
65
+ Every point tap or long-press in such a flow passes **carrying a warning**. The app loads no instrumentation, so every tree read fails and each [selector-less gesture](flow-yaml.md#directives) dispatches unsettled. Nothing here repairs it. Accept the warnings, read each green as "the gesture was sent, not that it landed", and put an explicit `wait:` or a raw `tool: await-ui-element` before a gesture that follows a transition. Raw `tool:` steps never take that settle, so they never warn.
66
+
65
67
  Report that the flow is injection-free and its coordinates are not portable. It cannot satisfy the QA contract. Report the artifact and platform blocker instead.
66
68
 
67
69
  The same fragment fallback covers a normally injectable app that is broken in the environment: raw `restart-app` in place of `launch:` still makes a self-resetting flow. Either way it is not e2e and cannot complete `argent-qa-flows`, which requires a leading `launch:`. Report the blocker rather than labeling that fallback a completed QA test.
@@ -111,6 +113,7 @@ Classify before editing:
111
113
  | Partial divergence | An intermediate result disagrees with its echo | Find the first divergent transition |
112
114
  | Acceptance failure | Actions pass but a requested check fails | Preserve the check and investigate behavior |
113
115
  | Idle warning | A readiness step passes without settling | Read [which of the six warnings](flow-yaml.md#idle-readiness) it is, then gate the next action on a stable element |
116
+ | Unsettled gesture | A selector-less gesture passes unsettled | Restore the tree source, usually by relaunching the app; the green says [only that the gesture was sent](flow-yaml.md#directives) |
114
117
 
115
118
  Then:
116
119
 
@@ -101,7 +101,7 @@ After the last edit and audit, set the streak to zero:
101
101
  2. Seed, review, and freeze snapshot baselines. Baseline updates do not count as passes.
102
102
  3. Before mobile pass 1, recycle Argent services for this flow's device: two warm passes are correlated evidence, because a fixed timing margin can pass twice simply because environment speed did not change. Scope `stop-all-simulator-servers` to `devices: [<device>]`. Never omit the scope — a bare call is the machine-wide sweep, and step 7 restarts this proof often enough to reap every other agent's devices repeatedly. Use the MCP call for `flow-execute`, or `argent run stop-all-simulator-servers --devices <device>` from the standalone runner's install. The reset must not change app or account data. For Chromium, let the runner boot the declared app and omit `device`. Vega owns no recyclable Argent services, so the teardown is a no-op there and both passes are warm.
103
103
  4. Run from the flow's launch and setup without baseline-update mode. Count a pass only when `ok: true` and every acceptance check executed. A false `when:` can skip optional setup only. An errored step does not advance the streak, and the count mixes two kinds — read each reason. One that could not run (an unreadable tree under `idle`, an unresolvable `run:` target) is environment: fix it and rerun. **A failed `launch:` also scores `errored`, and it is a verdict about the app** — an app that no longer installs or starts is the regression this test exists to catch, so report it instead of rerunning.
104
- 5. Resolve every passing-step warning before completion. `await: { idle: true }` raises [six different warnings](../argent-create-flow/references/flow-yaml.md#idle-readiness), so read which one it is first. Two say the screen was moving. One says the wait ran out mid-hold and needs a larger `timeout:`. One says the tree stayed empty. One — **settled on the UI tree alone** — says the hierarchy did hold still and only the screenshot pairs were missing, so inspect the capture path rather than the app's rendering. One says the step ended with no evidence either way. Inspect the screen, disclose the cause, and verify that surrounding acceptance checks use stable elements rather than stillness.
104
+ 5. Resolve every passing-step warning before completion. `await: { idle: true }` raises [six different warnings](../argent-create-flow/references/flow-yaml.md#idle-readiness), so read which one it is first. Two say the screen was moving. One says the wait ran out mid-hold and needs a larger `timeout:`. One says the tree stayed empty. One — **settled on the UI tree alone** — says the hierarchy did hold still and only the screenshot pairs were missing, so inspect the capture path rather than the app's rendering. One says the step ended with no evidence either way. Inspect the screen, disclose the cause, and verify that surrounding acceptance checks use stable elements rather than stillness. A [selector-less gesture](../argent-create-flow/references/flow-yaml.md#directives) — a coordinate `tap`/`long-press`, or a `pinch`/`rotate` with no `on:` — warns in a different shape: a tree-source outage left it unsettled, so it dispatched blind and the green says only that the gesture was sent. Restore the tree source, usually by relaunching the app so the instrumentation loads, and rerun. Accepting that warning needs an app that serves no tree, which cannot satisfy this contract anyway.
105
105
  6. Run the same YAML again immediately with the same runner. Do not manually reset app or account data.
106
106
  7. Reset the streak after any failure, edit, re-recording, baseline update, or state-changing manual recovery. Repair through `argent-create-flow`, audit again, and restart with fresh services.
107
107