@riddledc/riddle-proof 0.7.37 → 0.7.39

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/cli.cjs CHANGED
@@ -6888,6 +6888,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6888
6888
  "selector_visible",
6889
6889
  "selector_count_at_least",
6890
6890
  "selector_text_order",
6891
+ "frame_text_visible",
6892
+ "frame_no_horizontal_overflow",
6891
6893
  "text_visible",
6892
6894
  "text_absent",
6893
6895
  "route_inventory",
@@ -7176,6 +7178,13 @@ function normalizeExpectedTexts(value, index) {
7176
7178
  if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
7177
7179
  return texts;
7178
7180
  }
7181
+ function normalizeStringList(value, label) {
7182
+ if (value === void 0) return void 0;
7183
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
7184
+ const values = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
7185
+ if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
7186
+ return values;
7187
+ }
7179
7188
  function normalizeCheck(input, index) {
7180
7189
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
7181
7190
  const type = stringValue2(input.type);
@@ -7186,6 +7195,12 @@ function normalizeCheck(input, index) {
7186
7195
  if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue2(input.selector)) {
7187
7196
  throw new Error(`checks[${index}] ${type} requires selector.`);
7188
7197
  }
7198
+ if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue2(input.selector)) {
7199
+ throw new Error(`checks[${index}] ${type} requires selector.`);
7200
+ }
7201
+ if (type === "frame_text_visible" && !stringValue2(input.text) && !stringValue2(input.pattern)) {
7202
+ throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
7203
+ }
7189
7204
  if ((type === "text_visible" || type === "text_absent") && !stringValue2(input.text) && !stringValue2(input.pattern)) {
7190
7205
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
7191
7206
  }
@@ -7218,6 +7233,10 @@ function normalizeCheck(input, index) {
7218
7233
  text: stringValue2(input.text),
7219
7234
  pattern: stringValue2(input.pattern),
7220
7235
  flags: stringValue2(input.flags),
7236
+ allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
7237
+ allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
7238
+ allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
7239
+ allowed_page_error_patterns: normalizeStringList(input.allowed_page_error_patterns ?? input.allowedPageErrorPatterns ?? input.allow_page_error_patterns ?? input.allowPageErrorPatterns, `checks[${index}] allowed_page_error_patterns`),
7221
7240
  min_count: numberValue(input.min_count),
7222
7241
  max_overflow_px: numberValue(input.max_overflow_px),
7223
7242
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
@@ -7336,6 +7355,22 @@ function textOrderMatch(texts, expectedTexts) {
7336
7355
  }
7337
7356
  return { matched: true, positions };
7338
7357
  }
7358
+ function frameEvidenceForSelector(viewport, selector) {
7359
+ const container = viewport.frames?.[selector];
7360
+ if (!isRecord(container)) return [];
7361
+ const frames = Array.isArray(container.frames) ? container.frames : [];
7362
+ return frames.filter((frame) => isRecord(frame));
7363
+ }
7364
+ function frameTextSample(frame) {
7365
+ const parts = [
7366
+ frame.title,
7367
+ frame.text_sample,
7368
+ frame.body_text_sample,
7369
+ frame.body_text,
7370
+ frame.text
7371
+ ];
7372
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
7373
+ }
7339
7374
  function summarizeRouteInventory(viewport, inventory) {
7340
7375
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
7341
7376
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -7365,6 +7400,18 @@ function matchText(sample, check) {
7365
7400
  }
7366
7401
  return sample.includes(check.text || "");
7367
7402
  }
7403
+ function matchesAllowedMessage(message, texts, patterns) {
7404
+ const sample = String(message || "");
7405
+ if (texts?.some((text) => sample.includes(text))) return true;
7406
+ for (const pattern of patterns || []) {
7407
+ try {
7408
+ if (new RegExp(pattern).test(sample)) return true;
7409
+ } catch {
7410
+ continue;
7411
+ }
7412
+ }
7413
+ return false;
7414
+ }
7368
7415
  function normalizeRoutePath(path7) {
7369
7416
  const value = path7 || "/";
7370
7417
  if (value === "/") return "/";
@@ -7500,6 +7547,67 @@ function assessCheckFromEvidence(check, evidence) {
7500
7547
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
7501
7548
  };
7502
7549
  }
7550
+ if (check.type === "frame_text_visible") {
7551
+ const key = selectorKey(check);
7552
+ const results = viewports.map((viewport) => {
7553
+ const frames = frameEvidenceForSelector(viewport, key);
7554
+ const matches = frames.map((frame, index) => ({ index, frame, matched: matchText(frameTextSample(frame), check) })).filter((item) => item.matched);
7555
+ return {
7556
+ viewport: viewport.name,
7557
+ frame_count: frames.length,
7558
+ matched_count: matches.length,
7559
+ matched: matches.length > 0,
7560
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
7561
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3)
7562
+ };
7563
+ });
7564
+ const failed = results.filter((result) => !result.matched).length;
7565
+ return {
7566
+ type: check.type,
7567
+ label: checkLabel(check),
7568
+ status: failed ? "failed" : "passed",
7569
+ evidence: {
7570
+ selector: key,
7571
+ text: check.text || null,
7572
+ pattern: check.pattern || null,
7573
+ viewports: results.map((result) => toJsonValue(result))
7574
+ },
7575
+ message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
7576
+ };
7577
+ }
7578
+ if (check.type === "frame_no_horizontal_overflow") {
7579
+ const key = selectorKey(check);
7580
+ const maxOverflow = check.max_overflow_px ?? 4;
7581
+ const results = viewports.map((viewport) => {
7582
+ const frames = frameEvidenceForSelector(viewport, key);
7583
+ const frameOverflows = frames.map((frame, index) => ({
7584
+ index,
7585
+ url: String(frame.url || ""),
7586
+ overflow_px: horizontalBoundsOverflowPx(frame),
7587
+ offender_count: boundsOffendersForEvidence(frame).length
7588
+ }));
7589
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
7590
+ return {
7591
+ viewport: viewport.name,
7592
+ frame_count: frames.length,
7593
+ max_overflow_px: roundPixels(maxFrameOverflow),
7594
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
7595
+ frames: frameOverflows.map((frame) => toJsonValue(frame))
7596
+ };
7597
+ });
7598
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
7599
+ return {
7600
+ type: check.type,
7601
+ label: checkLabel(check),
7602
+ status: failed ? "failed" : "passed",
7603
+ evidence: {
7604
+ selector: key,
7605
+ max_overflow_px: maxOverflow,
7606
+ viewports: results.map((result) => toJsonValue(result))
7607
+ },
7608
+ message: failed ? `Frame selector ${key} overflow exceeded ${maxOverflow}px or was missing in ${failed} viewport(s).` : void 0
7609
+ };
7610
+ }
7503
7611
  if (check.type === "text_visible" || check.type === "text_absent") {
7504
7612
  const key = textKey(check);
7505
7613
  const expectedVisible = check.type === "text_visible";
@@ -7590,14 +7698,28 @@ function assessCheckFromEvidence(check, evidence) {
7590
7698
  };
7591
7699
  }
7592
7700
  if (check.type === "no_fatal_console_errors") {
7593
- const fatalCount = (evidence.console?.fatal_count || 0) + (evidence.page_errors?.length || 0);
7701
+ const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
7702
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
7703
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
7704
+ const pageErrors = evidence.page_errors || [];
7705
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7706
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7707
+ const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
7594
7708
  return {
7595
7709
  type: check.type,
7596
7710
  label: checkLabel(check),
7597
7711
  status: fatalCount ? "failed" : "passed",
7598
7712
  evidence: {
7599
- console_fatal_count: evidence.console?.fatal_count || 0,
7600
- page_error_count: evidence.page_errors?.length || 0
7713
+ console_fatal_count: unallowedConsoleEvents.length,
7714
+ page_error_count: unallowedPageErrors.length,
7715
+ total_console_fatal_count: fatalConsoleEvents.length,
7716
+ total_page_error_count: pageErrors.length,
7717
+ allowed_console_fatal_count: allowedConsoleEvents.length,
7718
+ allowed_page_error_count: allowedPageErrors.length,
7719
+ allowed_console_texts: check.allowed_console_texts || [],
7720
+ allowed_console_patterns: check.allowed_console_patterns || [],
7721
+ allowed_page_error_texts: check.allowed_page_error_texts || [],
7722
+ allowed_page_error_patterns: check.allowed_page_error_patterns || []
7601
7723
  },
7602
7724
  message: fatalCount ? `${fatalCount} fatal browser error(s) were captured.` : void 0
7603
7725
  };
@@ -7837,6 +7959,16 @@ function textMatches(sample, check) {
7837
7959
  }
7838
7960
  return String(sample || "").includes(check.text || "");
7839
7961
  }
7962
+ function matchesAllowedMessage(message, texts, patterns) {
7963
+ const sample = String(message || "");
7964
+ if ((texts || []).some((text) => sample.includes(text))) return true;
7965
+ for (const pattern of patterns || []) {
7966
+ try {
7967
+ if (new RegExp(pattern).test(sample)) return true;
7968
+ } catch {}
7969
+ }
7970
+ return false;
7971
+ }
7840
7972
  function textSequenceForCheck(viewport, check) {
7841
7973
  const key = check.selector || "";
7842
7974
  const sequence = viewport.text_sequences && viewport.text_sequences[key];
@@ -7859,6 +7991,21 @@ function textOrderMatch(texts, expectedTexts) {
7859
7991
  }
7860
7992
  return { matched: true, positions };
7861
7993
  }
7994
+ function frameEvidenceForSelector(viewport, selector) {
7995
+ const container = viewport.frames && viewport.frames[selector || ""];
7996
+ if (!container || typeof container !== "object" || Array.isArray(container)) return [];
7997
+ const frames = Array.isArray(container.frames) ? container.frames : [];
7998
+ return frames.filter((frame) => frame && typeof frame === "object" && !Array.isArray(frame));
7999
+ }
8000
+ function frameTextSample(frame) {
8001
+ return [
8002
+ frame && frame.title,
8003
+ frame && frame.text_sample,
8004
+ frame && frame.body_text_sample,
8005
+ frame && frame.body_text,
8006
+ frame && frame.text,
8007
+ ].map((part) => String(part || "")).filter(Boolean).join(" ");
8008
+ }
7862
8009
  function summarizeRouteInventory(viewport, inventory) {
7863
8010
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
7864
8011
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -8105,6 +8252,62 @@ function assessProfile(profile, evidence) {
8105
8252
  });
8106
8253
  continue;
8107
8254
  }
8255
+ if (check.type === "frame_text_visible") {
8256
+ const selector = check.selector || "";
8257
+ const results = viewports.map((viewport) => {
8258
+ const frames = frameEvidenceForSelector(viewport, selector);
8259
+ const matches = frames
8260
+ .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
8261
+ .filter((item) => item.matched);
8262
+ return {
8263
+ viewport: viewport.name,
8264
+ frame_count: frames.length,
8265
+ matched_count: matches.length,
8266
+ matched: matches.length > 0,
8267
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
8268
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3),
8269
+ };
8270
+ });
8271
+ const failed = results.filter((result) => !result.matched).length;
8272
+ checks.push({
8273
+ type: check.type,
8274
+ label: check.label || check.type,
8275
+ status: failed ? "failed" : "passed",
8276
+ evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
8277
+ message: failed ? "Frame selector " + selector + " did not contain expected text in " + failed + " viewport(s)." : undefined,
8278
+ });
8279
+ continue;
8280
+ }
8281
+ if (check.type === "frame_no_horizontal_overflow") {
8282
+ const selector = check.selector || "";
8283
+ const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
8284
+ const results = viewports.map((viewport) => {
8285
+ const frames = frameEvidenceForSelector(viewport, selector);
8286
+ const frameOverflows = frames.map((frame, index) => ({
8287
+ index,
8288
+ url: String(frame.url || ""),
8289
+ overflow_px: horizontalBoundsOverflowPx(frame),
8290
+ offender_count: boundsOffendersForEvidence(frame).length,
8291
+ }));
8292
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
8293
+ return {
8294
+ viewport: viewport.name,
8295
+ frame_count: frames.length,
8296
+ max_overflow_px: roundPixels(maxFrameOverflow),
8297
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
8298
+ frames: frameOverflows,
8299
+ };
8300
+ });
8301
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
8302
+ checks.push({
8303
+ type: check.type,
8304
+ label: check.label || check.type,
8305
+ status: failed ? "failed" : "passed",
8306
+ evidence: { selector, max_overflow_px: maxOverflow, viewports: results },
8307
+ message: failed ? "Frame selector " + selector + " overflow exceeded " + maxOverflow + "px or was missing in " + failed + " viewport(s)." : undefined,
8308
+ });
8309
+ continue;
8310
+ }
8108
8311
  if (check.type === "text_visible" || check.type === "text_absent") {
8109
8312
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
8110
8313
  const expectedVisible = check.type === "text_visible";
@@ -8200,12 +8403,29 @@ function assessProfile(profile, evidence) {
8200
8403
  continue;
8201
8404
  }
8202
8405
  if (check.type === "no_fatal_console_errors") {
8203
- const fatalCount = ((evidence.console && evidence.console.fatal_count) || 0) + ((evidence.page_errors || []).length);
8406
+ const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
8407
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
8408
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
8409
+ const pageErrors = evidence.page_errors || [];
8410
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8411
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8412
+ const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
8204
8413
  checks.push({
8205
8414
  type: check.type,
8206
8415
  label: check.label || check.type,
8207
8416
  status: fatalCount ? "failed" : "passed",
8208
- evidence: { console_fatal_count: (evidence.console && evidence.console.fatal_count) || 0, page_error_count: (evidence.page_errors || []).length },
8417
+ evidence: {
8418
+ console_fatal_count: unallowedConsoleEvents.length,
8419
+ page_error_count: unallowedPageErrors.length,
8420
+ total_console_fatal_count: fatalConsoleEvents.length,
8421
+ total_page_error_count: pageErrors.length,
8422
+ allowed_console_fatal_count: allowedConsoleEvents.length,
8423
+ allowed_page_error_count: allowedPageErrors.length,
8424
+ allowed_console_texts: check.allowed_console_texts || [],
8425
+ allowed_console_patterns: check.allowed_console_patterns || [],
8426
+ allowed_page_error_texts: check.allowed_page_error_texts || [],
8427
+ allowed_page_error_patterns: check.allowed_page_error_patterns || [],
8428
+ },
8209
8429
  message: fatalCount ? String(fatalCount) + " fatal browser error(s) were captured." : undefined,
8210
8430
  });
8211
8431
  continue;
@@ -8502,6 +8722,108 @@ async function selectorTextSequence(selector) {
8502
8722
  };
8503
8723
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
8504
8724
  }
8725
+ async function frameEvidence(selector) {
8726
+ const result = { selector, count: 0, frame_count: 0, frames: [], errors: [] };
8727
+ let handles = [];
8728
+ try {
8729
+ const locator = page.locator(selector);
8730
+ result.count = await locator.count();
8731
+ handles = await locator.elementHandles();
8732
+ } catch (error) {
8733
+ result.errors.push(String(error && error.message ? error.message : error).slice(0, 500));
8734
+ return result;
8735
+ }
8736
+ for (let index = 0; index < handles.length; index += 1) {
8737
+ const handle = handles[index];
8738
+ try {
8739
+ const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame() : null;
8740
+ if (!frame) {
8741
+ result.frames.push({ index, attached: false, error: "content_frame_unavailable" });
8742
+ continue;
8743
+ }
8744
+ const snapshot = await frame.evaluate(() => {
8745
+ const body = document.body;
8746
+ const documentElement = document.documentElement;
8747
+ const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
8748
+ const clientWidth = documentElement ? documentElement.clientWidth : window.innerWidth;
8749
+ const clientHeight = documentElement ? documentElement.clientHeight : window.innerHeight;
8750
+ const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
8751
+ const viewportWidth = clientWidth || window.innerWidth;
8752
+ const overflowOffenders = [];
8753
+ function isContainedByHorizontalScroller(element) {
8754
+ let current = element.parentElement;
8755
+ while (current && current !== body && current !== documentElement) {
8756
+ const style = window.getComputedStyle(current);
8757
+ const overflowX = style.overflowX || style.overflow || "";
8758
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
8759
+ const currentRect = current.getBoundingClientRect();
8760
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
8761
+ if (contained) return true;
8762
+ }
8763
+ current = current.parentElement;
8764
+ }
8765
+ return false;
8766
+ }
8767
+ for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
8768
+ const rect = element.getBoundingClientRect();
8769
+ if (!rect || rect.width < 1 || rect.height < 1) continue;
8770
+ const style = window.getComputedStyle(element);
8771
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
8772
+ const leftOverflow = Math.max(0, -rect.left);
8773
+ const rightOverflow = Math.max(0, rect.right - viewportWidth);
8774
+ const overflow = Math.max(leftOverflow, rightOverflow);
8775
+ if (overflow <= 0.5) continue;
8776
+ if (isContainedByHorizontalScroller(element)) continue;
8777
+ const tag = element.tagName ? element.tagName.toLowerCase() : "element";
8778
+ const id = element.id ? "#" + element.id : "";
8779
+ const className = typeof element.className === "string"
8780
+ ? element.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).join(".")
8781
+ : "";
8782
+ overflowOffenders.push({
8783
+ selector: tag + id + (className ? "." + className : ""),
8784
+ tag,
8785
+ text: (element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120),
8786
+ overflow,
8787
+ left_overflow_px: leftOverflow,
8788
+ right_overflow_px: rightOverflow,
8789
+ viewport_width: viewportWidth,
8790
+ rect: {
8791
+ left: rect.left,
8792
+ right: rect.right,
8793
+ width: rect.width,
8794
+ },
8795
+ });
8796
+ }
8797
+ overflowOffenders.sort((a, b) => b.overflow - a.overflow);
8798
+ const boundsOverflowPx = Math.max(
8799
+ 0,
8800
+ scrollWidth - (clientWidth || viewportWidth),
8801
+ ...overflowOffenders.map((offender) => offender.overflow),
8802
+ );
8803
+ return {
8804
+ url: location.href,
8805
+ title: document.title,
8806
+ text_length: text.length,
8807
+ text_sample: text.slice(0, 8000),
8808
+ body_text_sample: text.slice(0, 8000),
8809
+ viewport_width: viewportWidth,
8810
+ viewport_height: clientHeight || window.innerHeight,
8811
+ scroll_width: scrollWidth,
8812
+ client_width: clientWidth,
8813
+ overflow_px: Math.max(0, scrollWidth - (clientWidth || viewportWidth)),
8814
+ bounds_overflow_px: Math.round(boundsOverflowPx * 100) / 100,
8815
+ overflow_offender_count: overflowOffenders.length,
8816
+ overflow_offenders: overflowOffenders.slice(0, 10),
8817
+ };
8818
+ });
8819
+ result.frames.push({ index, attached: true, ...snapshot });
8820
+ } catch (error) {
8821
+ result.frames.push({ index, attached: false, error: String(error && error.message ? error.message : error).slice(0, 1000) });
8822
+ }
8823
+ }
8824
+ result.frame_count = result.frames.filter((frame) => frame && frame.attached !== false && !frame.error).length;
8825
+ return result;
8826
+ }
8505
8827
  function inventoryAppPathFromUrl(urlLike) {
8506
8828
  const url = new URL(String(urlLike), targetUrl);
8507
8829
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -8906,6 +9228,7 @@ async function captureViewport(viewport) {
8906
9228
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
8907
9229
  }));
8908
9230
  const selectors = {};
9231
+ const frames = {};
8909
9232
  const text_sequences = {};
8910
9233
  const text_matches = {};
8911
9234
  for (const check of profile.checks || []) {
@@ -8919,6 +9242,10 @@ async function captureViewport(viewport) {
8919
9242
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
8920
9243
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
8921
9244
  }
9245
+ if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
9246
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
9247
+ frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
9248
+ }
8922
9249
  }
8923
9250
  const screenshotLabel = profileSlug + "-" + viewport.name;
8924
9251
  try {
@@ -8979,6 +9306,7 @@ async function captureViewport(viewport) {
8979
9306
  bounds_overflow_px: dom.bounds_overflow_px,
8980
9307
  overflow_offenders: dom.overflow_offenders || [],
8981
9308
  selectors,
9309
+ frames,
8982
9310
  text_sequences,
8983
9311
  text_matches,
8984
9312
  route_inventory: routeInventory,
@@ -9014,6 +9342,20 @@ function buildProfileEvidence(currentViewports) {
9014
9342
  overflow_px: currentViewports.map((viewport) => viewport.overflow_px),
9015
9343
  bounds_overflow_px: currentViewports.map((viewport) => viewport.bounds_overflow_px),
9016
9344
  overflow_offender_counts: currentViewports.map((viewport) => (viewport.overflow_offenders || []).length),
9345
+ frames: currentViewports
9346
+ .filter((viewport) => viewport.frames)
9347
+ .map((viewport) => ({
9348
+ viewport: viewport.name,
9349
+ selectors: Object.entries(viewport.frames || {}).map(([selector, frameSet]) => ({
9350
+ selector,
9351
+ count: frameSet && frameSet.count,
9352
+ frame_count: frameSet && frameSet.frame_count,
9353
+ max_bounds_overflow_px: Math.max(
9354
+ 0,
9355
+ ...((frameSet && Array.isArray(frameSet.frames) ? frameSet.frames : [])).map((frame) => horizontalBoundsOverflowPx(frame)),
9356
+ ),
9357
+ })),
9358
+ })),
9017
9359
  route_inventory: currentViewports
9018
9360
  .filter((viewport) => viewport.route_inventory)
9019
9361
  .map((viewport) => ({
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-KZRDAMI5.js";
13
+ } from "./chunk-L4FLSU7L.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport