@riddledc/riddle-proof 0.7.37 → 0.7.38

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
@@ -8729,6 +8729,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
8729
8729
  "selector_visible",
8730
8730
  "selector_count_at_least",
8731
8731
  "selector_text_order",
8732
+ "frame_text_visible",
8733
+ "frame_no_horizontal_overflow",
8732
8734
  "text_visible",
8733
8735
  "text_absent",
8734
8736
  "route_inventory",
@@ -9027,6 +9029,12 @@ function normalizeCheck(input, index) {
9027
9029
  if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue5(input.selector)) {
9028
9030
  throw new Error(`checks[${index}] ${type} requires selector.`);
9029
9031
  }
9032
+ if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue5(input.selector)) {
9033
+ throw new Error(`checks[${index}] ${type} requires selector.`);
9034
+ }
9035
+ if (type === "frame_text_visible" && !stringValue5(input.text) && !stringValue5(input.pattern)) {
9036
+ throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
9037
+ }
9030
9038
  if ((type === "text_visible" || type === "text_absent") && !stringValue5(input.text) && !stringValue5(input.pattern)) {
9031
9039
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
9032
9040
  }
@@ -9177,6 +9185,22 @@ function textOrderMatch(texts, expectedTexts) {
9177
9185
  }
9178
9186
  return { matched: true, positions };
9179
9187
  }
9188
+ function frameEvidenceForSelector(viewport, selector) {
9189
+ const container = viewport.frames?.[selector];
9190
+ if (!isRecord2(container)) return [];
9191
+ const frames = Array.isArray(container.frames) ? container.frames : [];
9192
+ return frames.filter((frame) => isRecord2(frame));
9193
+ }
9194
+ function frameTextSample(frame) {
9195
+ const parts = [
9196
+ frame.title,
9197
+ frame.text_sample,
9198
+ frame.body_text_sample,
9199
+ frame.body_text,
9200
+ frame.text
9201
+ ];
9202
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
9203
+ }
9180
9204
  function summarizeRouteInventory(viewport, inventory) {
9181
9205
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
9182
9206
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -9341,6 +9365,67 @@ function assessCheckFromEvidence(check, evidence) {
9341
9365
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
9342
9366
  };
9343
9367
  }
9368
+ if (check.type === "frame_text_visible") {
9369
+ const key = selectorKey(check);
9370
+ const results = viewports.map((viewport) => {
9371
+ const frames = frameEvidenceForSelector(viewport, key);
9372
+ const matches = frames.map((frame, index) => ({ index, frame, matched: matchText(frameTextSample(frame), check) })).filter((item) => item.matched);
9373
+ return {
9374
+ viewport: viewport.name,
9375
+ frame_count: frames.length,
9376
+ matched_count: matches.length,
9377
+ matched: matches.length > 0,
9378
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
9379
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3)
9380
+ };
9381
+ });
9382
+ const failed = results.filter((result) => !result.matched).length;
9383
+ return {
9384
+ type: check.type,
9385
+ label: checkLabel(check),
9386
+ status: failed ? "failed" : "passed",
9387
+ evidence: {
9388
+ selector: key,
9389
+ text: check.text || null,
9390
+ pattern: check.pattern || null,
9391
+ viewports: results.map((result) => toJsonValue(result))
9392
+ },
9393
+ message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
9394
+ };
9395
+ }
9396
+ if (check.type === "frame_no_horizontal_overflow") {
9397
+ const key = selectorKey(check);
9398
+ const maxOverflow = check.max_overflow_px ?? 4;
9399
+ const results = viewports.map((viewport) => {
9400
+ const frames = frameEvidenceForSelector(viewport, key);
9401
+ const frameOverflows = frames.map((frame, index) => ({
9402
+ index,
9403
+ url: String(frame.url || ""),
9404
+ overflow_px: horizontalBoundsOverflowPx2(frame),
9405
+ offender_count: boundsOffendersForEvidence2(frame).length
9406
+ }));
9407
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
9408
+ return {
9409
+ viewport: viewport.name,
9410
+ frame_count: frames.length,
9411
+ max_overflow_px: roundPixels2(maxFrameOverflow),
9412
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
9413
+ frames: frameOverflows.map((frame) => toJsonValue(frame))
9414
+ };
9415
+ });
9416
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
9417
+ return {
9418
+ type: check.type,
9419
+ label: checkLabel(check),
9420
+ status: failed ? "failed" : "passed",
9421
+ evidence: {
9422
+ selector: key,
9423
+ max_overflow_px: maxOverflow,
9424
+ viewports: results.map((result) => toJsonValue(result))
9425
+ },
9426
+ message: failed ? `Frame selector ${key} overflow exceeded ${maxOverflow}px or was missing in ${failed} viewport(s).` : void 0
9427
+ };
9428
+ }
9344
9429
  if (check.type === "text_visible" || check.type === "text_absent") {
9345
9430
  const key = textKey(check);
9346
9431
  const expectedVisible = check.type === "text_visible";
@@ -9716,6 +9801,21 @@ function textOrderMatch(texts, expectedTexts) {
9716
9801
  }
9717
9802
  return { matched: true, positions };
9718
9803
  }
9804
+ function frameEvidenceForSelector(viewport, selector) {
9805
+ const container = viewport.frames && viewport.frames[selector || ""];
9806
+ if (!container || typeof container !== "object" || Array.isArray(container)) return [];
9807
+ const frames = Array.isArray(container.frames) ? container.frames : [];
9808
+ return frames.filter((frame) => frame && typeof frame === "object" && !Array.isArray(frame));
9809
+ }
9810
+ function frameTextSample(frame) {
9811
+ return [
9812
+ frame && frame.title,
9813
+ frame && frame.text_sample,
9814
+ frame && frame.body_text_sample,
9815
+ frame && frame.body_text,
9816
+ frame && frame.text,
9817
+ ].map((part) => String(part || "")).filter(Boolean).join(" ");
9818
+ }
9719
9819
  function summarizeRouteInventory(viewport, inventory) {
9720
9820
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
9721
9821
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -9962,6 +10062,62 @@ function assessProfile(profile, evidence) {
9962
10062
  });
9963
10063
  continue;
9964
10064
  }
10065
+ if (check.type === "frame_text_visible") {
10066
+ const selector = check.selector || "";
10067
+ const results = viewports.map((viewport) => {
10068
+ const frames = frameEvidenceForSelector(viewport, selector);
10069
+ const matches = frames
10070
+ .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
10071
+ .filter((item) => item.matched);
10072
+ return {
10073
+ viewport: viewport.name,
10074
+ frame_count: frames.length,
10075
+ matched_count: matches.length,
10076
+ matched: matches.length > 0,
10077
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
10078
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3),
10079
+ };
10080
+ });
10081
+ const failed = results.filter((result) => !result.matched).length;
10082
+ checks.push({
10083
+ type: check.type,
10084
+ label: check.label || check.type,
10085
+ status: failed ? "failed" : "passed",
10086
+ evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
10087
+ message: failed ? "Frame selector " + selector + " did not contain expected text in " + failed + " viewport(s)." : undefined,
10088
+ });
10089
+ continue;
10090
+ }
10091
+ if (check.type === "frame_no_horizontal_overflow") {
10092
+ const selector = check.selector || "";
10093
+ const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
10094
+ const results = viewports.map((viewport) => {
10095
+ const frames = frameEvidenceForSelector(viewport, selector);
10096
+ const frameOverflows = frames.map((frame, index) => ({
10097
+ index,
10098
+ url: String(frame.url || ""),
10099
+ overflow_px: horizontalBoundsOverflowPx(frame),
10100
+ offender_count: boundsOffendersForEvidence(frame).length,
10101
+ }));
10102
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
10103
+ return {
10104
+ viewport: viewport.name,
10105
+ frame_count: frames.length,
10106
+ max_overflow_px: roundPixels(maxFrameOverflow),
10107
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
10108
+ frames: frameOverflows,
10109
+ };
10110
+ });
10111
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
10112
+ checks.push({
10113
+ type: check.type,
10114
+ label: check.label || check.type,
10115
+ status: failed ? "failed" : "passed",
10116
+ evidence: { selector, max_overflow_px: maxOverflow, viewports: results },
10117
+ message: failed ? "Frame selector " + selector + " overflow exceeded " + maxOverflow + "px or was missing in " + failed + " viewport(s)." : undefined,
10118
+ });
10119
+ continue;
10120
+ }
9965
10121
  if (check.type === "text_visible" || check.type === "text_absent") {
9966
10122
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
9967
10123
  const expectedVisible = check.type === "text_visible";
@@ -10359,6 +10515,108 @@ async function selectorTextSequence(selector) {
10359
10515
  };
10360
10516
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
10361
10517
  }
10518
+ async function frameEvidence(selector) {
10519
+ const result = { selector, count: 0, frame_count: 0, frames: [], errors: [] };
10520
+ let handles = [];
10521
+ try {
10522
+ const locator = page.locator(selector);
10523
+ result.count = await locator.count();
10524
+ handles = await locator.elementHandles();
10525
+ } catch (error) {
10526
+ result.errors.push(String(error && error.message ? error.message : error).slice(0, 500));
10527
+ return result;
10528
+ }
10529
+ for (let index = 0; index < handles.length; index += 1) {
10530
+ const handle = handles[index];
10531
+ try {
10532
+ const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame() : null;
10533
+ if (!frame) {
10534
+ result.frames.push({ index, attached: false, error: "content_frame_unavailable" });
10535
+ continue;
10536
+ }
10537
+ const snapshot = await frame.evaluate(() => {
10538
+ const body = document.body;
10539
+ const documentElement = document.documentElement;
10540
+ const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
10541
+ const clientWidth = documentElement ? documentElement.clientWidth : window.innerWidth;
10542
+ const clientHeight = documentElement ? documentElement.clientHeight : window.innerHeight;
10543
+ const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
10544
+ const viewportWidth = clientWidth || window.innerWidth;
10545
+ const overflowOffenders = [];
10546
+ function isContainedByHorizontalScroller(element) {
10547
+ let current = element.parentElement;
10548
+ while (current && current !== body && current !== documentElement) {
10549
+ const style = window.getComputedStyle(current);
10550
+ const overflowX = style.overflowX || style.overflow || "";
10551
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
10552
+ const currentRect = current.getBoundingClientRect();
10553
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
10554
+ if (contained) return true;
10555
+ }
10556
+ current = current.parentElement;
10557
+ }
10558
+ return false;
10559
+ }
10560
+ for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
10561
+ const rect = element.getBoundingClientRect();
10562
+ if (!rect || rect.width < 1 || rect.height < 1) continue;
10563
+ const style = window.getComputedStyle(element);
10564
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
10565
+ const leftOverflow = Math.max(0, -rect.left);
10566
+ const rightOverflow = Math.max(0, rect.right - viewportWidth);
10567
+ const overflow = Math.max(leftOverflow, rightOverflow);
10568
+ if (overflow <= 0.5) continue;
10569
+ if (isContainedByHorizontalScroller(element)) continue;
10570
+ const tag = element.tagName ? element.tagName.toLowerCase() : "element";
10571
+ const id = element.id ? "#" + element.id : "";
10572
+ const className = typeof element.className === "string"
10573
+ ? element.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).join(".")
10574
+ : "";
10575
+ overflowOffenders.push({
10576
+ selector: tag + id + (className ? "." + className : ""),
10577
+ tag,
10578
+ text: (element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120),
10579
+ overflow,
10580
+ left_overflow_px: leftOverflow,
10581
+ right_overflow_px: rightOverflow,
10582
+ viewport_width: viewportWidth,
10583
+ rect: {
10584
+ left: rect.left,
10585
+ right: rect.right,
10586
+ width: rect.width,
10587
+ },
10588
+ });
10589
+ }
10590
+ overflowOffenders.sort((a, b) => b.overflow - a.overflow);
10591
+ const boundsOverflowPx = Math.max(
10592
+ 0,
10593
+ scrollWidth - (clientWidth || viewportWidth),
10594
+ ...overflowOffenders.map((offender) => offender.overflow),
10595
+ );
10596
+ return {
10597
+ url: location.href,
10598
+ title: document.title,
10599
+ text_length: text.length,
10600
+ text_sample: text.slice(0, 8000),
10601
+ body_text_sample: text.slice(0, 8000),
10602
+ viewport_width: viewportWidth,
10603
+ viewport_height: clientHeight || window.innerHeight,
10604
+ scroll_width: scrollWidth,
10605
+ client_width: clientWidth,
10606
+ overflow_px: Math.max(0, scrollWidth - (clientWidth || viewportWidth)),
10607
+ bounds_overflow_px: Math.round(boundsOverflowPx * 100) / 100,
10608
+ overflow_offender_count: overflowOffenders.length,
10609
+ overflow_offenders: overflowOffenders.slice(0, 10),
10610
+ };
10611
+ });
10612
+ result.frames.push({ index, attached: true, ...snapshot });
10613
+ } catch (error) {
10614
+ result.frames.push({ index, attached: false, error: String(error && error.message ? error.message : error).slice(0, 1000) });
10615
+ }
10616
+ }
10617
+ result.frame_count = result.frames.filter((frame) => frame && frame.attached !== false && !frame.error).length;
10618
+ return result;
10619
+ }
10362
10620
  function inventoryAppPathFromUrl(urlLike) {
10363
10621
  const url = new URL(String(urlLike), targetUrl);
10364
10622
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -10763,6 +11021,7 @@ async function captureViewport(viewport) {
10763
11021
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
10764
11022
  }));
10765
11023
  const selectors = {};
11024
+ const frames = {};
10766
11025
  const text_sequences = {};
10767
11026
  const text_matches = {};
10768
11027
  for (const check of profile.checks || []) {
@@ -10776,6 +11035,10 @@ async function captureViewport(viewport) {
10776
11035
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
10777
11036
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
10778
11037
  }
11038
+ if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
11039
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
11040
+ frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
11041
+ }
10779
11042
  }
10780
11043
  const screenshotLabel = profileSlug + "-" + viewport.name;
10781
11044
  try {
@@ -10836,6 +11099,7 @@ async function captureViewport(viewport) {
10836
11099
  bounds_overflow_px: dom.bounds_overflow_px,
10837
11100
  overflow_offenders: dom.overflow_offenders || [],
10838
11101
  selectors,
11102
+ frames,
10839
11103
  text_sequences,
10840
11104
  text_matches,
10841
11105
  route_inventory: routeInventory,
@@ -10871,6 +11135,20 @@ function buildProfileEvidence(currentViewports) {
10871
11135
  overflow_px: currentViewports.map((viewport) => viewport.overflow_px),
10872
11136
  bounds_overflow_px: currentViewports.map((viewport) => viewport.bounds_overflow_px),
10873
11137
  overflow_offender_counts: currentViewports.map((viewport) => (viewport.overflow_offenders || []).length),
11138
+ frames: currentViewports
11139
+ .filter((viewport) => viewport.frames)
11140
+ .map((viewport) => ({
11141
+ viewport: viewport.name,
11142
+ selectors: Object.entries(viewport.frames || {}).map(([selector, frameSet]) => ({
11143
+ selector,
11144
+ count: frameSet && frameSet.count,
11145
+ frame_count: frameSet && frameSet.frame_count,
11146
+ max_bounds_overflow_px: Math.max(
11147
+ 0,
11148
+ ...((frameSet && Array.isArray(frameSet.frames) ? frameSet.frames : [])).map((frame) => horizontalBoundsOverflowPx(frame)),
11149
+ ),
11150
+ })),
11151
+ })),
10874
11152
  route_inventory: currentViewports
10875
11153
  .filter((viewport) => viewport.route_inventory)
10876
11154
  .map((viewport) => ({
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-KZRDAMI5.js";
61
+ } from "./chunk-ZB7AFRN3.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,