@swmansion/argent 0.15.1-next.3 → 0.15.1-next.5
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/tool-server.cjs +172 -47
- package/package.json +1 -1
- package/skills/argent-create-flow/SKILL.md +13 -12
package/dist/tool-server.cjs
CHANGED
|
@@ -126102,7 +126102,10 @@ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
126102
126102
|
var zodSchema16 = external_exports.object({
|
|
126103
126103
|
udid: external_exports.string().describe("Target device id from `list-devices` (iOS UDID, Android serial, or Chromium id)."),
|
|
126104
126104
|
x: external_exports.number().describe("Normalized horizontal position 0.0\u20131.0 (left=0, right=1), not pixels"),
|
|
126105
|
-
y: external_exports.number().describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels")
|
|
126105
|
+
y: external_exports.number().describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels"),
|
|
126106
|
+
clickCount: external_exports.number().int().min(1).max(10).optional().describe(
|
|
126107
|
+
"Number of taps/clicks dispatched as ONE multi-tap gesture (2 = double-tap / double-click). The taps land inside the OS double-tap window; on Chromium each click carries an escalating CDP clickCount so dblclick actually fires. Default 1."
|
|
126108
|
+
)
|
|
126106
126109
|
});
|
|
126107
126110
|
var capability8 = {
|
|
126108
126111
|
apple: { simulator: true, device: true },
|
|
@@ -126110,19 +126113,25 @@ var capability8 = {
|
|
|
126110
126113
|
android: { emulator: true, device: true, unknown: true },
|
|
126111
126114
|
chromium: { app: true }
|
|
126112
126115
|
};
|
|
126113
|
-
|
|
126116
|
+
var TAP_HOLD_MS = 50;
|
|
126117
|
+
var MULTI_TAP_GAP_MS = 100;
|
|
126118
|
+
async function tapChromium(api, x, y, clickCount) {
|
|
126114
126119
|
const vp = api.getViewport();
|
|
126115
126120
|
const pxX = Math.max(0, Math.min(vp.width, x * vp.width));
|
|
126116
126121
|
const pxY = Math.max(0, Math.min(vp.height, y * vp.height));
|
|
126117
126122
|
await api.dispatchMouseEvent({ type: "mouseMoved", x: pxX, y: pxY });
|
|
126118
|
-
|
|
126119
|
-
|
|
126120
|
-
|
|
126123
|
+
for (let i = 1; i <= clickCount; i++) {
|
|
126124
|
+
if (i > 1) await sleep2(MULTI_TAP_GAP_MS);
|
|
126125
|
+
await api.dispatchMouseEvent({ type: "mousePressed", x: pxX, y: pxY, clickCount: i });
|
|
126126
|
+
await sleep2(TAP_HOLD_MS);
|
|
126127
|
+
await api.dispatchMouseEvent({ type: "mouseReleased", x: pxX, y: pxY, clickCount: i });
|
|
126128
|
+
}
|
|
126121
126129
|
}
|
|
126122
126130
|
var gestureTapTool = {
|
|
126123
126131
|
id: "gesture-tap",
|
|
126124
126132
|
description: `Press the device screen (iOS simulator, Android emulator, or Chromium app) at normalized coordinates: x and y are fractions of screen width and height in 0.0\u20131.0 (not pixels).
|
|
126125
126133
|
Sends a Down event followed by an Up event at the same point. For Chromium, this dispatches a CDP mouse-press/release on the renderer.
|
|
126134
|
+
Set clickCount: 2 for a double-tap / double-click \u2014 the taps are dispatched as one gesture with proper click counting, which two separate tap calls cannot guarantee.
|
|
126126
126135
|
Use when you need to tap a button, link, or any tappable element on the screen.
|
|
126127
126136
|
Returns { tapped: true, timestampMs }. Fails if the simulator-server / emulator backend / Chromium CDP is not reachable for the given device.
|
|
126128
126137
|
Before tapping, determine the correct coordinates by using discovery tools \u2014 pick by platform: iOS / Android use \`describe\`, \`native-describe-screen\`, or \`debugger-component-tree\`; Chromium uses \`describe\` (the DOM walker), since the native and RN-specific discovery tools don't apply. More information in \`argent-device-interact\` skill`,
|
|
@@ -126140,29 +126149,33 @@ Before tapping, determine the correct coordinates by using discovery tools \u201
|
|
|
126140
126149
|
async execute(services, params) {
|
|
126141
126150
|
const device = resolveDevice(params.udid);
|
|
126142
126151
|
const timestampMs = Date.now();
|
|
126152
|
+
const clickCount = params.clickCount ?? 1;
|
|
126143
126153
|
if (device.platform === "chromium") {
|
|
126144
126154
|
const chromium = services.chromium;
|
|
126145
|
-
await tapChromium(chromium, params.x, params.y);
|
|
126155
|
+
await tapChromium(chromium, params.x, params.y, clickCount);
|
|
126146
126156
|
return { tapped: true, timestampMs };
|
|
126147
126157
|
}
|
|
126148
126158
|
const api = services.simulatorServer;
|
|
126149
|
-
|
|
126150
|
-
|
|
126151
|
-
|
|
126152
|
-
|
|
126153
|
-
|
|
126154
|
-
|
|
126155
|
-
|
|
126156
|
-
|
|
126157
|
-
|
|
126158
|
-
|
|
126159
|
-
|
|
126160
|
-
|
|
126161
|
-
|
|
126162
|
-
|
|
126163
|
-
|
|
126164
|
-
|
|
126165
|
-
|
|
126159
|
+
for (let i = 1; i <= clickCount; i++) {
|
|
126160
|
+
if (i > 1) await sleep2(MULTI_TAP_GAP_MS);
|
|
126161
|
+
sendCommand(api, {
|
|
126162
|
+
cmd: "touch",
|
|
126163
|
+
type: "Down",
|
|
126164
|
+
x: params.x,
|
|
126165
|
+
y: params.y,
|
|
126166
|
+
second_x: null,
|
|
126167
|
+
second_y: null
|
|
126168
|
+
});
|
|
126169
|
+
await sleep2(TAP_HOLD_MS);
|
|
126170
|
+
sendCommand(api, {
|
|
126171
|
+
cmd: "touch",
|
|
126172
|
+
type: "Up",
|
|
126173
|
+
x: params.x,
|
|
126174
|
+
y: params.y,
|
|
126175
|
+
second_x: null,
|
|
126176
|
+
second_y: null
|
|
126177
|
+
});
|
|
126178
|
+
}
|
|
126166
126179
|
return { tapped: true, timestampMs };
|
|
126167
126180
|
}
|
|
126168
126181
|
};
|
|
@@ -133567,7 +133580,7 @@ a prior tap), use individual tool calls instead.
|
|
|
133567
133580
|
|
|
133568
133581
|
Allowed tools and their args (udid is auto-injected, do NOT include it in args):
|
|
133569
133582
|
|
|
133570
|
-
gesture-tap: { x: number, y: number }
|
|
133583
|
+
gesture-tap: { x: number, y: number, clickCount?: number } [ios/android/chromium]
|
|
133571
133584
|
gesture-swipe: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number } [ios/android]
|
|
133572
133585
|
gesture-scroll: { x: number, y: number, deltaX?: number, deltaY?: number, durationMs?: number } [chromium only]
|
|
133573
133586
|
gesture-drag: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number } [chromium only]
|
|
@@ -144544,9 +144557,20 @@ function toYamlStep(step) {
|
|
|
144544
144557
|
case "run":
|
|
144545
144558
|
return { run: step.flow };
|
|
144546
144559
|
case "tap": {
|
|
144547
|
-
|
|
144560
|
+
if (step.selector) {
|
|
144561
|
+
const sel = selectorToYaml(step.selector);
|
|
144562
|
+
return { tap: step.times !== void 0 ? { on: sel, times: step.times } : sel };
|
|
144563
|
+
}
|
|
144564
|
+
const body = { x: step.x, y: step.y };
|
|
144565
|
+
if (step.times !== void 0) body.times = step.times;
|
|
144548
144566
|
return { tap: body };
|
|
144549
144567
|
}
|
|
144568
|
+
case "long-press": {
|
|
144569
|
+
const sel = selectorToYaml(step.selector);
|
|
144570
|
+
return {
|
|
144571
|
+
"long-press": step.duration !== void 0 ? { on: sel, duration: step.duration } : sel
|
|
144572
|
+
};
|
|
144573
|
+
}
|
|
144550
144574
|
case "type": {
|
|
144551
144575
|
const body = {
|
|
144552
144576
|
into: selectorToYaml(step.into),
|
|
@@ -144796,6 +144820,7 @@ var STEP_DIRECTIVE_KEYS = [
|
|
|
144796
144820
|
"run",
|
|
144797
144821
|
"tool",
|
|
144798
144822
|
"tap",
|
|
144823
|
+
"long-press",
|
|
144799
144824
|
"type",
|
|
144800
144825
|
"await",
|
|
144801
144826
|
"assert",
|
|
@@ -144803,6 +144828,79 @@ var STEP_DIRECTIVE_KEYS = [
|
|
|
144803
144828
|
"scroll-to",
|
|
144804
144829
|
"snapshot"
|
|
144805
144830
|
];
|
|
144831
|
+
function parseLongPress(body, entry) {
|
|
144832
|
+
const obj = body !== null && typeof body === "object" ? body : {};
|
|
144833
|
+
if (obj.on !== void 0 || obj.duration !== void 0) {
|
|
144834
|
+
if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
|
|
144835
|
+
badEntry(
|
|
144836
|
+
entry,
|
|
144837
|
+
'the long-press options form takes a nested selector \u2014 e.g. long-press: { on: { text: "Row" }, duration: 1200 }'
|
|
144838
|
+
);
|
|
144839
|
+
}
|
|
144840
|
+
if (!Object.keys(obj).every((k) => k === "on" || k === "duration")) {
|
|
144841
|
+
badEntry(entry, "the long-press options form accepts only { on, duration }");
|
|
144842
|
+
}
|
|
144843
|
+
if (obj.on === void 0) {
|
|
144844
|
+
badEntry(entry, 'long-press needs a target \u2014 e.g. long-press: { on: "Row", duration: 1200 }');
|
|
144845
|
+
}
|
|
144846
|
+
const step = { kind: "long-press", selector: parseSelector(obj.on, "long-press.on") };
|
|
144847
|
+
if (obj.duration !== void 0) {
|
|
144848
|
+
if (typeof obj.duration !== "number" || !Number.isFinite(obj.duration) || obj.duration <= 0) {
|
|
144849
|
+
badEntry(
|
|
144850
|
+
entry,
|
|
144851
|
+
"long-press.duration needs a positive number of milliseconds (e.g. `duration: 1200`)"
|
|
144852
|
+
);
|
|
144853
|
+
}
|
|
144854
|
+
step.duration = obj.duration;
|
|
144855
|
+
}
|
|
144856
|
+
return step;
|
|
144857
|
+
}
|
|
144858
|
+
return { kind: "long-press", selector: parseSelector(body, "long-press") };
|
|
144859
|
+
}
|
|
144860
|
+
function parseTapTimes(raw, entry) {
|
|
144861
|
+
if (raw === void 0) return void 0;
|
|
144862
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1 || raw > 10) {
|
|
144863
|
+
badEntry(entry, "tap.times must be an integer between 1 and 10 (2 = double-tap)");
|
|
144864
|
+
}
|
|
144865
|
+
return raw === 1 ? void 0 : raw;
|
|
144866
|
+
}
|
|
144867
|
+
function parseTap(body, entry) {
|
|
144868
|
+
const obj = body !== null && typeof body === "object" ? body : {};
|
|
144869
|
+
if (obj.x !== void 0 || obj.y !== void 0) {
|
|
144870
|
+
if (obj.on !== void 0 || obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
|
|
144871
|
+
badEntry(entry, "tap takes a selector or x/y coordinates, not both");
|
|
144872
|
+
}
|
|
144873
|
+
if (typeof obj.x !== "number" || typeof obj.y !== "number") {
|
|
144874
|
+
badEntry(entry, "a coordinate tap needs numeric x and y");
|
|
144875
|
+
}
|
|
144876
|
+
if (!Object.keys(obj).every((k) => k === "x" || k === "y" || k === "times")) {
|
|
144877
|
+
badEntry(entry, "a coordinate tap takes only { x, y, times }");
|
|
144878
|
+
}
|
|
144879
|
+
const step = { kind: "tap", x: obj.x, y: obj.y };
|
|
144880
|
+
const times = parseTapTimes(obj.times, entry);
|
|
144881
|
+
if (times !== void 0) step.times = times;
|
|
144882
|
+
return step;
|
|
144883
|
+
}
|
|
144884
|
+
if (obj.on !== void 0 || obj.times !== void 0) {
|
|
144885
|
+
if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
|
|
144886
|
+
badEntry(
|
|
144887
|
+
entry,
|
|
144888
|
+
'the tap options form takes a nested selector \u2014 e.g. tap: { on: { text: "Photo" }, times: 2 }'
|
|
144889
|
+
);
|
|
144890
|
+
}
|
|
144891
|
+
if (!Object.keys(obj).every((k) => k === "on" || k === "times")) {
|
|
144892
|
+
badEntry(entry, "the tap options form accepts only { on, times }");
|
|
144893
|
+
}
|
|
144894
|
+
if (obj.on === void 0) {
|
|
144895
|
+
badEntry(entry, 'tap with times needs a target \u2014 e.g. tap: { on: "Photo", times: 2 }');
|
|
144896
|
+
}
|
|
144897
|
+
const step = { kind: "tap", selector: parseSelector(obj.on, "tap.on") };
|
|
144898
|
+
const times = parseTapTimes(obj.times, entry);
|
|
144899
|
+
if (times !== void 0) step.times = times;
|
|
144900
|
+
return step;
|
|
144901
|
+
}
|
|
144902
|
+
return { kind: "tap", selector: parseSelector(body, "tap") };
|
|
144903
|
+
}
|
|
144806
144904
|
function fromYamlStep(raw) {
|
|
144807
144905
|
const entry = raw;
|
|
144808
144906
|
const kinds = STEP_DIRECTIVE_KEYS.filter((k) => k in entry);
|
|
@@ -144828,22 +144926,9 @@ function fromYamlStep(raw) {
|
|
|
144828
144926
|
if ("echo" in raw) return { kind: "echo", message: String(raw.echo) };
|
|
144829
144927
|
if ("launch" in raw) return { kind: "launch", app: parseLaunch(raw.launch) };
|
|
144830
144928
|
if ("run" in raw) return { kind: "run", flow: String(raw.run) };
|
|
144831
|
-
if ("tap" in raw)
|
|
144832
|
-
|
|
144833
|
-
|
|
144834
|
-
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
144835
|
-
rejectUnknownKeys(raw, obj, [...SELECTOR_KEYS, "x", "y"], "tap");
|
|
144836
|
-
}
|
|
144837
|
-
if (obj.x !== void 0 || obj.y !== void 0) {
|
|
144838
|
-
if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
|
|
144839
|
-
badEntry(raw, "tap takes a selector or x/y coordinates, not both");
|
|
144840
|
-
}
|
|
144841
|
-
if (typeof obj.x !== "number" || typeof obj.y !== "number") {
|
|
144842
|
-
badEntry(raw, "a coordinate tap needs numeric x and y");
|
|
144843
|
-
}
|
|
144844
|
-
return { kind: "tap", x: obj.x, y: obj.y };
|
|
144845
|
-
}
|
|
144846
|
-
return { kind: "tap", selector: parseSelector(body, "tap") };
|
|
144929
|
+
if ("tap" in raw) return parseTap(raw.tap, raw);
|
|
144930
|
+
if ("long-press" in raw) {
|
|
144931
|
+
return parseLongPress(raw["long-press"], raw);
|
|
144847
144932
|
}
|
|
144848
144933
|
if ("type" in raw) {
|
|
144849
144934
|
const body = raw.type;
|
|
@@ -145680,13 +145765,15 @@ If a step was recorded by mistake, edit the .yaml file directly to remove it.`,
|
|
|
145680
145765
|
const runTarget = params.command === RUN_TARGET_COMMAND && params.delayMs === void 0 ? await captureRunTarget(session, args) : void 0;
|
|
145681
145766
|
const strippedArgs = stripDeviceKeys(args);
|
|
145682
145767
|
const isLaunch = params.command === "restart-app" && params.delayMs === void 0 && typeof strippedArgs.bundleId === "string" && Object.keys(strippedArgs).length === 1;
|
|
145768
|
+
const cc = args.clickCount;
|
|
145769
|
+
const tapTimes = isTap && typeof cc === "number" && Number.isInteger(cc) && cc >= 2 && cc <= 10 ? { times: cc } : {};
|
|
145683
145770
|
let step;
|
|
145684
145771
|
let warning;
|
|
145685
145772
|
if (captured?.selector) {
|
|
145686
|
-
step = { kind: "tap", selector: captured.selector };
|
|
145773
|
+
step = { kind: "tap", selector: captured.selector, ...tapTimes };
|
|
145687
145774
|
warning = captured.warning;
|
|
145688
145775
|
} else if (isTap) {
|
|
145689
|
-
step = { kind: "tap", x: args.x, y: args.y };
|
|
145776
|
+
step = { kind: "tap", x: args.x, y: args.y, ...tapTimes };
|
|
145690
145777
|
warning = captured?.warning;
|
|
145691
145778
|
} else if (isLaunch) {
|
|
145692
145779
|
step = { kind: "launch", app: strippedArgs.bundleId };
|
|
@@ -145776,6 +145863,8 @@ You can still edit the .yaml file directly afterwards to remove or reorder steps
|
|
|
145776
145863
|
return `${n}. run: ${step.flow}`;
|
|
145777
145864
|
case "tap":
|
|
145778
145865
|
return `${n}. tap: ${step.selector ? selectorLabel(step.selector) : `(${step.x}, ${step.y})`}`;
|
|
145866
|
+
case "long-press":
|
|
145867
|
+
return `${n}. long-press: ${selectorLabel(step.selector)}`;
|
|
145779
145868
|
case "type":
|
|
145780
145869
|
return `${n}. type: ${selectorLabel(step.into)} \u2190 "${step.text}"`;
|
|
145781
145870
|
case "await":
|
|
@@ -146022,7 +146111,7 @@ function offscreenHint(sel) {
|
|
|
146022
146111
|
return `no visible element matched selector ${describeSelector(sel)} \u2014 if it is off-screen, add a scroll-to step before this one`;
|
|
146023
146112
|
}
|
|
146024
146113
|
async function runDirective(env, step) {
|
|
146025
|
-
if (env.device.platform === "vega" && (step.kind === "tap" || step.kind === "type" || step.kind === "scroll-to")) {
|
|
146114
|
+
if (env.device.platform === "vega" && (step.kind === "tap" || step.kind === "long-press" || step.kind === "type" || step.kind === "scroll-to")) {
|
|
146026
146115
|
return {
|
|
146027
146116
|
ok: false,
|
|
146028
146117
|
reason: `${step.kind} is a touch directive and Vega is remote-driven \u2014 move focus with \`tool: tv-remote\` steps (and type via \`tool: keyboard\`) instead`
|
|
@@ -146031,6 +146120,8 @@ async function runDirective(env, step) {
|
|
|
146031
146120
|
switch (step.kind) {
|
|
146032
146121
|
case "tap":
|
|
146033
146122
|
return runTap(env, step);
|
|
146123
|
+
case "long-press":
|
|
146124
|
+
return runLongPress(env, step);
|
|
146034
146125
|
case "type":
|
|
146035
146126
|
return runType(env, step);
|
|
146036
146127
|
case "await":
|
|
@@ -146058,7 +146149,37 @@ async function runTap(env, target) {
|
|
|
146058
146149
|
} else {
|
|
146059
146150
|
return { ok: false, reason: "tap needs a selector or x/y coordinates" };
|
|
146060
146151
|
}
|
|
146061
|
-
await invokeOnDevice(env, "gesture-tap",
|
|
146152
|
+
await invokeOnDevice(env, "gesture-tap", {
|
|
146153
|
+
...point,
|
|
146154
|
+
...target.times !== void 0 ? { clickCount: target.times } : {}
|
|
146155
|
+
});
|
|
146156
|
+
return { ok: true };
|
|
146157
|
+
}
|
|
146158
|
+
var DEFAULT_LONG_PRESS_MS = 800;
|
|
146159
|
+
async function runLongPress(env, step) {
|
|
146160
|
+
const frame = await waitForFrame(env, step.selector);
|
|
146161
|
+
if (frame === "aborted") return ABORTED_OUTCOME;
|
|
146162
|
+
if (!frame) {
|
|
146163
|
+
return { ok: false, reason: offscreenHint(step.selector) };
|
|
146164
|
+
}
|
|
146165
|
+
const point = getDescribeTapPoint(frame);
|
|
146166
|
+
const duration3 = step.duration ?? DEFAULT_LONG_PRESS_MS;
|
|
146167
|
+
if (env.device.platform === "chromium") {
|
|
146168
|
+
await invokeOnDevice(env, "gesture-drag", {
|
|
146169
|
+
fromX: point.x,
|
|
146170
|
+
fromY: point.y,
|
|
146171
|
+
toX: point.x,
|
|
146172
|
+
toY: point.y,
|
|
146173
|
+
durationMs: duration3
|
|
146174
|
+
});
|
|
146175
|
+
} else {
|
|
146176
|
+
await invokeOnDevice(env, "gesture-custom", {
|
|
146177
|
+
events: [
|
|
146178
|
+
{ type: "Down", x: point.x, y: point.y, delayMs: 0 },
|
|
146179
|
+
{ type: "Up", x: point.x, y: point.y, delayMs: duration3 }
|
|
146180
|
+
]
|
|
146181
|
+
});
|
|
146182
|
+
}
|
|
146062
146183
|
return { ok: true };
|
|
146063
146184
|
}
|
|
146064
146185
|
async function runType(env, step) {
|
|
@@ -148495,8 +148616,9 @@ function createRunFlowTool(registry2) {
|
|
|
148495
148616
|
id: "flow-execute",
|
|
148496
148617
|
description: `Run a saved flow from the .argent/flows/ directory.
|
|
148497
148618
|
Steps run in order: \`launch\` starts an app from scratch (terminate + relaunch) and waits until it is
|
|
148498
|
-
ready; \`tool\` calls dispatch through the registry; \`tap\`/\`type\` resolve a selector to an
|
|
148499
|
-
act on it
|
|
148619
|
+
ready; \`tool\` calls dispatch through the registry; \`tap\`/\`long-press\`/\`type\` resolve a selector to an
|
|
148620
|
+
element and act on it (\`long-press: { on, duration }\` presses and holds); \`scroll-to\` scrolls
|
|
148621
|
+
(momentum-free) until a target is visible; \`await\` waits for a UI
|
|
148500
148622
|
condition; \`wait\` pauses for a fixed number of milliseconds; \`assert\` checks one now; \`snapshot\`
|
|
148501
148623
|
diffs a screenshot against a stored baseline (a missing baseline fails the step \u2014 set updateBaselines
|
|
148502
148624
|
to adopt the current screen); \`echo\` annotates; \`run\` executes a referenced fragment inline.
|
|
@@ -148651,6 +148773,8 @@ function stepTarget(step) {
|
|
|
148651
148773
|
if (step.selector) return selectorLabel2(step.selector);
|
|
148652
148774
|
if (step.x !== void 0 && step.y !== void 0) return `(${step.x}, ${step.y})`;
|
|
148653
148775
|
return void 0;
|
|
148776
|
+
case "long-press":
|
|
148777
|
+
return selectorLabel2(step.selector);
|
|
148654
148778
|
case "type":
|
|
148655
148779
|
return `into ${selectorLabel2(step.into)}`;
|
|
148656
148780
|
case "await":
|
|
@@ -148746,6 +148870,7 @@ async function execLeafStep(state3, step, index, sourceFlow) {
|
|
|
148746
148870
|
return { ...base, status: r.ok ? "pass" : "error", reason: r.reason };
|
|
148747
148871
|
}
|
|
148748
148872
|
case "tap":
|
|
148873
|
+
case "long-press":
|
|
148749
148874
|
case "type":
|
|
148750
148875
|
case "await":
|
|
148751
148876
|
case "assert":
|
package/package.json
CHANGED
|
@@ -20,17 +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
|
|
24
|
-
|
|
|
25
|
-
| `launch`
|
|
26
|
-
| `tap`
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
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 a fragment's steps inline |
|
|
34
35
|
|
|
35
36
|
### Selectors
|
|
36
37
|
|
|
@@ -60,7 +61,7 @@ For a custom poll interval or bundleId, drop to an explicit `- tool: await-ui-el
|
|
|
60
61
|
|
|
61
62
|
### TV targets (Vega)
|
|
62
63
|
|
|
63
|
-
A Vega (Fire TV) device is remote-driven — there is no touch input, so the touch directives (`tap`, `type`, `scroll-to`) fail on it with guidance. Drive focus with `tool: tv-remote` steps and type with `tool: keyboard` instead; everything else (`launch`, `await`, `assert`, `wait`, `snapshot`, `echo`, `run`, selectors) works unchanged — the tree comes from the on-device automation toolkit, which attaches at app launch (the `launch` step waits for it, so a leading `launch` also guarantees selectors resolve).
|
|
64
|
+
A Vega (Fire TV) device is remote-driven — there is no touch input, so the touch directives (`tap`, `long-press`, `type`, `scroll-to`) fail on it with guidance. Drive focus with `tool: tv-remote` steps and type with `tool: keyboard` instead; everything else (`launch`, `await`, `assert`, `wait`, `snapshot`, `echo`, `run`, selectors) works unchanged — the tree comes from the on-device automation toolkit, which attaches at app launch (the `launch` step waits for it, so a leading `launch` also guarantees selectors resolve).
|
|
64
65
|
|
|
65
66
|
```yaml
|
|
66
67
|
steps:
|