@swmansion/argent 0.16.1-next.1 → 0.16.1-next.2

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.
@@ -144726,6 +144726,28 @@ function textWaitToYaml(selector, expectedText, textMatch) {
144726
144726
  }
144727
144727
  }
144728
144728
  }
144729
+ function targetToYaml(step) {
144730
+ const hasPointField = step.x !== void 0 || step.y !== void 0;
144731
+ if (step.selector !== void 0) {
144732
+ if (hasPointField) {
144733
+ throw new Error(
144734
+ "Cannot serialize flow gesture target: use a selector or x/y coordinates, not both"
144735
+ );
144736
+ }
144737
+ return selectorToYaml(step.selector);
144738
+ }
144739
+ if (typeof step.x !== "number" || typeof step.y !== "number") {
144740
+ throw new Error(
144741
+ "Cannot serialize flow gesture target: a coordinate target needs numeric x and y"
144742
+ );
144743
+ }
144744
+ if (!(step.x >= 0 && step.x <= 1) || !(step.y >= 0 && step.y <= 1)) {
144745
+ throw new Error(
144746
+ "Cannot serialize flow gesture target: coordinates are normalized 0\u20131 fractions of the screen, not pixels"
144747
+ );
144748
+ }
144749
+ return { x: step.x, y: step.y };
144750
+ }
144729
144751
  function waitToYaml(condition, selector, expectedText, textMatch, timeoutMs) {
144730
144752
  const sel = selectorToYaml(selector);
144731
144753
  let body;
@@ -144755,18 +144777,13 @@ function toYamlStep(step) {
144755
144777
  case "run":
144756
144778
  return { run: step.flow };
144757
144779
  case "tap": {
144758
- if (step.selector) {
144759
- const sel = selectorToYaml(step.selector);
144760
- return { tap: step.times !== void 0 ? { on: sel, times: step.times } : sel };
144761
- }
144762
- const body = { x: step.x, y: step.y };
144763
- if (step.times !== void 0) body.times = step.times;
144764
- return { tap: body };
144780
+ const target = targetToYaml(step);
144781
+ return { tap: step.times !== void 0 ? { on: target, times: step.times } : target };
144765
144782
  }
144766
144783
  case "long-press": {
144767
- const sel = selectorToYaml(step.selector);
144784
+ const target = targetToYaml(step);
144768
144785
  return {
144769
- "long-press": step.duration !== void 0 ? { on: sel, duration: step.duration } : sel
144786
+ "long-press": step.duration !== void 0 ? { on: target, duration: step.duration } : target
144770
144787
  };
144771
144788
  }
144772
144789
  case "type": {
@@ -145079,35 +145096,6 @@ var STEP_DIRECTIVE_KEYS = [
145079
145096
  "scroll-to",
145080
145097
  "snapshot"
145081
145098
  ];
145082
- function parseLongPress(body, entry) {
145083
- const obj = body !== null && typeof body === "object" ? body : {};
145084
- if (obj.on !== void 0 || obj.duration !== void 0) {
145085
- if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145086
- badEntry(
145087
- entry,
145088
- 'the long-press options form takes a nested selector \u2014 e.g. long-press: { on: { text: "Row" }, duration: 1200 }'
145089
- );
145090
- }
145091
- if (!Object.keys(obj).every((k) => k === "on" || k === "duration")) {
145092
- badEntry(entry, "the long-press options form accepts only { on, duration }");
145093
- }
145094
- if (obj.on === void 0) {
145095
- badEntry(entry, 'long-press needs a target \u2014 e.g. long-press: { on: "Row", duration: 1200 }');
145096
- }
145097
- const step = { kind: "long-press", selector: parseSelector(obj.on, "long-press.on") };
145098
- if (obj.duration !== void 0) {
145099
- if (typeof obj.duration !== "number" || !Number.isFinite(obj.duration) || obj.duration <= 0) {
145100
- badEntry(
145101
- entry,
145102
- "long-press.duration needs a positive number of milliseconds (e.g. `duration: 1200`)"
145103
- );
145104
- }
145105
- step.duration = obj.duration;
145106
- }
145107
- return step;
145108
- }
145109
- return { kind: "long-press", selector: parseSelector(body, "long-press") };
145110
- }
145111
145099
  function parseTapTimes(raw, entry) {
145112
145100
  if (raw === void 0) return void 0;
145113
145101
  if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1 || raw > 10) {
@@ -145115,23 +145103,32 @@ function parseTapTimes(raw, entry) {
145115
145103
  }
145116
145104
  return raw === 1 ? void 0 : raw;
145117
145105
  }
145118
- function parseTap(body, entry) {
145119
- const obj = body !== null && typeof body === "object" ? body : {};
145120
- if (obj.x !== void 0 || obj.y !== void 0) {
145121
- if (obj.on !== void 0 || obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145122
- badEntry(entry, "tap takes a selector or x/y coordinates, not both");
145123
- }
145124
- if (typeof obj.x !== "number" || typeof obj.y !== "number") {
145125
- badEntry(entry, "a coordinate tap needs numeric x and y");
145126
- }
145127
- if (!Object.keys(obj).every((k) => k === "x" || k === "y" || k === "times")) {
145128
- badEntry(entry, "a coordinate tap takes only { x, y, times }");
145106
+ function parseTarget(raw, where) {
145107
+ if (raw !== null && typeof raw === "object") {
145108
+ const obj = raw;
145109
+ if (obj.x !== void 0 || obj.y !== void 0) {
145110
+ if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145111
+ badEntry(raw, `${where} takes a selector or x/y coordinates, not both`);
145112
+ }
145113
+ if (typeof obj.x !== "number" || typeof obj.y !== "number") {
145114
+ badEntry(raw, `${where}: a coordinate target needs numeric x and y`);
145115
+ }
145116
+ if (!(obj.x >= 0 && obj.x <= 1) || !(obj.y >= 0 && obj.y <= 1)) {
145117
+ badEntry(
145118
+ raw,
145119
+ `${where}: coordinates are normalized 0\u20131 fractions of the screen, not pixels`
145120
+ );
145121
+ }
145122
+ if (!Object.keys(obj).every((k) => k === "x" || k === "y")) {
145123
+ badEntry(raw, `${where}: a coordinate target takes only { x, y }`);
145124
+ }
145125
+ return { x: obj.x, y: obj.y };
145129
145126
  }
145130
- const step = { kind: "tap", x: obj.x, y: obj.y };
145131
- const times = parseTapTimes(obj.times, entry);
145132
- if (times !== void 0) step.times = times;
145133
- return step;
145134
145127
  }
145128
+ return { selector: parseSelector(raw, where) };
145129
+ }
145130
+ function parseTap(body, entry) {
145131
+ const obj = body !== null && typeof body === "object" ? body : {};
145135
145132
  if (obj.on !== void 0 || obj.times !== void 0) {
145136
145133
  if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145137
145134
  badEntry(
@@ -145139,18 +145136,59 @@ function parseTap(body, entry) {
145139
145136
  'the tap options form takes a nested selector \u2014 e.g. tap: { on: { text: "Photo" }, times: 2 }'
145140
145137
  );
145141
145138
  }
145139
+ if (obj.x !== void 0 || obj.y !== void 0) {
145140
+ badEntry(
145141
+ entry,
145142
+ "the tap options form takes a nested point \u2014 e.g. tap: { on: { x: 0.5, y: 0.5 }, times: 2 }"
145143
+ );
145144
+ }
145142
145145
  if (!Object.keys(obj).every((k) => k === "on" || k === "times")) {
145143
145146
  badEntry(entry, "the tap options form accepts only { on, times }");
145144
145147
  }
145145
145148
  if (obj.on === void 0) {
145146
145149
  badEntry(entry, 'tap with times needs a target \u2014 e.g. tap: { on: "Photo", times: 2 }');
145147
145150
  }
145148
- const step = { kind: "tap", selector: parseSelector(obj.on, "tap.on") };
145151
+ const step = { kind: "tap", ...parseTarget(obj.on, "tap.on") };
145149
145152
  const times = parseTapTimes(obj.times, entry);
145150
145153
  if (times !== void 0) step.times = times;
145151
145154
  return step;
145152
145155
  }
145153
- return { kind: "tap", selector: parseSelector(body, "tap") };
145156
+ return { kind: "tap", ...parseTarget(body, "tap") };
145157
+ }
145158
+ function parseLongPress(body, entry) {
145159
+ const obj = body !== null && typeof body === "object" ? body : {};
145160
+ if (obj.on !== void 0 || obj.duration !== void 0) {
145161
+ if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145162
+ badEntry(
145163
+ entry,
145164
+ 'the long-press options form takes a nested selector \u2014 e.g. long-press: { on: { text: "Row" }, duration: 1200 }'
145165
+ );
145166
+ }
145167
+ if (obj.x !== void 0 || obj.y !== void 0) {
145168
+ badEntry(
145169
+ entry,
145170
+ "the long-press options form takes a nested point \u2014 e.g. long-press: { on: { x: 0.5, y: 0.5 }, duration: 1200 }"
145171
+ );
145172
+ }
145173
+ if (!Object.keys(obj).every((k) => k === "on" || k === "duration")) {
145174
+ badEntry(entry, "the long-press options form accepts only { on, duration }");
145175
+ }
145176
+ if (obj.on === void 0) {
145177
+ badEntry(entry, 'long-press needs a target \u2014 e.g. long-press: { on: "Row", duration: 1200 }');
145178
+ }
145179
+ const step = { kind: "long-press", ...parseTarget(obj.on, "long-press.on") };
145180
+ if (obj.duration !== void 0) {
145181
+ if (typeof obj.duration !== "number" || !Number.isFinite(obj.duration) || obj.duration <= 0) {
145182
+ badEntry(
145183
+ entry,
145184
+ "long-press.duration needs a positive number of milliseconds (e.g. `duration: 1200`)"
145185
+ );
145186
+ }
145187
+ step.duration = obj.duration;
145188
+ }
145189
+ return step;
145190
+ }
145191
+ return { kind: "long-press", ...parseTarget(body, "long-press") };
145154
145192
  }
145155
145193
  function fromYamlStep(raw) {
145156
145194
  const entry = raw;
@@ -146108,9 +146146,8 @@ You can still edit the .yaml file directly afterwards to remove or reorder steps
146108
146146
  case "run":
146109
146147
  return `${n}. run: ${step.flow}`;
146110
146148
  case "tap":
146111
- return `${n}. tap: ${step.selector ? selectorLabel(step.selector) : `(${step.x}, ${step.y})`}`;
146112
146149
  case "long-press":
146113
- return `${n}. long-press: ${selectorLabel(step.selector)}`;
146150
+ return `${n}. ${step.kind}: ${step.selector ? selectorLabel(step.selector) : `(${step.x}, ${step.y})`}`;
146114
146151
  case "type":
146115
146152
  return `${n}. type: ${selectorLabel(step.into)} \u2190 "${step.text}"`;
146116
146153
  case "await":
@@ -146388,20 +146425,23 @@ async function runDirective(env, step) {
146388
146425
  }
146389
146426
  }
146390
146427
  }
146391
- async function runTap(env, target) {
146392
- let point;
146428
+ async function resolveTargetPoint(env, target) {
146393
146429
  if (target.selector) {
146394
146430
  const frame = await waitForFrame(env, target.selector);
146395
- if (frame === "aborted") return ABORTED_OUTCOME;
146431
+ if (frame === "aborted") return { fail: ABORTED_OUTCOME };
146396
146432
  if (!frame) {
146397
- return { ok: false, reason: offscreenHint(target.selector) };
146433
+ return { fail: { ok: false, reason: offscreenHint(target.selector) } };
146398
146434
  }
146399
- point = getDescribeTapPoint(frame);
146400
- } else if (typeof target.x === "number" && typeof target.y === "number") {
146401
- point = { x: target.x, y: target.y };
146402
- } else {
146403
- return { ok: false, reason: "tap needs a selector or x/y coordinates" };
146435
+ return getDescribeTapPoint(frame);
146436
+ }
146437
+ if (typeof target.x === "number" && typeof target.y === "number") {
146438
+ return { x: target.x, y: target.y };
146404
146439
  }
146440
+ return { fail: { ok: false, reason: "gesture needs a selector or x/y coordinates" } };
146441
+ }
146442
+ async function runTap(env, target) {
146443
+ const point = await resolveTargetPoint(env, target);
146444
+ if ("fail" in point) return point.fail;
146405
146445
  await invokeOnDevice(env, "gesture-tap", {
146406
146446
  ...point,
146407
146447
  ...target.times !== void 0 ? { clickCount: target.times } : {}
@@ -146410,12 +146450,8 @@ async function runTap(env, target) {
146410
146450
  }
146411
146451
  var DEFAULT_LONG_PRESS_MS = 800;
146412
146452
  async function runLongPress(env, step) {
146413
- const frame = await waitForFrame(env, step.selector);
146414
- if (frame === "aborted") return ABORTED_OUTCOME;
146415
- if (!frame) {
146416
- return { ok: false, reason: offscreenHint(step.selector) };
146417
- }
146418
- const point = getDescribeTapPoint(frame);
146453
+ const point = await resolveTargetPoint(env, step);
146454
+ if ("fail" in point) return point.fail;
146419
146455
  const duration3 = step.duration ?? DEFAULT_LONG_PRESS_MS;
146420
146456
  if (env.device.platform === "chromium") {
146421
146457
  await invokeOnDevice(env, "gesture-drag", {
@@ -148877,8 +148913,9 @@ function createRunFlowTool(registry2) {
148877
148913
  description: `Run a saved flow from the .argent/flows/ directory.
148878
148914
  Steps run in order: \`launch\` starts an app from scratch (terminate + relaunch) and waits until it is
148879
148915
  ready; \`tool\` calls dispatch through the registry; \`tap\`/\`long-press\`/\`type\` resolve a selector to an
148880
- element and act on it (\`long-press: { on, duration }\` presses and holds); \`scroll-to\` scrolls
148881
- (momentum-free) until a target is visible; \`await\` waits for a UI
148916
+ element and act on it (\`tap: { on, times: 2 }\` double-taps; \`long-press: { on, duration }\` presses and
148917
+ holds; \`tap\`/\`long-press\` alternatively take a raw normalized point \u2014 bare \`{ x, y }\` or \`on: { x, y }\`);
148918
+ \`scroll-to\` scrolls (momentum-free) until a target is visible; \`await\` waits for a UI
148882
148919
  condition; \`wait\` pauses for a fixed number of milliseconds; \`assert\` checks one now; \`snapshot\`
148883
148920
  diffs a screenshot against a stored baseline (a missing baseline fails the step \u2014 set updateBaselines
148884
148921
  to adopt the current screen); \`echo\` annotates; \`run\` executes a referenced fragment inline.
@@ -149032,11 +149069,10 @@ function selectorLabel2(sel) {
149032
149069
  function stepTarget(step) {
149033
149070
  switch (step.kind) {
149034
149071
  case "tap":
149072
+ case "long-press":
149035
149073
  if (step.selector) return selectorLabel2(step.selector);
149036
149074
  if (step.x !== void 0 && step.y !== void 0) return `(${step.x}, ${step.y})`;
149037
149075
  return void 0;
149038
- case "long-press":
149039
- return selectorLabel2(step.selector);
149040
149076
  case "type":
149041
149077
  return `into ${selectorLabel2(step.into)}`;
149042
149078
  case "await":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.16.1-next.1",
3
+ "version": "0.16.1-next.2",
4
4
  "description": "MCP server for iOS Simulator and Android Emulator control",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -20,18 +20,18 @@ Both run via `argent flow run <name>` — a fragment simply runs against whateve
20
20
 
21
21
  Beyond raw `tool:` steps and `echo:`, flows support declarative directives interpreted by the runner (they are **not** agent-callable tools). **Every directive hard-stops the flow on failure**; later steps are reported `skip`.
22
22
 
23
- | Directive | YAML | Meaning |
24
- | ------------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
25
- | `launch` | `- launch: com.acme.app` or `- launch: { ios: …, android: … }` | start the app from scratch (terminate + relaunch) and wait until ready |
26
- | `tap` | `- tap: Login`, `- tap: { x: 0.5, y: 0.57 }`, `- tap: { on: Login, times: 2 }` | tap by selector (auto-waits) or raw point; `times: 2` = double-tap |
27
- | `long-press` | `- long-press: Row 3` or `- long-press: { on: <sel>, duration: 1200 }` | press and hold an element (default 800ms; Chromium: mouse press-hold) |
28
- | `type` | `- type: { into: email, text: "a@b.com" }` | focus a field, type, then press Enter to submit + dismiss the keyboard |
29
- | `scroll-to` | `- scroll-to: "Order #1234"` (scrolls down) or `- scroll-to: { target: …, direction: right, within: … }` | momentum-free scroll until the target is visible |
30
- | `await` | `- await: { visible: Home }` | wait for a UI condition |
31
- | `wait` | `- wait: 500` | pause for a fixed number of milliseconds (last resort — prefer `await`) |
32
- | `assert` | `- assert: { visible: Welcome }` | check a condition, hard-fail if it never holds |
33
- | `snapshot` | `- snapshot: home` or `- snapshot: { name: home, maxMismatch: 0.5 }` | diff a screenshot against a stored baseline |
34
- | `run` | `- run: login` | execute another flow's steps inline (fragment or e2e) |
23
+ | Directive | YAML | Meaning |
24
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
25
+ | `launch` | `- launch: com.acme.app` or `- launch: { ios: …, android: … }` | start the app from scratch (terminate + relaunch) and wait until ready |
26
+ | `tap` | `- tap: Login`, `- tap: { x: 0.5, y: 0.57 }`, `- tap: { on: Login, times: 2 }`, `- tap: { on: { x: 0.5, y: 0.57 }, times: 2 }` | tap by selector (auto-waits) or raw point; `times` (2 = double-tap) needs the target nested under `on:` — a selector or a point (`{ x, y, times }` is rejected) |
27
+ | `long-press` | `- long-press: Row 3`, `- long-press: { x: 0.5, y: 0.6 }`, `- long-press: { on: <sel>, duration: 1200 }`, `- long-press: { on: { x: 0.5, y: 0.6 }, duration: 1200 }` | press and hold an element or raw point (default 800ms; Chromium: mouse press-hold); `duration` needs the target nested under `on:` — a selector or a point |
28
+ | `type` | `- type: { into: email, text: "a@b.com" }` | focus a field, type, then press Enter to submit + dismiss the keyboard |
29
+ | `scroll-to` | `- scroll-to: "Order #1234"` (scrolls down) or `- scroll-to: { target: …, direction: right, within: … }` | momentum-free scroll until the target is visible |
30
+ | `await` | `- await: { visible: Home }` | wait for a UI condition |
31
+ | `wait` | `- wait: 500` | pause for a fixed number of milliseconds (last resort — prefer `await`) |
32
+ | `assert` | `- assert: { visible: Welcome }` | check a condition, hard-fail if it never holds |
33
+ | `snapshot` | `- snapshot: home` or `- snapshot: { name: home, maxMismatch: 0.5 }` | diff a screenshot against a stored baseline |
34
+ | `run` | `- run: login` | execute another flow's steps inline (fragment or e2e) |
35
35
 
36
36
  ### Selectors
37
37