@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.
- package/bin/argent-android-devtools-0.3.0.apk +0 -0
- package/bin/darwin/ax-service +0 -0
- package/bin/darwin/tvos-ax-service +0 -0
- package/bin/darwin/tvos-hid-daemon +0 -0
- package/bin/tcp/ax-service +0 -0
- package/dist/cli-cmds.mjs +94 -24
- package/dist/client.js +71 -14
- package/dist/installer.mjs +8 -1
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentExceptionGuard.h +3 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentExceptionGuard.m +11 -3
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Gestures.swift +27 -19
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+Quiescence.swift +95 -0
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/ArgentRunnerSession+TextEntry.swift +2 -2
- package/dist/ios-device-runner/ArgentRunner/ArgentRunnerUITests/RunnerProtocol.swift +5 -5
- package/dist/mcp-server.mjs +75 -17
- package/dist/tool-args.generated.d.ts +16 -8
- package/dist/tool-server.cjs +202 -86
- package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/libKeyboardPatch.dylib +0 -0
- package/dylibs/libNativeDevtoolsIos.dylib +0 -0
- package/dylibs/tcp/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/tcp/libKeyboardPatch.dylib +0 -0
- package/dylibs/tcp/libNativeDevtoolsIos.dylib +0 -0
- package/dylibs/tvos/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/tvos/libKeyboardPatch.dylib +0 -0
- package/dylibs/tvos/libNativeDevtoolsIos.dylib +0 -0
- package/package.json +1 -1
- package/skills/argent-create-e2e/references/live-authoring.md +3 -3
package/dist/tool-server.cjs
CHANGED
|
@@ -1323,7 +1323,31 @@ var init_e2e_dirs = __esm({
|
|
|
1323
1323
|
function canonicalToolId(id) {
|
|
1324
1324
|
return Object.hasOwn(TOOL_ID_ALIASES, id) ? TOOL_ID_ALIASES[id] : id;
|
|
1325
1325
|
}
|
|
1326
|
-
|
|
1326
|
+
function toolParamAliases(tool) {
|
|
1327
|
+
const id = canonicalToolId(tool);
|
|
1328
|
+
return Object.hasOwn(TOOL_PARAM_ALIASES, id) ? TOOL_PARAM_ALIASES[id] : {};
|
|
1329
|
+
}
|
|
1330
|
+
function canonicalToolArgs(tool, args) {
|
|
1331
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
|
|
1332
|
+
const record2 = args;
|
|
1333
|
+
let out;
|
|
1334
|
+
for (const [former, current] of Object.entries(toolParamAliases(tool))) {
|
|
1335
|
+
if (record2[former] === void 0) continue;
|
|
1336
|
+
if (record2[current] !== void 0) throw new ToolParamAliasError(former, current);
|
|
1337
|
+
out ??= { ...record2 };
|
|
1338
|
+
out[current] = out[former];
|
|
1339
|
+
delete out[former];
|
|
1340
|
+
}
|
|
1341
|
+
return out ?? args;
|
|
1342
|
+
}
|
|
1343
|
+
function withFormerParamSpecs(tool, specs) {
|
|
1344
|
+
const former = Object.entries(toolParamAliases(tool));
|
|
1345
|
+
const copies = specs.flatMap(
|
|
1346
|
+
(spec) => spec.path === `\${${spec.target}}` ? former.filter(([, current]) => current === spec.target).map(([name]) => ({ ...spec, target: name, path: `\${${name}}` })) : []
|
|
1347
|
+
);
|
|
1348
|
+
return copies.length > 0 ? [...specs, ...copies] : specs;
|
|
1349
|
+
}
|
|
1350
|
+
var TOOL_ID_ALIASES, TOOL_PARAM_ALIASES, ToolParamAliasError;
|
|
1327
1351
|
var init_tool_ids = __esm({
|
|
1328
1352
|
"../registry/src/tool-ids.ts"() {
|
|
1329
1353
|
"use strict";
|
|
@@ -1336,6 +1360,19 @@ var init_tool_ids = __esm({
|
|
|
1336
1360
|
"flow-read-prerequisite": "e2e-read-prerequisite",
|
|
1337
1361
|
"flow-execute": "e2e-execute"
|
|
1338
1362
|
});
|
|
1363
|
+
TOOL_PARAM_ALIASES = Object.freeze({
|
|
1364
|
+
"e2e-execute": Object.freeze({ flow_path: "e2e_test_path", flow_file: "e2e_test_file" }),
|
|
1365
|
+
"e2e-read-prerequisite": Object.freeze({
|
|
1366
|
+
flow_path: "e2e_test_path",
|
|
1367
|
+
flow_file: "e2e_test_file"
|
|
1368
|
+
})
|
|
1369
|
+
});
|
|
1370
|
+
ToolParamAliasError = class extends Error {
|
|
1371
|
+
constructor(former, current) {
|
|
1372
|
+
super(`Pass ${current} or its former name ${former}, not both.`);
|
|
1373
|
+
this.name = "ToolParamAliasError";
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1339
1376
|
}
|
|
1340
1377
|
});
|
|
1341
1378
|
|
|
@@ -1382,7 +1419,13 @@ async function resolveFlowRelativeFile(anchorDir, target, addressable) {
|
|
|
1382
1419
|
}
|
|
1383
1420
|
function nestedFlowTarget(args) {
|
|
1384
1421
|
if (typeof args !== "object" || args === null || Array.isArray(args)) return void 0;
|
|
1385
|
-
|
|
1422
|
+
let canonical;
|
|
1423
|
+
try {
|
|
1424
|
+
canonical = canonicalToolArgs("e2e-execute", args);
|
|
1425
|
+
} catch {
|
|
1426
|
+
return void 0;
|
|
1427
|
+
}
|
|
1428
|
+
const { name, project_root: projectRoot, e2e_test_path: flowPath } = canonical;
|
|
1386
1429
|
if (flowPath === void 0) {
|
|
1387
1430
|
if (typeof name !== "string" || !FLOW_NAME_PATTERN.test(name)) return void 0;
|
|
1388
1431
|
if (!isResolvedAbsolute(projectRoot)) return void 0;
|
|
@@ -1391,7 +1434,7 @@ function nestedFlowTarget(args) {
|
|
|
1391
1434
|
}
|
|
1392
1435
|
if (name !== void 0 || !isResolvedAbsolute(flowPath)) return void 0;
|
|
1393
1436
|
if (!FLOW_FILE_NAME_PATTERN.test(path6.basename(flowPath))) return void 0;
|
|
1394
|
-
return { kind: "
|
|
1437
|
+
return { kind: "e2e_test_path", path: flowPath };
|
|
1395
1438
|
}
|
|
1396
1439
|
function isResolvedAbsolute(value) {
|
|
1397
1440
|
return typeof value === "string" && path6.isAbsolute(value) && !value.split(/[\\/]+/).includes("..");
|
|
@@ -22444,6 +22487,7 @@ var init_registry = __esm({
|
|
|
22444
22487
|
init_artifacts();
|
|
22445
22488
|
init_errors();
|
|
22446
22489
|
init_failure_codes();
|
|
22490
|
+
init_tool_ids();
|
|
22447
22491
|
init_urn();
|
|
22448
22492
|
init_zod_to_json_schema();
|
|
22449
22493
|
import_node_crypto2 = require("node:crypto");
|
|
@@ -22524,6 +22568,18 @@ var init_registry = __esm({
|
|
|
22524
22568
|
);
|
|
22525
22569
|
this.events.emit("toolInvoked", toolId, toolInvocationId, startedMsg, calledAs);
|
|
22526
22570
|
try {
|
|
22571
|
+
try {
|
|
22572
|
+
params = canonicalToolArgs(toolId, params);
|
|
22573
|
+
} catch (err) {
|
|
22574
|
+
if (!(err instanceof ToolParamAliasError)) throw err;
|
|
22575
|
+
throw new FailureError(`Invalid params for tool "${toolId}": ${err.message}`, {
|
|
22576
|
+
error_code: FAILURE_CODES.TOOL_INPUT_INVALID,
|
|
22577
|
+
failure_stage: "tool_params_parse",
|
|
22578
|
+
failure_area: "registry",
|
|
22579
|
+
error_kind: "validation"
|
|
22580
|
+
});
|
|
22581
|
+
}
|
|
22582
|
+
effectiveParams = params;
|
|
22527
22583
|
if (definition.zodSchema) {
|
|
22528
22584
|
const parsed = definition.zodSchema.safeParse(params ?? {});
|
|
22529
22585
|
if (!parsed.success) {
|
|
@@ -101024,7 +101080,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
101024
101080
|
var SESSION_ID = (0, import_node_crypto3.randomUUID)();
|
|
101025
101081
|
function readCliVersion() {
|
|
101026
101082
|
if (true) {
|
|
101027
|
-
return "0.31.2-next.
|
|
101083
|
+
return "0.31.2-next.3";
|
|
101028
101084
|
}
|
|
101029
101085
|
return "0.0.0";
|
|
101030
101086
|
}
|
|
@@ -131011,7 +131067,9 @@ function createHttpApp(registry2, options) {
|
|
|
131011
131067
|
)
|
|
131012
131068
|
};
|
|
131013
131069
|
if (def.outputHint) entry.outputHint = renderFlagGated(def.outputHint, isFlagEnabled);
|
|
131014
|
-
if (def.fileInputs && def.fileInputs.length > 0)
|
|
131070
|
+
if (def.fileInputs && def.fileInputs.length > 0) {
|
|
131071
|
+
entry.fileInputs = withFormerParamSpecs(def.id, def.fileInputs);
|
|
131072
|
+
}
|
|
131015
131073
|
if (def.alwaysLoad) entry.alwaysLoad = true;
|
|
131016
131074
|
if (def.searchHint) entry.searchHint = renderFlagGated(def.searchHint, isFlagEnabled);
|
|
131017
131075
|
if (def.longRunning) entry.longRunning = true;
|
|
@@ -131087,11 +131145,29 @@ function createHttpApp(registry2, options) {
|
|
|
131087
131145
|
return;
|
|
131088
131146
|
}
|
|
131089
131147
|
}
|
|
131148
|
+
let body;
|
|
131149
|
+
try {
|
|
131150
|
+
body = canonicalToolArgs(def.id, req.body);
|
|
131151
|
+
} catch (err) {
|
|
131152
|
+
if (!(err instanceof ToolParamAliasError)) throw err;
|
|
131153
|
+
emitHttpFailure(
|
|
131154
|
+
{
|
|
131155
|
+
error_code: FAILURE_CODES.HTTP_ZOD_VALIDATION_FAILED,
|
|
131156
|
+
failure_stage: "http_param_alias",
|
|
131157
|
+
failure_area: "http",
|
|
131158
|
+
error_kind: "validation"
|
|
131159
|
+
},
|
|
131160
|
+
req.body
|
|
131161
|
+
);
|
|
131162
|
+
const issues = [{ code: "custom", path: [], message: err.message }];
|
|
131163
|
+
res.status(400).json({ error: JSON.stringify(issues), message: err.message, issues });
|
|
131164
|
+
return;
|
|
131165
|
+
}
|
|
131090
131166
|
let bodyArgs;
|
|
131091
131167
|
let resolvedFileInputs;
|
|
131092
131168
|
let derivedTargets;
|
|
131093
131169
|
try {
|
|
131094
|
-
const resolved = await resolveFileInputs(def,
|
|
131170
|
+
const resolved = await resolveFileInputs(def, body, (id) => {
|
|
131095
131171
|
const entry = uploads.get(id);
|
|
131096
131172
|
if (entry) uploads.delete(id);
|
|
131097
131173
|
return entry;
|
|
@@ -142060,7 +142136,7 @@ var zodSchema17 = external_exports.object({
|
|
|
142060
142136
|
x: external_exports.number().min(0).max(1).describe("Normalized horizontal position 0.0\u20131.0 (left=0, right=1), not pixels"),
|
|
142061
142137
|
y: external_exports.number().min(0).max(1).describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels"),
|
|
142062
142138
|
clickCount: external_exports.number().int().min(1).max(10).optional().describe(
|
|
142063
|
-
"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; on physical iOS 2 is the native double-tap and higher counts
|
|
142139
|
+
"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; on physical iOS 2 is the native double-tap and higher counts loop single taps, which a double-tap recognizer may pair. <flag:harmonyos>On HarmonyOS only 2 is one gesture; 3 or more are separate injections a device round trip apart, which the OS reads as single taps. </flag:harmonyos>Default 1."
|
|
142064
142140
|
)
|
|
142065
142141
|
});
|
|
142066
142142
|
function tapDescription(params, tense) {
|
|
@@ -142133,6 +142209,9 @@ Returns { tapped: true, timestampMs }. On physical iOS, reactivated: true = app
|
|
|
142133
142209
|
On a physical iPhone use \`describe\`; \`native-describe-screen\` is simulator-only.
|
|
142134
142210
|
Before tapping, determine the correct coordinates by using discovery tools. Pick by platform: iOS / Android use \`describe\`, \`native-describe-screen\`, or \`debugger-component-tree\`; Chromium uses \`describe\` (the DOM walker)<flag:harmonyos> and HarmonyOS uses \`describe\` (the \`uitest\` layout dump)</flag:harmonyos>, since the native and RN-specific discovery tools don't apply<flag:harmonyos> to either</flag:harmonyos>. More information in \`argent-device-interact\` skill`,
|
|
142135
142211
|
alwaysLoad: true,
|
|
142212
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142213
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142214
|
+
longRunning: true,
|
|
142136
142215
|
searchHint: "tap press button element device simulator emulator chromium touch down up click",
|
|
142137
142216
|
zodSchema: zodSchema17,
|
|
142138
142217
|
capability: capability9,
|
|
@@ -142260,6 +142339,9 @@ Physical iOS: an edge gesture (back-swipe) needs fromX 0 exactly; durationMs set
|
|
|
142260
142339
|
HarmonyOS: durationMs sets drag speed, clamped to uitest's 200-40000 px/s, so a short slow swipe ends early and a long fast one runs late.</flag:harmonyos>
|
|
142261
142340
|
Pass momentum:false for a momentum-free swipe that lands where the finger lifts (little to no fling at the 300 default), when you need a deterministic scroll distance; it needs durationMs >= 150 and is rejected below that, a shorter ease-out leaving the OS too little wall clock to read the deceleration as a stop. At 150 it lands short of the lift point instead, and 2 of 47 runs still flung backwards. A plain swipe takes any duration up to 10000ms and is delivered as close to the speed it was authored as a 16ms frame allows: below ~32ms the whole travel lands in one or two frames, which the OS flings as hard as it flings anything. Returns { swiped: true, timestampMs }. On physical iOS, reactivated: true = app was re-fronted; re-describe. Fails if the simulator-server / emulator backend<flag:harmonyos>, or \`hdc\` on HarmonyOS,</flag:harmonyos> is not reachable for the given device.`,
|
|
142262
142341
|
alwaysLoad: true,
|
|
142342
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142343
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142344
|
+
longRunning: true,
|
|
142263
142345
|
searchHint: "swipe scroll drag pan gesture device simulator emulator touch move",
|
|
142264
142346
|
zodSchema: zodSchema18,
|
|
142265
142347
|
capability: capability10,
|
|
@@ -142817,6 +142899,9 @@ Example pinch-to-zoom (with interpolate:10 for smoothness):
|
|
|
142817
142899
|
interpolate: 10`,
|
|
142818
142900
|
zodSchema: zodSchema21,
|
|
142819
142901
|
capability: capability13,
|
|
142902
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142903
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142904
|
+
longRunning: true,
|
|
142820
142905
|
// Declare the runner only for a train execute can play. A rejected train must not pay a runner cold start.
|
|
142821
142906
|
services: (params) => {
|
|
142822
142907
|
const device = resolveDevice(params.udid);
|
|
@@ -142904,6 +142989,9 @@ Auto-generates interpolated frames at ~60fps<flag:harmonyos> on iOS and Android;
|
|
|
142904
142989
|
Use when you need to zoom in or out on a map, image, or zoomable view. Physical iPhones play both fingers through the runner as one event stream. Returns { pinched: true, timestampMs }. On physical iOS, reactivated: true = app was re-fronted; re-describe. Fails if the simulator-server / emulator backend<flag:harmonyos>, or \`hdc\` on HarmonyOS,</flag:harmonyos> is not reachable for the given device, or on a physical iPhone when the app is not drawn in portrait.`,
|
|
142905
142990
|
zodSchema: zodSchema22,
|
|
142906
142991
|
capability: capability14,
|
|
142992
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
142993
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
142994
|
+
longRunning: true,
|
|
142907
142995
|
services: (params) => {
|
|
142908
142996
|
const device = resolveDevice(params.udid);
|
|
142909
142997
|
if (device.platform === "harmony") return {};
|
|
@@ -143039,6 +143127,9 @@ Use when you need to rotate a map, image picker, or any rotateable UI element. R
|
|
|
143039
143127
|
Size the orbit with radius, or with radiusX and radiusY together (the pair overrides radius); one half of the pair alone, or none of the three, is rejected.`,
|
|
143040
143128
|
zodSchema: zodSchema23,
|
|
143041
143129
|
capability: capability15,
|
|
143130
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
143131
|
+
// whose abort replays the call: the gesture would land once per replay.
|
|
143132
|
+
longRunning: true,
|
|
143042
143133
|
services: (params) => {
|
|
143043
143134
|
const device = resolveDevice(params.udid);
|
|
143044
143135
|
return isIosPhysicalDevice(device) ? { iosDeviceRunner: iosDeviceRunnerRef(device) } : { simulatorServer: simulatorServerRef(device) };
|
|
@@ -143162,6 +143253,9 @@ Returns { pressed: buttonName }.
|
|
|
143162
143253
|
Fails if the device backend is not reachable: the simulator-server for iOS, <flag:harmonyos>\`hdc\` for HarmonyOS, </flag:harmonyos>or \`adb\` for Android (Android presses are injected with \`adb shell input keyevent\`).`,
|
|
143163
143254
|
zodSchema: zodSchema24,
|
|
143164
143255
|
capability: capability16,
|
|
143256
|
+
// A physical iPhone's first call waits out the runner build, past the MCP fetch timeout,
|
|
143257
|
+
// whose abort replays the call: the press would land once per replay.
|
|
143258
|
+
longRunning: true,
|
|
143165
143259
|
// Declare only the service the resolved path actually consumes. The Android
|
|
143166
143260
|
// path uses `adb`, so a sim-server here would spawn a service the tool never
|
|
143167
143261
|
// uses (up to a 30s ready-wait) and could throw ServiceInitializationError
|
|
@@ -168118,7 +168212,13 @@ function fromYamlStep(raw, blockDepth = 0) {
|
|
|
168118
168212
|
if ("script" in raw) return parseScriptStep(raw, raw.script);
|
|
168119
168213
|
if ("tool" in raw) {
|
|
168120
168214
|
const r = raw;
|
|
168121
|
-
|
|
168215
|
+
let args = r.args ?? {};
|
|
168216
|
+
try {
|
|
168217
|
+
args = canonicalToolArgs(r.tool, args);
|
|
168218
|
+
} catch (err) {
|
|
168219
|
+
badEntry(raw, err instanceof Error ? err.message : String(err));
|
|
168220
|
+
}
|
|
168221
|
+
const step = { kind: "tool", name: r.tool, args };
|
|
168122
168222
|
if (r.delayMs !== void 0) step.delayMs = r.delayMs;
|
|
168123
168223
|
return step;
|
|
168124
168224
|
}
|
|
@@ -168470,7 +168570,7 @@ var flowStartRecordingTool = {
|
|
|
168470
168570
|
},
|
|
168471
168571
|
description: `Start recording a new e2e test, resetting .argent/e2e/<name>.yaml to an empty e2e test and replacing any existing one.
|
|
168472
168572
|
Use when you want to capture a reusable sequence of device interactions for later replay.
|
|
168473
|
-
Returns { message,
|
|
168573
|
+
Returns { message, e2eTestFile, savedTo } and optionally { restarted, discardedSteps } if a live recording of the same e2e test was discarded.
|
|
168474
168574
|
Without a link, it creates that file and fails if the .argent/e2e/ directory cannot be created or the file cannot be written. Over a link (argent link or ARGENT_TOOLS_URL, also to 127.0.0.1), your client writes the file, and \`savedTo\` is its path on your computer, or null when that write failed.
|
|
168475
168575
|
|
|
168476
168576
|
Several e2e tests can be recorded at once, each keyed by the \`name\` + \`project_root\`
|
|
@@ -168541,12 +168641,14 @@ reports for those steps.`,
|
|
|
168541
168641
|
message: `Restarted recording "${params.name}" \u2014 ${lost} was discarded and ` + reset,
|
|
168542
168642
|
restarted: true,
|
|
168543
168643
|
...discardedSteps === void 0 ? {} : { discardedSteps },
|
|
168644
|
+
e2eTestFile: flowFile,
|
|
168544
168645
|
flowFile,
|
|
168545
168646
|
savedTo
|
|
168546
168647
|
};
|
|
168547
168648
|
}
|
|
168548
168649
|
return {
|
|
168549
168650
|
message: `Started recording "${params.name}" e2e test`,
|
|
168651
|
+
e2eTestFile: flowFile,
|
|
168550
168652
|
flowFile,
|
|
168551
168653
|
savedTo
|
|
168552
168654
|
};
|
|
@@ -169698,7 +169800,7 @@ async function runDirective(env, step) {
|
|
|
169698
169800
|
return {
|
|
169699
169801
|
ok: false,
|
|
169700
169802
|
indeterminate: true,
|
|
169701
|
-
reason: `\`await: { idle }\` cannot run on ${env.device.platform}:
|
|
169803
|
+
reason: `\`await: { idle }\` cannot run on ${env.device.platform}: e2e tests have no UI-tree source on this platform, so screen stillness can never be judged. Replace this step with an explicit \`wait:\`.`
|
|
169702
169804
|
};
|
|
169703
169805
|
}
|
|
169704
169806
|
switch (step.kind) {
|
|
@@ -170647,7 +170749,7 @@ var UPLOAD_STAGE_BY_KIND = {
|
|
|
170647
170749
|
};
|
|
170648
170750
|
function nestedFlowRef(args) {
|
|
170649
170751
|
if (typeof args.name === "string") return ` (name: ${args.name})`;
|
|
170650
|
-
if (typeof args.
|
|
170752
|
+
if (typeof args.e2e_test_path === "string") return ` (e2e_test_path: ${args.e2e_test_path})`;
|
|
170651
170753
|
return "";
|
|
170652
170754
|
}
|
|
170653
170755
|
function toolStepFilePaths(registry2, tool, args) {
|
|
@@ -170760,9 +170862,9 @@ async function prepareNestedFlow(registry2, project, toolId, args) {
|
|
|
170760
170862
|
};
|
|
170761
170863
|
const resolved = await resolveFileInputs(
|
|
170762
170864
|
{ fileInputs: registry2.getTool(toolId)?.fileInputs },
|
|
170763
|
-
{ ...args,
|
|
170865
|
+
{ ...args, e2e_test_file: wire }
|
|
170764
170866
|
);
|
|
170765
|
-
const input2 = resolved.fileInputs?.
|
|
170867
|
+
const input2 = resolved.fileInputs?.e2e_test_file;
|
|
170766
170868
|
if (input2) {
|
|
170767
170869
|
input2.canonical = hop.canonical;
|
|
170768
170870
|
input2.spelling = hop.spelling;
|
|
@@ -170797,7 +170899,8 @@ function count(value) {
|
|
|
170797
170899
|
return typeof value === "number" ? value : 0;
|
|
170798
170900
|
}
|
|
170799
170901
|
function flowExecuteOutcome(result) {
|
|
170800
|
-
const
|
|
170902
|
+
const name = result.e2eTest ?? result.flow;
|
|
170903
|
+
const flow = typeof name === "string" ? name : "the composed e2e test";
|
|
170801
170904
|
if (!("steps" in result) && typeof result.notice === "string") {
|
|
170802
170905
|
const prerequisite = typeof result.executionPrerequisite === "string" && result.executionPrerequisite ? `: ${result.executionPrerequisite}` : "";
|
|
170803
170906
|
return {
|
|
@@ -171350,7 +171453,7 @@ var DIRECTIVE_COMMAND_HINTS = {
|
|
|
171350
171453
|
run: {
|
|
171351
171454
|
tool: "e2e-execute",
|
|
171352
171455
|
rewritten: true,
|
|
171353
|
-
rewriteCondition: "when the target resolves as a sibling e2e test in this recording's folder, on the computer that has the project; a `name` that does not is kept as a raw `tool: e2e-execute` step, and
|
|
171456
|
+
rewriteCondition: "when the target resolves as a sibling e2e test in this recording's folder, on the computer that has the project; a `name` that does not is kept as a raw `tool: e2e-execute` step, and an `e2e_test_path` that is not a sibling is refused outright and records nothing"
|
|
171354
171457
|
},
|
|
171355
171458
|
type: { tool: "keyboard", rewritten: false },
|
|
171356
171459
|
await: { tool: AWAIT_UI_ELEMENT_TOOL_ID, rewritten: false },
|
|
@@ -171399,10 +171502,10 @@ function directiveCommandHint(command) {
|
|
|
171399
171502
|
}
|
|
171400
171503
|
var RUN_TARGET_COMMAND = "e2e-execute";
|
|
171401
171504
|
async function rewriteSiblingFlowPath(session, args, project, linked) {
|
|
171402
|
-
const flowPath = args.
|
|
171505
|
+
const flowPath = args.e2e_test_path;
|
|
171403
171506
|
if (typeof flowPath !== "string" || args.name !== void 0) return;
|
|
171404
171507
|
const invalid = (detail) => new FailureError(
|
|
171405
|
-
`Cannot record an e2e-execute of
|
|
171508
|
+
`Cannot record an e2e-execute of e2e_test_path "${flowPath}": ${detail}. e2e_test_path carries no file-input resolution through e2e-add-step's opaque args \u2014 pass name + project_root for an e2e test saved beside the recording, or add a \`run: <relative path>.yaml\` step to the e2e test YAML by hand for a cross-directory target.`,
|
|
171406
171509
|
{
|
|
171407
171510
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
171408
171511
|
failure_stage: "flow_add_step_flow_path",
|
|
@@ -171446,7 +171549,7 @@ async function rewriteSiblingFlowPath(session, args, project, linked) {
|
|
|
171446
171549
|
const suppliedBase = path45.basename(flowPath);
|
|
171447
171550
|
if (linked && project.mode === "host") {
|
|
171448
171551
|
throw new FailureError(
|
|
171449
|
-
`Cannot record an e2e-execute of
|
|
171552
|
+
`Cannot record an e2e-execute of e2e_test_path "${flowPath}": the argent client sent no files with this call, so the recorder cannot check that the file is a sibling of the recording. Update the argent CLI or MCP adapter on the client.`,
|
|
171450
171553
|
{
|
|
171451
171554
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
171452
171555
|
failure_stage: "flow_add_step_flow_path",
|
|
@@ -171461,11 +171564,11 @@ async function rewriteSiblingFlowPath(session, args, project, linked) {
|
|
|
171461
171564
|
} catch (err) {
|
|
171462
171565
|
if (path45.resolve(flowDir) === path45.resolve(flowsDir)) throw err;
|
|
171463
171566
|
throw invalid(
|
|
171464
|
-
`over a link, this argent client sends
|
|
171567
|
+
`over a link, this argent client sends an e2e_test_path only for an e2e test beside the recording; update the argent CLI or MCP adapter on the client (${err instanceof Error ? err.message : String(err)})`
|
|
171465
171568
|
);
|
|
171466
171569
|
}
|
|
171467
171570
|
if (spelling.state !== "listed") {
|
|
171468
|
-
const recovery = spelling.state === "absent" ? `pass the basename exactly as it appears on disk` : spelling.addressable ? `pass
|
|
171571
|
+
const recovery = spelling.state === "absent" ? `pass the basename exactly as it appears on disk` : spelling.addressable ? `pass e2e_test_path with the on-disk basename "${spelling.actual}"` : `rename "${spelling.actual}" to "${suppliedBase}" to record it \u2014 e2e test files must be lowercase .yaml`;
|
|
171469
171572
|
throw invalid(
|
|
171470
171573
|
`the file must be named as it appears on disk \u2014 no directory entry is named "${suppliedBase}"` + (spelling.state === "case_folded" ? ` (this filesystem matched it case-insensitively to "${spelling.actual}")` : "") + `, so the recorded run: step would name an e2e test no case-sensitive checkout can find \u2014 ` + recovery
|
|
171471
171574
|
);
|
|
@@ -171476,7 +171579,7 @@ async function rewriteSiblingFlowPath(session, args, project, linked) {
|
|
|
171476
171579
|
throw invalid(`project_root "${projectRoot}" resolves "${stem}" to "${picked.path}" instead`);
|
|
171477
171580
|
}
|
|
171478
171581
|
}
|
|
171479
|
-
delete args.
|
|
171582
|
+
delete args.e2e_test_path;
|
|
171480
171583
|
args.name = stem;
|
|
171481
171584
|
}
|
|
171482
171585
|
async function captureRunTarget(session, args, project) {
|
|
@@ -171538,7 +171641,7 @@ async function captureRunTarget(session, args, project) {
|
|
|
171538
171641
|
function replayRefusal(issue2, args) {
|
|
171539
171642
|
let hint;
|
|
171540
171643
|
if (issue2.kind === "nested") {
|
|
171541
|
-
hint = nestedFlowTarget(args)?.kind === "name" ? " Over a link, a nested e2e-execute runs only when the argent client sends the e2e test it names with the call. Update the argent CLI or MCP adapter on the client." : " Over a link, a nested e2e-execute runs only when it names its e2e test with a test name in name, has an absolute project_root with no .. segment, and has no
|
|
171644
|
+
hint = nestedFlowTarget(args)?.kind === "name" ? " Over a link, a nested e2e-execute runs only when the argent client sends the e2e test it names with the call. Update the argent CLI or MCP adapter on the client." : " Over a link, a nested e2e-execute runs only when it names its e2e test with a test name in name, has an absolute project_root with no .. segment, and has no e2e_test_path.";
|
|
171542
171645
|
} else if (issue2.fixes.length === 0) {
|
|
171543
171646
|
hint = " A replay without a link runs this step. To keep it, add it to the YAML by hand after e2e-finish-recording.";
|
|
171544
171647
|
} else {
|
|
@@ -171598,6 +171701,10 @@ Returns { message, stepCount, recorded, savedTo }; \`recorded\`, not the status,
|
|
|
171598
171701
|
throw err;
|
|
171599
171702
|
}
|
|
171600
171703
|
const authoredArgs = { ...args };
|
|
171704
|
+
try {
|
|
171705
|
+
args = canonicalToolArgs(command, args);
|
|
171706
|
+
} catch {
|
|
171707
|
+
}
|
|
171601
171708
|
const linked = isLinkedRecorderCall(session, ctx);
|
|
171602
171709
|
const stepFiles = ctx?.fileInputs?.project_root?.members;
|
|
171603
171710
|
const project = stepFiles ? new ClientProjectAccess(stepFiles) : new HostProjectAccess();
|
|
@@ -173303,7 +173410,7 @@ var flowFinishRecordingTool = {
|
|
|
173303
173410
|
completedMsg: ({ params }) => `Saved recorded e2e test ${params.name}`,
|
|
173304
173411
|
failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to finish recording of e2e test ${params.name}: ${failureSignal2.error_code}`
|
|
173305
173412
|
},
|
|
173306
|
-
description: `Finish recording the e2e test named by \`name\` + \`project_root\`, leaving recordings under any other key untouched. Returns { message, path, executionPrerequisite, steps, summary,
|
|
173413
|
+
description: `Finish recording the e2e test named by \`name\` + \`project_root\`, leaving recordings under any other key untouched. Returns { message, path, executionPrerequisite, steps, summary, e2eTestFile, savedTo } - a summary of all recorded steps plus the final YAML. Use when you have added all desired steps and want to finalize the e2e test file. Fails if that e2e test has no recording in progress.
|
|
173307
173414
|
A warning e2e-add-step raised on a recorded \`await-ui-element\` is repeated in \`summary\` as a \`warning:\` line of its own, right below the step it judges, and \`message\` counts them by kind. A warning is repeated only while the step it judges is still identifiable by its number: hand-editing the .yaml during the recording moves the steps, so those warnings are DROPPED rather than pinned on whichever step inherited the number, and \`message\` says how many were dropped. A step that carries a cross-tree warning was re-probed against the runner's tree: read it before converting that wait to \`await:\`/\`assert:\`, which is what the verdict is about and what this moment is for. A step that recorded a wait which did not pass was never probed at all, and its own warning names the CAUSE, because only one of them judges the condition: an unmet wait was read and found false, and it stops the run at replay; a wait whose tree source could not be read, or one that was cancelled, observed nothing and leaves the condition UNKNOWN rather than known-bad. Read those before replaying.
|
|
173308
173415
|
You can still edit the .yaml file directly afterwards to remove or reorder steps.`,
|
|
173309
173416
|
zodSchema: zodSchema70,
|
|
@@ -173341,6 +173448,7 @@ You can still edit the .yaml file directly afterwards to remove or reorder steps
|
|
|
173341
173448
|
executionPrerequisite: flow.executionPrerequisite,
|
|
173342
173449
|
steps: flow.steps.length,
|
|
173343
173450
|
summary,
|
|
173451
|
+
e2eTestFile: flowFile,
|
|
173344
173452
|
flowFile,
|
|
173345
173453
|
savedTo
|
|
173346
173454
|
};
|
|
@@ -175786,15 +175894,15 @@ async function restoreStatusBar(device) {
|
|
|
175786
175894
|
// ../tool-server/src/tools/flows/flow-run.ts
|
|
175787
175895
|
var zodSchema71 = external_exports.object({
|
|
175788
175896
|
name: external_exports.string().optional().describe(
|
|
175789
|
-
'Name of a saved e2e test to run from `.argent/e2e`, or `.argent/flows` when it is not there (e.g. "settings-explore"). Omit when
|
|
175897
|
+
'Name of a saved e2e test to run from `.argent/e2e`, or `.argent/flows` when it is not there (e.g. "settings-explore"). Omit when e2e_test_path is set.'
|
|
175790
175898
|
),
|
|
175791
175899
|
project_root: external_exports.string().describe(
|
|
175792
|
-
"Absolute path to the calling agent's project root: the cwd it is working in. With name, the saved e2e test is read from `.argent/e2e/<name>.yaml`, else `.argent/flows/<name>.yaml`, under this root; with
|
|
175900
|
+
"Absolute path to the calling agent's project root: the cwd it is working in. With name, the saved e2e test is read from `.argent/e2e/<name>.yaml`, else `.argent/flows/<name>.yaml`, under this root; with e2e_test_path, the e2e test, its run: siblings, its script: paths and baselines all resolve beside the YAML instead, so pass the agent's cwd. A script still RUNS in this root whichever source was used."
|
|
175793
175901
|
),
|
|
175794
|
-
|
|
175902
|
+
e2e_test_file: external_exports.string().optional().describe(
|
|
175795
175903
|
"Path to the e2e test .yaml as readable by the tool-server. Internal: the argent client derives it from project_root and name automatically; leave unset."
|
|
175796
175904
|
),
|
|
175797
|
-
|
|
175905
|
+
e2e_test_path: external_exports.string().optional().describe(
|
|
175798
175906
|
"Omit when name is set. Absolute path to an e2e test .yaml on the client. Over a link (argent link or ARGENT_TOOLS_URL), the argent client uploads the file, its run: fragments and the snapshot baselines of each run, and the tool-server runs those copies. Keep run: fragments under project_root or beside the e2e test file. With updateBaselines, the client writes the new baselines when the run ends. The client also uploads each absolute .png or .yaml path that a tool: step takes as a file argument, when the file is under project_root or beside the e2e test file. A tool: e2e-execute step that names an e2e test with name and an absolute project_root without a .. segment runs: the client also uploads that e2e test and its own files. Over a link, the uploaded e2e tests must not have: script: steps; tool: steps that run an e2e test another way or record an e2e test; tool: steps that take a directory, an app or an output directory; tool: steps whose tool builds a file path from several arguments; tool: steps that name a relative path or a file other than .png or .yaml. Without a link, the tool-server reads the files in place and all step kinds run."
|
|
175799
175907
|
),
|
|
175800
175908
|
device: external_exports.string().optional().describe(
|
|
@@ -175810,32 +175918,32 @@ var zodSchema71 = external_exports.object({
|
|
|
175810
175918
|
"Set to true to confirm the execution prerequisite has been met. Required (LLM path) when a fragment defines an executionPrerequisite."
|
|
175811
175919
|
)
|
|
175812
175920
|
}).superRefine((params, ctx) => {
|
|
175813
|
-
if (params.name === void 0 === (params.
|
|
175921
|
+
if (params.name === void 0 === (params.e2e_test_path === void 0)) {
|
|
175814
175922
|
ctx.addIssue({
|
|
175815
175923
|
code: external_exports.ZodIssueCode.custom,
|
|
175816
|
-
message: params.name !== void 0 ? "Pass exactly one e2e test source: name or
|
|
175817
|
-
// The ROOT, not `
|
|
175818
|
-
// path would prefix the message with "`
|
|
175924
|
+
message: params.name !== void 0 ? "Pass exactly one e2e test source: name or e2e_test_path." : "Pass exactly one e2e test source: name or e2e_test_path. e2e-execute needs the e2e test's name in `name` \u2014 it resolves <project_root>/.argent/e2e/<name>.yaml, else .argent/flows/<name>.yaml.",
|
|
175925
|
+
// The ROOT, not `e2e_test_path`: the rule spans both source fields, and a
|
|
175926
|
+
// path would prefix the message with "`e2e_test_path`:".
|
|
175819
175927
|
path: []
|
|
175820
175928
|
});
|
|
175821
175929
|
}
|
|
175822
175930
|
});
|
|
175823
175931
|
var fileInputs4 = [
|
|
175824
175932
|
{
|
|
175825
|
-
target: "
|
|
175826
|
-
path: "${
|
|
175933
|
+
target: "e2e_test_path",
|
|
175934
|
+
path: "${e2e_test_path}",
|
|
175827
175935
|
kind: "file",
|
|
175828
175936
|
optional: true,
|
|
175829
175937
|
unwrapWhenSet: "name",
|
|
175830
175938
|
collect: "flow"
|
|
175831
175939
|
},
|
|
175832
175940
|
{
|
|
175833
|
-
target: "
|
|
175941
|
+
target: "e2e_test_file",
|
|
175834
175942
|
// A client that predates `paths` reads the legacy dir only.
|
|
175835
175943
|
path: "${project_root}/.argent/flows/${name}.yaml",
|
|
175836
175944
|
paths: E2E_FILE_TEMPLATES,
|
|
175837
175945
|
kind: "file",
|
|
175838
|
-
skipWhenSet: "
|
|
175946
|
+
skipWhenSet: "e2e_test_path",
|
|
175839
175947
|
collect: "flow"
|
|
175840
175948
|
}
|
|
175841
175949
|
];
|
|
@@ -176126,8 +176234,8 @@ function deviceEnv(state3) {
|
|
|
176126
176234
|
return { ...state3, device: state3.device };
|
|
176127
176235
|
}
|
|
176128
176236
|
function displayFlowName(params) {
|
|
176129
|
-
const stem = params.
|
|
176130
|
-
return params.name || stem || params.
|
|
176237
|
+
const stem = params.e2e_test_path === void 0 ? void 0 : path50.basename(params.e2e_test_path, ".yaml");
|
|
176238
|
+
return params.name || stem || params.e2e_test_path || "(unspecified)";
|
|
176131
176239
|
}
|
|
176132
176240
|
function* walkSteps(steps, within = "") {
|
|
176133
176241
|
for (const [i, step] of steps.entries()) {
|
|
@@ -176273,7 +176381,9 @@ function assertUploadSelfContained(registry2, flow, closure, origin = {
|
|
|
176273
176381
|
throw new FailureError(
|
|
176274
176382
|
`${origin.subject} is not self-contained, and ${origin.arrival}. The steps below read or write project files, which stay on the client:
|
|
176275
176383
|
` + offending.map((o) => ` - ${o.line}`).join("\n") + `
|
|
176276
|
-
Run the e2e test on the computer that runs the tool-server, with no link and no ARGENT_TOOLS_URL, so that the tool-server reads the files in place.${uploadUpdateHint(servable)}
|
|
176384
|
+
Run the e2e test on the computer that runs the tool-server, with no link and no ARGENT_TOOLS_URL, so that the tool-server reads the files in place.${uploadUpdateHint(servable)}` + // An older client lists e2e_test_path from this schema but collects the
|
|
176385
|
+
// files only for flow_path.
|
|
176386
|
+
(servable.length > 0 ? " A client older than the e2e_test_path param sends those files for a test passed as flow_path." : "") + toolFilePathHints(toolFileFixes),
|
|
176277
176387
|
{
|
|
176278
176388
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
176279
176389
|
failure_stage: UPLOAD_STAGE_BY_KIND[offending[0].kind],
|
|
@@ -176294,7 +176404,7 @@ function createRunFlowTool(registry2) {
|
|
|
176294
176404
|
description: `Run a saved YAML e2e test end to end. Use when
|
|
176295
176405
|
asked to replay a recorded path, re-run a QA regression, or check that a known journey still passes; for a
|
|
176296
176406
|
one-off interaction use the gesture tools instead, and to author an e2e test use e2e-start-recording. Pass
|
|
176297
|
-
exactly one e2e test source: name (under project_root) or
|
|
176407
|
+
exactly one e2e test source: name (under project_root) or e2e_test_path.
|
|
176298
176408
|
Returns a per-step report: the first failure stops the run and the rest report as skipped.`,
|
|
176299
176409
|
longRunning: true,
|
|
176300
176410
|
zodSchema: zodSchema71,
|
|
@@ -176305,11 +176415,11 @@ Returns a per-step report: the first failure stops the run and the rest report a
|
|
|
176305
176415
|
const signal = ctx?.signal;
|
|
176306
176416
|
const { filePath, flowName, viaUpload, shadowed } = await resolveFlowSource(
|
|
176307
176417
|
params,
|
|
176308
|
-
ctx?.fileInputs?.
|
|
176309
|
-
ctx?.fileInputs?.
|
|
176418
|
+
ctx?.fileInputs?.e2e_test_file,
|
|
176419
|
+
ctx?.fileInputs?.e2e_test_path
|
|
176310
176420
|
);
|
|
176311
176421
|
const warning = shadowed ? { warning: e2eShadowWarning(filePath, shadowed) } : {};
|
|
176312
|
-
const rootInput = viaUpload ? ctx?.fileInputs?.
|
|
176422
|
+
const rootInput = viaUpload ? ctx?.fileInputs?.e2e_test_path ?? ctx?.fileInputs?.e2e_test_file : void 0;
|
|
176313
176423
|
const clientProject = rootInput?.members ? new ClientProjectAccess(rootInput.members) : void 0;
|
|
176314
176424
|
const project = clientProject ?? new HostProjectAccess();
|
|
176315
176425
|
const canonicalPath = await canonicalFlowPath(filePath);
|
|
@@ -176369,6 +176479,7 @@ Returns a per-step report: the first failure stops the run and the rest report a
|
|
|
176369
176479
|
}
|
|
176370
176480
|
if (flow.executionPrerequisite && !params.prerequisiteAcknowledged) {
|
|
176371
176481
|
return {
|
|
176482
|
+
e2eTest: flowName,
|
|
176372
176483
|
flow: flowName,
|
|
176373
176484
|
notice: "This e2e test has an execution prerequisite that must be fulfilled before it can run. Verify the prerequisite is met and call e2e-execute again with prerequisiteAcknowledged set to true.",
|
|
176374
176485
|
executionPrerequisite: flow.executionPrerequisite,
|
|
@@ -176622,6 +176733,7 @@ function summarize(flowName, deviceId, executionPrerequisite, steps, aborted2, t
|
|
|
176622
176733
|
else errored++;
|
|
176623
176734
|
}
|
|
176624
176735
|
return {
|
|
176736
|
+
e2eTest: flowName,
|
|
176625
176737
|
flow: flowName,
|
|
176626
176738
|
device: deviceId,
|
|
176627
176739
|
executionPrerequisite,
|
|
@@ -176640,6 +176752,7 @@ function summarize(flowName, deviceId, executionPrerequisite, steps, aborted2, t
|
|
|
176640
176752
|
};
|
|
176641
176753
|
}
|
|
176642
176754
|
function pushReport(state3, report) {
|
|
176755
|
+
if (report.e2eTest !== void 0) report.flow = report.e2eTest;
|
|
176643
176756
|
state3.reports.push(report);
|
|
176644
176757
|
state3.onStepReport?.(report);
|
|
176645
176758
|
}
|
|
@@ -176676,7 +176789,7 @@ async function execSteps(state3, steps, scope) {
|
|
|
176676
176789
|
index,
|
|
176677
176790
|
kind: step.kind,
|
|
176678
176791
|
status: "skip",
|
|
176679
|
-
|
|
176792
|
+
e2eTest: stepFlow(step, scope),
|
|
176680
176793
|
target: stepTarget(step),
|
|
176681
176794
|
...depthOf(scope),
|
|
176682
176795
|
...stopReason ? { reason: stopReason } : {},
|
|
@@ -176694,7 +176807,7 @@ async function execSteps(state3, steps, scope) {
|
|
|
176694
176807
|
index,
|
|
176695
176808
|
kind: step.kind,
|
|
176696
176809
|
status: "error",
|
|
176697
|
-
|
|
176810
|
+
e2eTest: scopeFlow(scope),
|
|
176698
176811
|
target: stepTarget(step),
|
|
176699
176812
|
...depthOf(scope),
|
|
176700
176813
|
reason: `step needs a device but the e2e test was resolved as device-free \u2014 pass an explicit device`
|
|
@@ -176710,7 +176823,7 @@ async function execSteps(state3, steps, scope) {
|
|
|
176710
176823
|
kind: step.kind,
|
|
176711
176824
|
status: "skip",
|
|
176712
176825
|
reason: "run aborted",
|
|
176713
|
-
|
|
176826
|
+
e2eTest: stepFlow(step, scope),
|
|
176714
176827
|
target: stepTarget(step),
|
|
176715
176828
|
...depthOf(scope),
|
|
176716
176829
|
...step.kind === "echo" ? { message: step.message } : {}
|
|
@@ -176762,7 +176875,7 @@ function reportBlockSkipped(state3, steps, scope, reason2) {
|
|
|
176762
176875
|
kind: step.kind,
|
|
176763
176876
|
status: "skip",
|
|
176764
176877
|
reason: reason2,
|
|
176765
|
-
|
|
176878
|
+
e2eTest: stepFlow(step, scope),
|
|
176766
176879
|
target: stepTarget(step),
|
|
176767
176880
|
...depthOf(scope),
|
|
176768
176881
|
...step.kind === "echo" ? { message: step.message } : {}
|
|
@@ -176788,7 +176901,7 @@ async function execWhenStep(state3, step, scope) {
|
|
|
176788
176901
|
const marker = {
|
|
176789
176902
|
index,
|
|
176790
176903
|
kind: "when",
|
|
176791
|
-
|
|
176904
|
+
e2eTest: scopeFlow(scope),
|
|
176792
176905
|
target,
|
|
176793
176906
|
...depthOf(scope)
|
|
176794
176907
|
};
|
|
@@ -176848,7 +176961,7 @@ async function execRunStep(state3, step, scope) {
|
|
|
176848
176961
|
index,
|
|
176849
176962
|
kind: "run",
|
|
176850
176963
|
status: "error",
|
|
176851
|
-
|
|
176964
|
+
e2eTest: display,
|
|
176852
176965
|
target,
|
|
176853
176966
|
reason: reason2,
|
|
176854
176967
|
...depthOf(scope),
|
|
@@ -176896,7 +177009,7 @@ async function execRunStep(state3, step, scope) {
|
|
|
176896
177009
|
index,
|
|
176897
177010
|
kind: "run",
|
|
176898
177011
|
status: "pass",
|
|
176899
|
-
|
|
177012
|
+
e2eTest: display,
|
|
176900
177013
|
target,
|
|
176901
177014
|
...depthOf(scope),
|
|
176902
177015
|
durationMs: Date.now() - startedAt
|
|
@@ -176950,7 +177063,7 @@ async function execLeafStep(state3, step, index, scope) {
|
|
|
176950
177063
|
const base = {
|
|
176951
177064
|
index,
|
|
176952
177065
|
kind: step.kind,
|
|
176953
|
-
|
|
177066
|
+
e2eTest: scopeFlow(scope),
|
|
176954
177067
|
target: stepTarget(step),
|
|
176955
177068
|
...depthOf(scope)
|
|
176956
177069
|
};
|
|
@@ -177158,7 +177271,7 @@ function errMsg4(err) {
|
|
|
177158
177271
|
return err instanceof Error ? err.message : String(err);
|
|
177159
177272
|
}
|
|
177160
177273
|
function assertFlowPathShape(flowPath) {
|
|
177161
|
-
const windows = windowsPathRefusal("
|
|
177274
|
+
const windows = windowsPathRefusal("e2e_test_path", flowPath);
|
|
177162
177275
|
if (windows) {
|
|
177163
177276
|
throw new FailureError(windows, {
|
|
177164
177277
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
@@ -177169,7 +177282,7 @@ function assertFlowPathShape(flowPath) {
|
|
|
177169
177282
|
}
|
|
177170
177283
|
if (!path50.isAbsolute(flowPath)) {
|
|
177171
177284
|
throw new FailureError(
|
|
177172
|
-
`Invalid
|
|
177285
|
+
`Invalid e2e_test_path "${flowPath}": e2e test paths must be absolute \u2014 a relative path is resolved against the tool server's working directory, not the caller's. Pass the absolute path to the e2e test's YAML.`,
|
|
177173
177286
|
{
|
|
177174
177287
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
177175
177288
|
failure_stage: "flow_path_absolute",
|
|
@@ -177180,7 +177293,7 @@ function assertFlowPathShape(flowPath) {
|
|
|
177180
177293
|
}
|
|
177181
177294
|
if (flowPath.split(/[\\/]+/).includes("..")) {
|
|
177182
177295
|
throw new FailureError(
|
|
177183
|
-
`Invalid
|
|
177296
|
+
`Invalid e2e_test_path "${flowPath}": e2e test paths must not contain ".." segments \u2014 a ".." after a symlinked directory can name a different file than the spelling suggests, and the argent client always sends fully resolved paths. Pass the fully resolved absolute path to the e2e test's YAML.`,
|
|
177184
177297
|
{
|
|
177185
177298
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
177186
177299
|
failure_stage: "flow_path_dotdot",
|
|
@@ -177195,7 +177308,7 @@ function flowNameOf(clientPath) {
|
|
|
177195
177308
|
const bareExtension = path50.basename(clientPath).toLowerCase() === ".yaml";
|
|
177196
177309
|
if (!bareExtension && clientExt !== ".yaml") {
|
|
177197
177310
|
const detail = clientExt.toLowerCase() === ".yaml" ? `e2e test files must use the lowercase .yaml extension, not "${clientExt}".` : `e2e test files must use the .yaml extension.`;
|
|
177198
|
-
throw new FailureError(`Invalid
|
|
177311
|
+
throw new FailureError(`Invalid e2e_test_path "${clientPath}": ${detail}`, {
|
|
177199
177312
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
177200
177313
|
failure_stage: "flow_path_extension",
|
|
177201
177314
|
failure_area: "tool_server",
|
|
@@ -177208,9 +177321,9 @@ function flowNameOf(clientPath) {
|
|
|
177208
177321
|
}
|
|
177209
177322
|
function flowPathCasingError(clientPath, spelling) {
|
|
177210
177323
|
const suppliedBase = path50.basename(clientPath);
|
|
177211
|
-
const recovery = spelling.state === "absent" ? `Pass the basename exactly as it appears on disk.` : spelling.addressable ? `Pass
|
|
177324
|
+
const recovery = spelling.state === "absent" ? `Pass the basename exactly as it appears on disk.` : spelling.addressable ? `Pass e2e_test_path with the on-disk basename "${spelling.actual}".` : `Rename "${spelling.actual}" to "${suppliedBase}" to run it \u2014 e2e test files must be lowercase .yaml.`;
|
|
177212
177325
|
return new FailureError(
|
|
177213
|
-
`Invalid
|
|
177326
|
+
`Invalid e2e_test_path "${clientPath}": the file must be named as it appears on disk \u2014 this filesystem matched "${suppliedBase}" case-insensitively` + (spelling.state === "case_folded" ? ` to "${spelling.actual}"` : "") + `, so the e2e test name (which keys the report and __baselines__/) would be one no directory entry carries. ${recovery}`,
|
|
177214
177327
|
{
|
|
177215
177328
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
177216
177329
|
failure_stage: "flow_path_casing",
|
|
@@ -177220,8 +177333,8 @@ function flowPathCasingError(clientPath, spelling) {
|
|
|
177220
177333
|
);
|
|
177221
177334
|
}
|
|
177222
177335
|
async function resolveFlowSource(params, fileInput, flowPathInput) {
|
|
177223
|
-
if (params.name === void 0 === (params.
|
|
177224
|
-
throw new FailureError("Pass exactly one e2e test source: name or
|
|
177336
|
+
if (params.name === void 0 === (params.e2e_test_path === void 0)) {
|
|
177337
|
+
throw new FailureError("Pass exactly one e2e test source: name or e2e_test_path.", {
|
|
177225
177338
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
177226
177339
|
failure_stage: "flow_source",
|
|
177227
177340
|
failure_area: "tool_server",
|
|
@@ -177229,16 +177342,16 @@ async function resolveFlowSource(params, fileInput, flowPathInput) {
|
|
|
177229
177342
|
});
|
|
177230
177343
|
}
|
|
177231
177344
|
assertValidProjectRoot(params.project_root);
|
|
177232
|
-
if (params.
|
|
177345
|
+
if (params.e2e_test_path !== void 0) {
|
|
177233
177346
|
if (flowPathInput?.viaUpload) {
|
|
177234
177347
|
const clientPath2 = flowPathInput.clientPath;
|
|
177235
177348
|
assertFlowPathShape(clientPath2);
|
|
177236
|
-
return { filePath: params.
|
|
177349
|
+
return { filePath: params.e2e_test_path, flowName: flowNameOf(clientPath2), viaUpload: true };
|
|
177237
177350
|
}
|
|
177238
|
-
const isVerifiedHostPath = flowPathInput?.presentOnHost === true && flowPathInput.statVerified === true && path50.resolve(params.
|
|
177351
|
+
const isVerifiedHostPath = flowPathInput?.presentOnHost === true && flowPathInput.statVerified === true && path50.resolve(params.e2e_test_path) === path50.resolve(flowPathInput.clientPath);
|
|
177239
177352
|
if (!isVerifiedHostPath) {
|
|
177240
177353
|
throw new FailureError(
|
|
177241
|
-
`Invalid
|
|
177354
|
+
`Invalid e2e_test_path "${params.e2e_test_path}": explicit e2e test paths must be supplied through the e2e_test_path file-input boundary. Pass the client-local path and let the argent client resolve it.`,
|
|
177242
177355
|
{
|
|
177243
177356
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
177244
177357
|
failure_stage: "flow_path_boundary",
|
|
@@ -177247,24 +177360,26 @@ async function resolveFlowSource(params, fileInput, flowPathInput) {
|
|
|
177247
177360
|
}
|
|
177248
177361
|
);
|
|
177249
177362
|
}
|
|
177250
|
-
assertFlowPathShape(params.
|
|
177363
|
+
assertFlowPathShape(params.e2e_test_path);
|
|
177251
177364
|
const clientPath = flowPathInput.clientPath;
|
|
177252
177365
|
const flowName2 = flowNameOf(clientPath);
|
|
177253
177366
|
const spelling = await classifyOnDiskSpelling(
|
|
177254
|
-
path50.dirname(params.
|
|
177367
|
+
path50.dirname(params.e2e_test_path),
|
|
177255
177368
|
path50.basename(clientPath)
|
|
177256
177369
|
);
|
|
177257
177370
|
if (spelling.state !== "listed") throw flowPathCasingError(clientPath, spelling);
|
|
177258
|
-
return { filePath: params.
|
|
177371
|
+
return { filePath: params.e2e_test_path, flowName: flowName2, viaUpload: false };
|
|
177259
177372
|
}
|
|
177260
177373
|
const flowName = params.name;
|
|
177261
177374
|
assertSafeFlowName(flowName);
|
|
177262
177375
|
const candidates = e2eFileCandidates(params.project_root, flowName);
|
|
177263
|
-
if (params.
|
|
177264
|
-
return { filePath: params.
|
|
177265
|
-
if (params.
|
|
177376
|
+
if (params.e2e_test_file && fileInput?.viaUpload)
|
|
177377
|
+
return { filePath: params.e2e_test_file, flowName, viaUpload: true };
|
|
177378
|
+
if (params.e2e_test_file && (!path50.isAbsolute(params.e2e_test_file) || params.e2e_test_file.split(/[\\/]+/).includes("..") || !candidates.some(
|
|
177379
|
+
(candidate) => path50.resolve(candidate) === path50.resolve(params.e2e_test_file)
|
|
177380
|
+
))) {
|
|
177266
177381
|
throw new FailureError(
|
|
177267
|
-
`Invalid
|
|
177382
|
+
`Invalid e2e_test_file "${params.e2e_test_file}": it must resolve to the e2e test's path under the project root ("${candidates.join('" or "')}"). e2e_test_file is internal \u2014 leave it unset and pass project_root + name.`,
|
|
177268
177383
|
{
|
|
177269
177384
|
error_code: FAILURE_CODES.FLOW_FILE_INVALID,
|
|
177270
177385
|
failure_stage: "flow_file_containment",
|
|
@@ -177274,8 +177389,8 @@ async function resolveFlowSource(params, fileInput, flowPathInput) {
|
|
|
177274
177389
|
);
|
|
177275
177390
|
}
|
|
177276
177391
|
const picked = await pickE2eFile(candidates);
|
|
177277
|
-
const filePath = params.
|
|
177278
|
-
const spellingDirs = params.
|
|
177392
|
+
const filePath = params.e2e_test_file || picked.path;
|
|
177393
|
+
const spellingDirs = params.e2e_test_file || picked.exists ? [path50.dirname(filePath)] : candidates.map((candidate) => path50.dirname(candidate));
|
|
177279
177394
|
for (const dir of spellingDirs) {
|
|
177280
177395
|
const spelling = await classifyOnDiskSpelling(dir, `${flowName}.yaml`);
|
|
177281
177396
|
if (spelling.state === "case_folded") throw flowNameCasingError(flowName, spelling);
|
|
@@ -177290,22 +177405,22 @@ var fs66 = __toESM(require("node:fs/promises"));
|
|
|
177290
177405
|
init_src2();
|
|
177291
177406
|
var zodSchema72 = external_exports.object({
|
|
177292
177407
|
name: external_exports.string().optional().describe(
|
|
177293
|
-
'Name of a saved e2e test to inspect from `.argent/e2e`, or `.argent/flows` when it is not there (e.g. "settings-explore"). Omit when
|
|
177408
|
+
'Name of a saved e2e test to inspect from `.argent/e2e`, or `.argent/flows` when it is not there (e.g. "settings-explore"). Omit when e2e_test_path is set.'
|
|
177294
177409
|
),
|
|
177295
177410
|
project_root: external_exports.string().describe(
|
|
177296
|
-
"Absolute path to the calling agent's project root: the cwd it is working in. With name, the saved e2e test is read from `.argent/e2e/<name>.yaml`, else `.argent/flows/<name>.yaml`, under this root; with
|
|
177411
|
+
"Absolute path to the calling agent's project root: the cwd it is working in. With name, the saved e2e test is read from `.argent/e2e/<name>.yaml`, else `.argent/flows/<name>.yaml`, under this root; with e2e_test_path, the prerequisite is read from that YAML instead, so pass the agent's cwd."
|
|
177297
177412
|
),
|
|
177298
|
-
|
|
177413
|
+
e2e_test_file: external_exports.string().optional().describe(
|
|
177299
177414
|
"Path to the e2e test .yaml as readable by the tool-server. Internal: the argent client derives it from project_root and name automatically; leave unset."
|
|
177300
177415
|
),
|
|
177301
|
-
|
|
177416
|
+
e2e_test_path: external_exports.string().optional().describe(
|
|
177302
177417
|
"Omit when name is set. Absolute path to an e2e test .yaml on the client. Over a link (argent link or ARGENT_TOOLS_URL), the argent client uploads the file and the tool-server reads the uploaded copy; without a link, the tool-server reads the file in place. Pass the path through the argent client; a raw path that the file-input boundary did not verify is rejected. Pass the same e2e test source here as to e2e-execute, so the prerequisite you read belongs to the e2e test that will run."
|
|
177303
177418
|
)
|
|
177304
177419
|
}).superRefine((params, ctx) => {
|
|
177305
|
-
if (params.name === void 0 === (params.
|
|
177420
|
+
if (params.name === void 0 === (params.e2e_test_path === void 0)) {
|
|
177306
177421
|
ctx.addIssue({
|
|
177307
177422
|
code: external_exports.ZodIssueCode.custom,
|
|
177308
|
-
message: params.name !== void 0 ? "Pass exactly one e2e test source: name or
|
|
177423
|
+
message: params.name !== void 0 ? "Pass exactly one e2e test source: name or e2e_test_path." : "Pass exactly one e2e test source: name or e2e_test_path. e2e-read-prerequisite needs the e2e test's name in `name` \u2014 it resolves <project_root>/.argent/e2e/<name>.yaml, else .argent/flows/<name>.yaml.",
|
|
177309
177424
|
// The ROOT, matching flow-execute: the rule spans both source fields,
|
|
177310
177425
|
// so it must not be anchored on one of them.
|
|
177311
177426
|
path: []
|
|
@@ -177314,18 +177429,18 @@ var zodSchema72 = external_exports.object({
|
|
|
177314
177429
|
});
|
|
177315
177430
|
var fileInputs5 = [
|
|
177316
177431
|
{
|
|
177317
|
-
target: "
|
|
177318
|
-
path: "${
|
|
177432
|
+
target: "e2e_test_path",
|
|
177433
|
+
path: "${e2e_test_path}",
|
|
177319
177434
|
kind: "file",
|
|
177320
177435
|
optional: true,
|
|
177321
177436
|
unwrapWhenSet: "name"
|
|
177322
177437
|
},
|
|
177323
177438
|
{
|
|
177324
|
-
target: "
|
|
177439
|
+
target: "e2e_test_file",
|
|
177325
177440
|
path: "${project_root}/.argent/flows/${name}.yaml",
|
|
177326
177441
|
paths: E2E_FILE_TEMPLATES,
|
|
177327
177442
|
kind: "file",
|
|
177328
|
-
skipWhenSet: "
|
|
177443
|
+
skipWhenSet: "e2e_test_path"
|
|
177329
177444
|
}
|
|
177330
177445
|
];
|
|
177331
177446
|
var flowReadPrerequisiteTool = {
|
|
@@ -177336,8 +177451,8 @@ var flowReadPrerequisiteTool = {
|
|
|
177336
177451
|
completedMsg: () => "Read e2e test prerequisite",
|
|
177337
177452
|
failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to read e2e test prerequisite: ${failureSignal2.error_code}`
|
|
177338
177453
|
},
|
|
177339
|
-
description: `Read the execution prerequisite of an e2e test without running it: a saved e2e test from the .argent/e2e/ directory (or the older .argent/flows/), or an explicit boundary-managed
|
|
177340
|
-
Returns {
|
|
177454
|
+
description: `Read the execution prerequisite of an e2e test without running it: a saved e2e test from the .argent/e2e/ directory (or the older .argent/flows/), or an explicit boundary-managed e2e_test_path.
|
|
177455
|
+
Returns { e2eTest, executionPrerequisite, warning? }: the logical name, plus the precondition its author recorded
|
|
177341
177456
|
verbatim. Empty when none was declared, which is always so for a self-contained scenario: one opening
|
|
177342
177457
|
on a launch may declare no prerequisite, because it builds its own start state.
|
|
177343
177458
|
Use when deciding whether the device already sits where a fragment expects it (correct app foregrounded,
|
|
@@ -177345,7 +177460,7 @@ correct account, correct screen) before committing to a run, or when relaying th
|
|
|
177345
177460
|
Touches no device: nothing is launched, tapped, dispatched or torn down, and no simulator or emulator
|
|
177346
177461
|
needs booting, so calling this costs nothing but a file read.
|
|
177347
177462
|
Fails if the e2e test file does not exist.
|
|
177348
|
-
Address the e2e test exactly as you will address it in e2e-execute: name or
|
|
177463
|
+
Address the e2e test exactly as you will address it in e2e-execute: name or e2e_test_path, one and only one; supplying both or neither is rejected. The name goes in \`name\`, which resolves <project_root>/.argent/e2e/<name>.yaml, else .argent/flows/<name>.yaml.`,
|
|
177349
177464
|
readOnly: true,
|
|
177350
177465
|
zodSchema: zodSchema72,
|
|
177351
177466
|
fileInputs: fileInputs5,
|
|
@@ -177353,11 +177468,12 @@ Address the e2e test exactly as you will address it in e2e-execute: name or flow
|
|
|
177353
177468
|
async execute(_services, params, ctx) {
|
|
177354
177469
|
const { filePath, flowName, shadowed } = await resolveFlowSource(
|
|
177355
177470
|
params,
|
|
177356
|
-
ctx?.fileInputs?.
|
|
177357
|
-
ctx?.fileInputs?.
|
|
177471
|
+
ctx?.fileInputs?.e2e_test_file,
|
|
177472
|
+
ctx?.fileInputs?.e2e_test_path
|
|
177358
177473
|
);
|
|
177359
177474
|
const flow = parseFlow(await fs66.readFile(filePath, "utf8"));
|
|
177360
177475
|
return {
|
|
177476
|
+
e2eTest: flowName,
|
|
177361
177477
|
flow: flowName,
|
|
177362
177478
|
executionPrerequisite: flow.executionPrerequisite,
|
|
177363
177479
|
...shadowed ? { warning: e2eShadowWarning(filePath, shadowed) } : {}
|