@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/dist/index.cjs CHANGED
@@ -8736,6 +8736,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
8736
8736
  "selector_count_eq",
8737
8737
  "selector_text_order",
8738
8738
  "frame_text_visible",
8739
+ "frame_url_equals",
8740
+ "frame_url_matches",
8739
8741
  "frame_no_horizontal_overflow",
8740
8742
  "text_visible",
8741
8743
  "text_absent",
@@ -8746,6 +8748,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
8746
8748
  ];
8747
8749
  var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
8748
8750
  "click",
8751
+ "drag",
8749
8752
  "press",
8750
8753
  "fill",
8751
8754
  "set_input_value",
@@ -8916,7 +8919,7 @@ function isSupportedCheckType(value) {
8916
8919
  }
8917
8920
  function normalizeSetupActionType(value, index) {
8918
8921
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
8919
- const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
8922
+ 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;
8920
8923
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
8921
8924
  return normalized;
8922
8925
  }
@@ -8946,6 +8949,13 @@ function normalizeSetupActionRepeat(input, index) {
8946
8949
  }
8947
8950
  return repeat;
8948
8951
  }
8952
+ function normalizeSetupActionCoordinateMode(value, index) {
8953
+ if (value === void 0 || value === null || value === "") return void 0;
8954
+ const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
8955
+ if (normalized === "pixels" || normalized === "pixel" || normalized === "px") return "pixels";
8956
+ if (normalized === "ratio" || normalized === "relative" || normalized === "fraction") return "ratio";
8957
+ throw new Error(`target.setup_actions[${index}].coordinate_mode ${String(value)} is not supported. Supported coordinate modes: pixels, ratio.`);
8958
+ }
8949
8959
  function normalizeSetupAction(input, index) {
8950
8960
  if (!isRecord2(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
8951
8961
  const type = normalizeSetupActionType(stringValue5(input.type), index);
@@ -8955,9 +8965,25 @@ function normalizeSetupAction(input, index) {
8955
8965
  if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
8956
8966
  throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
8957
8967
  }
8958
- 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) {
8968
+ 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) {
8959
8969
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
8960
8970
  }
8971
+ const fromX = numberValue3(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
8972
+ const fromY = numberValue3(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
8973
+ const toX = numberValue3(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
8974
+ const toY = numberValue3(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
8975
+ const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
8976
+ if (type === "drag") {
8977
+ if (fromX === void 0 || fromY === void 0 || toX === void 0 || toY === void 0) {
8978
+ throw new Error(`target.setup_actions[${index}] drag requires from_x, from_y, to_x, and to_y.`);
8979
+ }
8980
+ if (coordinateMode === "ratio" && [fromX, fromY, toX, toY].some((value2) => value2 < 0 || value2 > 1)) {
8981
+ throw new Error(`target.setup_actions[${index}] drag ratio coordinates must be between 0 and 1.`);
8982
+ }
8983
+ if ((coordinateMode === void 0 || coordinateMode === "pixels") && [fromX, fromY, toX, toY].some((value2) => value2 < 0)) {
8984
+ throw new Error(`target.setup_actions[${index}] drag pixel coordinates must be non-negative.`);
8985
+ }
8986
+ }
8961
8987
  if (type === "wait_for_text" && !stringValue5(input.text) && !stringValue5(input.pattern)) {
8962
8988
  throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
8963
8989
  }
@@ -9004,12 +9030,23 @@ function normalizeSetupAction(input, index) {
9004
9030
  }
9005
9031
  }
9006
9032
  const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
9033
+ const steps = numberValue3(input.steps);
9034
+ if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
9035
+ throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
9036
+ }
9007
9037
  return {
9008
9038
  type,
9009
9039
  selector,
9010
9040
  frame_selector: frameSelector,
9011
9041
  frame_index: frameIndex,
9012
9042
  force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
9043
+ coordinate_mode: coordinateMode,
9044
+ from_x: fromX,
9045
+ from_y: fromY,
9046
+ to_x: toX,
9047
+ to_y: toY,
9048
+ duration_ms: numberValue3(input.duration_ms) ?? numberValue3(input.durationMs),
9049
+ steps,
9013
9050
  key,
9014
9051
  value,
9015
9052
  value_json: hasJsonValue ? toJsonValue(input.value_json ?? input.valueJson ?? input.json) : void 0,
@@ -9220,12 +9257,19 @@ function normalizeCheck(input, index) {
9220
9257
  if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue5(input.selector)) {
9221
9258
  throw new Error(`checks[${index}] ${type} requires selector.`);
9222
9259
  }
9223
- if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue5(input.selector)) {
9260
+ if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue5(input.selector)) {
9224
9261
  throw new Error(`checks[${index}] ${type} requires selector.`);
9225
9262
  }
9226
9263
  if (type === "frame_text_visible" && !stringValue5(input.text) && !stringValue5(input.pattern)) {
9227
9264
  throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
9228
9265
  }
9266
+ const expectedUrl = stringFromOwn(input, "expected_url", "expectedUrl", "url", "expected_value", "expectedValue", "value");
9267
+ if (type === "frame_url_equals" && expectedUrl === void 0) {
9268
+ throw new Error(`checks[${index}] frame_url_equals requires expected_url.`);
9269
+ }
9270
+ if (type === "frame_url_matches" && !stringValue5(input.pattern)) {
9271
+ throw new Error(`checks[${index}] frame_url_matches requires pattern.`);
9272
+ }
9229
9273
  if ((type === "text_visible" || type === "text_absent") && !stringValue5(input.text) && !stringValue5(input.pattern)) {
9230
9274
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
9231
9275
  }
@@ -9258,6 +9302,7 @@ function normalizeCheck(input, index) {
9258
9302
  expected_path: stringValue5(input.expected_path),
9259
9303
  param: stringValue5(input.param) || stringValue5(input.search_param) || stringValue5(input.searchParam) || stringValue5(input.key),
9260
9304
  expected_value: expectedValue,
9305
+ expected_url: expectedUrl,
9261
9306
  expected_routes: expectedRoutes,
9262
9307
  selector: stringValue5(input.selector),
9263
9308
  expected_texts: expectedTexts,
@@ -9725,6 +9770,35 @@ function assessCheckFromEvidence(check, evidence) {
9725
9770
  message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
9726
9771
  };
9727
9772
  }
9773
+ if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
9774
+ const key = selectorKey(check);
9775
+ const expectedUrl = check.expected_url || check.expected_value || "";
9776
+ const results = viewports.map((viewport) => {
9777
+ const frames = frameEvidenceForSelector(viewport, key);
9778
+ const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
9779
+ const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : matchText(url, check));
9780
+ return {
9781
+ viewport: viewport.name,
9782
+ frame_count: frames.length,
9783
+ matched_count: matches.length,
9784
+ matched: matches.length > 0,
9785
+ urls: urls.slice(0, 10)
9786
+ };
9787
+ });
9788
+ const failed = results.filter((result) => !result.matched).length;
9789
+ return {
9790
+ type: check.type,
9791
+ label: checkLabel(check),
9792
+ status: failed ? "failed" : "passed",
9793
+ evidence: {
9794
+ selector: key,
9795
+ expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
9796
+ pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
9797
+ viewports: results.map((result) => toJsonValue(result))
9798
+ },
9799
+ message: failed ? `Frame selector ${key} URL assertion failed in ${failed} viewport(s).` : void 0
9800
+ };
9801
+ }
9728
9802
  if (check.type === "frame_no_horizontal_overflow") {
9729
9803
  const key = selectorKey(check);
9730
9804
  const maxOverflow = check.max_overflow_px ?? 4;
@@ -10616,6 +10690,36 @@ function assessProfile(profile, evidence) {
10616
10690
  });
10617
10691
  continue;
10618
10692
  }
10693
+ if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
10694
+ const selector = check.selector || "";
10695
+ const expectedUrl = check.expected_url || check.expected_value || "";
10696
+ const results = checkViewports.map((viewport) => {
10697
+ const frames = frameEvidenceForSelector(viewport, selector);
10698
+ const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
10699
+ const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : textMatches(url, check));
10700
+ return {
10701
+ viewport: viewport.name,
10702
+ frame_count: frames.length,
10703
+ matched_count: matches.length,
10704
+ matched: matches.length > 0,
10705
+ urls: urls.slice(0, 10),
10706
+ };
10707
+ });
10708
+ const failed = results.filter((result) => !result.matched).length;
10709
+ checks.push({
10710
+ type: check.type,
10711
+ label: check.label || check.type,
10712
+ status: failed ? "failed" : "passed",
10713
+ evidence: {
10714
+ selector,
10715
+ expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
10716
+ pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
10717
+ viewports: results,
10718
+ },
10719
+ message: failed ? "Frame selector " + selector + " URL assertion failed in " + failed + " viewport(s)." : undefined,
10720
+ });
10721
+ continue;
10722
+ }
10619
10723
  if (check.type === "frame_no_horizontal_overflow") {
10620
10724
  const selector = check.selector || "";
10621
10725
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
@@ -11150,6 +11254,71 @@ async function executeSetupAction(action, ordinal) {
11150
11254
  await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
11151
11255
  return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
11152
11256
  }
11257
+ if (type === "drag") {
11258
+ const scope = await setupActionScope(action, timeout);
11259
+ if (!scope.ok) return setupScopeFailure(base, scope);
11260
+ const locator = scope.context.locator(action.selector);
11261
+ const count = await locator.count();
11262
+ if (!count) return { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count };
11263
+ const targetIndex = Number.isInteger(action.index) ? action.index : 0;
11264
+ if (targetIndex < 0 || targetIndex >= count) return { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex };
11265
+ const target = locator.nth(targetIndex);
11266
+ await target.waitFor({ state: "visible", timeout });
11267
+ const box = await target.boundingBox();
11268
+ if (!box) return { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex };
11269
+ const mode = String(action.coordinate_mode || action.coordinateMode || "pixels").trim();
11270
+ const coordinate = (value, size) => mode === "ratio" ? value * size : value;
11271
+ const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.start_x ?? action.startX ?? action.x1);
11272
+ const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.start_y ?? action.startY ?? action.y1);
11273
+ const toX = setupFiniteNumber(action.to_x ?? action.toX ?? action.end_x ?? action.endX ?? action.x2);
11274
+ const toY = setupFiniteNumber(action.to_y ?? action.toY ?? action.end_y ?? action.endY ?? action.y2);
11275
+ if (fromX === undefined || fromY === undefined || toX === undefined || toY === undefined) return { ...base, ...setupScopeEvidence(scope), reason: "missing_drag_coordinates", count, target_index: targetIndex };
11276
+ 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 };
11277
+ if (mode !== "ratio" && [fromX, fromY, toX, toY].some((value) => value < 0)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex };
11278
+ const start = {
11279
+ x: box.x + coordinate(fromX, box.width),
11280
+ y: box.y + coordinate(fromY, box.height),
11281
+ };
11282
+ const end = {
11283
+ x: box.x + coordinate(toX, box.width),
11284
+ y: box.y + coordinate(toY, box.height),
11285
+ };
11286
+ const requestedSteps = setupNumber(action.steps, 8);
11287
+ const steps = Math.min(100, Math.max(1, Math.floor(requestedSteps || 8)));
11288
+ const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
11289
+ await page.mouse.move(start.x, start.y);
11290
+ await page.mouse.down();
11291
+ try {
11292
+ if (durationMs && steps > 1) {
11293
+ for (let step = 1; step <= steps; step += 1) {
11294
+ const progress = step / steps;
11295
+ await page.mouse.move(
11296
+ start.x + (end.x - start.x) * progress,
11297
+ start.y + (end.y - start.y) * progress,
11298
+ );
11299
+ await page.waitForTimeout(durationMs / steps);
11300
+ }
11301
+ } else {
11302
+ await page.mouse.move(end.x, end.y, { steps });
11303
+ }
11304
+ } finally {
11305
+ await page.mouse.up().catch(() => {});
11306
+ }
11307
+ return {
11308
+ ...base,
11309
+ ...setupScopeEvidence(scope),
11310
+ ok: true,
11311
+ count,
11312
+ target_index: targetIndex,
11313
+ coordinate_mode: mode,
11314
+ from_x: fromX,
11315
+ from_y: fromY,
11316
+ to_x: toX,
11317
+ to_y: toY,
11318
+ steps,
11319
+ duration_ms: durationMs || undefined,
11320
+ };
11321
+ }
11153
11322
  if (type === "press") {
11154
11323
  const key = String(action.key || "").trim();
11155
11324
  if (!key) return { ...base, reason: "missing_key" };
@@ -12087,7 +12256,7 @@ async function captureViewport(viewport) {
12087
12256
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
12088
12257
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
12089
12258
  }
12090
- if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
12259
+ if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
12091
12260
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
12092
12261
  frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
12093
12262
  }
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-Q5VW6MAP.js";
61
+ } from "./chunk-T66DWLBE.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -65,6 +65,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
65
65
  "selector_count_eq",
66
66
  "selector_text_order",
67
67
  "frame_text_visible",
68
+ "frame_url_equals",
69
+ "frame_url_matches",
68
70
  "frame_no_horizontal_overflow",
69
71
  "text_visible",
70
72
  "text_absent",
@@ -75,6 +77,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
75
77
  ];
76
78
  var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
77
79
  "click",
80
+ "drag",
78
81
  "press",
79
82
  "fill",
80
83
  "set_input_value",
@@ -245,7 +248,7 @@ function isSupportedCheckType(value) {
245
248
  }
246
249
  function normalizeSetupActionType(value, index) {
247
250
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
248
- const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
251
+ 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;
249
252
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
250
253
  return normalized;
251
254
  }
@@ -275,6 +278,13 @@ function normalizeSetupActionRepeat(input, index) {
275
278
  }
276
279
  return repeat;
277
280
  }
281
+ function normalizeSetupActionCoordinateMode(value, index) {
282
+ if (value === void 0 || value === null || value === "") return void 0;
283
+ const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
284
+ if (normalized === "pixels" || normalized === "pixel" || normalized === "px") return "pixels";
285
+ if (normalized === "ratio" || normalized === "relative" || normalized === "fraction") return "ratio";
286
+ throw new Error(`target.setup_actions[${index}].coordinate_mode ${String(value)} is not supported. Supported coordinate modes: pixels, ratio.`);
287
+ }
278
288
  function normalizeSetupAction(input, index) {
279
289
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
280
290
  const type = normalizeSetupActionType(stringValue(input.type), index);
@@ -284,9 +294,25 @@ function normalizeSetupAction(input, index) {
284
294
  if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
285
295
  throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
286
296
  }
287
- 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) {
297
+ 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) {
288
298
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
289
299
  }
300
+ const fromX = numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
301
+ const fromY = numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
302
+ const toX = numberValue(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
303
+ const toY = numberValue(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
304
+ const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
305
+ if (type === "drag") {
306
+ if (fromX === void 0 || fromY === void 0 || toX === void 0 || toY === void 0) {
307
+ throw new Error(`target.setup_actions[${index}] drag requires from_x, from_y, to_x, and to_y.`);
308
+ }
309
+ if (coordinateMode === "ratio" && [fromX, fromY, toX, toY].some((value2) => value2 < 0 || value2 > 1)) {
310
+ throw new Error(`target.setup_actions[${index}] drag ratio coordinates must be between 0 and 1.`);
311
+ }
312
+ if ((coordinateMode === void 0 || coordinateMode === "pixels") && [fromX, fromY, toX, toY].some((value2) => value2 < 0)) {
313
+ throw new Error(`target.setup_actions[${index}] drag pixel coordinates must be non-negative.`);
314
+ }
315
+ }
290
316
  if (type === "wait_for_text" && !stringValue(input.text) && !stringValue(input.pattern)) {
291
317
  throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
292
318
  }
@@ -333,12 +359,23 @@ function normalizeSetupAction(input, index) {
333
359
  }
334
360
  }
335
361
  const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
362
+ const steps = numberValue(input.steps);
363
+ if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
364
+ throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
365
+ }
336
366
  return {
337
367
  type,
338
368
  selector,
339
369
  frame_selector: frameSelector,
340
370
  frame_index: frameIndex,
341
371
  force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
372
+ coordinate_mode: coordinateMode,
373
+ from_x: fromX,
374
+ from_y: fromY,
375
+ to_x: toX,
376
+ to_y: toY,
377
+ duration_ms: numberValue(input.duration_ms) ?? numberValue(input.durationMs),
378
+ steps,
342
379
  key,
343
380
  value,
344
381
  value_json: hasJsonValue ? toJsonValue(input.value_json ?? input.valueJson ?? input.json) : void 0,
@@ -549,12 +586,19 @@ function normalizeCheck(input, index) {
549
586
  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)) {
550
587
  throw new Error(`checks[${index}] ${type} requires selector.`);
551
588
  }
552
- if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
589
+ if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
553
590
  throw new Error(`checks[${index}] ${type} requires selector.`);
554
591
  }
555
592
  if (type === "frame_text_visible" && !stringValue(input.text) && !stringValue(input.pattern)) {
556
593
  throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
557
594
  }
595
+ const expectedUrl = stringFromOwn(input, "expected_url", "expectedUrl", "url", "expected_value", "expectedValue", "value");
596
+ if (type === "frame_url_equals" && expectedUrl === void 0) {
597
+ throw new Error(`checks[${index}] frame_url_equals requires expected_url.`);
598
+ }
599
+ if (type === "frame_url_matches" && !stringValue(input.pattern)) {
600
+ throw new Error(`checks[${index}] frame_url_matches requires pattern.`);
601
+ }
558
602
  if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
559
603
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
560
604
  }
@@ -587,6 +631,7 @@ function normalizeCheck(input, index) {
587
631
  expected_path: stringValue(input.expected_path),
588
632
  param: stringValue(input.param) || stringValue(input.search_param) || stringValue(input.searchParam) || stringValue(input.key),
589
633
  expected_value: expectedValue,
634
+ expected_url: expectedUrl,
590
635
  expected_routes: expectedRoutes,
591
636
  selector: stringValue(input.selector),
592
637
  expected_texts: expectedTexts,
@@ -1054,6 +1099,35 @@ function assessCheckFromEvidence(check, evidence) {
1054
1099
  message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
1055
1100
  };
1056
1101
  }
1102
+ if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
1103
+ const key = selectorKey(check);
1104
+ const expectedUrl = check.expected_url || check.expected_value || "";
1105
+ const results = viewports.map((viewport) => {
1106
+ const frames = frameEvidenceForSelector(viewport, key);
1107
+ const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
1108
+ const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : matchText(url, check));
1109
+ return {
1110
+ viewport: viewport.name,
1111
+ frame_count: frames.length,
1112
+ matched_count: matches.length,
1113
+ matched: matches.length > 0,
1114
+ urls: urls.slice(0, 10)
1115
+ };
1116
+ });
1117
+ const failed = results.filter((result) => !result.matched).length;
1118
+ return {
1119
+ type: check.type,
1120
+ label: checkLabel(check),
1121
+ status: failed ? "failed" : "passed",
1122
+ evidence: {
1123
+ selector: key,
1124
+ expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
1125
+ pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
1126
+ viewports: results.map((result) => toJsonValue(result))
1127
+ },
1128
+ message: failed ? `Frame selector ${key} URL assertion failed in ${failed} viewport(s).` : void 0
1129
+ };
1130
+ }
1057
1131
  if (check.type === "frame_no_horizontal_overflow") {
1058
1132
  const key = selectorKey(check);
1059
1133
  const maxOverflow = check.max_overflow_px ?? 4;
@@ -1945,6 +2019,36 @@ function assessProfile(profile, evidence) {
1945
2019
  });
1946
2020
  continue;
1947
2021
  }
2022
+ if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
2023
+ const selector = check.selector || "";
2024
+ const expectedUrl = check.expected_url || check.expected_value || "";
2025
+ const results = checkViewports.map((viewport) => {
2026
+ const frames = frameEvidenceForSelector(viewport, selector);
2027
+ const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
2028
+ const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : textMatches(url, check));
2029
+ return {
2030
+ viewport: viewport.name,
2031
+ frame_count: frames.length,
2032
+ matched_count: matches.length,
2033
+ matched: matches.length > 0,
2034
+ urls: urls.slice(0, 10),
2035
+ };
2036
+ });
2037
+ const failed = results.filter((result) => !result.matched).length;
2038
+ checks.push({
2039
+ type: check.type,
2040
+ label: check.label || check.type,
2041
+ status: failed ? "failed" : "passed",
2042
+ evidence: {
2043
+ selector,
2044
+ expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
2045
+ pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
2046
+ viewports: results,
2047
+ },
2048
+ message: failed ? "Frame selector " + selector + " URL assertion failed in " + failed + " viewport(s)." : undefined,
2049
+ });
2050
+ continue;
2051
+ }
1948
2052
  if (check.type === "frame_no_horizontal_overflow") {
1949
2053
  const selector = check.selector || "";
1950
2054
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
@@ -2479,6 +2583,71 @@ async function executeSetupAction(action, ordinal) {
2479
2583
  await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
2480
2584
  return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
2481
2585
  }
2586
+ if (type === "drag") {
2587
+ const scope = await setupActionScope(action, timeout);
2588
+ if (!scope.ok) return setupScopeFailure(base, scope);
2589
+ const locator = scope.context.locator(action.selector);
2590
+ const count = await locator.count();
2591
+ if (!count) return { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count };
2592
+ const targetIndex = Number.isInteger(action.index) ? action.index : 0;
2593
+ if (targetIndex < 0 || targetIndex >= count) return { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex };
2594
+ const target = locator.nth(targetIndex);
2595
+ await target.waitFor({ state: "visible", timeout });
2596
+ const box = await target.boundingBox();
2597
+ if (!box) return { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex };
2598
+ const mode = String(action.coordinate_mode || action.coordinateMode || "pixels").trim();
2599
+ const coordinate = (value, size) => mode === "ratio" ? value * size : value;
2600
+ const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.start_x ?? action.startX ?? action.x1);
2601
+ const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.start_y ?? action.startY ?? action.y1);
2602
+ const toX = setupFiniteNumber(action.to_x ?? action.toX ?? action.end_x ?? action.endX ?? action.x2);
2603
+ const toY = setupFiniteNumber(action.to_y ?? action.toY ?? action.end_y ?? action.endY ?? action.y2);
2604
+ if (fromX === undefined || fromY === undefined || toX === undefined || toY === undefined) return { ...base, ...setupScopeEvidence(scope), reason: "missing_drag_coordinates", count, target_index: targetIndex };
2605
+ 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 };
2606
+ if (mode !== "ratio" && [fromX, fromY, toX, toY].some((value) => value < 0)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex };
2607
+ const start = {
2608
+ x: box.x + coordinate(fromX, box.width),
2609
+ y: box.y + coordinate(fromY, box.height),
2610
+ };
2611
+ const end = {
2612
+ x: box.x + coordinate(toX, box.width),
2613
+ y: box.y + coordinate(toY, box.height),
2614
+ };
2615
+ const requestedSteps = setupNumber(action.steps, 8);
2616
+ const steps = Math.min(100, Math.max(1, Math.floor(requestedSteps || 8)));
2617
+ const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
2618
+ await page.mouse.move(start.x, start.y);
2619
+ await page.mouse.down();
2620
+ try {
2621
+ if (durationMs && steps > 1) {
2622
+ for (let step = 1; step <= steps; step += 1) {
2623
+ const progress = step / steps;
2624
+ await page.mouse.move(
2625
+ start.x + (end.x - start.x) * progress,
2626
+ start.y + (end.y - start.y) * progress,
2627
+ );
2628
+ await page.waitForTimeout(durationMs / steps);
2629
+ }
2630
+ } else {
2631
+ await page.mouse.move(end.x, end.y, { steps });
2632
+ }
2633
+ } finally {
2634
+ await page.mouse.up().catch(() => {});
2635
+ }
2636
+ return {
2637
+ ...base,
2638
+ ...setupScopeEvidence(scope),
2639
+ ok: true,
2640
+ count,
2641
+ target_index: targetIndex,
2642
+ coordinate_mode: mode,
2643
+ from_x: fromX,
2644
+ from_y: fromY,
2645
+ to_x: toX,
2646
+ to_y: toY,
2647
+ steps,
2648
+ duration_ms: durationMs || undefined,
2649
+ };
2650
+ }
2482
2651
  if (type === "press") {
2483
2652
  const key = String(action.key || "").trim();
2484
2653
  if (!key) return { ...base, reason: "missing_key" };
@@ -3416,7 +3585,7 @@ async function captureViewport(viewport) {
3416
3585
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
3417
3586
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
3418
3587
  }
3419
- if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
3588
+ if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
3420
3589
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
3421
3590
  frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
3422
3591
  }
@@ -4,8 +4,8 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
4
4
  declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
- declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
- declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text", "window_call"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
+ declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text", "window_call"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
11
11
  type RiddleProofProfileSetupActionType = typeof RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES[number];
@@ -23,6 +23,13 @@ interface RiddleProofProfileSetupAction {
23
23
  frame_selector?: string;
24
24
  frame_index?: number;
25
25
  force?: boolean;
26
+ coordinate_mode?: "pixels" | "ratio";
27
+ from_x?: number;
28
+ from_y?: number;
29
+ to_x?: number;
30
+ to_y?: number;
31
+ duration_ms?: number;
32
+ steps?: number;
26
33
  key?: string;
27
34
  value?: string;
28
35
  value_json?: JsonValue;
@@ -94,6 +101,7 @@ interface RiddleProofProfileCheck {
94
101
  expected_path?: string;
95
102
  param?: string;
96
103
  expected_value?: string;
104
+ expected_url?: string;
97
105
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
98
106
  selector?: string;
99
107
  expected_texts?: string[];
package/dist/profile.d.ts CHANGED
@@ -4,8 +4,8 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
4
4
  declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
- declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
- declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text", "window_call"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
+ declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text", "window_call"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
11
11
  type RiddleProofProfileSetupActionType = typeof RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES[number];
@@ -23,6 +23,13 @@ interface RiddleProofProfileSetupAction {
23
23
  frame_selector?: string;
24
24
  frame_index?: number;
25
25
  force?: boolean;
26
+ coordinate_mode?: "pixels" | "ratio";
27
+ from_x?: number;
28
+ from_y?: number;
29
+ to_x?: number;
30
+ to_y?: number;
31
+ duration_ms?: number;
32
+ steps?: number;
26
33
  key?: string;
27
34
  value?: string;
28
35
  value_json?: JsonValue;
@@ -94,6 +101,7 @@ interface RiddleProofProfileCheck {
94
101
  expected_path?: string;
95
102
  param?: string;
96
103
  expected_value?: string;
104
+ expected_url?: string;
97
105
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
98
106
  selector?: string;
99
107
  expected_texts?: string[];
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-Q5VW6MAP.js";
22
+ } from "./chunk-T66DWLBE.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,