@swmansion/argent 0.31.2-next.2 → 0.31.2-next.4

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.
Files changed (28) hide show
  1. package/bin/argent-android-devtools-0.3.0.apk +0 -0
  2. package/bin/darwin/ax-service +0 -0
  3. package/bin/darwin/tvos-ax-service +0 -0
  4. package/bin/darwin/tvos-hid-daemon +0 -0
  5. package/bin/tcp/ax-service +0 -0
  6. package/dist/cli-cmds.mjs +94 -24
  7. package/dist/client.js +71 -14
  8. package/dist/installer.mjs +8 -1
  9. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentExceptionGuard.h +3 -0
  10. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentExceptionGuard.m +11 -3
  11. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Gestures.swift +27 -19
  12. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Quiescence.swift +95 -0
  13. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+TextEntry.swift +2 -2
  14. package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/RunnerProtocol.swift +5 -5
  15. package/dist/mcp-server.mjs +75 -17
  16. package/dist/tool-args.generated.d.ts +16 -8
  17. package/dist/tool-server.cjs +202 -86
  18. package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
  19. package/dylibs/libKeyboardPatch.dylib +0 -0
  20. package/dylibs/libNativeDevtoolsIos.dylib +0 -0
  21. package/dylibs/tcp/libArgentInjectionBootstrap.dylib +0 -0
  22. package/dylibs/tcp/libKeyboardPatch.dylib +0 -0
  23. package/dylibs/tcp/libNativeDevtoolsIos.dylib +0 -0
  24. package/dylibs/tvos/libArgentInjectionBootstrap.dylib +0 -0
  25. package/dylibs/tvos/libKeyboardPatch.dylib +0 -0
  26. package/dylibs/tvos/libNativeDevtoolsIos.dylib +0 -0
  27. package/package.json +1 -1
  28. package/skills/argent-create-e2e/references/live-authoring.md +3 -3
Binary file
Binary file
Binary file
Binary file
package/dist/cli-cmds.mjs CHANGED
@@ -10310,6 +10310,36 @@ var TOOL_ID_ALIASES = Object.freeze({
10310
10310
  function canonicalToolId(id) {
10311
10311
  return Object.hasOwn(TOOL_ID_ALIASES, id) ? TOOL_ID_ALIASES[id] : id;
10312
10312
  }
10313
+ var TOOL_PARAM_ALIASES = Object.freeze({
10314
+ "e2e-execute": Object.freeze({ flow_path: "e2e_test_path", flow_file: "e2e_test_file" }),
10315
+ "e2e-read-prerequisite": Object.freeze({
10316
+ flow_path: "e2e_test_path",
10317
+ flow_file: "e2e_test_file"
10318
+ })
10319
+ });
10320
+ var ToolParamAliasError = class extends Error {
10321
+ constructor(former, current) {
10322
+ super(`Pass ${current} or its former name ${former}, not both.`);
10323
+ this.name = "ToolParamAliasError";
10324
+ }
10325
+ };
10326
+ function toolParamAliases(tool) {
10327
+ const id = canonicalToolId(tool);
10328
+ return Object.hasOwn(TOOL_PARAM_ALIASES, id) ? TOOL_PARAM_ALIASES[id] : {};
10329
+ }
10330
+ function canonicalToolArgs(tool, args) {
10331
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
10332
+ const record2 = args;
10333
+ let out;
10334
+ for (const [former, current] of Object.entries(toolParamAliases(tool))) {
10335
+ if (record2[former] === void 0) continue;
10336
+ if (record2[current] !== void 0) throw new ToolParamAliasError(former, current);
10337
+ out ??= { ...record2 };
10338
+ out[current] = out[former];
10339
+ delete out[former];
10340
+ }
10341
+ return out ?? args;
10342
+ }
10313
10343
 
10314
10344
  // ../registry/src/flow-file-refs.ts
10315
10345
  async function canonicalFlowPath(p) {
@@ -10379,7 +10409,13 @@ async function classifyOnDiskSpelling(dir, base, addressable = FLOW_FILE_NAME_PA
10379
10409
  }
10380
10410
  function nestedFlowTarget(args) {
10381
10411
  if (typeof args !== "object" || args === null || Array.isArray(args)) return void 0;
10382
- const { name, project_root: projectRoot, flow_path: flowPath } = args;
10412
+ let canonical;
10413
+ try {
10414
+ canonical = canonicalToolArgs("e2e-execute", args);
10415
+ } catch {
10416
+ return void 0;
10417
+ }
10418
+ const { name, project_root: projectRoot, e2e_test_path: flowPath } = canonical;
10383
10419
  if (flowPath === void 0) {
10384
10420
  if (typeof name !== "string" || !FLOW_NAME_PATTERN.test(name)) return void 0;
10385
10421
  if (!isResolvedAbsolute(projectRoot)) return void 0;
@@ -10388,7 +10424,7 @@ function nestedFlowTarget(args) {
10388
10424
  }
10389
10425
  if (name !== void 0 || !isResolvedAbsolute(flowPath)) return void 0;
10390
10426
  if (!FLOW_FILE_NAME_PATTERN.test(path8.basename(flowPath))) return void 0;
10391
- return { kind: "flow_path", path: flowPath };
10427
+ return { kind: "e2e_test_path", path: flowPath };
10392
10428
  }
10393
10429
  function isResolvedAbsolute(value) {
10394
10430
  return typeof value === "string" && path8.isAbsolute(value) && !value.split(/[\\/]+/).includes("..");
@@ -31111,12 +31147,18 @@ function isResolvedAbsolute2(value) {
31111
31147
  return typeof value === "string" && path11.isAbsolute(value) && !value.split(/[\\/]+/).includes("..");
31112
31148
  }
31113
31149
  function namesValidFlow(args) {
31114
- const { project_root, flow_path, name } = args;
31150
+ let canonical;
31151
+ try {
31152
+ canonical = canonicalToolArgs("e2e-execute", args);
31153
+ } catch {
31154
+ return false;
31155
+ }
31156
+ const { project_root, e2e_test_path: testPath, name } = canonical;
31115
31157
  if (!isResolvedAbsolute2(project_root)) return false;
31116
31158
  if (name === void 0) {
31117
- return isResolvedAbsolute2(flow_path) && FLOW_FILE_NAME_PATTERN.test(path11.basename(flow_path));
31159
+ return isResolvedAbsolute2(testPath) && FLOW_FILE_NAME_PATTERN.test(path11.basename(testPath));
31118
31160
  }
31119
- return flow_path === void 0 && typeof name === "string" && FLOW_NAME_PATTERN.test(name);
31161
+ return testPath === void 0 && typeof name === "string" && FLOW_NAME_PATTERN.test(name);
31120
31162
  }
31121
31163
  function savedFlowProject(file2) {
31122
31164
  if (!path11.isAbsolute(file2)) return null;
@@ -31290,8 +31332,8 @@ function memberCollector(roots, opts) {
31290
31332
  `));
31291
31333
  const emit = (member, sent) => {
31292
31334
  members2.push(member);
31293
- const subject = member.role === "flow" ? member.canonical : member.key;
31294
- if (logging) log2(printable(`[flow-files] ${member.role} ${subject}: ${sent}`));
31335
+ const [label, subject] = member.role === "flow" ? ["e2e test", member.canonical] : [member.role, member.key];
31336
+ if (logging) log2(printable(`[e2e-files] ${label} ${subject}: ${sent}`));
31295
31337
  };
31296
31338
  async function flow(anchorDir, target, onlyPresent) {
31297
31339
  const key = flowMemberKey(anchorDir, target);
@@ -31457,7 +31499,7 @@ function recordedStep(args) {
31457
31499
  if (typeof text2 !== "string") return void 0;
31458
31500
  let parsed;
31459
31501
  try {
31460
- parsed = JSON.parse(text2);
31502
+ parsed = canonicalToolArgs(command, JSON.parse(text2));
31461
31503
  } catch {
31462
31504
  return void 0;
31463
31505
  }
@@ -31480,7 +31522,7 @@ async function collectStepMembers(args, opts, recordingDirFor = e2eDirFor) {
31480
31522
  const self2 = await collector.flow(path11.dirname(recording), path11.basename(recording));
31481
31523
  let runArgs = step.args;
31482
31524
  const named = nestedFlowTarget(runArgs);
31483
- if (named?.kind === "flow_path") {
31525
+ if (named?.kind === "e2e_test_path") {
31484
31526
  const stem = path11.basename(named.path, ".yaml");
31485
31527
  const base = path11.basename(named.path);
31486
31528
  const dir = path11.resolve(path11.dirname(named.path));
@@ -31489,7 +31531,7 @@ async function collectStepMembers(args, opts, recordingDirFor = e2eDirFor) {
31489
31531
  if (dirs.some((d) => path11.resolve(d) === dir)) {
31490
31532
  await collector.flow(path11.dirname(named.path), base);
31491
31533
  runArgs = { ...runArgs, name: stem };
31492
- delete runArgs.flow_path;
31534
+ delete runArgs.e2e_test_path;
31493
31535
  }
31494
31536
  }
31495
31537
  const target = nestedFlowTarget(runArgs);
@@ -31517,7 +31559,21 @@ async function nameToE2ePathForOlderServer(tool, fileInputs, args) {
31517
31559
  const picked = await pickE2eFile(target.paths);
31518
31560
  if (!picked.exists || picked.legacy) return args;
31519
31561
  const { name: _name, ...rest } = args;
31520
- return { ...rest, flow_path: picked.path };
31562
+ return { ...rest, e2e_test_path: picked.path };
31563
+ }
31564
+ function formerParamsForOlderServer(meta3, args) {
31565
+ const listed = meta3.inputSchema.properties;
31566
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
31567
+ if (typeof listed !== "object" || listed === null) return args;
31568
+ const record2 = args;
31569
+ let out;
31570
+ for (const [former, current] of Object.entries(toolParamAliases(meta3.name))) {
31571
+ if (record2[current] === void 0 || current in listed || !(former in listed)) continue;
31572
+ out ??= { ...record2 };
31573
+ out[former] = out[current];
31574
+ delete out[current];
31575
+ }
31576
+ return out ?? args;
31521
31577
  }
31522
31578
  async function stepForOlderServer(args, listedId) {
31523
31579
  if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
@@ -31775,12 +31831,13 @@ function createToolsClient(options = {}) {
31775
31831
  async function callTool(name, args, opts) {
31776
31832
  opts?.signal?.throwIfAborted();
31777
31833
  const { url: url2, token, remote } = await route();
31778
- let finalArgs = args;
31834
+ let finalArgs = canonicalToolArgs(name, args);
31779
31835
  const tools2 = await fetchTools({ signal: opts?.signal });
31780
31836
  const meta3 = findTool(tools2, name);
31781
31837
  const olderServer = meta3 !== null && !tools2.some((t2) => t2.name === canonicalToolId(meta3.name));
31782
31838
  if (meta3) {
31783
- finalArgs = await nameToE2ePathForOlderServer(meta3.name, meta3.fileInputs, args);
31839
+ finalArgs = await nameToE2ePathForOlderServer(meta3.name, meta3.fileInputs, finalArgs);
31840
+ finalArgs = formerParamsForOlderServer(meta3, finalArgs);
31784
31841
  }
31785
31842
  if (olderServer && canonicalToolId(meta3.name) === "e2e-add-step") {
31786
31843
  finalArgs = await stepForOlderServer(finalArgs, (tool) => findTool(tools2, tool)?.name);
@@ -31808,7 +31865,7 @@ function createToolsClient(options = {}) {
31808
31865
  if (filesLogEnabled()) {
31809
31866
  for (const file2 of applied.written) {
31810
31867
  if (baselineDirs.includes(path12.dirname(file2))) {
31811
- diagnose(`[flow-files] baseline ${file2}: written`);
31868
+ diagnose(`[e2e-files] baseline ${file2}: written`);
31812
31869
  }
31813
31870
  }
31814
31871
  }
@@ -35740,7 +35797,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
35740
35797
  var SESSION_ID2 = randomUUID5();
35741
35798
  function readCliVersion() {
35742
35799
  if (true) {
35743
- return "0.31.2-next.2";
35800
+ return "0.31.2-next.3";
35744
35801
  }
35745
35802
  return "0.0.0";
35746
35803
  }
@@ -37203,8 +37260,12 @@ function renderEchoLine(s) {
37203
37260
  }
37204
37261
  return ` ${indent}\u203A ${s.message}`;
37205
37262
  }
37263
+ function e2eTestOf(r2) {
37264
+ return r2.e2eTest ?? r2.flow;
37265
+ }
37206
37266
  function stepLabel(s, topFlow) {
37207
- const where = s.flow && s.flow !== topFlow ? ` [${s.flow}]` : "";
37267
+ const owner = e2eTestOf(s);
37268
+ const where = owner && owner !== topFlow ? ` [${owner}]` : "";
37208
37269
  const what = s.tool ?? s.target;
37209
37270
  return `${what ? `${s.kind} ${what}` : s.kind}${where}`;
37210
37271
  }
@@ -37261,7 +37322,7 @@ function renderFailedSteps(report) {
37261
37322
  if (s.status !== "fail" && s.status !== "error" && !s.warning && !scriptNote && scriptLog.length === 0) {
37262
37323
  continue;
37263
37324
  }
37264
- lines.push(renderStepLine(s, n3, report.flow));
37325
+ lines.push(renderStepLine(s, n3, e2eTestOf(report)));
37265
37326
  if (s.warning) lines.push(renderUnderStepLine(s, n3, `\u26A0 ${s.warning}`));
37266
37327
  lines.push(...scriptLog);
37267
37328
  if (s.artifacts && typeof s.artifacts === "object") {
@@ -37282,7 +37343,7 @@ function summarizeFailure(report) {
37282
37343
  n3++;
37283
37344
  if (s.status === "fail" || s.status === "error") {
37284
37345
  const detail = s.reason ? String(s.reason) : void 0;
37285
- return { headline: `step ${n3} ${stepLabel(s, report.flow)}`, detail };
37346
+ return { headline: `step ${n3} ${stepLabel(s, e2eTestOf(report))}`, detail };
37286
37347
  }
37287
37348
  }
37288
37349
  return { headline: "failed with no failing step" };
@@ -37322,11 +37383,15 @@ function rerunCommand(flowPath, rel, projectRoot, args) {
37322
37383
  if (args.output) parts.push("--output", pathArg(path18.join(args.output, path18.dirname(rel))));
37323
37384
  return parts.join(" ");
37324
37385
  }
37325
- var EXPORT_SOURCE_MARKER = ".argent-flow-source";
37386
+ var EXPORT_SOURCE_MARKER = ".argent-e2e-source";
37387
+ var LEGACY_EXPORT_SOURCE_MARKER = ".argent-flow-source";
37326
37388
  var MARKER_READ_RETRIES = 4;
37327
37389
  var MARKER_READ_DELAY_MS = 5;
37328
37390
  async function readExportMarker(dir) {
37329
- const marker = path18.join(dir, EXPORT_SOURCE_MARKER);
37391
+ const owner = await readMarkerFile(path18.join(dir, EXPORT_SOURCE_MARKER));
37392
+ return owner !== void 0 ? owner : await readMarkerFile(path18.join(dir, LEGACY_EXPORT_SOURCE_MARKER));
37393
+ }
37394
+ async function readMarkerFile(marker) {
37330
37395
  for (let attempt = 0; ; attempt++) {
37331
37396
  let owner;
37332
37397
  try {
@@ -37469,7 +37534,7 @@ function exitAfterFlush(code, streams = [process.stdout, process.stderr]) {
37469
37534
  }
37470
37535
  function renderReport(report) {
37471
37536
  const lines = [];
37472
- lines.push(`E2E test "${report.flow}"${report.device ? ` on ${report.device}` : ""}`);
37537
+ lines.push(`E2E test "${e2eTestOf(report)}"${report.device ? ` on ${report.device}` : ""}`);
37473
37538
  if (report.executionPrerequisite) {
37474
37539
  lines.push(` assumes: ${report.executionPrerequisite}`);
37475
37540
  }
@@ -37481,7 +37546,7 @@ function renderReport(report) {
37481
37546
  continue;
37482
37547
  }
37483
37548
  n3++;
37484
- lines.push(renderStepLine(s, n3, report.flow));
37549
+ lines.push(renderStepLine(s, n3, e2eTestOf(report)));
37485
37550
  if (s.warning) lines.push(renderUnderStepLine(s, n3, `\u26A0 ${s.warning}`));
37486
37551
  lines.push(...renderScriptLogLines(s, n3));
37487
37552
  if (s.artifacts && typeof s.artifacts === "object") {
@@ -37575,7 +37640,7 @@ async function collectFlowFiles(dir, recursive2) {
37575
37640
  var RUN_TOOL_ID = "e2e-execute";
37576
37641
  function buildRunPayload(flowPath, projectRoot, args) {
37577
37642
  const payload = {
37578
- flow_path: flowPath,
37643
+ e2e_test_path: flowPath,
37579
37644
  project_root: projectRoot,
37580
37645
  // Headless runs never block on the LLM prerequisite handshake.
37581
37646
  prerequisiteAcknowledged: true
@@ -37785,7 +37850,12 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
37785
37850
  const durationMs = Date.now() - batchStartedAt;
37786
37851
  if (args.json) {
37787
37852
  console.log(
37788
- JSON.stringify({ ok: counts.failed === 0, ...counts, durationMs, flows: results }, null, 2)
37853
+ JSON.stringify(
37854
+ // `flows` is the deprecated spelling of `e2eTests`.
37855
+ { ok: counts.failed === 0, ...counts, durationMs, e2eTests: results, flows: results },
37856
+ null,
37857
+ 2
37858
+ )
37789
37859
  );
37790
37860
  } else {
37791
37861
  for (const line of renderFailedFlows(failures)) console.log(line);
package/dist/client.js CHANGED
@@ -8883,6 +8883,36 @@ var TOOL_ID_ALIASES = Object.freeze({
8883
8883
  function canonicalToolId(id) {
8884
8884
  return Object.hasOwn(TOOL_ID_ALIASES, id) ? TOOL_ID_ALIASES[id] : id;
8885
8885
  }
8886
+ var TOOL_PARAM_ALIASES = Object.freeze({
8887
+ "e2e-execute": Object.freeze({ flow_path: "e2e_test_path", flow_file: "e2e_test_file" }),
8888
+ "e2e-read-prerequisite": Object.freeze({
8889
+ flow_path: "e2e_test_path",
8890
+ flow_file: "e2e_test_file"
8891
+ })
8892
+ });
8893
+ var ToolParamAliasError = class extends Error {
8894
+ constructor(former, current) {
8895
+ super(`Pass ${current} or its former name ${former}, not both.`);
8896
+ this.name = "ToolParamAliasError";
8897
+ }
8898
+ };
8899
+ function toolParamAliases(tool) {
8900
+ const id = canonicalToolId(tool);
8901
+ return Object.hasOwn(TOOL_PARAM_ALIASES, id) ? TOOL_PARAM_ALIASES[id] : {};
8902
+ }
8903
+ function canonicalToolArgs(tool, args) {
8904
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
8905
+ const record = args;
8906
+ let out;
8907
+ for (const [former, current] of Object.entries(toolParamAliases(tool))) {
8908
+ if (record[former] === void 0) continue;
8909
+ if (record[current] !== void 0) throw new ToolParamAliasError(former, current);
8910
+ out ??= { ...record };
8911
+ out[current] = out[former];
8912
+ delete out[former];
8913
+ }
8914
+ return out ?? args;
8915
+ }
8886
8916
 
8887
8917
  // ../registry/src/flow-file-refs.ts
8888
8918
  async function canonicalFlowPath(p) {
@@ -8952,7 +8982,13 @@ async function classifyOnDiskSpelling(dir, base, addressable = FLOW_FILE_NAME_PA
8952
8982
  }
8953
8983
  function nestedFlowTarget(args) {
8954
8984
  if (typeof args !== "object" || args === null || Array.isArray(args)) return void 0;
8955
- const { name, project_root: projectRoot, flow_path: flowPath } = args;
8985
+ let canonical;
8986
+ try {
8987
+ canonical = canonicalToolArgs("e2e-execute", args);
8988
+ } catch {
8989
+ return void 0;
8990
+ }
8991
+ const { name, project_root: projectRoot, e2e_test_path: flowPath } = canonical;
8956
8992
  if (flowPath === void 0) {
8957
8993
  if (typeof name !== "string" || !FLOW_NAME_PATTERN.test(name)) return void 0;
8958
8994
  if (!isResolvedAbsolute(projectRoot)) return void 0;
@@ -8961,7 +8997,7 @@ function nestedFlowTarget(args) {
8961
8997
  }
8962
8998
  if (name !== void 0 || !isResolvedAbsolute(flowPath)) return void 0;
8963
8999
  if (!FLOW_FILE_NAME_PATTERN.test(path8.basename(flowPath))) return void 0;
8964
- return { kind: "flow_path", path: flowPath };
9000
+ return { kind: "e2e_test_path", path: flowPath };
8965
9001
  }
8966
9002
  function isResolvedAbsolute(value) {
8967
9003
  return typeof value === "string" && path8.isAbsolute(value) && !value.split(/[\\/]+/).includes("..");
@@ -9857,12 +9893,18 @@ function isResolvedAbsolute2(value) {
9857
9893
  return typeof value === "string" && path11.isAbsolute(value) && !value.split(/[\\/]+/).includes("..");
9858
9894
  }
9859
9895
  function namesValidFlow(args) {
9860
- const { project_root, flow_path, name } = args;
9896
+ let canonical;
9897
+ try {
9898
+ canonical = canonicalToolArgs("e2e-execute", args);
9899
+ } catch {
9900
+ return false;
9901
+ }
9902
+ const { project_root, e2e_test_path: testPath, name } = canonical;
9861
9903
  if (!isResolvedAbsolute2(project_root)) return false;
9862
9904
  if (name === void 0) {
9863
- return isResolvedAbsolute2(flow_path) && FLOW_FILE_NAME_PATTERN.test(path11.basename(flow_path));
9905
+ return isResolvedAbsolute2(testPath) && FLOW_FILE_NAME_PATTERN.test(path11.basename(testPath));
9864
9906
  }
9865
- return flow_path === void 0 && typeof name === "string" && FLOW_NAME_PATTERN.test(name);
9907
+ return testPath === void 0 && typeof name === "string" && FLOW_NAME_PATTERN.test(name);
9866
9908
  }
9867
9909
  function savedFlowProject(file) {
9868
9910
  if (!path11.isAbsolute(file)) return null;
@@ -10036,8 +10078,8 @@ function memberCollector(roots, opts) {
10036
10078
  `));
10037
10079
  const emit = (member, sent) => {
10038
10080
  members.push(member);
10039
- const subject = member.role === "flow" ? member.canonical : member.key;
10040
- if (logging) log(printable(`[flow-files] ${member.role} ${subject}: ${sent}`));
10081
+ const [label, subject] = member.role === "flow" ? ["e2e test", member.canonical] : [member.role, member.key];
10082
+ if (logging) log(printable(`[e2e-files] ${label} ${subject}: ${sent}`));
10041
10083
  };
10042
10084
  async function flow(anchorDir, target, onlyPresent) {
10043
10085
  const key = flowMemberKey(anchorDir, target);
@@ -10203,7 +10245,7 @@ function recordedStep(args) {
10203
10245
  if (typeof text !== "string") return void 0;
10204
10246
  let parsed;
10205
10247
  try {
10206
- parsed = JSON.parse(text);
10248
+ parsed = canonicalToolArgs(command, JSON.parse(text));
10207
10249
  } catch {
10208
10250
  return void 0;
10209
10251
  }
@@ -10226,7 +10268,7 @@ async function collectStepMembers(args, opts, recordingDirFor = e2eDirFor) {
10226
10268
  const self = await collector.flow(path11.dirname(recording), path11.basename(recording));
10227
10269
  let runArgs = step.args;
10228
10270
  const named = nestedFlowTarget(runArgs);
10229
- if (named?.kind === "flow_path") {
10271
+ if (named?.kind === "e2e_test_path") {
10230
10272
  const stem = path11.basename(named.path, ".yaml");
10231
10273
  const base = path11.basename(named.path);
10232
10274
  const dir = path11.resolve(path11.dirname(named.path));
@@ -10235,7 +10277,7 @@ async function collectStepMembers(args, opts, recordingDirFor = e2eDirFor) {
10235
10277
  if (dirs.some((d) => path11.resolve(d) === dir)) {
10236
10278
  await collector.flow(path11.dirname(named.path), base);
10237
10279
  runArgs = { ...runArgs, name: stem };
10238
- delete runArgs.flow_path;
10280
+ delete runArgs.e2e_test_path;
10239
10281
  }
10240
10282
  }
10241
10283
  const target = nestedFlowTarget(runArgs);
@@ -10263,7 +10305,21 @@ async function nameToE2ePathForOlderServer(tool, fileInputs, args) {
10263
10305
  const picked = await pickE2eFile(target.paths);
10264
10306
  if (!picked.exists || picked.legacy) return args;
10265
10307
  const { name: _name, ...rest } = args;
10266
- return { ...rest, flow_path: picked.path };
10308
+ return { ...rest, e2e_test_path: picked.path };
10309
+ }
10310
+ function formerParamsForOlderServer(meta, args) {
10311
+ const listed = meta.inputSchema.properties;
10312
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
10313
+ if (typeof listed !== "object" || listed === null) return args;
10314
+ const record = args;
10315
+ let out;
10316
+ for (const [former, current] of Object.entries(toolParamAliases(meta.name))) {
10317
+ if (record[current] === void 0 || current in listed || !(former in listed)) continue;
10318
+ out ??= { ...record };
10319
+ out[former] = out[current];
10320
+ delete out[current];
10321
+ }
10322
+ return out ?? args;
10267
10323
  }
10268
10324
  async function stepForOlderServer(args, listedId) {
10269
10325
  if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
@@ -10521,12 +10577,13 @@ function createToolsClient(options = {}) {
10521
10577
  async function callTool(name, args, opts) {
10522
10578
  opts?.signal?.throwIfAborted();
10523
10579
  const { url, token, remote } = await route();
10524
- let finalArgs = args;
10580
+ let finalArgs = canonicalToolArgs(name, args);
10525
10581
  const tools = await fetchTools({ signal: opts?.signal });
10526
10582
  const meta = findTool(tools, name);
10527
10583
  const olderServer = meta !== null && !tools.some((t) => t.name === canonicalToolId(meta.name));
10528
10584
  if (meta) {
10529
- finalArgs = await nameToE2ePathForOlderServer(meta.name, meta.fileInputs, args);
10585
+ finalArgs = await nameToE2ePathForOlderServer(meta.name, meta.fileInputs, finalArgs);
10586
+ finalArgs = formerParamsForOlderServer(meta, finalArgs);
10530
10587
  }
10531
10588
  if (olderServer && canonicalToolId(meta.name) === "e2e-add-step") {
10532
10589
  finalArgs = await stepForOlderServer(finalArgs, (tool) => findTool(tools, tool)?.name);
@@ -10554,7 +10611,7 @@ function createToolsClient(options = {}) {
10554
10611
  if (filesLogEnabled()) {
10555
10612
  for (const file of applied.written) {
10556
10613
  if (baselineDirs.includes(path12.dirname(file))) {
10557
- diagnose(`[flow-files] baseline ${file}: written`);
10614
+ diagnose(`[e2e-files] baseline ${file}: written`);
10558
10615
  }
10559
10616
  }
10560
10617
  }
@@ -15759,6 +15759,13 @@ var TOOL_ID_ALIASES = Object.freeze({
15759
15759
  "flow-read-prerequisite": "e2e-read-prerequisite",
15760
15760
  "flow-execute": "e2e-execute"
15761
15761
  });
15762
+ var TOOL_PARAM_ALIASES = Object.freeze({
15763
+ "e2e-execute": Object.freeze({ flow_path: "e2e_test_path", flow_file: "e2e_test_file" }),
15764
+ "e2e-read-prerequisite": Object.freeze({
15765
+ flow_path: "e2e_test_path",
15766
+ flow_file: "e2e_test_file"
15767
+ })
15768
+ });
15762
15769
 
15763
15770
  // ../registry/src/failure-codes.ts
15764
15771
  var FAILURE_CODES = {
@@ -16915,7 +16922,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
16915
16922
  var SESSION_ID = randomUUID4();
16916
16923
  function readCliVersion() {
16917
16924
  if (true) {
16918
- return "0.31.2-next.2";
16925
+ return "0.31.2-next.3";
16919
16926
  }
16920
16927
  return "0.0.0";
16921
16928
  }
@@ -11,6 +11,9 @@ NS_ASSUME_NONNULL_BEGIN
11
11
  /// Returns nil when the block completed, otherwise "Name: reason".
12
12
  + (NSString *_Nullable)runCatching:(NS_NOESCAPE dispatch_block_t)block;
13
13
 
14
+ /// Returns nil when the block completed, otherwise the exception it raised.
15
+ + (NSException *_Nullable)exceptionFrom:(NS_NOESCAPE dispatch_block_t)block;
16
+
14
17
  @end
15
18
 
16
19
  NS_ASSUME_NONNULL_END
@@ -3,13 +3,21 @@
3
3
  @implementation ArgentExceptionGuard
4
4
 
5
5
  + (NSString *_Nullable)runCatching:(NS_NOESCAPE dispatch_block_t)block {
6
+ NSException *exception = [self exceptionFrom:block];
7
+ if (exception == nil) {
8
+ return nil;
9
+ }
10
+ NSString *name = exception.name ?: @"NSException";
11
+ NSString *reason = exception.reason ?: @"unhandled Objective-C exception";
12
+ return [NSString stringWithFormat:@"%@: %@", name, reason];
13
+ }
14
+
15
+ + (NSException *_Nullable)exceptionFrom:(NS_NOESCAPE dispatch_block_t)block {
6
16
  @try {
7
17
  block();
8
18
  return nil;
9
19
  } @catch (NSException *exception) {
10
- NSString *name = exception.name ?: @"NSException";
11
- NSString *reason = exception.reason ?: @"unhandled Objective-C exception";
12
- return [NSString stringWithFormat:@"%@: %@", name, reason];
20
+ return exception;
13
21
  }
14
22
  }
15
23
 
@@ -21,10 +21,10 @@ extension ArgentRunnerSession {
21
21
 
22
22
  /// Taps at the wire coordinates: one tap, the native double-tap when
23
23
  /// `numberOfTaps` is 2, or a loop of single taps above that. XCUICoordinate
24
- /// has no N-tap API, and the loop is not one gesture on hardware: each tap
25
- /// is its own synthesized event behind XCTest's idle wait and an XPC round
26
- /// trip, hundreds of milliseconds apart, so the taps land separately and do
27
- /// not trigger a multi-tap recognizer. That is documented, not refused.
24
+ /// has no N-tap API, and the loop is not one gesture: each tap is its own
25
+ /// synthesized event and XPC round trip, about 300 ms apart on a
26
+ /// simulator, which a double-tap recognizer can still pair. That is
27
+ /// documented, not refused.
28
28
  func performTap(_ request: CommandRequest, on app: XCUIApplication)
29
29
  -> Envelope
30
30
  {
@@ -39,15 +39,17 @@ extension ArgentRunnerSession {
39
39
 
40
40
  let coordinate = point(app, x, y, relativeTo: app.frame.origin)
41
41
 
42
- switch taps {
43
- case 1:
44
- coordinate.tap()
45
- case 2:
46
- coordinate.doubleTap()
47
- default:
48
- // Separate taps, not one gesture (see the doc comment above).
49
- for _ in 0..<taps {
42
+ skippingQuiescence(of: app) {
43
+ switch taps {
44
+ case 1:
50
45
  coordinate.tap()
46
+ case 2:
47
+ coordinate.doubleTap()
48
+ default:
49
+ // Separate taps, not one gesture (see the doc comment above).
50
+ for _ in 0..<taps {
51
+ coordinate.tap()
52
+ }
51
53
  }
52
54
  }
53
55
 
@@ -63,7 +65,11 @@ extension ArgentRunnerSession {
63
65
  }
64
66
 
65
67
  let seconds = max(0.05, (request.durationMs ?? 800) / 1000)
66
- point(app, x, y, relativeTo: app.frame.origin).press(forDuration: seconds)
68
+ let coordinate = point(app, x, y, relativeTo: app.frame.origin)
69
+
70
+ skippingQuiescence(of: app) {
71
+ coordinate.press(forDuration: seconds)
72
+ }
67
73
 
68
74
  return .success(MessagePayload(message: "long-pressed"))
69
75
  }
@@ -112,12 +118,14 @@ extension ArgentRunnerSession {
112
118
  velocity = .default
113
119
  }
114
120
 
115
- start.press(
116
- forDuration: startHold,
117
- thenDragTo: end,
118
- withVelocity: velocity,
119
- thenHoldForDuration: endHold
120
- )
121
+ skippingQuiescence(of: app) {
122
+ start.press(
123
+ forDuration: startHold,
124
+ thenDragTo: end,
125
+ withVelocity: velocity,
126
+ thenHoldForDuration: endHold
127
+ )
128
+ }
121
129
 
122
130
  return .success(MessagePayload(message: "dragged"))
123
131
  }
@@ -0,0 +1,95 @@
1
+ import XCTest
2
+
3
+ /// XCTest waits up to 60s before and after every synthesized event for the
4
+ /// app to report idle. A screen that never does, such as one holding a paused
5
+ /// UIViewPropertyAnimator (expo-blur), stalls each gesture for two minutes,
6
+ /// past its watchdog budget. The runner skips those waits and runs XCTest's
7
+ /// idle check itself under a short cap. Every API here is XCTest SPI, probed
8
+ /// before use: when any is missing, the event runs with XCTest's own waits.
9
+ extension ArgentRunnerSession {
10
+ /// The longest a gesture waits for the app to go idle before its event.
11
+ /// Covers a navigation transition, so a gesture sent right after the one
12
+ /// that started it lands on the new screen instead of mid-transition.
13
+ static let preEventSettleTimeout: TimeInterval = 2
14
+
15
+ private static let performWithInteractionOptions = NSSelectorFromString(
16
+ "_performWithInteractionOptions:block:"
17
+ )
18
+ /// Bit 1 skips the pre-event wait, bit 2 the post-event one.
19
+ private static let skipQuiescenceOptions: UInt32 = 0b11
20
+
21
+ private static let applicationImpl = NSSelectorFromString("applicationImpl")
22
+ private static let currentProcess = NSSelectorFromString("currentProcess")
23
+ private static let initiateQuiescenceChecks = NSSelectorFromString(
24
+ "_initiateQuiescenceChecksIncludingAnimationsIdle:"
25
+ )
26
+ private static let makeQuiescenceExpectation = NSSelectorFromString(
27
+ "_makeQuiescenceExpectation"
28
+ )
29
+
30
+ /// Waits at most `preEventSettleTimeout` for `app` to go idle, then runs
31
+ /// `body`, which synthesizes events into it, without XCTest's waits.
32
+ func skippingQuiescence(of app: XCUIApplication, _ body: () -> Void) {
33
+ let selector = Self.performWithInteractionOptions
34
+
35
+ guard app.responds(to: selector),
36
+ awaitQuiescence(of: app, timeout: Self.preEventSettleTimeout)
37
+ else {
38
+ body()
39
+ return
40
+ }
41
+
42
+ typealias Perform = @convention(c) (
43
+ NSObject, Selector, UInt32, @convention(block) () -> Void
44
+ ) -> Void
45
+ let perform = unsafeBitCast(app.method(for: selector), to: Perform.self)
46
+
47
+ // XCTest restores the prior options only when the block returns, and
48
+ // every XCUIApplication for this bundle id shares them, so an
49
+ // exception must not unwind through it.
50
+ var exception: NSException?
51
+ withoutActuallyEscaping(body) { body in
52
+ perform(app, selector, Self.skipQuiescenceOptions) {
53
+ exception = ArgentExceptionGuard.exception(from: body)
54
+ }
55
+ }
56
+ exception?.raise()
57
+ }
58
+
59
+ /// XCTest's own idle check (event loop and animations), under `timeout`.
60
+ /// False when any SPI it needs is missing.
61
+ private func awaitQuiescence(of app: XCUIApplication, timeout: TimeInterval)
62
+ -> Bool
63
+ {
64
+ guard
65
+ let impl = Self.object(app, Self.applicationImpl),
66
+ let process = Self.object(impl, Self.currentProcess),
67
+ process.responds(to: Self.initiateQuiescenceChecks)
68
+ else {
69
+ return false
70
+ }
71
+
72
+ typealias Initiate = @convention(c) (NSObject, Selector, Bool) -> Void
73
+ let initiate = unsafeBitCast(
74
+ process.method(for: Self.initiateQuiescenceChecks),
75
+ to: Initiate.self
76
+ )
77
+ initiate(process, Self.initiateQuiescenceChecks, true)
78
+
79
+ guard
80
+ let expectation = Self.object(process, Self.makeQuiescenceExpectation)
81
+ as? XCTestExpectation
82
+ else {
83
+ return false
84
+ }
85
+
86
+ _ = XCTWaiter.wait(for: [expectation], timeout: timeout)
87
+ return true
88
+ }
89
+
90
+ /// The object a zero-argument getter returns, or nil when `target` lacks it.
91
+ private static func object(_ target: NSObject, _ getter: Selector) -> NSObject? {
92
+ guard target.responds(to: getter) else { return nil }
93
+ return target.perform(getter)?.takeUnretainedValue() as? NSObject
94
+ }
95
+ }