@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/README.md +23 -0
- package/dist/{chunk-KZRDAMI5.js → chunk-ZB7AFRN3.js} +278 -0
- package/dist/cli.cjs +278 -0
- package/dist/cli.js +1 -1
- package/dist/index.cjs +278 -0
- package/dist/index.js +1 -1
- package/dist/profile.cjs +278 -0
- package/dist/profile.d.cts +2 -1
- package/dist/profile.d.ts +2 -1
- package/dist/profile.js +1 -1
- package/dist/proof-run-engine.d.cts +3 -3
- package/dist/proof-run-engine.d.ts +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -217,6 +217,29 @@ The check records the visible text sequence for the selector and passes when
|
|
|
217
217
|
the expected texts appear in that order as a subsequence. This is less brittle
|
|
218
218
|
than matching one large body-text regex when only row or card order matters.
|
|
219
219
|
|
|
220
|
+
Use `frame_text_visible` and `frame_no_horizontal_overflow` for embedded app,
|
|
221
|
+
game, or preview surfaces that render inside iframes:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
[
|
|
225
|
+
{
|
|
226
|
+
"type": "frame_text_visible",
|
|
227
|
+
"selector": ".game-player-root iframe",
|
|
228
|
+
"text": "Start Game"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"type": "frame_no_horizontal_overflow",
|
|
232
|
+
"selector": ".game-player-root iframe",
|
|
233
|
+
"max_overflow_px": 1
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Frame checks capture each matching iframe's URL, title, compact text sample,
|
|
239
|
+
scroll width, client width, measured horizontal overflow, and top visible
|
|
240
|
+
overflow offenders. This keeps embedded-player audits in profile mode instead
|
|
241
|
+
of requiring bespoke iframe inspection scripts.
|
|
242
|
+
|
|
220
243
|
Use the `route_inventory` check for source-page route coverage audits where a
|
|
221
244
|
navigation surface must expose a known set of routes and each route must load
|
|
222
245
|
both directly and through real link clicks:
|
|
@@ -15,6 +15,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
15
15
|
"selector_visible",
|
|
16
16
|
"selector_count_at_least",
|
|
17
17
|
"selector_text_order",
|
|
18
|
+
"frame_text_visible",
|
|
19
|
+
"frame_no_horizontal_overflow",
|
|
18
20
|
"text_visible",
|
|
19
21
|
"text_absent",
|
|
20
22
|
"route_inventory",
|
|
@@ -313,6 +315,12 @@ function normalizeCheck(input, index) {
|
|
|
313
315
|
if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue(input.selector)) {
|
|
314
316
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
315
317
|
}
|
|
318
|
+
if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
319
|
+
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
320
|
+
}
|
|
321
|
+
if (type === "frame_text_visible" && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
322
|
+
throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
|
|
323
|
+
}
|
|
316
324
|
if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
317
325
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
318
326
|
}
|
|
@@ -463,6 +471,22 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
463
471
|
}
|
|
464
472
|
return { matched: true, positions };
|
|
465
473
|
}
|
|
474
|
+
function frameEvidenceForSelector(viewport, selector) {
|
|
475
|
+
const container = viewport.frames?.[selector];
|
|
476
|
+
if (!isRecord(container)) return [];
|
|
477
|
+
const frames = Array.isArray(container.frames) ? container.frames : [];
|
|
478
|
+
return frames.filter((frame) => isRecord(frame));
|
|
479
|
+
}
|
|
480
|
+
function frameTextSample(frame) {
|
|
481
|
+
const parts = [
|
|
482
|
+
frame.title,
|
|
483
|
+
frame.text_sample,
|
|
484
|
+
frame.body_text_sample,
|
|
485
|
+
frame.body_text,
|
|
486
|
+
frame.text
|
|
487
|
+
];
|
|
488
|
+
return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
|
|
489
|
+
}
|
|
466
490
|
function summarizeRouteInventory(viewport, inventory) {
|
|
467
491
|
const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
|
|
468
492
|
const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
|
|
@@ -627,6 +651,67 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
627
651
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
628
652
|
};
|
|
629
653
|
}
|
|
654
|
+
if (check.type === "frame_text_visible") {
|
|
655
|
+
const key = selectorKey(check);
|
|
656
|
+
const results = viewports.map((viewport) => {
|
|
657
|
+
const frames = frameEvidenceForSelector(viewport, key);
|
|
658
|
+
const matches = frames.map((frame, index) => ({ index, frame, matched: matchText(frameTextSample(frame), check) })).filter((item) => item.matched);
|
|
659
|
+
return {
|
|
660
|
+
viewport: viewport.name,
|
|
661
|
+
frame_count: frames.length,
|
|
662
|
+
matched_count: matches.length,
|
|
663
|
+
matched: matches.length > 0,
|
|
664
|
+
urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
|
|
665
|
+
samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3)
|
|
666
|
+
};
|
|
667
|
+
});
|
|
668
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
669
|
+
return {
|
|
670
|
+
type: check.type,
|
|
671
|
+
label: checkLabel(check),
|
|
672
|
+
status: failed ? "failed" : "passed",
|
|
673
|
+
evidence: {
|
|
674
|
+
selector: key,
|
|
675
|
+
text: check.text || null,
|
|
676
|
+
pattern: check.pattern || null,
|
|
677
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
678
|
+
},
|
|
679
|
+
message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
if (check.type === "frame_no_horizontal_overflow") {
|
|
683
|
+
const key = selectorKey(check);
|
|
684
|
+
const maxOverflow = check.max_overflow_px ?? 4;
|
|
685
|
+
const results = viewports.map((viewport) => {
|
|
686
|
+
const frames = frameEvidenceForSelector(viewport, key);
|
|
687
|
+
const frameOverflows = frames.map((frame, index) => ({
|
|
688
|
+
index,
|
|
689
|
+
url: String(frame.url || ""),
|
|
690
|
+
overflow_px: horizontalBoundsOverflowPx(frame),
|
|
691
|
+
offender_count: boundsOffendersForEvidence(frame).length
|
|
692
|
+
}));
|
|
693
|
+
const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
|
|
694
|
+
return {
|
|
695
|
+
viewport: viewport.name,
|
|
696
|
+
frame_count: frames.length,
|
|
697
|
+
max_overflow_px: roundPixels(maxFrameOverflow),
|
|
698
|
+
failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
|
|
699
|
+
frames: frameOverflows.map((frame) => toJsonValue(frame))
|
|
700
|
+
};
|
|
701
|
+
});
|
|
702
|
+
const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
|
|
703
|
+
return {
|
|
704
|
+
type: check.type,
|
|
705
|
+
label: checkLabel(check),
|
|
706
|
+
status: failed ? "failed" : "passed",
|
|
707
|
+
evidence: {
|
|
708
|
+
selector: key,
|
|
709
|
+
max_overflow_px: maxOverflow,
|
|
710
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
711
|
+
},
|
|
712
|
+
message: failed ? `Frame selector ${key} overflow exceeded ${maxOverflow}px or was missing in ${failed} viewport(s).` : void 0
|
|
713
|
+
};
|
|
714
|
+
}
|
|
630
715
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
631
716
|
const key = textKey(check);
|
|
632
717
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -1002,6 +1087,21 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
1002
1087
|
}
|
|
1003
1088
|
return { matched: true, positions };
|
|
1004
1089
|
}
|
|
1090
|
+
function frameEvidenceForSelector(viewport, selector) {
|
|
1091
|
+
const container = viewport.frames && viewport.frames[selector || ""];
|
|
1092
|
+
if (!container || typeof container !== "object" || Array.isArray(container)) return [];
|
|
1093
|
+
const frames = Array.isArray(container.frames) ? container.frames : [];
|
|
1094
|
+
return frames.filter((frame) => frame && typeof frame === "object" && !Array.isArray(frame));
|
|
1095
|
+
}
|
|
1096
|
+
function frameTextSample(frame) {
|
|
1097
|
+
return [
|
|
1098
|
+
frame && frame.title,
|
|
1099
|
+
frame && frame.text_sample,
|
|
1100
|
+
frame && frame.body_text_sample,
|
|
1101
|
+
frame && frame.body_text,
|
|
1102
|
+
frame && frame.text,
|
|
1103
|
+
].map((part) => String(part || "")).filter(Boolean).join(" ");
|
|
1104
|
+
}
|
|
1005
1105
|
function summarizeRouteInventory(viewport, inventory) {
|
|
1006
1106
|
const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
|
|
1007
1107
|
const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
|
|
@@ -1248,6 +1348,62 @@ function assessProfile(profile, evidence) {
|
|
|
1248
1348
|
});
|
|
1249
1349
|
continue;
|
|
1250
1350
|
}
|
|
1351
|
+
if (check.type === "frame_text_visible") {
|
|
1352
|
+
const selector = check.selector || "";
|
|
1353
|
+
const results = viewports.map((viewport) => {
|
|
1354
|
+
const frames = frameEvidenceForSelector(viewport, selector);
|
|
1355
|
+
const matches = frames
|
|
1356
|
+
.map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
|
|
1357
|
+
.filter((item) => item.matched);
|
|
1358
|
+
return {
|
|
1359
|
+
viewport: viewport.name,
|
|
1360
|
+
frame_count: frames.length,
|
|
1361
|
+
matched_count: matches.length,
|
|
1362
|
+
matched: matches.length > 0,
|
|
1363
|
+
urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
|
|
1364
|
+
samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3),
|
|
1365
|
+
};
|
|
1366
|
+
});
|
|
1367
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
1368
|
+
checks.push({
|
|
1369
|
+
type: check.type,
|
|
1370
|
+
label: check.label || check.type,
|
|
1371
|
+
status: failed ? "failed" : "passed",
|
|
1372
|
+
evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
|
|
1373
|
+
message: failed ? "Frame selector " + selector + " did not contain expected text in " + failed + " viewport(s)." : undefined,
|
|
1374
|
+
});
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
if (check.type === "frame_no_horizontal_overflow") {
|
|
1378
|
+
const selector = check.selector || "";
|
|
1379
|
+
const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
|
|
1380
|
+
const results = viewports.map((viewport) => {
|
|
1381
|
+
const frames = frameEvidenceForSelector(viewport, selector);
|
|
1382
|
+
const frameOverflows = frames.map((frame, index) => ({
|
|
1383
|
+
index,
|
|
1384
|
+
url: String(frame.url || ""),
|
|
1385
|
+
overflow_px: horizontalBoundsOverflowPx(frame),
|
|
1386
|
+
offender_count: boundsOffendersForEvidence(frame).length,
|
|
1387
|
+
}));
|
|
1388
|
+
const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
|
|
1389
|
+
return {
|
|
1390
|
+
viewport: viewport.name,
|
|
1391
|
+
frame_count: frames.length,
|
|
1392
|
+
max_overflow_px: roundPixels(maxFrameOverflow),
|
|
1393
|
+
failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
|
|
1394
|
+
frames: frameOverflows,
|
|
1395
|
+
};
|
|
1396
|
+
});
|
|
1397
|
+
const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
|
|
1398
|
+
checks.push({
|
|
1399
|
+
type: check.type,
|
|
1400
|
+
label: check.label || check.type,
|
|
1401
|
+
status: failed ? "failed" : "passed",
|
|
1402
|
+
evidence: { selector, max_overflow_px: maxOverflow, viewports: results },
|
|
1403
|
+
message: failed ? "Frame selector " + selector + " overflow exceeded " + maxOverflow + "px or was missing in " + failed + " viewport(s)." : undefined,
|
|
1404
|
+
});
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1251
1407
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
1252
1408
|
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
1253
1409
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -1645,6 +1801,108 @@ async function selectorTextSequence(selector) {
|
|
|
1645
1801
|
};
|
|
1646
1802
|
}).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
1647
1803
|
}
|
|
1804
|
+
async function frameEvidence(selector) {
|
|
1805
|
+
const result = { selector, count: 0, frame_count: 0, frames: [], errors: [] };
|
|
1806
|
+
let handles = [];
|
|
1807
|
+
try {
|
|
1808
|
+
const locator = page.locator(selector);
|
|
1809
|
+
result.count = await locator.count();
|
|
1810
|
+
handles = await locator.elementHandles();
|
|
1811
|
+
} catch (error) {
|
|
1812
|
+
result.errors.push(String(error && error.message ? error.message : error).slice(0, 500));
|
|
1813
|
+
return result;
|
|
1814
|
+
}
|
|
1815
|
+
for (let index = 0; index < handles.length; index += 1) {
|
|
1816
|
+
const handle = handles[index];
|
|
1817
|
+
try {
|
|
1818
|
+
const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame() : null;
|
|
1819
|
+
if (!frame) {
|
|
1820
|
+
result.frames.push({ index, attached: false, error: "content_frame_unavailable" });
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
const snapshot = await frame.evaluate(() => {
|
|
1824
|
+
const body = document.body;
|
|
1825
|
+
const documentElement = document.documentElement;
|
|
1826
|
+
const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
|
|
1827
|
+
const clientWidth = documentElement ? documentElement.clientWidth : window.innerWidth;
|
|
1828
|
+
const clientHeight = documentElement ? documentElement.clientHeight : window.innerHeight;
|
|
1829
|
+
const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
|
|
1830
|
+
const viewportWidth = clientWidth || window.innerWidth;
|
|
1831
|
+
const overflowOffenders = [];
|
|
1832
|
+
function isContainedByHorizontalScroller(element) {
|
|
1833
|
+
let current = element.parentElement;
|
|
1834
|
+
while (current && current !== body && current !== documentElement) {
|
|
1835
|
+
const style = window.getComputedStyle(current);
|
|
1836
|
+
const overflowX = style.overflowX || style.overflow || "";
|
|
1837
|
+
if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
|
|
1838
|
+
const currentRect = current.getBoundingClientRect();
|
|
1839
|
+
const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
|
|
1840
|
+
if (contained) return true;
|
|
1841
|
+
}
|
|
1842
|
+
current = current.parentElement;
|
|
1843
|
+
}
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
|
|
1847
|
+
const rect = element.getBoundingClientRect();
|
|
1848
|
+
if (!rect || rect.width < 1 || rect.height < 1) continue;
|
|
1849
|
+
const style = window.getComputedStyle(element);
|
|
1850
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
|
|
1851
|
+
const leftOverflow = Math.max(0, -rect.left);
|
|
1852
|
+
const rightOverflow = Math.max(0, rect.right - viewportWidth);
|
|
1853
|
+
const overflow = Math.max(leftOverflow, rightOverflow);
|
|
1854
|
+
if (overflow <= 0.5) continue;
|
|
1855
|
+
if (isContainedByHorizontalScroller(element)) continue;
|
|
1856
|
+
const tag = element.tagName ? element.tagName.toLowerCase() : "element";
|
|
1857
|
+
const id = element.id ? "#" + element.id : "";
|
|
1858
|
+
const className = typeof element.className === "string"
|
|
1859
|
+
? element.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).join(".")
|
|
1860
|
+
: "";
|
|
1861
|
+
overflowOffenders.push({
|
|
1862
|
+
selector: tag + id + (className ? "." + className : ""),
|
|
1863
|
+
tag,
|
|
1864
|
+
text: (element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120),
|
|
1865
|
+
overflow,
|
|
1866
|
+
left_overflow_px: leftOverflow,
|
|
1867
|
+
right_overflow_px: rightOverflow,
|
|
1868
|
+
viewport_width: viewportWidth,
|
|
1869
|
+
rect: {
|
|
1870
|
+
left: rect.left,
|
|
1871
|
+
right: rect.right,
|
|
1872
|
+
width: rect.width,
|
|
1873
|
+
},
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
overflowOffenders.sort((a, b) => b.overflow - a.overflow);
|
|
1877
|
+
const boundsOverflowPx = Math.max(
|
|
1878
|
+
0,
|
|
1879
|
+
scrollWidth - (clientWidth || viewportWidth),
|
|
1880
|
+
...overflowOffenders.map((offender) => offender.overflow),
|
|
1881
|
+
);
|
|
1882
|
+
return {
|
|
1883
|
+
url: location.href,
|
|
1884
|
+
title: document.title,
|
|
1885
|
+
text_length: text.length,
|
|
1886
|
+
text_sample: text.slice(0, 8000),
|
|
1887
|
+
body_text_sample: text.slice(0, 8000),
|
|
1888
|
+
viewport_width: viewportWidth,
|
|
1889
|
+
viewport_height: clientHeight || window.innerHeight,
|
|
1890
|
+
scroll_width: scrollWidth,
|
|
1891
|
+
client_width: clientWidth,
|
|
1892
|
+
overflow_px: Math.max(0, scrollWidth - (clientWidth || viewportWidth)),
|
|
1893
|
+
bounds_overflow_px: Math.round(boundsOverflowPx * 100) / 100,
|
|
1894
|
+
overflow_offender_count: overflowOffenders.length,
|
|
1895
|
+
overflow_offenders: overflowOffenders.slice(0, 10),
|
|
1896
|
+
};
|
|
1897
|
+
});
|
|
1898
|
+
result.frames.push({ index, attached: true, ...snapshot });
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
result.frames.push({ index, attached: false, error: String(error && error.message ? error.message : error).slice(0, 1000) });
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
result.frame_count = result.frames.filter((frame) => frame && frame.attached !== false && !frame.error).length;
|
|
1904
|
+
return result;
|
|
1905
|
+
}
|
|
1648
1906
|
function inventoryAppPathFromUrl(urlLike) {
|
|
1649
1907
|
const url = new URL(String(urlLike), targetUrl);
|
|
1650
1908
|
const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
|
|
@@ -2049,6 +2307,7 @@ async function captureViewport(viewport) {
|
|
|
2049
2307
|
evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
|
|
2050
2308
|
}));
|
|
2051
2309
|
const selectors = {};
|
|
2310
|
+
const frames = {};
|
|
2052
2311
|
const text_sequences = {};
|
|
2053
2312
|
const text_matches = {};
|
|
2054
2313
|
for (const check of profile.checks || []) {
|
|
@@ -2062,6 +2321,10 @@ async function captureViewport(viewport) {
|
|
|
2062
2321
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
2063
2322
|
text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
|
|
2064
2323
|
}
|
|
2324
|
+
if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
2325
|
+
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
2326
|
+
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
2327
|
+
}
|
|
2065
2328
|
}
|
|
2066
2329
|
const screenshotLabel = profileSlug + "-" + viewport.name;
|
|
2067
2330
|
try {
|
|
@@ -2122,6 +2385,7 @@ async function captureViewport(viewport) {
|
|
|
2122
2385
|
bounds_overflow_px: dom.bounds_overflow_px,
|
|
2123
2386
|
overflow_offenders: dom.overflow_offenders || [],
|
|
2124
2387
|
selectors,
|
|
2388
|
+
frames,
|
|
2125
2389
|
text_sequences,
|
|
2126
2390
|
text_matches,
|
|
2127
2391
|
route_inventory: routeInventory,
|
|
@@ -2157,6 +2421,20 @@ function buildProfileEvidence(currentViewports) {
|
|
|
2157
2421
|
overflow_px: currentViewports.map((viewport) => viewport.overflow_px),
|
|
2158
2422
|
bounds_overflow_px: currentViewports.map((viewport) => viewport.bounds_overflow_px),
|
|
2159
2423
|
overflow_offender_counts: currentViewports.map((viewport) => (viewport.overflow_offenders || []).length),
|
|
2424
|
+
frames: currentViewports
|
|
2425
|
+
.filter((viewport) => viewport.frames)
|
|
2426
|
+
.map((viewport) => ({
|
|
2427
|
+
viewport: viewport.name,
|
|
2428
|
+
selectors: Object.entries(viewport.frames || {}).map(([selector, frameSet]) => ({
|
|
2429
|
+
selector,
|
|
2430
|
+
count: frameSet && frameSet.count,
|
|
2431
|
+
frame_count: frameSet && frameSet.frame_count,
|
|
2432
|
+
max_bounds_overflow_px: Math.max(
|
|
2433
|
+
0,
|
|
2434
|
+
...((frameSet && Array.isArray(frameSet.frames) ? frameSet.frames : [])).map((frame) => horizontalBoundsOverflowPx(frame)),
|
|
2435
|
+
),
|
|
2436
|
+
})),
|
|
2437
|
+
})),
|
|
2160
2438
|
route_inventory: currentViewports
|
|
2161
2439
|
.filter((viewport) => viewport.route_inventory)
|
|
2162
2440
|
.map((viewport) => ({
|