@riddledc/riddle-proof 0.7.60 → 0.7.62
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/README.md +13 -1
- package/dist/{chunk-FLQ4HHTT.js → chunk-MIHU2AWC.js} +260 -2
- package/dist/cli.cjs +260 -2
- package/dist/cli.js +1 -1
- package/dist/index.cjs +260 -2
- package/dist/index.js +1 -1
- package/dist/profile.cjs +260 -2
- package/dist/profile.d.cts +8 -1
- package/dist/profile.d.ts +8 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,7 +240,7 @@ where the second request must carry newer state.
|
|
|
240
240
|
`target.setup_actions` is optional. Use it when the meaningful proof surface
|
|
241
241
|
appears only after a picker, tab, login stub, storage seed, form fill,
|
|
242
242
|
transport control, or other bounded interaction. Supported setup actions are
|
|
243
|
-
`click`, `press`, `fill`, `set_input_value`, `assert_text_visible`,
|
|
243
|
+
`click`, `drag`, `press`, `fill`, `set_input_value`, `assert_text_visible`,
|
|
244
244
|
`assert_text_absent`, `assert_selector_count`, `assert_window_value`,
|
|
245
245
|
`assert_window_number`, `local_storage`, `session_storage`, `clear_storage`, `wait`,
|
|
246
246
|
`wait_for_selector`, `wait_for_text`, and `window_call`; a failed setup action
|
|
@@ -253,6 +253,12 @@ stable enough for Playwright's default click actionability checks. Use `press`
|
|
|
253
253
|
with a Playwright key name, such as `Enter`, `Space`, or `ArrowLeft`,
|
|
254
254
|
when a route's intended browser control is keyboard-driven; omit `selector` for
|
|
255
255
|
a page-level key press, or provide `selector` to press against a focused element.
|
|
256
|
+
Use `drag` for pointer-driven controls such as canvas launch areas, sliders, or
|
|
257
|
+
drag-to-aim games. Provide `selector`, `from_x`, `from_y`, `to_x`, and `to_y`;
|
|
258
|
+
coordinates are element-relative pixels by default. Set `coordinate_mode:
|
|
259
|
+
"ratio"` to make coordinates relative to the target element size, for example
|
|
260
|
+
`from_x: 0.5, from_y: 0.5, to_x: 0.2, to_y: 0.5`. Optional `steps` and
|
|
261
|
+
`duration_ms` control how gradually the pointer moves before release.
|
|
256
262
|
Use setup assertions when the pre-click or pre-navigation state is part of the contract,
|
|
257
263
|
for example a fresh row must be present, stale copy must be absent, exactly one
|
|
258
264
|
source link must exist before clicking into the final route, or a canvas app's
|
|
@@ -278,6 +284,12 @@ and setup assertions then execute in that frame context and record
|
|
|
278
284
|
Use `frame_index` / `frameIndex` when more than one matching iframe is present;
|
|
279
285
|
it defaults to the first frame.
|
|
280
286
|
|
|
287
|
+
Profiles with setup actions also include a compact
|
|
288
|
+
`setup_actions_succeeded.evidence.setup_summary`. The summary groups each
|
|
289
|
+
viewport's final route, final URL, action counts, clicked targets, iframe URLs,
|
|
290
|
+
compact text samples, and failures so setup-heavy clickthrough or iframe proofs
|
|
291
|
+
can be reviewed without reading every raw setup-action result.
|
|
292
|
+
|
|
281
293
|
`target.timeout_sec` is optional. Use it for known-heavy profile targets so the
|
|
282
294
|
profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
|
|
283
295
|
still overrides the profile value for one-off runs.
|
|
@@ -34,6 +34,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
34
34
|
];
|
|
35
35
|
var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
|
|
36
36
|
"click",
|
|
37
|
+
"drag",
|
|
37
38
|
"press",
|
|
38
39
|
"fill",
|
|
39
40
|
"set_input_value",
|
|
@@ -174,6 +175,78 @@ function toJsonValue(value) {
|
|
|
174
175
|
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
175
176
|
return String(value);
|
|
176
177
|
}
|
|
178
|
+
function compactProfileSetupSummaryText(value, limit = 160) {
|
|
179
|
+
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
180
|
+
if (!text) return void 0;
|
|
181
|
+
if (text.length <= limit) return text;
|
|
182
|
+
return `${text.slice(0, Math.max(0, limit - 15)).trimEnd()}... (${text.length} chars)`;
|
|
183
|
+
}
|
|
184
|
+
function profileSetupResultAction(value) {
|
|
185
|
+
const action = value.action ?? value.type;
|
|
186
|
+
return typeof action === "string" && action ? action : "unknown";
|
|
187
|
+
}
|
|
188
|
+
function profileSetupFrameUrls(viewport) {
|
|
189
|
+
const urls = [];
|
|
190
|
+
const frames = viewport.frames || {};
|
|
191
|
+
for (const container of Object.values(frames)) {
|
|
192
|
+
if (!isRecord(container) || !Array.isArray(container.frames)) continue;
|
|
193
|
+
for (const frame of container.frames) {
|
|
194
|
+
if (!isRecord(frame)) continue;
|
|
195
|
+
const url = typeof frame.url === "string" ? frame.url : void 0;
|
|
196
|
+
if (url && !urls.includes(url)) urls.push(url);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return urls.slice(0, 10);
|
|
200
|
+
}
|
|
201
|
+
function profileSetupActionCounts(results) {
|
|
202
|
+
const counts = {};
|
|
203
|
+
for (const result of results) {
|
|
204
|
+
const action = profileSetupResultAction(result);
|
|
205
|
+
counts[action] = (counts[action] || 0) + 1;
|
|
206
|
+
}
|
|
207
|
+
return toJsonValue(counts);
|
|
208
|
+
}
|
|
209
|
+
function profileSetupSummary(viewports, actionCount) {
|
|
210
|
+
return toJsonValue({
|
|
211
|
+
viewport_count: viewports.length,
|
|
212
|
+
action_count: actionCount ?? null,
|
|
213
|
+
viewports: viewports.map((viewport) => {
|
|
214
|
+
const results = viewport.setup_action_results || [];
|
|
215
|
+
const failed = results.filter((result) => result.ok === false);
|
|
216
|
+
const clicked = results.filter((result) => profileSetupResultAction(result) === "click" && result.ok !== false).map((result) => ({
|
|
217
|
+
ordinal: result.ordinal ?? null,
|
|
218
|
+
selector: result.selector ?? null,
|
|
219
|
+
frame_selector: result.frame_selector ?? null,
|
|
220
|
+
text: compactProfileSetupSummaryText(result.text)
|
|
221
|
+
})).slice(0, 8);
|
|
222
|
+
const text_samples = results.filter((result) => result.ok !== false && typeof result.text === "string" && (profileSetupResultAction(result) === "assert_text_visible" || profileSetupResultAction(result) === "assert_text_absent" || profileSetupResultAction(result) === "wait_for_text")).map((result) => ({
|
|
223
|
+
ordinal: result.ordinal ?? null,
|
|
224
|
+
action: profileSetupResultAction(result),
|
|
225
|
+
frame_selector: result.frame_selector ?? null,
|
|
226
|
+
text: compactProfileSetupSummaryText(result.text)
|
|
227
|
+
})).filter((item) => item.text).slice(-6);
|
|
228
|
+
return {
|
|
229
|
+
name: viewport.name,
|
|
230
|
+
ok: (actionCount === void 0 ? results.length > 0 : results.length >= actionCount) && failed.length === 0,
|
|
231
|
+
result_count: results.length,
|
|
232
|
+
observed_path: viewport.route?.observed ?? null,
|
|
233
|
+
final_url: viewport.url ?? null,
|
|
234
|
+
action_counts: profileSetupActionCounts(results),
|
|
235
|
+
frame_action_count: results.filter((result) => result.frame_selector).length,
|
|
236
|
+
frame_urls: profileSetupFrameUrls(viewport),
|
|
237
|
+
clicked,
|
|
238
|
+
text_samples,
|
|
239
|
+
failed: failed.map((result) => ({
|
|
240
|
+
ordinal: result.ordinal ?? null,
|
|
241
|
+
action: profileSetupResultAction(result),
|
|
242
|
+
selector: result.selector ?? null,
|
|
243
|
+
frame_selector: result.frame_selector ?? null,
|
|
244
|
+
reason: result.reason ?? result.error ?? null
|
|
245
|
+
}))
|
|
246
|
+
};
|
|
247
|
+
})
|
|
248
|
+
});
|
|
249
|
+
}
|
|
177
250
|
function normalizeName(value, fallback) {
|
|
178
251
|
const name = stringValue(value) || fallback;
|
|
179
252
|
return name.replace(/\s+/g, " ").trim();
|
|
@@ -204,7 +277,7 @@ function isSupportedCheckType(value) {
|
|
|
204
277
|
}
|
|
205
278
|
function normalizeSetupActionType(value, index) {
|
|
206
279
|
const normalizedInput = String(value || "").trim().replace(/-/g, "_");
|
|
207
|
-
const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
|
|
280
|
+
const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "pointer_drag" || normalizedInput === "mouse_drag" || normalizedInput === "drag_to" ? "drag" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
|
|
208
281
|
if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
|
|
209
282
|
return normalized;
|
|
210
283
|
}
|
|
@@ -234,6 +307,13 @@ function normalizeSetupActionRepeat(input, index) {
|
|
|
234
307
|
}
|
|
235
308
|
return repeat;
|
|
236
309
|
}
|
|
310
|
+
function normalizeSetupActionCoordinateMode(value, index) {
|
|
311
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
312
|
+
const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
|
|
313
|
+
if (normalized === "pixels" || normalized === "pixel" || normalized === "px") return "pixels";
|
|
314
|
+
if (normalized === "ratio" || normalized === "relative" || normalized === "fraction") return "ratio";
|
|
315
|
+
throw new Error(`target.setup_actions[${index}].coordinate_mode ${String(value)} is not supported. Supported coordinate modes: pixels, ratio.`);
|
|
316
|
+
}
|
|
237
317
|
function normalizeSetupAction(input, index) {
|
|
238
318
|
if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
|
|
239
319
|
const type = normalizeSetupActionType(stringValue(input.type), index);
|
|
@@ -243,9 +323,25 @@ function normalizeSetupAction(input, index) {
|
|
|
243
323
|
if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
|
|
244
324
|
throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
|
|
245
325
|
}
|
|
246
|
-
if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
|
|
326
|
+
if ((type === "click" || type === "drag" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
|
|
247
327
|
throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
|
|
248
328
|
}
|
|
329
|
+
const fromX = numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
|
|
330
|
+
const fromY = numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
|
|
331
|
+
const toX = numberValue(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
|
|
332
|
+
const toY = numberValue(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
|
|
333
|
+
const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
|
|
334
|
+
if (type === "drag") {
|
|
335
|
+
if (fromX === void 0 || fromY === void 0 || toX === void 0 || toY === void 0) {
|
|
336
|
+
throw new Error(`target.setup_actions[${index}] drag requires from_x, from_y, to_x, and to_y.`);
|
|
337
|
+
}
|
|
338
|
+
if (coordinateMode === "ratio" && [fromX, fromY, toX, toY].some((value2) => value2 < 0 || value2 > 1)) {
|
|
339
|
+
throw new Error(`target.setup_actions[${index}] drag ratio coordinates must be between 0 and 1.`);
|
|
340
|
+
}
|
|
341
|
+
if ((coordinateMode === void 0 || coordinateMode === "pixels") && [fromX, fromY, toX, toY].some((value2) => value2 < 0)) {
|
|
342
|
+
throw new Error(`target.setup_actions[${index}] drag pixel coordinates must be non-negative.`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
249
345
|
if (type === "wait_for_text" && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
250
346
|
throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
|
|
251
347
|
}
|
|
@@ -292,12 +388,23 @@ function normalizeSetupAction(input, index) {
|
|
|
292
388
|
}
|
|
293
389
|
}
|
|
294
390
|
const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
|
|
391
|
+
const steps = numberValue(input.steps);
|
|
392
|
+
if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
|
|
393
|
+
throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
|
|
394
|
+
}
|
|
295
395
|
return {
|
|
296
396
|
type,
|
|
297
397
|
selector,
|
|
298
398
|
frame_selector: frameSelector,
|
|
299
399
|
frame_index: frameIndex,
|
|
300
400
|
force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
|
|
401
|
+
coordinate_mode: coordinateMode,
|
|
402
|
+
from_x: fromX,
|
|
403
|
+
from_y: fromY,
|
|
404
|
+
to_x: toX,
|
|
405
|
+
to_y: toY,
|
|
406
|
+
duration_ms: numberValue(input.duration_ms) ?? numberValue(input.durationMs),
|
|
407
|
+
steps,
|
|
301
408
|
key,
|
|
302
409
|
value,
|
|
303
410
|
value_json: hasJsonValue ? toJsonValue(input.value_json ?? input.valueJson ?? input.json) : void 0,
|
|
@@ -1246,6 +1353,7 @@ function assessSetupActionsFromEvidence(profile, evidence) {
|
|
|
1246
1353
|
ok: (viewport.setup_action_results || []).length >= actionCount && (viewport.setup_action_results || []).every((result) => result.ok !== false),
|
|
1247
1354
|
result_count: (viewport.setup_action_results || []).length
|
|
1248
1355
|
})),
|
|
1356
|
+
setup_summary: profileSetupSummary(viewports, actionCount),
|
|
1249
1357
|
failed
|
|
1250
1358
|
},
|
|
1251
1359
|
message: failed.length ? `Setup actions failed in ${failed.length} viewport action(s).` : void 0
|
|
@@ -1659,6 +1767,90 @@ function requiredNetworkMockHitCount(mock) {
|
|
|
1659
1767
|
if (Array.isArray(mock.responses) && mock.responses.length) return mock.responses.length;
|
|
1660
1768
|
return 1;
|
|
1661
1769
|
}
|
|
1770
|
+
function compactProfileSetupSummaryText(value, limit) {
|
|
1771
|
+
limit = limit || 160;
|
|
1772
|
+
const text = typeof value === "string" ? value.replace(/\\s+/g, " ").trim() : "";
|
|
1773
|
+
if (!text) return undefined;
|
|
1774
|
+
if (text.length <= limit) return text;
|
|
1775
|
+
return text.slice(0, Math.max(0, limit - 15)).trimEnd() + "... (" + text.length + " chars)";
|
|
1776
|
+
}
|
|
1777
|
+
function profileSetupResultAction(result) {
|
|
1778
|
+
const action = result && (result.action || result.type);
|
|
1779
|
+
return typeof action === "string" && action ? action : "unknown";
|
|
1780
|
+
}
|
|
1781
|
+
function profileSetupFrameUrls(viewport) {
|
|
1782
|
+
const urls = [];
|
|
1783
|
+
const frames = viewport && viewport.frames || {};
|
|
1784
|
+
for (const container of Object.values(frames)) {
|
|
1785
|
+
if (!container || typeof container !== "object" || Array.isArray(container) || !Array.isArray(container.frames)) continue;
|
|
1786
|
+
for (const frame of container.frames) {
|
|
1787
|
+
if (!frame || typeof frame !== "object" || Array.isArray(frame)) continue;
|
|
1788
|
+
const url = typeof frame.url === "string" ? frame.url : null;
|
|
1789
|
+
if (url && !urls.includes(url)) urls.push(url);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return urls.slice(0, 10);
|
|
1793
|
+
}
|
|
1794
|
+
function profileSetupActionCounts(results) {
|
|
1795
|
+
const counts = {};
|
|
1796
|
+
for (const result of results || []) {
|
|
1797
|
+
const action = profileSetupResultAction(result);
|
|
1798
|
+
counts[action] = (counts[action] || 0) + 1;
|
|
1799
|
+
}
|
|
1800
|
+
return counts;
|
|
1801
|
+
}
|
|
1802
|
+
function profileSetupSummary(viewports, actionCount) {
|
|
1803
|
+
return {
|
|
1804
|
+
viewport_count: (viewports || []).length,
|
|
1805
|
+
action_count: actionCount ?? null,
|
|
1806
|
+
viewports: (viewports || []).map((viewport) => {
|
|
1807
|
+
const results = viewport.setup_action_results || [];
|
|
1808
|
+
const failed = results.filter((result) => result && result.ok === false);
|
|
1809
|
+
const clicked = results
|
|
1810
|
+
.filter((result) => result && profileSetupResultAction(result) === "click" && result.ok !== false)
|
|
1811
|
+
.map((result) => ({
|
|
1812
|
+
ordinal: result.ordinal ?? null,
|
|
1813
|
+
selector: result.selector ?? null,
|
|
1814
|
+
frame_selector: result.frame_selector ?? null,
|
|
1815
|
+
text: compactProfileSetupSummaryText(result.text),
|
|
1816
|
+
}))
|
|
1817
|
+
.slice(0, 8);
|
|
1818
|
+
const textSamples = results
|
|
1819
|
+
.filter((result) => result && result.ok !== false && typeof result.text === "string" && (
|
|
1820
|
+
profileSetupResultAction(result) === "assert_text_visible"
|
|
1821
|
+
|| profileSetupResultAction(result) === "assert_text_absent"
|
|
1822
|
+
|| profileSetupResultAction(result) === "wait_for_text"
|
|
1823
|
+
))
|
|
1824
|
+
.map((result) => ({
|
|
1825
|
+
ordinal: result.ordinal ?? null,
|
|
1826
|
+
action: profileSetupResultAction(result),
|
|
1827
|
+
frame_selector: result.frame_selector ?? null,
|
|
1828
|
+
text: compactProfileSetupSummaryText(result.text),
|
|
1829
|
+
}))
|
|
1830
|
+
.filter((item) => item.text)
|
|
1831
|
+
.slice(-6);
|
|
1832
|
+
return {
|
|
1833
|
+
name: viewport.name,
|
|
1834
|
+
ok: (actionCount === undefined ? results.length > 0 : results.length >= actionCount) && failed.length === 0,
|
|
1835
|
+
result_count: results.length,
|
|
1836
|
+
observed_path: viewport.route && viewport.route.observed || null,
|
|
1837
|
+
final_url: viewport.url || null,
|
|
1838
|
+
action_counts: profileSetupActionCounts(results),
|
|
1839
|
+
frame_action_count: results.filter((result) => result && result.frame_selector).length,
|
|
1840
|
+
frame_urls: profileSetupFrameUrls(viewport),
|
|
1841
|
+
clicked,
|
|
1842
|
+
text_samples: textSamples,
|
|
1843
|
+
failed: failed.map((result) => ({
|
|
1844
|
+
ordinal: result.ordinal ?? null,
|
|
1845
|
+
action: profileSetupResultAction(result),
|
|
1846
|
+
selector: result.selector ?? null,
|
|
1847
|
+
frame_selector: result.frame_selector ?? null,
|
|
1848
|
+
reason: result.reason || result.error || null,
|
|
1849
|
+
})),
|
|
1850
|
+
};
|
|
1851
|
+
}),
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1662
1854
|
function assessProfile(profile, evidence) {
|
|
1663
1855
|
const checks = [];
|
|
1664
1856
|
const viewports = evidence.viewports || [];
|
|
@@ -1763,6 +1955,7 @@ function assessProfile(profile, evidence) {
|
|
|
1763
1955
|
&& (viewport.setup_action_results || []).every((result) => !result || result.ok !== false),
|
|
1764
1956
|
result_count: (viewport.setup_action_results || []).length,
|
|
1765
1957
|
})),
|
|
1958
|
+
setup_summary: profileSetupSummary(viewports, actionCount),
|
|
1766
1959
|
failed,
|
|
1767
1960
|
},
|
|
1768
1961
|
message: failed.length ? "Setup actions failed in " + failed.length + " viewport action(s)." : undefined,
|
|
@@ -2505,6 +2698,71 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2505
2698
|
await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
|
|
2506
2699
|
return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
|
|
2507
2700
|
}
|
|
2701
|
+
if (type === "drag") {
|
|
2702
|
+
const scope = await setupActionScope(action, timeout);
|
|
2703
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2704
|
+
const locator = scope.context.locator(action.selector);
|
|
2705
|
+
const count = await locator.count();
|
|
2706
|
+
if (!count) return { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count };
|
|
2707
|
+
const targetIndex = Number.isInteger(action.index) ? action.index : 0;
|
|
2708
|
+
if (targetIndex < 0 || targetIndex >= count) return { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex };
|
|
2709
|
+
const target = locator.nth(targetIndex);
|
|
2710
|
+
await target.waitFor({ state: "visible", timeout });
|
|
2711
|
+
const box = await target.boundingBox();
|
|
2712
|
+
if (!box) return { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex };
|
|
2713
|
+
const mode = String(action.coordinate_mode || action.coordinateMode || "pixels").trim();
|
|
2714
|
+
const coordinate = (value, size) => mode === "ratio" ? value * size : value;
|
|
2715
|
+
const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.start_x ?? action.startX ?? action.x1);
|
|
2716
|
+
const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.start_y ?? action.startY ?? action.y1);
|
|
2717
|
+
const toX = setupFiniteNumber(action.to_x ?? action.toX ?? action.end_x ?? action.endX ?? action.x2);
|
|
2718
|
+
const toY = setupFiniteNumber(action.to_y ?? action.toY ?? action.end_y ?? action.endY ?? action.y2);
|
|
2719
|
+
if (fromX === undefined || fromY === undefined || toX === undefined || toY === undefined) return { ...base, ...setupScopeEvidence(scope), reason: "missing_drag_coordinates", count, target_index: targetIndex };
|
|
2720
|
+
if (mode === "ratio" && [fromX, fromY, toX, toY].some((value) => value < 0 || value > 1)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_ratio_coordinates", count, target_index: targetIndex };
|
|
2721
|
+
if (mode !== "ratio" && [fromX, fromY, toX, toY].some((value) => value < 0)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex };
|
|
2722
|
+
const start = {
|
|
2723
|
+
x: box.x + coordinate(fromX, box.width),
|
|
2724
|
+
y: box.y + coordinate(fromY, box.height),
|
|
2725
|
+
};
|
|
2726
|
+
const end = {
|
|
2727
|
+
x: box.x + coordinate(toX, box.width),
|
|
2728
|
+
y: box.y + coordinate(toY, box.height),
|
|
2729
|
+
};
|
|
2730
|
+
const requestedSteps = setupNumber(action.steps, 8);
|
|
2731
|
+
const steps = Math.min(100, Math.max(1, Math.floor(requestedSteps || 8)));
|
|
2732
|
+
const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
|
|
2733
|
+
await page.mouse.move(start.x, start.y);
|
|
2734
|
+
await page.mouse.down();
|
|
2735
|
+
try {
|
|
2736
|
+
if (durationMs && steps > 1) {
|
|
2737
|
+
for (let step = 1; step <= steps; step += 1) {
|
|
2738
|
+
const progress = step / steps;
|
|
2739
|
+
await page.mouse.move(
|
|
2740
|
+
start.x + (end.x - start.x) * progress,
|
|
2741
|
+
start.y + (end.y - start.y) * progress,
|
|
2742
|
+
);
|
|
2743
|
+
await page.waitForTimeout(durationMs / steps);
|
|
2744
|
+
}
|
|
2745
|
+
} else {
|
|
2746
|
+
await page.mouse.move(end.x, end.y, { steps });
|
|
2747
|
+
}
|
|
2748
|
+
} finally {
|
|
2749
|
+
await page.mouse.up().catch(() => {});
|
|
2750
|
+
}
|
|
2751
|
+
return {
|
|
2752
|
+
...base,
|
|
2753
|
+
...setupScopeEvidence(scope),
|
|
2754
|
+
ok: true,
|
|
2755
|
+
count,
|
|
2756
|
+
target_index: targetIndex,
|
|
2757
|
+
coordinate_mode: mode,
|
|
2758
|
+
from_x: fromX,
|
|
2759
|
+
from_y: fromY,
|
|
2760
|
+
to_x: toX,
|
|
2761
|
+
to_y: toY,
|
|
2762
|
+
steps,
|
|
2763
|
+
duration_ms: durationMs || undefined,
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2508
2766
|
if (type === "press") {
|
|
2509
2767
|
const key = String(action.key || "").trim();
|
|
2510
2768
|
if (!key) return { ...base, reason: "missing_key" };
|