@swmansion/argent 0.25.1-next.20 → 0.25.1-next.22

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.
package/dist/cli-cmds.mjs CHANGED
@@ -21857,7 +21857,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
21857
21857
  var SESSION_ID2 = randomUUID5();
21858
21858
  function readCliVersion() {
21859
21859
  if (true) {
21860
- return "0.25.1-next.20";
21860
+ return "0.25.1-next.22";
21861
21861
  }
21862
21862
  return "0.0.0";
21863
21863
  }
@@ -23211,7 +23211,8 @@ Subcommands:
23211
23211
 
23212
23212
  Options (run):
23213
23213
  --device <id> Device id to run against (auto-detected when omitted)
23214
- --platform <p> ios | android | chromium | vega \u2014 narrow auto-detection
23214
+ --platform <p> ios | android | chromium | vega | ios-remote \u2014 narrow
23215
+ auto-detection (ios never picks a remote simulator)
23215
23216
  --update-baselines Write/refresh screenshot baselines instead of diffing
23216
23217
  --output <dir> Also write failed snapshot images (baseline/current/diff)
23217
23218
  under <dir>/<flow>/ \u2014 a stable path for CI artifact
@@ -16710,7 +16710,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
16710
16710
  var SESSION_ID = randomUUID4();
16711
16711
  function readCliVersion() {
16712
16712
  if (true) {
16713
- return "0.25.1-next.20";
16713
+ return "0.25.1-next.22";
16714
16714
  }
16715
16715
  return "0.0.0";
16716
16716
  }
@@ -94704,7 +94704,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
94704
94704
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
94705
94705
  function readCliVersion() {
94706
94706
  if (true) {
94707
- return "0.25.1-next.20";
94707
+ return "0.25.1-next.22";
94708
94708
  }
94709
94709
  return "0.0.0";
94710
94710
  }
@@ -146123,6 +146123,7 @@ function isIdleCondition(raw, kind) {
146123
146123
  return true;
146124
146124
  }
146125
146125
  var LAUNCH_PLATFORMS = ["ios", "android", "chromium", "vega"];
146126
+ var SELECTABLE_PLATFORMS = [...LAUNCH_PLATFORMS, "ios-remote"];
146126
146127
  var LAUNCH_MAP_KEYS = ["native", ...LAUNCH_PLATFORMS];
146127
146128
  function parseChromiumLaunch(raw) {
146128
146129
  if (typeof raw === "string" && raw.length > 0) return raw;
@@ -147375,7 +147376,7 @@ var DEVICE_BIND_KEYS = ["udid", "device_id", "device"];
147375
147376
  var DEVICE_BIND_LIST_KEYS = ["devices"];
147376
147377
  var DEVICE_ARG_KEYS = DEVICE_BIND_KEYS;
147377
147378
  function deviceEntryId(d) {
147378
- if (d.platform === "ios") return d.udid;
147379
+ if (d.platform === "ios" || d.platform === "ios-remote") return d.udid;
147379
147380
  if (d.platform === "chromium") return d.id;
147380
147381
  return d.serial;
147381
147382
  }
@@ -147383,6 +147384,8 @@ function isBooted(d) {
147383
147384
  switch (d.platform) {
147384
147385
  case "ios":
147385
147386
  return d.state === "Booted";
147387
+ case "ios-remote":
147388
+ return d.state === "Booted";
147386
147389
  case "android":
147387
147390
  return d.state === "device";
147388
147391
  case "vega":
@@ -147677,6 +147680,7 @@ function adaptFullHierarchy(raw) {
147677
147680
  });
147678
147681
  return screenW > 0 && screenH > 0 ? { tree, screen: { width: screenW, height: screenH } } : { tree };
147679
147682
  }
147683
+ var FLOW_TREE_MAX_DEPTH = 100;
147680
147684
  var FULL_HIERARCHY_FIELDS = [
147681
147685
  "className",
147682
147686
  "identifier",
@@ -147708,9 +147712,9 @@ async function queryFullHierarchyTree(registry2, device, target) {
147708
147712
  const ndRef = nativeDevtoolsRef(device);
147709
147713
  nativeApi = await registry2.resolveService(ndRef.urn, ndRef.options);
147710
147714
  } catch (err) {
147711
- throw new Error(
147715
+ throw wrapPreservingFailure(
147712
147716
  `native devtools is unavailable (${errMsg2(err)}) \u2014 flows resolve selectors against the full view hierarchy it serves`,
147713
- { cause: err }
147717
+ err
147714
147718
  );
147715
147719
  }
147716
147720
  let bundleId;
@@ -147779,7 +147783,7 @@ async function queryFullHierarchyTree(registry2, device, target) {
147779
147783
  resolved = await resolveNativeTargetApp(nativeApi, void 0);
147780
147784
  } catch (err) {
147781
147785
  const timedOut2 = getFailureSignal(err)?.error_code === FAILURE_CODES.NATIVE_DEVTOOLS_RPC_TIMEOUT;
147782
- if (!timedOut2 || !target) throw err;
147786
+ if (!timedOut2 || !target) throw await explainTargetingFailure(err, nativeApi, device, target);
147783
147787
  if (!isInjectableBundleId(target.bundleId)) {
147784
147788
  throw new FailureError(
147785
147789
  systemAppFlowTargetRefusal(target.bundleId),
@@ -147792,13 +147796,15 @@ async function queryFullHierarchyTree(registry2, device, target) {
147792
147796
  err instanceof Error ? { cause: err } : void 0
147793
147797
  );
147794
147798
  }
147795
- if (!nativeApi.listConnectedBundleIds().includes(target.bundleId)) throw err;
147799
+ if (!nativeApi.listConnectedBundleIds().includes(target.bundleId)) {
147800
+ throw await explainTargetingFailure(err, nativeApi, device, target);
147801
+ }
147796
147802
  let hintState;
147797
147803
  try {
147798
147804
  hintState = await nativeApi.getAppState(target.bundleId);
147799
147805
  } catch (probeErr) {
147800
147806
  if (getFailureSignal(probeErr)?.error_code === FAILURE_CODES.NATIVE_DEVTOOLS_RPC_TIMEOUT) {
147801
- throw err;
147807
+ throw await explainTargetingFailure(err, nativeApi, device, target);
147802
147808
  }
147803
147809
  throw probeErr;
147804
147810
  }
@@ -147817,13 +147823,18 @@ async function queryFullHierarchyTree(registry2, device, target) {
147817
147823
  resolved = { bundleId: target.bundleId };
147818
147824
  }
147819
147825
  bundleId = resolved.bundleId;
147826
+ if (!nativeApi.listConnectedBundleIds().includes(bundleId)) {
147827
+ throw new Error(
147828
+ `${bundleId} answered the target probe and then dropped its native-devtools connection before the view hierarchy could be read. It was instrumented, so a retry may ride this out; if the connection does not come back, relaunch with restart-app (or a flow \`launch\` step) \u2014 launch-app would only foreground the process that just lost it.`
147829
+ );
147830
+ }
147820
147831
  }
147821
147832
  const rawResult = await nativeApi.queryViewHierarchy(
147822
147833
  bundleId,
147823
147834
  "ViewHierarchy.getFullHierarchy",
147824
147835
  {
147825
147836
  fields: FULL_HIERARCHY_FIELDS,
147826
- maxDepth: 40
147837
+ maxDepth: FLOW_TREE_MAX_DEPTH
147827
147838
  }
147828
147839
  );
147829
147840
  if (rawResult.error) {
@@ -147840,6 +147851,86 @@ async function queryFullHierarchyTree(registry2, device, target) {
147840
147851
  function errMsg2(err) {
147841
147852
  return err instanceof Error ? err.message : String(err);
147842
147853
  }
147854
+ async function explainTargetingFailure(err, nativeApi, device, launched) {
147855
+ const failureCode = getFailureSignal(err)?.error_code;
147856
+ if (failureCode === FAILURE_CODES.NATIVE_TARGET_MULTIPLE_APPS_AMBIGUOUS) {
147857
+ const terminate = await terminateCommand(device);
147858
+ const clearOthers = terminate ? `; clear the others with \`${terminate}\` (argent has no terminate tool, and restart-app would just bring that app back to the front).` : `.`;
147859
+ return wrapPreservingFailure(
147860
+ // Short header: the embedded diagnostic already says the set is
147861
+ // ambiguous, and the per-app entries need those 90 characters.
147862
+ `could not target an app to read the view hierarchy from:
147863
+ ${cappedAppDiagnostic(withoutExplicitBundleIdAdvice(errMsg2(err)))}
147864
+ Flow selectors auto-target and cannot name a bundleId. Foreground the intended app with launch-app (it does not terminate), then retry${clearOthers}`,
147865
+ err
147866
+ );
147867
+ }
147868
+ if (failureCode === FAILURE_CODES.NATIVE_TARGET_SINGLE_APP_NOT_FOREGROUND) {
147869
+ return wrapPreservingFailure(
147870
+ `the only native-devtools-connected app is not foreground, so it cannot be auto-targeted:
147871
+ ${withoutExplicitBundleIdAdvice(errMsg2(err))}
147872
+ Flow selector steps auto-target and cannot provide a bundleId. Bring that app to the foreground with launch-app (it does not terminate \u2014 the app is already instrumented, just not frontmost), then retry.`,
147873
+ err
147874
+ );
147875
+ }
147876
+ const stillConnected = nativeApi.listConnectedBundleIds();
147877
+ if (stillConnected.length > 0) {
147878
+ const launchedGone = launched !== void 0 && !stillConnected.includes(launched.bundleId);
147879
+ const terminate = stillConnected.length > (launchedGone ? 0 : 1) ? await terminateCommand(device) : void 0;
147880
+ const clearOthers = terminate ? ` To clear the others use \`${terminate}\` \u2014 argent exposes no terminate tool, and restart-app would bring the app you cleared back to the front instead.` : ``;
147881
+ return wrapPreservingFailure(
147882
+ `could not read the state of the native-devtools-connected apps, so none could be auto-targeted (${firstClause(err)}). Connected: ${cappedList(stillConnected)}. ` + (launchedGone ? `${launched.bundleId} \u2014 the app this flow launched \u2014 is NOT among them, so relaunch it with restart-app (or a flow \`launch\` step); launch-app does not terminate, so it would only foreground the same uninstrumented process.` : `They are instrumented \u2014 do not relaunch. A suspended app stops answering: foreground the app the flow drives with launch-app (it does not terminate), then retry.`) + clearOthers,
147883
+ err
147884
+ );
147885
+ }
147886
+ return wrapPreservingFailure(
147887
+ `no app is connected to native devtools, so flow selectors have no instrumented process to read the view hierarchy from (${firstClause(err)}). Relaunch with restart-app (or a flow \`launch\` step): launch-app does not terminate, so on an app already running from Metro/Expo, Xcode, or its icon it only foregrounds that uninstrumented process. Argent treats an Apple system app (com.apple.*) as non-injectable \u2014 the native-devtools feature tools refuse it too \u2014 so if one never connects, drive it with raw point taps and tool: await-ui-element steps.`,
147888
+ err
147889
+ );
147890
+ }
147891
+ async function terminateCommand(device) {
147892
+ try {
147893
+ const { prefix } = await simctlTargetForUdid(device.id);
147894
+ return `xcrun ${prefix.join(" ")} terminate <udid> <bundleId>`;
147895
+ } catch {
147896
+ return void 0;
147897
+ }
147898
+ }
147899
+ function firstClause(err) {
147900
+ const firstLine2 = errMsg2(err).split("\n", 1)[0];
147901
+ const sentenceEnd = /\.(?=\s|$)/.exec(firstLine2);
147902
+ return sentenceEnd === null ? firstLine2 : firstLine2.slice(0, sentenceEnd.index + 1);
147903
+ }
147904
+ var MAX_LISTED_APPS = 2;
147905
+ function cappedList(bundleIds) {
147906
+ if (bundleIds.length <= MAX_LISTED_APPS) return bundleIds.join(", ");
147907
+ const dropped = bundleIds.length - MAX_LISTED_APPS;
147908
+ return `${bundleIds.slice(0, MAX_LISTED_APPS).join(", ")} (+${dropped} more)`;
147909
+ }
147910
+ function cappedAppDiagnostic(message) {
147911
+ const lines = message.split("\n");
147912
+ const isEntry = (line) => line.startsWith("- ");
147913
+ const firstEntry = lines.findIndex(isEntry);
147914
+ if (firstEntry === -1) return message;
147915
+ const entries = lines.filter(isEntry);
147916
+ if (entries.length <= MAX_LISTED_APPS) return message;
147917
+ const kept = entries.slice(0, MAX_LISTED_APPS);
147918
+ const dropped = entries.length - MAX_LISTED_APPS;
147919
+ return [
147920
+ ...lines.slice(0, firstEntry),
147921
+ ...kept,
147922
+ `- (+${dropped} more connected app${dropped === 1 ? "" : "s"})`,
147923
+ ...lines.slice(firstEntry + entries.length).filter((line) => !isEntry(line))
147924
+ ].join("\n");
147925
+ }
147926
+ function withoutExplicitBundleIdAdvice(message) {
147927
+ return message.replace(/\nProvide bundleId explicitly[^\n]*$/, "");
147928
+ }
147929
+ function wrapPreservingFailure(message, err) {
147930
+ const cause = err instanceof Error ? err : new Error(String(err));
147931
+ const signal = getFailureSignal(err);
147932
+ return signal ? new FailureError(message, signal, { cause }) : new Error(message, { cause });
147933
+ }
147843
147934
  function projectIosDeviceNode(node) {
147844
147935
  const onScreen = node.frame.width > 0 && node.frame.height > 0;
147845
147936
  return {
@@ -149496,7 +149587,7 @@ function unmetWaitWarningFor(cause) {
149496
149587
  }
149497
149588
  function indeterminateReasonCaveat(udid) {
149498
149589
  if (platformOf(udid) !== "ios") return "";
149499
- return ". That reason may tell you to pass `bundleId` \u2014 it is quoted from the shared native-target error, and it does not apply here: the probe predicts an `await:`/`assert:` directive, and no directive takes a bundleId, so neither this probe nor the runner accepts one (the `bundleId` on this step reached the live wait only). What the runner's iOS tree needs is an app with argent's instrumentation loaded \u2014 relaunch it with `launch-app` or a flow `launch:` step. An app that cannot load it at all, such as a `com.apple.*` system app, can never be probed or converted: keep the check as a raw `tool:` step";
149590
+ return ". One thing that reason cannot see is this step: the probe predicts an `await:`/`assert:` directive, and no directive takes a bundleId, so neither this probe nor the runner accepts one (the `bundleId` on this step reached the live wait only)";
149500
149591
  }
149501
149592
  var CANCELLED_PROBE_WARNING = "recorded, but the re-probe against the tree the RUNNER reads was cancelled before it answered. The step itself ran and is written to the flow; only the verdict is missing, so whether it would convert to `await:`/`assert:` is UNKNOWN, not known-bad \u2014 record the wait again, uncancelled, before trusting the conversion";
149502
149593
  var PROBE_MAX_TREE_READ_MS = 2500;
@@ -149570,6 +149661,11 @@ async function probeAgainstRunnerTree(registry2, ctx, args) {
149570
149661
  (condition === "text" ? textTieClause(args.udid) : "") + " " + SPELLING_CLAUSE + ` ${treeDivergenceFor(args.udid, condition)} ${runnerSideReadClause(args.udid, condition)}`
149571
149662
  };
149572
149663
  }
149664
+ function roleOnlySelectorWarning(selector) {
149665
+ if (selector.role === void 0 || selector.identifier !== void 0) return void 0;
149666
+ if (selector.text !== void 0 || selector.textMatches !== void 0) return void 0;
149667
+ return `selector ${describeSelector(selector)} matches by role alone (the tapped element has no id or visible text) \u2014 replay takes whichever element of that role ranks first, so re-record against a labelled element if that is not reliably this one`;
149668
+ }
149573
149669
  async function captureTapSelector(registry2, session, udid, point) {
149574
149670
  try {
149575
149671
  const device = resolveDevice(udid);
@@ -149595,7 +149691,11 @@ async function captureTapSelector(registry2, session, udid, point) {
149595
149691
  warning: `selector ${describeSelector(selector)} resolves to a different element on this screen; kept coordinates (brittle)`
149596
149692
  };
149597
149693
  }
149598
- return { selector, warning: fallbackSourceWarning(source, device.platform) };
149694
+ const warnings = [
149695
+ roleOnlySelectorWarning(selector),
149696
+ fallbackSourceWarning(source, device.platform)
149697
+ ].filter((w) => w !== void 0);
149698
+ return { selector, ...warnings.length > 0 ? { warning: warnings.join("; ") } : {} };
149599
149699
  } catch (err) {
149600
149700
  return {
149601
149701
  warning: `selector capture failed (${err instanceof Error ? err.message : String(err)}); kept coordinates`
@@ -153964,8 +154064,8 @@ var zodSchema66 = external_exports.object({
153964
154064
  device: external_exports.string().optional().describe(
153965
154065
  "Device id to run against (iOS UDID, Android/Vega serial, Chromium id) \u2014 the id list-devices reports. Auto-detected when omitted, but only when exactly one booted device matches (optionally narrowed by `platform`); with several booted the run fails and lists them, so pass this explicitly whenever more than one device is up."
153966
154066
  ),
153967
- platform: external_exports.enum(LAUNCH_PLATFORMS).optional().describe(
153968
- "Restrict auto-detection to this platform when several devices are booted. `chromium` does more than filter: with no `device` it SELECTS the self-boot branch for an e2e flow - the runner boots an Electron instance from the `launch` step's chromium value and tears it down after the run (a single-key `launch: { chromium: \u2026 }` map selects it on its own, without this parameter). When it selects that branch it never falls back to device auto-detection (a fragment, or an e2e launch map with no `chromium` key, still does), and the launch value must be a real Electron app path on the tool-server host: a bare-string `launch:` - what the recorder writes - holds an installed-app bundle id, so passing `chromium` for one fails the whole run with `Electron boot: path does not exist`. Edit the launch to `{ chromium: <app path> }` first."
154067
+ platform: external_exports.enum(SELECTABLE_PLATFORMS).optional().describe(
154068
+ "Restrict auto-detection to this platform when several devices are booted. `ios` selects local simulators only \u2014 pass `ios-remote` to select a remote one. `chromium` does more than filter: with no `device` it SELECTS the self-boot branch for an e2e flow - the runner boots an Electron instance from the `launch` step's chromium value and tears it down after the run (a single-key `launch: { chromium: \u2026 }` map selects it on its own, without this parameter). When it selects that branch it never falls back to device auto-detection (a fragment, or an e2e launch map with no `chromium` key, still does), and the launch value must be a real Electron app path on the tool-server host: a bare-string `launch:` - what the recorder writes - holds an installed-app bundle id, so passing `chromium` for one fails the whole run with `Electron boot: path does not exist`. Edit the launch to `{ chromium: <app path> }` first."
153969
154069
  ),
153970
154070
  updateBaselines: external_exports.boolean().optional().describe(
153971
154071
  "Write/refresh screenshot baselines for `snapshot` steps instead of diffing against them."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.25.1-next.20",
3
+ "version": "0.25.1-next.22",
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",