@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/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",
@@ -9017,6 +9019,13 @@ function normalizeExpectedTexts(value, index) {
9017
9019
  if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
9018
9020
  return texts;
9019
9021
  }
9022
+ function normalizeStringList(value, label) {
9023
+ if (value === void 0) return void 0;
9024
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
9025
+ const values = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
9026
+ if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
9027
+ return values;
9028
+ }
9020
9029
  function normalizeCheck(input, index) {
9021
9030
  if (!isRecord2(input)) throw new Error(`checks[${index}] must be an object.`);
9022
9031
  const type = stringValue5(input.type);
@@ -9027,6 +9036,12 @@ function normalizeCheck(input, index) {
9027
9036
  if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue5(input.selector)) {
9028
9037
  throw new Error(`checks[${index}] ${type} requires selector.`);
9029
9038
  }
9039
+ if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue5(input.selector)) {
9040
+ throw new Error(`checks[${index}] ${type} requires selector.`);
9041
+ }
9042
+ if (type === "frame_text_visible" && !stringValue5(input.text) && !stringValue5(input.pattern)) {
9043
+ throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
9044
+ }
9030
9045
  if ((type === "text_visible" || type === "text_absent") && !stringValue5(input.text) && !stringValue5(input.pattern)) {
9031
9046
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
9032
9047
  }
@@ -9059,6 +9074,10 @@ function normalizeCheck(input, index) {
9059
9074
  text: stringValue5(input.text),
9060
9075
  pattern: stringValue5(input.pattern),
9061
9076
  flags: stringValue5(input.flags),
9077
+ allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
9078
+ allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
9079
+ 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`),
9080
+ 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`),
9062
9081
  min_count: numberValue3(input.min_count),
9063
9082
  max_overflow_px: numberValue3(input.max_overflow_px),
9064
9083
  timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs),
@@ -9177,6 +9196,22 @@ function textOrderMatch(texts, expectedTexts) {
9177
9196
  }
9178
9197
  return { matched: true, positions };
9179
9198
  }
9199
+ function frameEvidenceForSelector(viewport, selector) {
9200
+ const container = viewport.frames?.[selector];
9201
+ if (!isRecord2(container)) return [];
9202
+ const frames = Array.isArray(container.frames) ? container.frames : [];
9203
+ return frames.filter((frame) => isRecord2(frame));
9204
+ }
9205
+ function frameTextSample(frame) {
9206
+ const parts = [
9207
+ frame.title,
9208
+ frame.text_sample,
9209
+ frame.body_text_sample,
9210
+ frame.body_text,
9211
+ frame.text
9212
+ ];
9213
+ return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
9214
+ }
9180
9215
  function summarizeRouteInventory(viewport, inventory) {
9181
9216
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
9182
9217
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -9206,6 +9241,18 @@ function matchText(sample, check) {
9206
9241
  }
9207
9242
  return sample.includes(check.text || "");
9208
9243
  }
9244
+ function matchesAllowedMessage(message, texts, patterns) {
9245
+ const sample = String(message || "");
9246
+ if (texts?.some((text) => sample.includes(text))) return true;
9247
+ for (const pattern of patterns || []) {
9248
+ try {
9249
+ if (new RegExp(pattern).test(sample)) return true;
9250
+ } catch {
9251
+ continue;
9252
+ }
9253
+ }
9254
+ return false;
9255
+ }
9209
9256
  function normalizeRoutePath(path6) {
9210
9257
  const value = path6 || "/";
9211
9258
  if (value === "/") return "/";
@@ -9341,6 +9388,67 @@ function assessCheckFromEvidence(check, evidence) {
9341
9388
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
9342
9389
  };
9343
9390
  }
9391
+ if (check.type === "frame_text_visible") {
9392
+ const key = selectorKey(check);
9393
+ const results = viewports.map((viewport) => {
9394
+ const frames = frameEvidenceForSelector(viewport, key);
9395
+ const matches = frames.map((frame, index) => ({ index, frame, matched: matchText(frameTextSample(frame), check) })).filter((item) => item.matched);
9396
+ return {
9397
+ viewport: viewport.name,
9398
+ frame_count: frames.length,
9399
+ matched_count: matches.length,
9400
+ matched: matches.length > 0,
9401
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
9402
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3)
9403
+ };
9404
+ });
9405
+ const failed = results.filter((result) => !result.matched).length;
9406
+ return {
9407
+ type: check.type,
9408
+ label: checkLabel(check),
9409
+ status: failed ? "failed" : "passed",
9410
+ evidence: {
9411
+ selector: key,
9412
+ text: check.text || null,
9413
+ pattern: check.pattern || null,
9414
+ viewports: results.map((result) => toJsonValue(result))
9415
+ },
9416
+ message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
9417
+ };
9418
+ }
9419
+ if (check.type === "frame_no_horizontal_overflow") {
9420
+ const key = selectorKey(check);
9421
+ const maxOverflow = check.max_overflow_px ?? 4;
9422
+ const results = viewports.map((viewport) => {
9423
+ const frames = frameEvidenceForSelector(viewport, key);
9424
+ const frameOverflows = frames.map((frame, index) => ({
9425
+ index,
9426
+ url: String(frame.url || ""),
9427
+ overflow_px: horizontalBoundsOverflowPx2(frame),
9428
+ offender_count: boundsOffendersForEvidence2(frame).length
9429
+ }));
9430
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
9431
+ return {
9432
+ viewport: viewport.name,
9433
+ frame_count: frames.length,
9434
+ max_overflow_px: roundPixels2(maxFrameOverflow),
9435
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
9436
+ frames: frameOverflows.map((frame) => toJsonValue(frame))
9437
+ };
9438
+ });
9439
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
9440
+ return {
9441
+ type: check.type,
9442
+ label: checkLabel(check),
9443
+ status: failed ? "failed" : "passed",
9444
+ evidence: {
9445
+ selector: key,
9446
+ max_overflow_px: maxOverflow,
9447
+ viewports: results.map((result) => toJsonValue(result))
9448
+ },
9449
+ message: failed ? `Frame selector ${key} overflow exceeded ${maxOverflow}px or was missing in ${failed} viewport(s).` : void 0
9450
+ };
9451
+ }
9344
9452
  if (check.type === "text_visible" || check.type === "text_absent") {
9345
9453
  const key = textKey(check);
9346
9454
  const expectedVisible = check.type === "text_visible";
@@ -9431,14 +9539,28 @@ function assessCheckFromEvidence(check, evidence) {
9431
9539
  };
9432
9540
  }
9433
9541
  if (check.type === "no_fatal_console_errors") {
9434
- const fatalCount = (evidence.console?.fatal_count || 0) + (evidence.page_errors?.length || 0);
9542
+ const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
9543
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
9544
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
9545
+ const pageErrors = evidence.page_errors || [];
9546
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
9547
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
9548
+ const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
9435
9549
  return {
9436
9550
  type: check.type,
9437
9551
  label: checkLabel(check),
9438
9552
  status: fatalCount ? "failed" : "passed",
9439
9553
  evidence: {
9440
- console_fatal_count: evidence.console?.fatal_count || 0,
9441
- page_error_count: evidence.page_errors?.length || 0
9554
+ console_fatal_count: unallowedConsoleEvents.length,
9555
+ page_error_count: unallowedPageErrors.length,
9556
+ total_console_fatal_count: fatalConsoleEvents.length,
9557
+ total_page_error_count: pageErrors.length,
9558
+ allowed_console_fatal_count: allowedConsoleEvents.length,
9559
+ allowed_page_error_count: allowedPageErrors.length,
9560
+ allowed_console_texts: check.allowed_console_texts || [],
9561
+ allowed_console_patterns: check.allowed_console_patterns || [],
9562
+ allowed_page_error_texts: check.allowed_page_error_texts || [],
9563
+ allowed_page_error_patterns: check.allowed_page_error_patterns || []
9442
9564
  },
9443
9565
  message: fatalCount ? `${fatalCount} fatal browser error(s) were captured.` : void 0
9444
9566
  };
@@ -9694,6 +9816,16 @@ function textMatches(sample, check) {
9694
9816
  }
9695
9817
  return String(sample || "").includes(check.text || "");
9696
9818
  }
9819
+ function matchesAllowedMessage(message, texts, patterns) {
9820
+ const sample = String(message || "");
9821
+ if ((texts || []).some((text) => sample.includes(text))) return true;
9822
+ for (const pattern of patterns || []) {
9823
+ try {
9824
+ if (new RegExp(pattern).test(sample)) return true;
9825
+ } catch {}
9826
+ }
9827
+ return false;
9828
+ }
9697
9829
  function textSequenceForCheck(viewport, check) {
9698
9830
  const key = check.selector || "";
9699
9831
  const sequence = viewport.text_sequences && viewport.text_sequences[key];
@@ -9716,6 +9848,21 @@ function textOrderMatch(texts, expectedTexts) {
9716
9848
  }
9717
9849
  return { matched: true, positions };
9718
9850
  }
9851
+ function frameEvidenceForSelector(viewport, selector) {
9852
+ const container = viewport.frames && viewport.frames[selector || ""];
9853
+ if (!container || typeof container !== "object" || Array.isArray(container)) return [];
9854
+ const frames = Array.isArray(container.frames) ? container.frames : [];
9855
+ return frames.filter((frame) => frame && typeof frame === "object" && !Array.isArray(frame));
9856
+ }
9857
+ function frameTextSample(frame) {
9858
+ return [
9859
+ frame && frame.title,
9860
+ frame && frame.text_sample,
9861
+ frame && frame.body_text_sample,
9862
+ frame && frame.body_text,
9863
+ frame && frame.text,
9864
+ ].map((part) => String(part || "")).filter(Boolean).join(" ");
9865
+ }
9719
9866
  function summarizeRouteInventory(viewport, inventory) {
9720
9867
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
9721
9868
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -9962,6 +10109,62 @@ function assessProfile(profile, evidence) {
9962
10109
  });
9963
10110
  continue;
9964
10111
  }
10112
+ if (check.type === "frame_text_visible") {
10113
+ const selector = check.selector || "";
10114
+ const results = viewports.map((viewport) => {
10115
+ const frames = frameEvidenceForSelector(viewport, selector);
10116
+ const matches = frames
10117
+ .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
10118
+ .filter((item) => item.matched);
10119
+ return {
10120
+ viewport: viewport.name,
10121
+ frame_count: frames.length,
10122
+ matched_count: matches.length,
10123
+ matched: matches.length > 0,
10124
+ urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
10125
+ samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3),
10126
+ };
10127
+ });
10128
+ const failed = results.filter((result) => !result.matched).length;
10129
+ checks.push({
10130
+ type: check.type,
10131
+ label: check.label || check.type,
10132
+ status: failed ? "failed" : "passed",
10133
+ evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
10134
+ message: failed ? "Frame selector " + selector + " did not contain expected text in " + failed + " viewport(s)." : undefined,
10135
+ });
10136
+ continue;
10137
+ }
10138
+ if (check.type === "frame_no_horizontal_overflow") {
10139
+ const selector = check.selector || "";
10140
+ const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
10141
+ const results = viewports.map((viewport) => {
10142
+ const frames = frameEvidenceForSelector(viewport, selector);
10143
+ const frameOverflows = frames.map((frame, index) => ({
10144
+ index,
10145
+ url: String(frame.url || ""),
10146
+ overflow_px: horizontalBoundsOverflowPx(frame),
10147
+ offender_count: boundsOffendersForEvidence(frame).length,
10148
+ }));
10149
+ const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
10150
+ return {
10151
+ viewport: viewport.name,
10152
+ frame_count: frames.length,
10153
+ max_overflow_px: roundPixels(maxFrameOverflow),
10154
+ failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
10155
+ frames: frameOverflows,
10156
+ };
10157
+ });
10158
+ const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
10159
+ checks.push({
10160
+ type: check.type,
10161
+ label: check.label || check.type,
10162
+ status: failed ? "failed" : "passed",
10163
+ evidence: { selector, max_overflow_px: maxOverflow, viewports: results },
10164
+ message: failed ? "Frame selector " + selector + " overflow exceeded " + maxOverflow + "px or was missing in " + failed + " viewport(s)." : undefined,
10165
+ });
10166
+ continue;
10167
+ }
9965
10168
  if (check.type === "text_visible" || check.type === "text_absent") {
9966
10169
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
9967
10170
  const expectedVisible = check.type === "text_visible";
@@ -10057,12 +10260,29 @@ function assessProfile(profile, evidence) {
10057
10260
  continue;
10058
10261
  }
10059
10262
  if (check.type === "no_fatal_console_errors") {
10060
- const fatalCount = ((evidence.console && evidence.console.fatal_count) || 0) + ((evidence.page_errors || []).length);
10263
+ const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
10264
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
10265
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
10266
+ const pageErrors = evidence.page_errors || [];
10267
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
10268
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
10269
+ const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
10061
10270
  checks.push({
10062
10271
  type: check.type,
10063
10272
  label: check.label || check.type,
10064
10273
  status: fatalCount ? "failed" : "passed",
10065
- evidence: { console_fatal_count: (evidence.console && evidence.console.fatal_count) || 0, page_error_count: (evidence.page_errors || []).length },
10274
+ evidence: {
10275
+ console_fatal_count: unallowedConsoleEvents.length,
10276
+ page_error_count: unallowedPageErrors.length,
10277
+ total_console_fatal_count: fatalConsoleEvents.length,
10278
+ total_page_error_count: pageErrors.length,
10279
+ allowed_console_fatal_count: allowedConsoleEvents.length,
10280
+ allowed_page_error_count: allowedPageErrors.length,
10281
+ allowed_console_texts: check.allowed_console_texts || [],
10282
+ allowed_console_patterns: check.allowed_console_patterns || [],
10283
+ allowed_page_error_texts: check.allowed_page_error_texts || [],
10284
+ allowed_page_error_patterns: check.allowed_page_error_patterns || [],
10285
+ },
10066
10286
  message: fatalCount ? String(fatalCount) + " fatal browser error(s) were captured." : undefined,
10067
10287
  });
10068
10288
  continue;
@@ -10359,6 +10579,108 @@ async function selectorTextSequence(selector) {
10359
10579
  };
10360
10580
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
10361
10581
  }
10582
+ async function frameEvidence(selector) {
10583
+ const result = { selector, count: 0, frame_count: 0, frames: [], errors: [] };
10584
+ let handles = [];
10585
+ try {
10586
+ const locator = page.locator(selector);
10587
+ result.count = await locator.count();
10588
+ handles = await locator.elementHandles();
10589
+ } catch (error) {
10590
+ result.errors.push(String(error && error.message ? error.message : error).slice(0, 500));
10591
+ return result;
10592
+ }
10593
+ for (let index = 0; index < handles.length; index += 1) {
10594
+ const handle = handles[index];
10595
+ try {
10596
+ const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame() : null;
10597
+ if (!frame) {
10598
+ result.frames.push({ index, attached: false, error: "content_frame_unavailable" });
10599
+ continue;
10600
+ }
10601
+ const snapshot = await frame.evaluate(() => {
10602
+ const body = document.body;
10603
+ const documentElement = document.documentElement;
10604
+ const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
10605
+ const clientWidth = documentElement ? documentElement.clientWidth : window.innerWidth;
10606
+ const clientHeight = documentElement ? documentElement.clientHeight : window.innerHeight;
10607
+ const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
10608
+ const viewportWidth = clientWidth || window.innerWidth;
10609
+ const overflowOffenders = [];
10610
+ function isContainedByHorizontalScroller(element) {
10611
+ let current = element.parentElement;
10612
+ while (current && current !== body && current !== documentElement) {
10613
+ const style = window.getComputedStyle(current);
10614
+ const overflowX = style.overflowX || style.overflow || "";
10615
+ if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
10616
+ const currentRect = current.getBoundingClientRect();
10617
+ const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
10618
+ if (contained) return true;
10619
+ }
10620
+ current = current.parentElement;
10621
+ }
10622
+ return false;
10623
+ }
10624
+ for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
10625
+ const rect = element.getBoundingClientRect();
10626
+ if (!rect || rect.width < 1 || rect.height < 1) continue;
10627
+ const style = window.getComputedStyle(element);
10628
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
10629
+ const leftOverflow = Math.max(0, -rect.left);
10630
+ const rightOverflow = Math.max(0, rect.right - viewportWidth);
10631
+ const overflow = Math.max(leftOverflow, rightOverflow);
10632
+ if (overflow <= 0.5) continue;
10633
+ if (isContainedByHorizontalScroller(element)) continue;
10634
+ const tag = element.tagName ? element.tagName.toLowerCase() : "element";
10635
+ const id = element.id ? "#" + element.id : "";
10636
+ const className = typeof element.className === "string"
10637
+ ? element.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).join(".")
10638
+ : "";
10639
+ overflowOffenders.push({
10640
+ selector: tag + id + (className ? "." + className : ""),
10641
+ tag,
10642
+ text: (element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120),
10643
+ overflow,
10644
+ left_overflow_px: leftOverflow,
10645
+ right_overflow_px: rightOverflow,
10646
+ viewport_width: viewportWidth,
10647
+ rect: {
10648
+ left: rect.left,
10649
+ right: rect.right,
10650
+ width: rect.width,
10651
+ },
10652
+ });
10653
+ }
10654
+ overflowOffenders.sort((a, b) => b.overflow - a.overflow);
10655
+ const boundsOverflowPx = Math.max(
10656
+ 0,
10657
+ scrollWidth - (clientWidth || viewportWidth),
10658
+ ...overflowOffenders.map((offender) => offender.overflow),
10659
+ );
10660
+ return {
10661
+ url: location.href,
10662
+ title: document.title,
10663
+ text_length: text.length,
10664
+ text_sample: text.slice(0, 8000),
10665
+ body_text_sample: text.slice(0, 8000),
10666
+ viewport_width: viewportWidth,
10667
+ viewport_height: clientHeight || window.innerHeight,
10668
+ scroll_width: scrollWidth,
10669
+ client_width: clientWidth,
10670
+ overflow_px: Math.max(0, scrollWidth - (clientWidth || viewportWidth)),
10671
+ bounds_overflow_px: Math.round(boundsOverflowPx * 100) / 100,
10672
+ overflow_offender_count: overflowOffenders.length,
10673
+ overflow_offenders: overflowOffenders.slice(0, 10),
10674
+ };
10675
+ });
10676
+ result.frames.push({ index, attached: true, ...snapshot });
10677
+ } catch (error) {
10678
+ result.frames.push({ index, attached: false, error: String(error && error.message ? error.message : error).slice(0, 1000) });
10679
+ }
10680
+ }
10681
+ result.frame_count = result.frames.filter((frame) => frame && frame.attached !== false && !frame.error).length;
10682
+ return result;
10683
+ }
10362
10684
  function inventoryAppPathFromUrl(urlLike) {
10363
10685
  const url = new URL(String(urlLike), targetUrl);
10364
10686
  const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
@@ -10763,6 +11085,7 @@ async function captureViewport(viewport) {
10763
11085
  evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
10764
11086
  }));
10765
11087
  const selectors = {};
11088
+ const frames = {};
10766
11089
  const text_sequences = {};
10767
11090
  const text_matches = {};
10768
11091
  for (const check of profile.checks || []) {
@@ -10776,6 +11099,10 @@ async function captureViewport(viewport) {
10776
11099
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
10777
11100
  text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
10778
11101
  }
11102
+ if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
11103
+ selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
11104
+ frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
11105
+ }
10779
11106
  }
10780
11107
  const screenshotLabel = profileSlug + "-" + viewport.name;
10781
11108
  try {
@@ -10836,6 +11163,7 @@ async function captureViewport(viewport) {
10836
11163
  bounds_overflow_px: dom.bounds_overflow_px,
10837
11164
  overflow_offenders: dom.overflow_offenders || [],
10838
11165
  selectors,
11166
+ frames,
10839
11167
  text_sequences,
10840
11168
  text_matches,
10841
11169
  route_inventory: routeInventory,
@@ -10871,6 +11199,20 @@ function buildProfileEvidence(currentViewports) {
10871
11199
  overflow_px: currentViewports.map((viewport) => viewport.overflow_px),
10872
11200
  bounds_overflow_px: currentViewports.map((viewport) => viewport.bounds_overflow_px),
10873
11201
  overflow_offender_counts: currentViewports.map((viewport) => (viewport.overflow_offenders || []).length),
11202
+ frames: currentViewports
11203
+ .filter((viewport) => viewport.frames)
11204
+ .map((viewport) => ({
11205
+ viewport: viewport.name,
11206
+ selectors: Object.entries(viewport.frames || {}).map(([selector, frameSet]) => ({
11207
+ selector,
11208
+ count: frameSet && frameSet.count,
11209
+ frame_count: frameSet && frameSet.frame_count,
11210
+ max_bounds_overflow_px: Math.max(
11211
+ 0,
11212
+ ...((frameSet && Array.isArray(frameSet.frames) ? frameSet.frames : [])).map((frame) => horizontalBoundsOverflowPx(frame)),
11213
+ ),
11214
+ })),
11215
+ })),
10874
11216
  route_inventory: currentViewports
10875
11217
  .filter((viewport) => viewport.route_inventory)
10876
11218
  .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-L4FLSU7L.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,