@riddledc/riddle-proof 0.7.58 → 0.7.60

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/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",
@@ -279,6 +281,11 @@ function normalizeSetupAction(input, index) {
279
281
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
280
282
  const type = normalizeSetupActionType(stringValue(input.type), index);
281
283
  const selector = stringValue(input.selector);
284
+ const frameSelector = stringFromOwn(input, "frame_selector", "frameSelector", "iframe_selector", "iframeSelector");
285
+ const frameIndex = numberValue(valueFromOwn(input, "frame_index", "frameIndex", "iframe_index", "iframeIndex"));
286
+ if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
287
+ throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
288
+ }
282
289
  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) {
283
290
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
284
291
  }
@@ -331,6 +338,8 @@ function normalizeSetupAction(input, index) {
331
338
  return {
332
339
  type,
333
340
  selector,
341
+ frame_selector: frameSelector,
342
+ frame_index: frameIndex,
334
343
  force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
335
344
  key,
336
345
  value,
@@ -542,12 +551,19 @@ function normalizeCheck(input, index) {
542
551
  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)) {
543
552
  throw new Error(`checks[${index}] ${type} requires selector.`);
544
553
  }
545
- if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
554
+ if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
546
555
  throw new Error(`checks[${index}] ${type} requires selector.`);
547
556
  }
548
557
  if (type === "frame_text_visible" && !stringValue(input.text) && !stringValue(input.pattern)) {
549
558
  throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
550
559
  }
560
+ const expectedUrl = stringFromOwn(input, "expected_url", "expectedUrl", "url", "expected_value", "expectedValue", "value");
561
+ if (type === "frame_url_equals" && expectedUrl === void 0) {
562
+ throw new Error(`checks[${index}] frame_url_equals requires expected_url.`);
563
+ }
564
+ if (type === "frame_url_matches" && !stringValue(input.pattern)) {
565
+ throw new Error(`checks[${index}] frame_url_matches requires pattern.`);
566
+ }
551
567
  if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
552
568
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
553
569
  }
@@ -580,6 +596,7 @@ function normalizeCheck(input, index) {
580
596
  expected_path: stringValue(input.expected_path),
581
597
  param: stringValue(input.param) || stringValue(input.search_param) || stringValue(input.searchParam) || stringValue(input.key),
582
598
  expected_value: expectedValue,
599
+ expected_url: expectedUrl,
583
600
  expected_routes: expectedRoutes,
584
601
  selector: stringValue(input.selector),
585
602
  expected_texts: expectedTexts,
@@ -1047,6 +1064,35 @@ function assessCheckFromEvidence(check, evidence) {
1047
1064
  message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
1048
1065
  };
1049
1066
  }
1067
+ if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
1068
+ const key = selectorKey(check);
1069
+ const expectedUrl = check.expected_url || check.expected_value || "";
1070
+ const results = viewports.map((viewport) => {
1071
+ const frames = frameEvidenceForSelector(viewport, key);
1072
+ const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
1073
+ const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : matchText(url, check));
1074
+ return {
1075
+ viewport: viewport.name,
1076
+ frame_count: frames.length,
1077
+ matched_count: matches.length,
1078
+ matched: matches.length > 0,
1079
+ urls: urls.slice(0, 10)
1080
+ };
1081
+ });
1082
+ const failed = results.filter((result) => !result.matched).length;
1083
+ return {
1084
+ type: check.type,
1085
+ label: checkLabel(check),
1086
+ status: failed ? "failed" : "passed",
1087
+ evidence: {
1088
+ selector: key,
1089
+ expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
1090
+ pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
1091
+ viewports: results.map((result) => toJsonValue(result))
1092
+ },
1093
+ message: failed ? `Frame selector ${key} URL assertion failed in ${failed} viewport(s).` : void 0
1094
+ };
1095
+ }
1050
1096
  if (check.type === "frame_no_horizontal_overflow") {
1051
1097
  const key = selectorKey(check);
1052
1098
  const maxOverflow = check.max_overflow_px ?? 4;
@@ -1217,6 +1263,8 @@ function assessSetupActionsFromEvidence(profile, evidence) {
1217
1263
  viewport: viewport.name,
1218
1264
  action: result.action ?? result.type ?? null,
1219
1265
  selector: result.selector ?? null,
1266
+ frame_selector: result.frame_selector ?? null,
1267
+ frame_index: result.frame_index ?? null,
1220
1268
  reason: result.reason ?? result.error ?? null
1221
1269
  });
1222
1270
  }
@@ -1729,10 +1777,12 @@ function assessProfile(profile, evidence) {
1729
1777
  if (result && result.ok === false) {
1730
1778
  failed.push({
1731
1779
  viewport: viewport.name,
1732
- action: result.action || result.type || null,
1733
- selector: result.selector || null,
1734
- reason: result.reason || result.error || null,
1735
- });
1780
+ action: result.action || result.type || null,
1781
+ selector: result.selector || null,
1782
+ frame_selector: result.frame_selector || null,
1783
+ frame_index: result.frame_index ?? null,
1784
+ reason: result.reason || result.error || null,
1785
+ });
1736
1786
  }
1737
1787
  }
1738
1788
  if (results.length < actionCount && results.every((result) => !result || result.ok !== false)) {
@@ -1934,6 +1984,36 @@ function assessProfile(profile, evidence) {
1934
1984
  });
1935
1985
  continue;
1936
1986
  }
1987
+ if (check.type === "frame_url_equals" || check.type === "frame_url_matches") {
1988
+ const selector = check.selector || "";
1989
+ const expectedUrl = check.expected_url || check.expected_value || "";
1990
+ const results = checkViewports.map((viewport) => {
1991
+ const frames = frameEvidenceForSelector(viewport, selector);
1992
+ const urls = frames.map((frame) => String(frame.url || "")).filter(Boolean);
1993
+ const matches = urls.filter((url) => check.type === "frame_url_equals" ? url === expectedUrl : textMatches(url, check));
1994
+ return {
1995
+ viewport: viewport.name,
1996
+ frame_count: frames.length,
1997
+ matched_count: matches.length,
1998
+ matched: matches.length > 0,
1999
+ urls: urls.slice(0, 10),
2000
+ };
2001
+ });
2002
+ const failed = results.filter((result) => !result.matched).length;
2003
+ checks.push({
2004
+ type: check.type,
2005
+ label: check.label || check.type,
2006
+ status: failed ? "failed" : "passed",
2007
+ evidence: {
2008
+ selector,
2009
+ expected_url: check.type === "frame_url_equals" ? expectedUrl : null,
2010
+ pattern: check.type === "frame_url_matches" ? check.pattern || null : null,
2011
+ viewports: results,
2012
+ },
2013
+ message: failed ? "Frame selector " + selector + " URL assertion failed in " + failed + " viewport(s)." : undefined,
2014
+ });
2015
+ continue;
2016
+ }
1937
2017
  if (check.type === "frame_no_horizontal_overflow") {
1938
2018
  const selector = check.selector || "";
1939
2019
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
@@ -2195,8 +2275,8 @@ function setupJsonValue(value) {
2195
2275
  function setupValuesEqual(left, right) {
2196
2276
  return JSON.stringify(setupJsonValue(left)) === JSON.stringify(setupJsonValue(right));
2197
2277
  }
2198
- async function setupReadWindowValue(path) {
2199
- return await page.evaluate(({ path }) => {
2278
+ async function setupReadWindowValue(context, path) {
2279
+ return await context.evaluate(({ path }) => {
2200
2280
  const toJsonValue = (value) => {
2201
2281
  if (value === null || value === undefined) return null;
2202
2282
  if (typeof value === "string" || typeof value === "boolean") return value;
@@ -2218,6 +2298,80 @@ async function setupReadWindowValue(path) {
2218
2298
  return { ok: true, value: toJsonValue(current) };
2219
2299
  }, { path });
2220
2300
  }
2301
+ function setupFrameSelector(action) {
2302
+ return String(action?.frame_selector || action?.frameSelector || action?.iframe_selector || action?.iframeSelector || "").trim();
2303
+ }
2304
+ function setupFrameIndex(action) {
2305
+ const raw = action?.frame_index ?? action?.frameIndex ?? action?.iframe_index ?? action?.iframeIndex ?? 0;
2306
+ const number = Number(raw);
2307
+ return Number.isInteger(number) && number >= 0 ? number : 0;
2308
+ }
2309
+ function setupScopeEvidence(scope) {
2310
+ if (!scope || !scope.frame_selector) return {};
2311
+ return {
2312
+ frame_selector: scope.frame_selector,
2313
+ frame_index: scope.frame_index,
2314
+ frame_count: scope.frame_count,
2315
+ };
2316
+ }
2317
+ function setupScopeFailure(base, scope) {
2318
+ return {
2319
+ ...base,
2320
+ ...setupScopeEvidence(scope),
2321
+ reason: scope?.reason || "frame_scope_unavailable",
2322
+ error: scope?.error || undefined,
2323
+ };
2324
+ }
2325
+ async function setupActionScope(action, timeout) {
2326
+ const frameSelector = setupFrameSelector(action);
2327
+ if (!frameSelector) return { ok: true, context: page };
2328
+ const frameIndex = setupFrameIndex(action);
2329
+ let frameCount = 0;
2330
+ let locator = null;
2331
+ try {
2332
+ await page.waitForSelector(frameSelector, { state: "attached", timeout });
2333
+ locator = page.locator(frameSelector);
2334
+ frameCount = await locator.count();
2335
+ } catch (error) {
2336
+ return {
2337
+ ok: false,
2338
+ reason: "frame_selector_not_found",
2339
+ frame_selector: frameSelector,
2340
+ frame_index: frameIndex,
2341
+ frame_count: frameCount,
2342
+ error: String(error && error.message ? error.message : error).slice(0, 1000),
2343
+ };
2344
+ }
2345
+ if (!frameCount) {
2346
+ return { ok: false, reason: "frame_selector_not_found", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2347
+ }
2348
+ if (frameIndex >= frameCount) {
2349
+ return { ok: false, reason: "frame_index_out_of_range", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2350
+ }
2351
+ const handle = await locator.nth(frameIndex).elementHandle({ timeout }).catch((error) => ({ __riddle_error: error }));
2352
+ if (!handle || handle.__riddle_error) {
2353
+ return {
2354
+ ok: false,
2355
+ reason: "frame_element_unavailable",
2356
+ frame_selector: frameSelector,
2357
+ frame_index: frameIndex,
2358
+ frame_count: frameCount,
2359
+ error: handle?.__riddle_error ? String(handle.__riddle_error && handle.__riddle_error.message ? handle.__riddle_error.message : handle.__riddle_error).slice(0, 1000) : undefined,
2360
+ };
2361
+ }
2362
+ const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame().catch((error) => ({ __riddle_error: error })) : null;
2363
+ if (!frame || frame.__riddle_error) {
2364
+ return {
2365
+ ok: false,
2366
+ reason: "content_frame_unavailable",
2367
+ frame_selector: frameSelector,
2368
+ frame_index: frameIndex,
2369
+ frame_count: frameCount,
2370
+ error: frame?.__riddle_error ? String(frame.__riddle_error && frame.__riddle_error.message ? frame.__riddle_error.message : frame.__riddle_error).slice(0, 1000) : undefined,
2371
+ };
2372
+ }
2373
+ return { ok: true, context: frame, frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2374
+ }
2221
2375
  async function setupLocatorText(locator, index) {
2222
2376
  return await locator.nth(index).textContent({ timeout: 1000 }).catch(() => "");
2223
2377
  }
@@ -2379,7 +2533,8 @@ async function registerNetworkMocks(mocks) {
2379
2533
  }
2380
2534
  async function executeSetupAction(action, ordinal) {
2381
2535
  const type = setupActionType(action);
2382
- const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null };
2536
+ const frameSelector = setupFrameSelector(action);
2537
+ const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null, frame_selector: frameSelector || null };
2383
2538
  const timeout = setupNumber(action.timeout_ms, 5000);
2384
2539
  try {
2385
2540
  if (type === "wait") {
@@ -2388,51 +2543,65 @@ async function executeSetupAction(action, ordinal) {
2388
2543
  return { ...base, ok: true, ms };
2389
2544
  }
2390
2545
  if (type === "wait_for_selector") {
2391
- await page.waitForSelector(action.selector, { state: "visible", timeout });
2392
- return { ...base, ok: true, timeout_ms: timeout };
2546
+ const scope = await setupActionScope(action, timeout);
2547
+ if (!scope.ok) return setupScopeFailure(base, scope);
2548
+ await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
2549
+ return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
2393
2550
  }
2394
2551
  if (type === "press") {
2395
2552
  const key = String(action.key || "").trim();
2396
2553
  if (!key) return { ...base, reason: "missing_key" };
2554
+ const scope = await setupActionScope(action, timeout);
2555
+ if (!scope.ok) return setupScopeFailure(base, scope);
2397
2556
  if (!action.selector) {
2398
- await page.keyboard.press(key);
2399
- return { ...base, ok: true, key };
2557
+ if (scope.frame_selector) {
2558
+ await scope.context.locator("body").press(key, { timeout });
2559
+ } else {
2560
+ await page.keyboard.press(key);
2561
+ }
2562
+ return { ...base, ...setupScopeEvidence(scope), ok: true, key };
2400
2563
  }
2401
- const locator = page.locator(action.selector);
2564
+ const locator = scope.context.locator(action.selector);
2402
2565
  const count = await locator.count();
2403
2566
  if (!count) return { ...base, reason: "selector_not_found", count, key };
2404
2567
  const targetIndex = Number.isInteger(action.index) ? action.index : 0;
2405
2568
  if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex, key };
2406
2569
  await locator.nth(targetIndex).press(key, { timeout });
2407
- return { ...base, ok: true, count, target_index: targetIndex, key };
2570
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, key };
2408
2571
  }
2409
2572
  if (type === "local_storage" || type === "session_storage") {
2410
2573
  const value = setupActionValue(action);
2411
- await page.evaluate(({ type, key, value }) => {
2574
+ const scope = await setupActionScope(action, timeout);
2575
+ if (!scope.ok) return setupScopeFailure(base, scope);
2576
+ await scope.context.evaluate(({ type, key, value }) => {
2412
2577
  const storage = type === "session_storage" ? window.sessionStorage : window.localStorage;
2413
2578
  storage.setItem(key, value);
2414
2579
  }, { type, key: action.key, value });
2415
2580
  if (action.reload === true) {
2416
2581
  await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
2417
2582
  }
2418
- return { ...base, ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
2583
+ return { ...base, ...setupScopeEvidence(scope), ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
2419
2584
  }
2420
2585
  if (type === "clear_storage") {
2421
2586
  const storage = action.storage || "both";
2422
- await page.evaluate(({ storage }) => {
2587
+ const scope = await setupActionScope(action, timeout);
2588
+ if (!scope.ok) return setupScopeFailure(base, scope);
2589
+ await scope.context.evaluate(({ storage }) => {
2423
2590
  if (storage === "local" || storage === "both") window.localStorage.clear();
2424
2591
  if (storage === "session" || storage === "both") window.sessionStorage.clear();
2425
2592
  }, { storage });
2426
2593
  if (action.reload === true) {
2427
2594
  await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
2428
2595
  }
2429
- return { ...base, ok: true, storage, reload: action.reload === true };
2596
+ return { ...base, ...setupScopeEvidence(scope), ok: true, storage, reload: action.reload === true };
2430
2597
  }
2431
2598
  if (type === "window_call") {
2432
2599
  const path = String(action.path || action.function_path || action.functionPath || "");
2433
2600
  const args = Array.isArray(action.args) ? action.args : [];
2434
2601
  if (!path) return { ...base, path, reason: "missing_path" };
2435
- const result = await page.evaluate(async ({ path, args }) => {
2602
+ const scope = await setupActionScope(action, timeout);
2603
+ if (!scope.ok) return setupScopeFailure(base, scope);
2604
+ const result = await scope.context.evaluate(async ({ path, args }) => {
2436
2605
  const toJsonValue = (value) => {
2437
2606
  if (value === null || value === undefined) return null;
2438
2607
  if (typeof value === "string" || typeof value === "boolean") return value;
@@ -2472,6 +2641,7 @@ async function executeSetupAction(action, ordinal) {
2472
2641
  const expectationMet = !hasExpectation || setupValuesEqual(result.returned, expected);
2473
2642
  return {
2474
2643
  ...base,
2644
+ ...setupScopeEvidence(scope),
2475
2645
  ok: Boolean(result.ok && expectationMet),
2476
2646
  path,
2477
2647
  arg_count: args.length,
@@ -2502,13 +2672,16 @@ async function executeSetupAction(action, ordinal) {
2502
2672
  : action.expect;
2503
2673
  if (!path) return { ...base, path, reason: "missing_path" };
2504
2674
  if (!hasExpected) return { ...base, path, reason: "missing_expected_value" };
2675
+ const scope = await setupActionScope(action, timeout);
2676
+ if (!scope.ok) return setupScopeFailure(base, scope);
2505
2677
  const startedAt = Date.now();
2506
2678
  let result = null;
2507
2679
  while (Date.now() - startedAt <= timeout) {
2508
- result = await setupReadWindowValue(path);
2680
+ result = await setupReadWindowValue(scope.context, path);
2509
2681
  if (result.ok && setupValuesEqual(result.value, expected)) {
2510
2682
  return {
2511
2683
  ...base,
2684
+ ...setupScopeEvidence(scope),
2512
2685
  ok: true,
2513
2686
  path,
2514
2687
  value: setupJsonValue(result.value),
@@ -2520,6 +2693,7 @@ async function executeSetupAction(action, ordinal) {
2520
2693
  }
2521
2694
  return {
2522
2695
  ...base,
2696
+ ...setupScopeEvidence(scope),
2523
2697
  path,
2524
2698
  reason: result?.ok ? "unexpected_value" : result?.reason || "path_not_found",
2525
2699
  missing_part: result?.missing_part || undefined,
@@ -2536,11 +2710,13 @@ async function executeSetupAction(action, ordinal) {
2536
2710
  const hasExpected = expected !== undefined;
2537
2711
  if (!path) return { ...base, path, reason: "missing_path" };
2538
2712
  if (!hasExpected && minValue === undefined && maxValue === undefined) return { ...base, path, reason: "missing_number_expectation" };
2713
+ const scope = await setupActionScope(action, timeout);
2714
+ if (!scope.ok) return setupScopeFailure(base, scope);
2539
2715
  const startedAt = Date.now();
2540
2716
  let result = null;
2541
2717
  let lastReason = "path_not_found";
2542
2718
  while (Date.now() - startedAt <= timeout) {
2543
- result = await setupReadWindowValue(path);
2719
+ result = await setupReadWindowValue(scope.context, path);
2544
2720
  if (result.ok) {
2545
2721
  const actual = setupFiniteNumber(result.value);
2546
2722
  if (actual === undefined) {
@@ -2554,6 +2730,7 @@ async function executeSetupAction(action, ordinal) {
2554
2730
  } else {
2555
2731
  return {
2556
2732
  ...base,
2733
+ ...setupScopeEvidence(scope),
2557
2734
  ok: true,
2558
2735
  path,
2559
2736
  value: actual,
@@ -2570,6 +2747,7 @@ async function executeSetupAction(action, ordinal) {
2570
2747
  }
2571
2748
  return {
2572
2749
  ...base,
2750
+ ...setupScopeEvidence(scope),
2573
2751
  path,
2574
2752
  reason: lastReason,
2575
2753
  missing_part: result?.missing_part || undefined,
@@ -2581,7 +2759,9 @@ async function executeSetupAction(action, ordinal) {
2581
2759
  };
2582
2760
  }
2583
2761
  if (type === "click") {
2584
- const locator = page.locator(action.selector);
2762
+ const scope = await setupActionScope(action, timeout);
2763
+ if (!scope.ok) return setupScopeFailure(base, scope);
2764
+ const locator = scope.context.locator(action.selector);
2585
2765
  const count = await locator.count();
2586
2766
  if (!count) return { ...base, reason: "selector_not_found", count };
2587
2767
  let targetIndex = Number.isInteger(action.index) ? action.index : 0;
@@ -2613,33 +2793,39 @@ async function executeSetupAction(action, ordinal) {
2613
2793
  ? { timeout, noWaitAfter: true, force: true }
2614
2794
  : { timeout, noWaitAfter: true };
2615
2795
  await locator.nth(targetIndex).click(clickOptions);
2616
- return { ...base, ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
2796
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
2617
2797
  }
2618
2798
  if (type === "fill" || type === "set_input_value") {
2619
- const locator = page.locator(action.selector);
2799
+ const scope = await setupActionScope(action, timeout);
2800
+ if (!scope.ok) return setupScopeFailure(base, scope);
2801
+ const locator = scope.context.locator(action.selector);
2620
2802
  const count = await locator.count();
2621
2803
  if (!count) return { ...base, reason: "selector_not_found", count };
2622
2804
  const targetIndex = Number.isInteger(action.index) ? action.index : 0;
2623
2805
  if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex };
2624
2806
  const value = setupActionValue(action);
2625
2807
  await locator.nth(targetIndex).fill(value, { timeout });
2626
- return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
2808
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, value_length: value.length };
2627
2809
  }
2628
2810
  if (type === "assert_selector_count") {
2629
- const locator = page.locator(action.selector);
2811
+ const scope = await setupActionScope(action, timeout);
2812
+ if (!scope.ok) return setupScopeFailure(base, scope);
2813
+ const locator = scope.context.locator(action.selector);
2630
2814
  const expectedCount = setupNumber(action.expected_count, -1);
2631
2815
  if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
2632
2816
  const startedAt = Date.now();
2633
2817
  let count = 0;
2634
2818
  while (Date.now() - startedAt <= timeout) {
2635
2819
  count = await locator.count().catch(() => 0);
2636
- if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2820
+ if (count === expectedCount) return { ...base, ...setupScopeEvidence(scope), ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2637
2821
  await page.waitForTimeout(100);
2638
2822
  }
2639
- return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2823
+ return { ...base, ...setupScopeEvidence(scope), reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2640
2824
  }
2641
2825
  if (type === "wait_for_text") {
2642
- const locator = page.locator(action.selector);
2826
+ const scope = await setupActionScope(action, timeout);
2827
+ if (!scope.ok) return setupScopeFailure(base, scope);
2828
+ const locator = scope.context.locator(action.selector);
2643
2829
  const startedAt = Date.now();
2644
2830
  let lastText = "";
2645
2831
  while (Date.now() - startedAt <= timeout) {
@@ -2648,15 +2834,17 @@ async function executeSetupAction(action, ordinal) {
2648
2834
  const text = await setupLocatorText(locator, index);
2649
2835
  lastText = text || lastText;
2650
2836
  if (setupTextMatches(text, action)) {
2651
- return { ...base, ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2837
+ return { ...base, ...setupScopeEvidence(scope), ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2652
2838
  }
2653
2839
  }
2654
2840
  await page.waitForTimeout(100);
2655
2841
  }
2656
- return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2842
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2657
2843
  }
2658
2844
  if (type === "assert_text_visible" || type === "assert_text_absent") {
2659
- const locator = page.locator(action.selector);
2845
+ const scope = await setupActionScope(action, timeout);
2846
+ if (!scope.ok) return setupScopeFailure(base, scope);
2847
+ const locator = scope.context.locator(action.selector);
2660
2848
  const startedAt = Date.now();
2661
2849
  let lastText = "";
2662
2850
  let matchedText = "";
@@ -2675,7 +2863,7 @@ async function executeSetupAction(action, ordinal) {
2675
2863
  if (type === "assert_text_visible") {
2676
2864
  const visible = await setupLocatorVisible(locator, index);
2677
2865
  if (visible) {
2678
- return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2866
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2679
2867
  }
2680
2868
  hiddenMatch = true;
2681
2869
  break;
@@ -2684,17 +2872,17 @@ async function executeSetupAction(action, ordinal) {
2684
2872
  }
2685
2873
  }
2686
2874
  if (type === "assert_text_absent" && !matched) {
2687
- return { ...base, ok: true, count, timeout_ms: timeout };
2875
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, timeout_ms: timeout };
2688
2876
  }
2689
2877
  await page.waitForTimeout(100);
2690
2878
  }
2691
2879
  if (type === "assert_text_visible") {
2692
2880
  if (hiddenMatch) {
2693
- return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2881
+ return { ...base, ...setupScopeEvidence(scope), reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2694
2882
  }
2695
- return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2883
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2696
2884
  }
2697
- return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2885
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2698
2886
  }
2699
2887
  return { ...base, reason: "unsupported_action" };
2700
2888
  } catch (error) {
@@ -3297,7 +3485,7 @@ async function captureViewport(viewport) {
3297
3485
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
3298
3486
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
3299
3487
  }
3300
- if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
3488
+ if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
3301
3489
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
3302
3490
  frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
3303
3491
  }
@@ -4,7 +4,7 @@ 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"];
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
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"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -20,6 +20,8 @@ interface RiddleProofProfileViewport {
20
20
  interface RiddleProofProfileSetupAction {
21
21
  type: RiddleProofProfileSetupActionType;
22
22
  selector?: string;
23
+ frame_selector?: string;
24
+ frame_index?: number;
23
25
  force?: boolean;
24
26
  key?: string;
25
27
  value?: string;
@@ -92,6 +94,7 @@ interface RiddleProofProfileCheck {
92
94
  expected_path?: string;
93
95
  param?: string;
94
96
  expected_value?: string;
97
+ expected_url?: string;
95
98
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
96
99
  selector?: string;
97
100
  expected_texts?: string[];
package/dist/profile.d.ts CHANGED
@@ -4,7 +4,7 @@ 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"];
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
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"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -20,6 +20,8 @@ interface RiddleProofProfileViewport {
20
20
  interface RiddleProofProfileSetupAction {
21
21
  type: RiddleProofProfileSetupActionType;
22
22
  selector?: string;
23
+ frame_selector?: string;
24
+ frame_index?: number;
23
25
  force?: boolean;
24
26
  key?: string;
25
27
  value?: string;
@@ -92,6 +94,7 @@ interface RiddleProofProfileCheck {
92
94
  expected_path?: string;
93
95
  param?: string;
94
96
  expected_value?: string;
97
+ expected_url?: string;
95
98
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
96
99
  selector?: string;
97
100
  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-7ZBK2GQX.js";
22
+ } from "./chunk-FLQ4HHTT.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.58",
3
+ "version": "0.7.60",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",