@riddledc/riddle-proof 0.7.59 → 0.7.61
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 +17 -1
- package/dist/{chunk-Q5VW6MAP.js → chunk-T66DWLBE.js} +173 -4
- package/dist/cli.cjs +173 -4
- package/dist/cli.js +1 -1
- package/dist/index.cjs +173 -4
- package/dist/index.js +1 -1
- package/dist/profile.cjs +173 -4
- package/dist/profile.d.cts +10 -2
- package/dist/profile.d.ts +10 -2
- package/dist/profile.js +1 -1
- package/dist/proof-run-engine.d.cts +3 -3
- package/dist/proof-run-engine.d.ts +3 -3
- 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
|
|
@@ -355,6 +361,11 @@ game, or preview surfaces that render inside iframes:
|
|
|
355
361
|
|
|
356
362
|
```json
|
|
357
363
|
[
|
|
364
|
+
{
|
|
365
|
+
"type": "frame_url_matches",
|
|
366
|
+
"selector": ".game-player-root iframe",
|
|
367
|
+
"pattern": "/saved/hot-path-.+/index\\.html$"
|
|
368
|
+
},
|
|
358
369
|
{
|
|
359
370
|
"type": "frame_text_visible",
|
|
360
371
|
"selector": ".game-player-root iframe",
|
|
@@ -368,6 +379,11 @@ game, or preview surfaces that render inside iframes:
|
|
|
368
379
|
]
|
|
369
380
|
```
|
|
370
381
|
|
|
382
|
+
Use `frame_url_equals` when the iframe must resolve to one exact embedded
|
|
383
|
+
resource, or `frame_url_matches` when a preview/job/saved-game URL has a stable
|
|
384
|
+
shape but a generated ID. URL checks fail when the frame is missing, just like
|
|
385
|
+
frame text and overflow checks.
|
|
386
|
+
|
|
371
387
|
Frame checks capture each matching iframe's URL, title, compact text sample,
|
|
372
388
|
scroll width, client width, measured horizontal overflow, and top visible
|
|
373
389
|
overflow offenders. This keeps embedded-player audits in profile mode instead
|
|
@@ -22,6 +22,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
22
22
|
"selector_count_eq",
|
|
23
23
|
"selector_text_order",
|
|
24
24
|
"frame_text_visible",
|
|
25
|
+
"frame_url_equals",
|
|
26
|
+
"frame_url_matches",
|
|
25
27
|
"frame_no_horizontal_overflow",
|
|
26
28
|
"text_visible",
|
|
27
29
|
"text_absent",
|
|
@@ -32,6 +34,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
32
34
|
];
|
|
33
35
|
var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
|
|
34
36
|
"click",
|
|
37
|
+
"drag",
|
|
35
38
|
"press",
|
|
36
39
|
"fill",
|
|
37
40
|
"set_input_value",
|
|
@@ -202,7 +205,7 @@ function isSupportedCheckType(value) {
|
|
|
202
205
|
}
|
|
203
206
|
function normalizeSetupActionType(value, index) {
|
|
204
207
|
const normalizedInput = String(value || "").trim().replace(/-/g, "_");
|
|
205
|
-
const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
|
|
208
|
+
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;
|
|
206
209
|
if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
|
|
207
210
|
return normalized;
|
|
208
211
|
}
|
|
@@ -232,6 +235,13 @@ function normalizeSetupActionRepeat(input, index) {
|
|
|
232
235
|
}
|
|
233
236
|
return repeat;
|
|
234
237
|
}
|
|
238
|
+
function normalizeSetupActionCoordinateMode(value, index) {
|
|
239
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
240
|
+
const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
|
|
241
|
+
if (normalized === "pixels" || normalized === "pixel" || normalized === "px") return "pixels";
|
|
242
|
+
if (normalized === "ratio" || normalized === "relative" || normalized === "fraction") return "ratio";
|
|
243
|
+
throw new Error(`target.setup_actions[${index}].coordinate_mode ${String(value)} is not supported. Supported coordinate modes: pixels, ratio.`);
|
|
244
|
+
}
|
|
235
245
|
function normalizeSetupAction(input, index) {
|
|
236
246
|
if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
|
|
237
247
|
const type = normalizeSetupActionType(stringValue(input.type), index);
|
|
@@ -241,9 +251,25 @@ function normalizeSetupAction(input, index) {
|
|
|
241
251
|
if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
|
|
242
252
|
throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
|
|
243
253
|
}
|
|
244
|
-
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) {
|
|
254
|
+
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) {
|
|
245
255
|
throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
|
|
246
256
|
}
|
|
257
|
+
const fromX = numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
|
|
258
|
+
const fromY = numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
|
|
259
|
+
const toX = numberValue(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
|
|
260
|
+
const toY = numberValue(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
|
|
261
|
+
const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
|
|
262
|
+
if (type === "drag") {
|
|
263
|
+
if (fromX === void 0 || fromY === void 0 || toX === void 0 || toY === void 0) {
|
|
264
|
+
throw new Error(`target.setup_actions[${index}] drag requires from_x, from_y, to_x, and to_y.`);
|
|
265
|
+
}
|
|
266
|
+
if (coordinateMode === "ratio" && [fromX, fromY, toX, toY].some((value2) => value2 < 0 || value2 > 1)) {
|
|
267
|
+
throw new Error(`target.setup_actions[${index}] drag ratio coordinates must be between 0 and 1.`);
|
|
268
|
+
}
|
|
269
|
+
if ((coordinateMode === void 0 || coordinateMode === "pixels") && [fromX, fromY, toX, toY].some((value2) => value2 < 0)) {
|
|
270
|
+
throw new Error(`target.setup_actions[${index}] drag pixel coordinates must be non-negative.`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
247
273
|
if (type === "wait_for_text" && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
248
274
|
throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
|
|
249
275
|
}
|
|
@@ -290,12 +316,23 @@ function normalizeSetupAction(input, index) {
|
|
|
290
316
|
}
|
|
291
317
|
}
|
|
292
318
|
const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
|
|
319
|
+
const steps = numberValue(input.steps);
|
|
320
|
+
if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
|
|
321
|
+
throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
|
|
322
|
+
}
|
|
293
323
|
return {
|
|
294
324
|
type,
|
|
295
325
|
selector,
|
|
296
326
|
frame_selector: frameSelector,
|
|
297
327
|
frame_index: frameIndex,
|
|
298
328
|
force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
|
|
329
|
+
coordinate_mode: coordinateMode,
|
|
330
|
+
from_x: fromX,
|
|
331
|
+
from_y: fromY,
|
|
332
|
+
to_x: toX,
|
|
333
|
+
to_y: toY,
|
|
334
|
+
duration_ms: numberValue(input.duration_ms) ?? numberValue(input.durationMs),
|
|
335
|
+
steps,
|
|
299
336
|
key,
|
|
300
337
|
value,
|
|
301
338
|
value_json: hasJsonValue ? toJsonValue(input.value_json ?? input.valueJson ?? input.json) : void 0,
|
|
@@ -506,12 +543,19 @@ function normalizeCheck(input, index) {
|
|
|
506
543
|
if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue(input.selector)) {
|
|
507
544
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
508
545
|
}
|
|
509
|
-
if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
546
|
+
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
510
547
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
511
548
|
}
|
|
512
549
|
if (type === "frame_text_visible" && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
513
550
|
throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
|
|
514
551
|
}
|
|
552
|
+
const expectedUrl = stringFromOwn(input, "expected_url", "expectedUrl", "url", "expected_value", "expectedValue", "value");
|
|
553
|
+
if (type === "frame_url_equals" && expectedUrl === void 0) {
|
|
554
|
+
throw new Error(`checks[${index}] frame_url_equals requires expected_url.`);
|
|
555
|
+
}
|
|
556
|
+
if (type === "frame_url_matches" && !stringValue(input.pattern)) {
|
|
557
|
+
throw new Error(`checks[${index}] frame_url_matches requires pattern.`);
|
|
558
|
+
}
|
|
515
559
|
if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
516
560
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
517
561
|
}
|
|
@@ -544,6 +588,7 @@ function normalizeCheck(input, index) {
|
|
|
544
588
|
expected_path: stringValue(input.expected_path),
|
|
545
589
|
param: stringValue(input.param) || stringValue(input.search_param) || stringValue(input.searchParam) || stringValue(input.key),
|
|
546
590
|
expected_value: expectedValue,
|
|
591
|
+
expected_url: expectedUrl,
|
|
547
592
|
expected_routes: expectedRoutes,
|
|
548
593
|
selector: stringValue(input.selector),
|
|
549
594
|
expected_texts: expectedTexts,
|
|
@@ -1011,6 +1056,35 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1011
1056
|
message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
|
|
1012
1057
|
};
|
|
1013
1058
|
}
|
|
1059
|
+
if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
|
|
1060
|
+
const key = selectorKey(check);
|
|
1061
|
+
const expectedUrl = check.expected_url || check.expected_value || "";
|
|
1062
|
+
const results = viewports.map((viewport) => {
|
|
1063
|
+
const frames = frameEvidenceForSelector(viewport, key);
|
|
1064
|
+
const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
|
|
1065
|
+
const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : matchText(url, check));
|
|
1066
|
+
return {
|
|
1067
|
+
viewport: viewport.name,
|
|
1068
|
+
frame_count: frames.length,
|
|
1069
|
+
matched_count: matches.length,
|
|
1070
|
+
matched: matches.length > 0,
|
|
1071
|
+
urls: urls.slice(0, 10)
|
|
1072
|
+
};
|
|
1073
|
+
});
|
|
1074
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
1075
|
+
return {
|
|
1076
|
+
type: check.type,
|
|
1077
|
+
label: checkLabel(check),
|
|
1078
|
+
status: failed ? "failed" : "passed",
|
|
1079
|
+
evidence: {
|
|
1080
|
+
selector: key,
|
|
1081
|
+
expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
|
|
1082
|
+
pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
|
|
1083
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
1084
|
+
},
|
|
1085
|
+
message: failed ? `Frame selector ${key} URL assertion failed in ${failed} viewport(s).` : void 0
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1014
1088
|
if (check.type === "frame_no_horizontal_overflow") {
|
|
1015
1089
|
const key = selectorKey(check);
|
|
1016
1090
|
const maxOverflow = check.max_overflow_px ?? 4;
|
|
@@ -1902,6 +1976,36 @@ function assessProfile(profile, evidence) {
|
|
|
1902
1976
|
});
|
|
1903
1977
|
continue;
|
|
1904
1978
|
}
|
|
1979
|
+
if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
|
|
1980
|
+
const selector = check.selector || "";
|
|
1981
|
+
const expectedUrl = check.expected_url || check.expected_value || "";
|
|
1982
|
+
const results = checkViewports.map((viewport) => {
|
|
1983
|
+
const frames = frameEvidenceForSelector(viewport, selector);
|
|
1984
|
+
const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
|
|
1985
|
+
const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : textMatches(url, check));
|
|
1986
|
+
return {
|
|
1987
|
+
viewport: viewport.name,
|
|
1988
|
+
frame_count: frames.length,
|
|
1989
|
+
matched_count: matches.length,
|
|
1990
|
+
matched: matches.length > 0,
|
|
1991
|
+
urls: urls.slice(0, 10),
|
|
1992
|
+
};
|
|
1993
|
+
});
|
|
1994
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
1995
|
+
checks.push({
|
|
1996
|
+
type: check.type,
|
|
1997
|
+
label: check.label || check.type,
|
|
1998
|
+
status: failed ? "failed" : "passed",
|
|
1999
|
+
evidence: {
|
|
2000
|
+
selector,
|
|
2001
|
+
expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
|
|
2002
|
+
pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
|
|
2003
|
+
viewports: results,
|
|
2004
|
+
},
|
|
2005
|
+
message: failed ? "Frame selector " + selector + " URL assertion failed in " + failed + " viewport(s)." : undefined,
|
|
2006
|
+
});
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
1905
2009
|
if (check.type === "frame_no_horizontal_overflow") {
|
|
1906
2010
|
const selector = check.selector || "";
|
|
1907
2011
|
const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
|
|
@@ -2436,6 +2540,71 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2436
2540
|
await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
|
|
2437
2541
|
return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
|
|
2438
2542
|
}
|
|
2543
|
+
if (type === "drag") {
|
|
2544
|
+
const scope = await setupActionScope(action, timeout);
|
|
2545
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2546
|
+
const locator = scope.context.locator(action.selector);
|
|
2547
|
+
const count = await locator.count();
|
|
2548
|
+
if (!count) return { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count };
|
|
2549
|
+
const targetIndex = Number.isInteger(action.index) ? action.index : 0;
|
|
2550
|
+
if (targetIndex < 0 || targetIndex >= count) return { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex };
|
|
2551
|
+
const target = locator.nth(targetIndex);
|
|
2552
|
+
await target.waitFor({ state: "visible", timeout });
|
|
2553
|
+
const box = await target.boundingBox();
|
|
2554
|
+
if (!box) return { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex };
|
|
2555
|
+
const mode = String(action.coordinate_mode || action.coordinateMode || "pixels").trim();
|
|
2556
|
+
const coordinate = (value, size) => mode === "ratio" ? value * size : value;
|
|
2557
|
+
const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.start_x ?? action.startX ?? action.x1);
|
|
2558
|
+
const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.start_y ?? action.startY ?? action.y1);
|
|
2559
|
+
const toX = setupFiniteNumber(action.to_x ?? action.toX ?? action.end_x ?? action.endX ?? action.x2);
|
|
2560
|
+
const toY = setupFiniteNumber(action.to_y ?? action.toY ?? action.end_y ?? action.endY ?? action.y2);
|
|
2561
|
+
if (fromX === undefined || fromY === undefined || toX === undefined || toY === undefined) return { ...base, ...setupScopeEvidence(scope), reason: "missing_drag_coordinates", count, target_index: targetIndex };
|
|
2562
|
+
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 };
|
|
2563
|
+
if (mode !== "ratio" && [fromX, fromY, toX, toY].some((value) => value < 0)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex };
|
|
2564
|
+
const start = {
|
|
2565
|
+
x: box.x + coordinate(fromX, box.width),
|
|
2566
|
+
y: box.y + coordinate(fromY, box.height),
|
|
2567
|
+
};
|
|
2568
|
+
const end = {
|
|
2569
|
+
x: box.x + coordinate(toX, box.width),
|
|
2570
|
+
y: box.y + coordinate(toY, box.height),
|
|
2571
|
+
};
|
|
2572
|
+
const requestedSteps = setupNumber(action.steps, 8);
|
|
2573
|
+
const steps = Math.min(100, Math.max(1, Math.floor(requestedSteps || 8)));
|
|
2574
|
+
const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
|
|
2575
|
+
await page.mouse.move(start.x, start.y);
|
|
2576
|
+
await page.mouse.down();
|
|
2577
|
+
try {
|
|
2578
|
+
if (durationMs && steps > 1) {
|
|
2579
|
+
for (let step = 1; step <= steps; step += 1) {
|
|
2580
|
+
const progress = step / steps;
|
|
2581
|
+
await page.mouse.move(
|
|
2582
|
+
start.x + (end.x - start.x) * progress,
|
|
2583
|
+
start.y + (end.y - start.y) * progress,
|
|
2584
|
+
);
|
|
2585
|
+
await page.waitForTimeout(durationMs / steps);
|
|
2586
|
+
}
|
|
2587
|
+
} else {
|
|
2588
|
+
await page.mouse.move(end.x, end.y, { steps });
|
|
2589
|
+
}
|
|
2590
|
+
} finally {
|
|
2591
|
+
await page.mouse.up().catch(() => {});
|
|
2592
|
+
}
|
|
2593
|
+
return {
|
|
2594
|
+
...base,
|
|
2595
|
+
...setupScopeEvidence(scope),
|
|
2596
|
+
ok: true,
|
|
2597
|
+
count,
|
|
2598
|
+
target_index: targetIndex,
|
|
2599
|
+
coordinate_mode: mode,
|
|
2600
|
+
from_x: fromX,
|
|
2601
|
+
from_y: fromY,
|
|
2602
|
+
to_x: toX,
|
|
2603
|
+
to_y: toY,
|
|
2604
|
+
steps,
|
|
2605
|
+
duration_ms: durationMs || undefined,
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2439
2608
|
if (type === "press") {
|
|
2440
2609
|
const key = String(action.key || "").trim();
|
|
2441
2610
|
if (!key) return { ...base, reason: "missing_key" };
|
|
@@ -3373,7 +3542,7 @@ async function captureViewport(viewport) {
|
|
|
3373
3542
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
3374
3543
|
text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
|
|
3375
3544
|
}
|
|
3376
|
-
if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
3545
|
+
if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
3377
3546
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
3378
3547
|
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
3379
3548
|
}
|
package/dist/cli.cjs
CHANGED
|
@@ -6895,6 +6895,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
6895
6895
|
"selector_count_eq",
|
|
6896
6896
|
"selector_text_order",
|
|
6897
6897
|
"frame_text_visible",
|
|
6898
|
+
"frame_url_equals",
|
|
6899
|
+
"frame_url_matches",
|
|
6898
6900
|
"frame_no_horizontal_overflow",
|
|
6899
6901
|
"text_visible",
|
|
6900
6902
|
"text_absent",
|
|
@@ -6905,6 +6907,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
6905
6907
|
];
|
|
6906
6908
|
var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
|
|
6907
6909
|
"click",
|
|
6910
|
+
"drag",
|
|
6908
6911
|
"press",
|
|
6909
6912
|
"fill",
|
|
6910
6913
|
"set_input_value",
|
|
@@ -7075,7 +7078,7 @@ function isSupportedCheckType(value) {
|
|
|
7075
7078
|
}
|
|
7076
7079
|
function normalizeSetupActionType(value, index) {
|
|
7077
7080
|
const normalizedInput = String(value || "").trim().replace(/-/g, "_");
|
|
7078
|
-
const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
|
|
7081
|
+
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;
|
|
7079
7082
|
if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
|
|
7080
7083
|
return normalized;
|
|
7081
7084
|
}
|
|
@@ -7105,6 +7108,13 @@ function normalizeSetupActionRepeat(input, index) {
|
|
|
7105
7108
|
}
|
|
7106
7109
|
return repeat;
|
|
7107
7110
|
}
|
|
7111
|
+
function normalizeSetupActionCoordinateMode(value, index) {
|
|
7112
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
7113
|
+
const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
|
|
7114
|
+
if (normalized === "pixels" || normalized === "pixel" || normalized === "px") return "pixels";
|
|
7115
|
+
if (normalized === "ratio" || normalized === "relative" || normalized === "fraction") return "ratio";
|
|
7116
|
+
throw new Error(`target.setup_actions[${index}].coordinate_mode ${String(value)} is not supported. Supported coordinate modes: pixels, ratio.`);
|
|
7117
|
+
}
|
|
7108
7118
|
function normalizeSetupAction(input, index) {
|
|
7109
7119
|
if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
|
|
7110
7120
|
const type = normalizeSetupActionType(stringValue2(input.type), index);
|
|
@@ -7114,9 +7124,25 @@ function normalizeSetupAction(input, index) {
|
|
|
7114
7124
|
if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
|
|
7115
7125
|
throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
|
|
7116
7126
|
}
|
|
7117
|
-
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) {
|
|
7127
|
+
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) {
|
|
7118
7128
|
throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
|
|
7119
7129
|
}
|
|
7130
|
+
const fromX = numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
|
|
7131
|
+
const fromY = numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
|
|
7132
|
+
const toX = numberValue(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
|
|
7133
|
+
const toY = numberValue(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
|
|
7134
|
+
const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
|
|
7135
|
+
if (type === "drag") {
|
|
7136
|
+
if (fromX === void 0 || fromY === void 0 || toX === void 0 || toY === void 0) {
|
|
7137
|
+
throw new Error(`target.setup_actions[${index}] drag requires from_x, from_y, to_x, and to_y.`);
|
|
7138
|
+
}
|
|
7139
|
+
if (coordinateMode === "ratio" && [fromX, fromY, toX, toY].some((value2) => value2 < 0 || value2 > 1)) {
|
|
7140
|
+
throw new Error(`target.setup_actions[${index}] drag ratio coordinates must be between 0 and 1.`);
|
|
7141
|
+
}
|
|
7142
|
+
if ((coordinateMode === void 0 || coordinateMode === "pixels") && [fromX, fromY, toX, toY].some((value2) => value2 < 0)) {
|
|
7143
|
+
throw new Error(`target.setup_actions[${index}] drag pixel coordinates must be non-negative.`);
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
7120
7146
|
if (type === "wait_for_text" && !stringValue2(input.text) && !stringValue2(input.pattern)) {
|
|
7121
7147
|
throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
|
|
7122
7148
|
}
|
|
@@ -7163,12 +7189,23 @@ function normalizeSetupAction(input, index) {
|
|
|
7163
7189
|
}
|
|
7164
7190
|
}
|
|
7165
7191
|
const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
|
|
7192
|
+
const steps = numberValue(input.steps);
|
|
7193
|
+
if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
|
|
7194
|
+
throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
|
|
7195
|
+
}
|
|
7166
7196
|
return {
|
|
7167
7197
|
type,
|
|
7168
7198
|
selector,
|
|
7169
7199
|
frame_selector: frameSelector,
|
|
7170
7200
|
frame_index: frameIndex,
|
|
7171
7201
|
force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
|
|
7202
|
+
coordinate_mode: coordinateMode,
|
|
7203
|
+
from_x: fromX,
|
|
7204
|
+
from_y: fromY,
|
|
7205
|
+
to_x: toX,
|
|
7206
|
+
to_y: toY,
|
|
7207
|
+
duration_ms: numberValue(input.duration_ms) ?? numberValue(input.durationMs),
|
|
7208
|
+
steps,
|
|
7172
7209
|
key,
|
|
7173
7210
|
value,
|
|
7174
7211
|
value_json: hasJsonValue ? toJsonValue(input.value_json ?? input.valueJson ?? input.json) : void 0,
|
|
@@ -7379,12 +7416,19 @@ function normalizeCheck(input, index) {
|
|
|
7379
7416
|
if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue2(input.selector)) {
|
|
7380
7417
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
7381
7418
|
}
|
|
7382
|
-
if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue2(input.selector)) {
|
|
7419
|
+
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue2(input.selector)) {
|
|
7383
7420
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
7384
7421
|
}
|
|
7385
7422
|
if (type === "frame_text_visible" && !stringValue2(input.text) && !stringValue2(input.pattern)) {
|
|
7386
7423
|
throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
|
|
7387
7424
|
}
|
|
7425
|
+
const expectedUrl = stringFromOwn(input, "expected_url", "expectedUrl", "url", "expected_value", "expectedValue", "value");
|
|
7426
|
+
if (type === "frame_url_equals" && expectedUrl === void 0) {
|
|
7427
|
+
throw new Error(`checks[${index}] frame_url_equals requires expected_url.`);
|
|
7428
|
+
}
|
|
7429
|
+
if (type === "frame_url_matches" && !stringValue2(input.pattern)) {
|
|
7430
|
+
throw new Error(`checks[${index}] frame_url_matches requires pattern.`);
|
|
7431
|
+
}
|
|
7388
7432
|
if ((type === "text_visible" || type === "text_absent") && !stringValue2(input.text) && !stringValue2(input.pattern)) {
|
|
7389
7433
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
7390
7434
|
}
|
|
@@ -7417,6 +7461,7 @@ function normalizeCheck(input, index) {
|
|
|
7417
7461
|
expected_path: stringValue2(input.expected_path),
|
|
7418
7462
|
param: stringValue2(input.param) || stringValue2(input.search_param) || stringValue2(input.searchParam) || stringValue2(input.key),
|
|
7419
7463
|
expected_value: expectedValue,
|
|
7464
|
+
expected_url: expectedUrl,
|
|
7420
7465
|
expected_routes: expectedRoutes,
|
|
7421
7466
|
selector: stringValue2(input.selector),
|
|
7422
7467
|
expected_texts: expectedTexts,
|
|
@@ -7884,6 +7929,35 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
7884
7929
|
message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
|
|
7885
7930
|
};
|
|
7886
7931
|
}
|
|
7932
|
+
if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
|
|
7933
|
+
const key = selectorKey(check);
|
|
7934
|
+
const expectedUrl = check.expected_url || check.expected_value || "";
|
|
7935
|
+
const results = viewports.map((viewport) => {
|
|
7936
|
+
const frames = frameEvidenceForSelector(viewport, key);
|
|
7937
|
+
const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
|
|
7938
|
+
const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : matchText(url, check));
|
|
7939
|
+
return {
|
|
7940
|
+
viewport: viewport.name,
|
|
7941
|
+
frame_count: frames.length,
|
|
7942
|
+
matched_count: matches.length,
|
|
7943
|
+
matched: matches.length > 0,
|
|
7944
|
+
urls: urls.slice(0, 10)
|
|
7945
|
+
};
|
|
7946
|
+
});
|
|
7947
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
7948
|
+
return {
|
|
7949
|
+
type: check.type,
|
|
7950
|
+
label: checkLabel(check),
|
|
7951
|
+
status: failed ? "failed" : "passed",
|
|
7952
|
+
evidence: {
|
|
7953
|
+
selector: key,
|
|
7954
|
+
expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
|
|
7955
|
+
pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
|
|
7956
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
7957
|
+
},
|
|
7958
|
+
message: failed ? `Frame selector ${key} URL assertion failed in ${failed} viewport(s).` : void 0
|
|
7959
|
+
};
|
|
7960
|
+
}
|
|
7887
7961
|
if (check.type === "frame_no_horizontal_overflow") {
|
|
7888
7962
|
const key = selectorKey(check);
|
|
7889
7963
|
const maxOverflow = check.max_overflow_px ?? 4;
|
|
@@ -8759,6 +8833,36 @@ function assessProfile(profile, evidence) {
|
|
|
8759
8833
|
});
|
|
8760
8834
|
continue;
|
|
8761
8835
|
}
|
|
8836
|
+
if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
|
|
8837
|
+
const selector = check.selector || "";
|
|
8838
|
+
const expectedUrl = check.expected_url || check.expected_value || "";
|
|
8839
|
+
const results = checkViewports.map((viewport) => {
|
|
8840
|
+
const frames = frameEvidenceForSelector(viewport, selector);
|
|
8841
|
+
const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
|
|
8842
|
+
const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : textMatches(url, check));
|
|
8843
|
+
return {
|
|
8844
|
+
viewport: viewport.name,
|
|
8845
|
+
frame_count: frames.length,
|
|
8846
|
+
matched_count: matches.length,
|
|
8847
|
+
matched: matches.length > 0,
|
|
8848
|
+
urls: urls.slice(0, 10),
|
|
8849
|
+
};
|
|
8850
|
+
});
|
|
8851
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
8852
|
+
checks.push({
|
|
8853
|
+
type: check.type,
|
|
8854
|
+
label: check.label || check.type,
|
|
8855
|
+
status: failed ? "failed" : "passed",
|
|
8856
|
+
evidence: {
|
|
8857
|
+
selector,
|
|
8858
|
+
expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
|
|
8859
|
+
pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
|
|
8860
|
+
viewports: results,
|
|
8861
|
+
},
|
|
8862
|
+
message: failed ? "Frame selector " + selector + " URL assertion failed in " + failed + " viewport(s)." : undefined,
|
|
8863
|
+
});
|
|
8864
|
+
continue;
|
|
8865
|
+
}
|
|
8762
8866
|
if (check.type === "frame_no_horizontal_overflow") {
|
|
8763
8867
|
const selector = check.selector || "";
|
|
8764
8868
|
const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
|
|
@@ -9293,6 +9397,71 @@ async function executeSetupAction(action, ordinal) {
|
|
|
9293
9397
|
await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
|
|
9294
9398
|
return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
|
|
9295
9399
|
}
|
|
9400
|
+
if (type === "drag") {
|
|
9401
|
+
const scope = await setupActionScope(action, timeout);
|
|
9402
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
9403
|
+
const locator = scope.context.locator(action.selector);
|
|
9404
|
+
const count = await locator.count();
|
|
9405
|
+
if (!count) return { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count };
|
|
9406
|
+
const targetIndex = Number.isInteger(action.index) ? action.index : 0;
|
|
9407
|
+
if (targetIndex < 0 || targetIndex >= count) return { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex };
|
|
9408
|
+
const target = locator.nth(targetIndex);
|
|
9409
|
+
await target.waitFor({ state: "visible", timeout });
|
|
9410
|
+
const box = await target.boundingBox();
|
|
9411
|
+
if (!box) return { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex };
|
|
9412
|
+
const mode = String(action.coordinate_mode || action.coordinateMode || "pixels").trim();
|
|
9413
|
+
const coordinate = (value, size) => mode === "ratio" ? value * size : value;
|
|
9414
|
+
const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.start_x ?? action.startX ?? action.x1);
|
|
9415
|
+
const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.start_y ?? action.startY ?? action.y1);
|
|
9416
|
+
const toX = setupFiniteNumber(action.to_x ?? action.toX ?? action.end_x ?? action.endX ?? action.x2);
|
|
9417
|
+
const toY = setupFiniteNumber(action.to_y ?? action.toY ?? action.end_y ?? action.endY ?? action.y2);
|
|
9418
|
+
if (fromX === undefined || fromY === undefined || toX === undefined || toY === undefined) return { ...base, ...setupScopeEvidence(scope), reason: "missing_drag_coordinates", count, target_index: targetIndex };
|
|
9419
|
+
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 };
|
|
9420
|
+
if (mode !== "ratio" && [fromX, fromY, toX, toY].some((value) => value < 0)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex };
|
|
9421
|
+
const start = {
|
|
9422
|
+
x: box.x + coordinate(fromX, box.width),
|
|
9423
|
+
y: box.y + coordinate(fromY, box.height),
|
|
9424
|
+
};
|
|
9425
|
+
const end = {
|
|
9426
|
+
x: box.x + coordinate(toX, box.width),
|
|
9427
|
+
y: box.y + coordinate(toY, box.height),
|
|
9428
|
+
};
|
|
9429
|
+
const requestedSteps = setupNumber(action.steps, 8);
|
|
9430
|
+
const steps = Math.min(100, Math.max(1, Math.floor(requestedSteps || 8)));
|
|
9431
|
+
const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
|
|
9432
|
+
await page.mouse.move(start.x, start.y);
|
|
9433
|
+
await page.mouse.down();
|
|
9434
|
+
try {
|
|
9435
|
+
if (durationMs && steps > 1) {
|
|
9436
|
+
for (let step = 1; step <= steps; step += 1) {
|
|
9437
|
+
const progress = step / steps;
|
|
9438
|
+
await page.mouse.move(
|
|
9439
|
+
start.x + (end.x - start.x) * progress,
|
|
9440
|
+
start.y + (end.y - start.y) * progress,
|
|
9441
|
+
);
|
|
9442
|
+
await page.waitForTimeout(durationMs / steps);
|
|
9443
|
+
}
|
|
9444
|
+
} else {
|
|
9445
|
+
await page.mouse.move(end.x, end.y, { steps });
|
|
9446
|
+
}
|
|
9447
|
+
} finally {
|
|
9448
|
+
await page.mouse.up().catch(() => {});
|
|
9449
|
+
}
|
|
9450
|
+
return {
|
|
9451
|
+
...base,
|
|
9452
|
+
...setupScopeEvidence(scope),
|
|
9453
|
+
ok: true,
|
|
9454
|
+
count,
|
|
9455
|
+
target_index: targetIndex,
|
|
9456
|
+
coordinate_mode: mode,
|
|
9457
|
+
from_x: fromX,
|
|
9458
|
+
from_y: fromY,
|
|
9459
|
+
to_x: toX,
|
|
9460
|
+
to_y: toY,
|
|
9461
|
+
steps,
|
|
9462
|
+
duration_ms: durationMs || undefined,
|
|
9463
|
+
};
|
|
9464
|
+
}
|
|
9296
9465
|
if (type === "press") {
|
|
9297
9466
|
const key = String(action.key || "").trim();
|
|
9298
9467
|
if (!key) return { ...base, reason: "missing_key" };
|
|
@@ -10230,7 +10399,7 @@ async function captureViewport(viewport) {
|
|
|
10230
10399
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
10231
10400
|
text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
|
|
10232
10401
|
}
|
|
10233
|
-
if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
10402
|
+
if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
10234
10403
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
10235
10404
|
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
10236
10405
|
}
|
package/dist/cli.js
CHANGED