@swmansion/argent 0.25.1-next.31 → 0.25.1-next.33

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.31";
21860
+ return "0.25.1-next.33";
21861
21861
  }
21862
21862
  return "0.0.0";
21863
21863
  }
@@ -23461,7 +23461,10 @@ async function exportFailureArtifacts(report, outputDir, flowPath, ctx) {
23461
23461
  if (dirName === null) return;
23462
23462
  dir = path14.join(outputDir, dirName);
23463
23463
  }
23464
- const dest = path14.join(dir, `${key}-${role}.png`);
23464
+ const dest = path14.join(
23465
+ dir,
23466
+ `${key}${s.snapshotRemote === true ? "-remote" : ""}-${role}.png`
23467
+ );
23465
23468
  const rel = path14.relative(outputDir, dest);
23466
23469
  if (rel.startsWith("..") || path14.isAbsolute(rel)) continue;
23467
23470
  try {
@@ -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.31";
16713
+ return "0.25.1-next.33";
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.31";
94707
+ return "0.25.1-next.33";
94708
94708
  }
94709
94709
  return "0.0.0";
94710
94710
  }
@@ -96676,8 +96676,8 @@ async function run(args, options) {
96676
96676
  throw new Error(`sim-remote ${args.join(" ")} failed: ${suffix}`, { cause: err });
96677
96677
  }
96678
96678
  }
96679
- async function simctlListDevices() {
96680
- const { stdout } = await run(["simctl", "list", "devices", "--json"]);
96679
+ async function simctlListDevices(options) {
96680
+ const { stdout } = await run(["simctl", "list", "devices", "--json"], options);
96681
96681
  try {
96682
96682
  return JSON.parse(stdout);
96683
96683
  } catch (err) {
@@ -96688,12 +96688,12 @@ async function simctlListDevices() {
96688
96688
  }
96689
96689
  }
96690
96690
  var remoteRuntimeKindCache = /* @__PURE__ */ new Map();
96691
- async function isRemoteTvOsSimulator(udid) {
96691
+ async function isRemoteTvOsSimulator(udid, options) {
96692
96692
  const id = stripRemotePrefix(udid);
96693
96693
  const cached3 = remoteRuntimeKindCache.get(id);
96694
96694
  if (cached3) return cached3 === "tv";
96695
96695
  try {
96696
- const { devices } = await simctlListDevices();
96696
+ const { devices } = await simctlListDevices(options);
96697
96697
  for (const [runtime, entries] of Object.entries(devices)) {
96698
96698
  if (!entries.some((d) => d.udid === id)) continue;
96699
96699
  const kind = runtime.includes("tvOS") ? "tv" : "mobile";
@@ -96735,6 +96735,9 @@ async function simctlOpenUrl(udid, url2) {
96735
96735
  async function simctlPrivacy(udid, action, service, bundleId) {
96736
96736
  await run(["simctl", "privacy", stripRemotePrefix(udid), action, service, bundleId]);
96737
96737
  }
96738
+ async function simctlStatusBar(udid, args, options) {
96739
+ await run(["simctl", "status_bar", stripRemotePrefix(udid), ...args], options);
96740
+ }
96738
96741
  async function simctlPbcopy(udid, text) {
96739
96742
  await run(["simctl", "pbcopy", stripRemotePrefix(udid)], { stdin: text });
96740
96743
  }
@@ -125765,7 +125768,7 @@ init_adb();
125765
125768
 
125766
125769
  // ../tool-server/src/utils/poll-describe-tree.ts
125767
125770
  async function pollDescribeTree(args) {
125768
- const { fetchTree: fetchTree2, timeoutMs, pollIntervalMs, signal, onSample } = args;
125771
+ const { fetchTree, timeoutMs, pollIntervalMs, signal, onSample } = args;
125769
125772
  const start2 = Date.now();
125770
125773
  const deadline = start2 + timeoutMs;
125771
125774
  let polls = 0;
@@ -125784,7 +125787,7 @@ async function pollDescribeTree(args) {
125784
125787
  for (; ; ) {
125785
125788
  if (signal?.aborted) return outcome(void 0, true);
125786
125789
  const remaining = Math.max(0, deadline - Date.now());
125787
- const settled = await settleWithin(fetchTree2(), remaining, signal);
125790
+ const settled = await settleWithin(fetchTree(), remaining, signal);
125788
125791
  polls += 1;
125789
125792
  if (settled.type === "aborted") return outcome(void 0, true);
125790
125793
  lastAttemptSettled = settled.type !== "timeout";
@@ -131945,23 +131948,6 @@ function deriveSelector(node) {
131945
131948
  if (node.role && !GENERIC_ROLES.has(node.role.toLowerCase())) return { role: node.role };
131946
131949
  return null;
131947
131950
  }
131948
- async function fetchTree(registry2, device, opts = {}) {
131949
- if (device.platform === "ios" || device.platform === "ios-remote") {
131950
- return describeIos(registry2, device, { bundleId: opts.bundleId });
131951
- }
131952
- if (device.platform === "android") {
131953
- return describeAndroid(registry2, device.id);
131954
- }
131955
- if (device.platform === "chromium") {
131956
- const ref = chromiumCdpRef(device);
131957
- const api = await registry2.resolveService(ref.urn, ref.options);
131958
- return describeChromium(api);
131959
- }
131960
- if (device.platform === "vega") {
131961
- return describeVega(device.id);
131962
- }
131963
- throw new Error(`ui-tree matching is not supported on platform "${device.platform}"`);
131964
- }
131965
131951
 
131966
131952
  // ../tool-server/src/tools/await-ui-element/index.ts
131967
131953
  var AWAIT_UI_ELEMENT_TOOL_ID = "await-ui-element";
@@ -132076,7 +132062,7 @@ function timeoutNote(params, lastTree, fetchError, lastData) {
132076
132062
  return appendDiagnostics(base, lastData);
132077
132063
  }
132078
132064
  function createAwaitUiElementTool(registry2) {
132079
- async function fetchTree2(device, params, services, isTvOs, androidIsTv) {
132065
+ async function fetchTree(device, params, services, isTvOs, androidIsTv) {
132080
132066
  if (device.platform === "ios" || device.platform === "ios-remote") {
132081
132067
  if (device.kind === "device") {
132082
132068
  return describeIosDevice(registry2, device);
@@ -132158,7 +132144,7 @@ tap/navigation to wait for the next screen, or before tapping an element that ap
132158
132144
  let everMatched = false;
132159
132145
  let lastTrustedReadAt;
132160
132146
  const poll = await pollDescribeTree({
132161
- fetchTree: () => fetchTree2(device, params, services, isTvOs, androidIsTv),
132147
+ fetchTree: () => fetchTree(device, params, services, isTvOs, androidIsTv),
132162
132148
  timeoutMs,
132163
132149
  pollIntervalMs,
132164
132150
  signal,
@@ -134831,7 +134817,7 @@ function treeSignature(root) {
134831
134817
  return parts2.join("\n");
134832
134818
  }
134833
134819
  function createAwaitScreenIdleTool(registry2) {
134834
- function fetchTree2(device, services, isTvOs, androidIsTv) {
134820
+ function fetchTree(device, services, isTvOs, androidIsTv) {
134835
134821
  if (device.platform === "ios" || device.platform === "ios-remote") {
134836
134822
  if (device.kind === "device") {
134837
134823
  return describeIosDevice(registry2, device);
@@ -134879,7 +134865,7 @@ still before the timeout. Use after a launch/navigation to wait for the UI to re
134879
134865
  let stableSignature;
134880
134866
  let stableSince = 0;
134881
134867
  const poll = await pollDescribeTree({
134882
- fetchTree: () => fetchTree2(device, services, isTvOs, androidIsTv),
134868
+ fetchTree: () => fetchTree(device, services, isTvOs, androidIsTv),
134883
134869
  timeoutMs: params.timeoutMs ?? DEFAULT_TIMEOUT_MS4,
134884
134870
  pollIntervalMs: params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2,
134885
134871
  signal: ctx?.signal,
@@ -148174,9 +148160,7 @@ async function queryVegaTree(device) {
148174
148160
 
148175
148161
  // ../tool-server/src/tools/flows/flow-tree.ts
148176
148162
  async function fetchFlowTree(registry2, device, target) {
148177
- const source = FLOW_TREE_SOURCES[device.platform];
148178
- if (!source) return fetchTree(registry2, device);
148179
- return source(registry2, device, target);
148163
+ return FLOW_TREE_SOURCES[device.platform](registry2, device, target);
148180
148164
  }
148181
148165
  var FLOW_TREE_SOURCES = {
148182
148166
  // Simulator iOS uses the injected hierarchy and an optional target.
@@ -148192,9 +148176,6 @@ var FLOW_TREE_SOURCES = {
148192
148176
  "chromium": (registry2, device) => queryChromiumTree(registry2, device),
148193
148177
  "vega": (_registry, device) => queryVegaTree(device)
148194
148178
  };
148195
- function supportsFlowTree(platform) {
148196
- return FLOW_TREE_SOURCES[platform] !== void 0;
148197
- }
148198
148179
 
148199
148180
  // ../tool-server/src/tools/flows/flow-pixels.ts
148200
148181
  var fs53 = __toESM(require("node:fs/promises"));
@@ -148225,10 +148206,14 @@ var FIRST_CAPTURE_COMPLETION_MARGIN_MS = 500;
148225
148206
  var FIRST_PIXEL_CAPTURE_TIMEOUT_MS = FIRST_FRAME_WAIT_MS + FIRST_CAPTURE_COMPLETION_MARGIN_MS;
148226
148207
  var PIXEL_CAPTURE_TIMEOUT_MS = 2e3;
148227
148208
  var IOS_DEVICE_PIXEL_CAPTURE_TIMEOUT_MS = 4e3;
148209
+ var REMOTE_PIXEL_CAPTURE_TIMEOUT_MS = 4e3;
148228
148210
  function pixelCaptureTimeoutMs(device, firstCapture) {
148229
148211
  if (isIosPhysicalDevice(device)) {
148230
148212
  return IOS_DEVICE_PIXEL_CAPTURE_TIMEOUT_MS;
148231
148213
  }
148214
+ if (device.platform === "ios-remote" && !firstCapture) {
148215
+ return REMOTE_PIXEL_CAPTURE_TIMEOUT_MS;
148216
+ }
148232
148217
  const warmFromTheStart = device.platform === "chromium" || device.platform === "vega";
148233
148218
  return firstCapture && !warmFromTheStart ? FIRST_PIXEL_CAPTURE_TIMEOUT_MS : PIXEL_CAPTURE_TIMEOUT_MS;
148234
148219
  }
@@ -148638,12 +148623,10 @@ async function runDirective(env, step) {
148638
148623
  }
148639
148624
  async function settleForGesture(env) {
148640
148625
  let warning;
148641
- if (supportsFlowTree(env.device.platform)) {
148642
- try {
148643
- await settleTree(env, { skipProvenOutage: true });
148644
- } catch (err) {
148645
- warning = unsettledGestureWarning(err);
148646
- }
148626
+ try {
148627
+ await settleTree(env, { skipProvenOutage: true });
148628
+ } catch (err) {
148629
+ warning = unsettledGestureWarning(err);
148647
148630
  }
148648
148631
  if (env.signal?.aborted) return { aborted: true };
148649
148632
  return warning !== void 0 ? { warning } : {};
@@ -149526,9 +149509,6 @@ function fallbackSourceWarning(source, platform) {
149526
149509
  function platformOf(udid) {
149527
149510
  return typeof udid === "string" ? authoringPlatform(resolveDevice(udid).platform) : void 0;
149528
149511
  }
149529
- function hasRunnerTree(udid) {
149530
- return typeof udid === "string" && supportsFlowTree(resolveDevice(udid).platform);
149531
- }
149532
149512
  var UNSUPPORTED_PLATFORM = {
149533
149513
  divergence: "The recorder and the runner read different projections of the screen.",
149534
149514
  read: "No read-only tool is known to report the runner's projection on this platform \u2014 keep the step raw"
@@ -149602,7 +149582,6 @@ function unmetWaitWarningFor(cause) {
149602
149582
  }
149603
149583
  function indeterminateReasonCaveat(udid) {
149604
149584
  if (platformOf(udid) !== "ios") return "";
149605
- if (!hasRunnerTree(udid)) return "";
149606
149585
  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)";
149607
149586
  }
149608
149587
  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";
@@ -149667,9 +149646,8 @@ async function probeAgainstRunnerTree(registry2, ctx, args) {
149667
149646
  // The reason is quoted whole rather than through `cappedReason`; see
149668
149647
  // {@link MAX_PROBE_REASON_CHARS}.
149669
149648
  warning: `this check could not be re-verified against the tree the RUNNER reads (${outcome.reason ?? "no reason given"}), so it passed against the tree \`${AWAIT_UI_ELEMENT_TOOL_ID}\` reads and nothing else. Whether it would convert to \`await:\`/\`assert:\` is UNKNOWN, not known-bad \u2014 ` + // A timeout and an outage need different next moves: "once that tree
149670
- // source is back" is nonsense for a source that never left, and for one
149671
- // that never existed.
149672
- (timedOut2 ? `re-record this step when the device is quieter, or settle the conversion directly by putting the directive in a flow and running \`flow-execute\`, which has no such ceiling` : (hasRunnerTree(args.udid) ? `re-probe once that tree source is back before trusting the conversion` : `this platform's runner has no tree to probe, so the conversion stays unverified`) + indeterminateReasonCaveat(args.udid))
149649
+ // source is back" is nonsense for a source that never left.
149650
+ (timedOut2 ? `re-record this step when the device is quieter, or settle the conversion directly by putting the directive in a flow and running \`flow-execute\`, which has no such ceiling` : `re-probe once that tree source is back before trusting the conversion` + indeterminateReasonCaveat(args.udid))
149673
149651
  };
149674
149652
  }
149675
149653
  return {
@@ -153885,7 +153863,7 @@ async function runSnapshot(env, opts) {
153885
153863
  });
153886
153864
  const { w, h } = await pngDimensions(shot.image.hostPath);
153887
153865
  const cropSuffix = opts.cropOn === void 0 ? "" : `-crop-${(0, import_node_crypto12.createHash)("sha256").update(cropIdentity(opts.cropOn)).digest("hex").slice(0, 8)}`;
153888
- const snapshotKey = `${opts.name}__${env.device.platform}-${w}x${h}${cropSuffix}`;
153866
+ const snapshotKey = `${opts.name}__${authoringPlatform(env.device.platform)}-${w}x${h}${cropSuffix}`;
153889
153867
  const key2 = `${snapshotKey}.png`;
153890
153868
  const dir = baselineDir(opts.flowsDir, opts.flowName);
153891
153869
  const baselinePath = path44.join(dir, key2);
@@ -153936,9 +153914,10 @@ async function runSnapshot(env, opts) {
153936
153914
  kind: "screenshot",
153937
153915
  mimeType: "image/png"
153938
153916
  });
153917
+ const source = env.device.platform === "ios-remote" ? " from a remote simulator" : "";
153939
153918
  return {
153940
153919
  status: "pass",
153941
- reason: exists2 ? `baseline updated (${key2})` : `baseline written (${key2})`,
153920
+ reason: exists2 ? `baseline updated${source} (${key2})` : `baseline written${source} (${key2})`,
153942
153921
  snapshotKey,
153943
153922
  artifacts: { baseline }
153944
153923
  };
@@ -154024,30 +154003,41 @@ init_adb();
154024
154003
  init_device_info();
154025
154004
  var execFileAsync27 = (0, import_node_util25.promisify)(import_node_child_process35.execFile);
154026
154005
  var DEMO_BROADCAST = "am broadcast -a com.android.systemui.demo";
154027
- async function pinStatusBar(device) {
154006
+ var IOS_STATUS_BAR_OVERRIDE = [
154007
+ "override",
154008
+ "--time",
154009
+ "9:37",
154010
+ "--batteryState",
154011
+ "charged",
154012
+ "--batteryLevel",
154013
+ "100",
154014
+ "--wifiBars",
154015
+ "3",
154016
+ "--cellularBars",
154017
+ "4"
154018
+ ];
154019
+ var REMOTE_STATUS_BAR_TIMEOUT_MS = 5e3;
154020
+ function isRemoteTvOs(device) {
154021
+ return isRemoteTvOsSimulator(device.id, { timeoutMs: REMOTE_STATUS_BAR_TIMEOUT_MS });
154022
+ }
154023
+ async function pinStatusBar(device, signal) {
154024
+ if (signal?.aborted) return false;
154028
154025
  if (isIosPhysicalDevice(device)) return false;
154029
154026
  try {
154030
154027
  if (device.platform === "ios") {
154031
154028
  await execFileAsync27(
154032
154029
  "xcrun",
154033
- await simctlArgsForUdid(device.id, [
154034
- "status_bar",
154035
- device.id,
154036
- "override",
154037
- "--time",
154038
- "9:37",
154039
- "--batteryState",
154040
- "charged",
154041
- "--batteryLevel",
154042
- "100",
154043
- "--wifiBars",
154044
- "3",
154045
- "--cellularBars",
154046
- "4"
154047
- ])
154030
+ await simctlArgsForUdid(device.id, ["status_bar", device.id, ...IOS_STATUS_BAR_OVERRIDE])
154048
154031
  );
154049
154032
  return true;
154050
154033
  }
154034
+ if (device.platform === "ios-remote") {
154035
+ if (await isRemoteTvOs(device)) return false;
154036
+ await simctlStatusBar(device.id, IOS_STATUS_BAR_OVERRIDE, {
154037
+ timeoutMs: REMOTE_STATUS_BAR_TIMEOUT_MS
154038
+ });
154039
+ return true;
154040
+ }
154051
154041
  if (device.platform === "android") {
154052
154042
  await adbShell(device.id, "settings put global sysui_demo_allowed 1");
154053
154043
  await adbShell(device.id, `${DEMO_BROADCAST} -e command enter`);
@@ -154065,7 +154055,7 @@ async function pinStatusBar(device) {
154065
154055
  return false;
154066
154056
  } catch {
154067
154057
  const restored = await restoreStatusBar(device);
154068
- return device.platform === "android" && !restored;
154058
+ return (device.platform === "android" || device.platform === "ios-remote") && !restored;
154069
154059
  }
154070
154060
  }
154071
154061
  async function restoreStatusBar(device) {
@@ -154075,6 +154065,10 @@ async function restoreStatusBar(device) {
154075
154065
  "xcrun",
154076
154066
  await simctlArgsForUdid(device.id, ["status_bar", device.id, "clear"])
154077
154067
  );
154068
+ } else if (device.platform === "ios-remote") {
154069
+ if (!await isRemoteTvOs(device)) {
154070
+ await simctlStatusBar(device.id, ["clear"], { timeoutMs: REMOTE_STATUS_BAR_TIMEOUT_MS });
154071
+ }
154078
154072
  } else if (device.platform === "android") {
154079
154073
  try {
154080
154074
  await adbShell(device.id, `${DEMO_BROADCAST} -e command exit`);
@@ -154583,7 +154577,7 @@ Returns a per-step report: the first failure stops the run and the rest report a
154583
154577
  viaUpload
154584
154578
  );
154585
154579
  const device = resolved.device;
154586
- const statusBarPinned = device !== null && await pinStatusBar(device);
154580
+ const statusBarPinned = device !== null && await pinStatusBar(device, signal);
154587
154581
  if (device?.platform === "chromium") await frontChromiumPage(registry2, device);
154588
154582
  const state3 = {
154589
154583
  registry: registry2,
@@ -155109,6 +155103,7 @@ async function execLeafStep(state3, step, index, scope) {
155109
155103
  status: r.status,
155110
155104
  reason: r.reason,
155111
155105
  snapshotKey: r.snapshotKey,
155106
+ ...r.snapshotKey !== void 0 && state3.device?.platform === "ios-remote" ? { snapshotRemote: true } : {},
155112
155107
  artifacts: r.artifacts
155113
155108
  };
155114
155109
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.25.1-next.31",
3
+ "version": "0.25.1-next.33",
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",
@@ -255,7 +255,7 @@ A screenshot is human evidence. A `snapshot:` is executable visual verification.
255
255
 
256
256
  Do not use a snapshot as the only proof of navigation, persistence, data, accessibility state, logs, or network behavior. Avoid unstable timestamps, live data, ads, animation, and device drift. First establish deterministic state, identity, and readiness.
257
257
 
258
- Baselines live under `.argent/flows/__baselines__/<flow>/` and are keyed by platform and full-capture geometry; `cropOn` also contributes its selector. Seed from a known-good state with `--update-baselines`. Inspect every baseline and require user review. Do not commit it yourself. Baseline creation or update is not a test pass. Never update a baseline only to make a diff pass. The default `maxMismatch` is 0.5 percent.
258
+ Baselines live under `.argent/flows/__baselines__/<flow>/` and are keyed by platform and full-capture geometry; `cropOn` also contributes its selector. A run on a remote simulator uses `ios` in the key, so it uses the same baseline as a local iOS run with the same capture geometry. Seed from a known-good state with `--update-baselines`. A run on a remote simulator with `--update-baselines` rewrites the same baseline file that a local run uses, and its step reason says that a remote simulator wrote it. Inspect every baseline and require user review. Do not commit it yourself. Baseline creation or update is not a test pass. Never update a baseline only to make a diff pass. The default `maxMismatch` is 0.5 percent.
259
259
 
260
260
  Pin `--platform` and `--device` for iOS, Android, or Vega. For Chromium the device class is the window's own pixel size, which the app sets and no launch argument changes: pass `--platform chromium` and omit `--device` so the runner boots the declared app path instead of attaching to a running window of another size. A window sized from host or session state produces a key CI cannot reproduce, and the step fails for a missing baseline. The runner pins mobile status bars during visual runs. `--output <dir>` writes failed baseline, current, and diff images under `<dir>/<flow>/` for CI artifact upload.
261
261