@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/README.md +42 -0
- package/dist/{chunk-KZRDAMI5.js → chunk-L4FLSU7L.js} +347 -5
- package/dist/cli.cjs +347 -5
- package/dist/cli.js +1 -1
- package/dist/index.cjs +347 -5
- package/dist/index.js +1 -1
- package/dist/profile.cjs +347 -5
- package/dist/profile.d.cts +6 -1
- package/dist/profile.d.ts +6 -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/dist/profile.cjs
CHANGED
|
@@ -58,6 +58,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
58
58
|
"selector_visible",
|
|
59
59
|
"selector_count_at_least",
|
|
60
60
|
"selector_text_order",
|
|
61
|
+
"frame_text_visible",
|
|
62
|
+
"frame_no_horizontal_overflow",
|
|
61
63
|
"text_visible",
|
|
62
64
|
"text_absent",
|
|
63
65
|
"route_inventory",
|
|
@@ -346,6 +348,13 @@ function normalizeExpectedTexts(value, index) {
|
|
|
346
348
|
if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
|
|
347
349
|
return texts;
|
|
348
350
|
}
|
|
351
|
+
function normalizeStringList(value, label) {
|
|
352
|
+
if (value === void 0) return void 0;
|
|
353
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
354
|
+
const values = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
355
|
+
if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
|
|
356
|
+
return values;
|
|
357
|
+
}
|
|
349
358
|
function normalizeCheck(input, index) {
|
|
350
359
|
if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
351
360
|
const type = stringValue(input.type);
|
|
@@ -356,6 +365,12 @@ function normalizeCheck(input, index) {
|
|
|
356
365
|
if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue(input.selector)) {
|
|
357
366
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
358
367
|
}
|
|
368
|
+
if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
369
|
+
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
370
|
+
}
|
|
371
|
+
if (type === "frame_text_visible" && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
372
|
+
throw new Error(`checks[${index}] frame_text_visible requires text or pattern.`);
|
|
373
|
+
}
|
|
359
374
|
if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
360
375
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
361
376
|
}
|
|
@@ -388,6 +403,10 @@ function normalizeCheck(input, index) {
|
|
|
388
403
|
text: stringValue(input.text),
|
|
389
404
|
pattern: stringValue(input.pattern),
|
|
390
405
|
flags: stringValue(input.flags),
|
|
406
|
+
allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
|
|
407
|
+
allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
|
|
408
|
+
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`),
|
|
409
|
+
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`),
|
|
391
410
|
min_count: numberValue(input.min_count),
|
|
392
411
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
393
412
|
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
@@ -506,6 +525,22 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
506
525
|
}
|
|
507
526
|
return { matched: true, positions };
|
|
508
527
|
}
|
|
528
|
+
function frameEvidenceForSelector(viewport, selector) {
|
|
529
|
+
const container = viewport.frames?.[selector];
|
|
530
|
+
if (!isRecord(container)) return [];
|
|
531
|
+
const frames = Array.isArray(container.frames) ? container.frames : [];
|
|
532
|
+
return frames.filter((frame) => isRecord(frame));
|
|
533
|
+
}
|
|
534
|
+
function frameTextSample(frame) {
|
|
535
|
+
const parts = [
|
|
536
|
+
frame.title,
|
|
537
|
+
frame.text_sample,
|
|
538
|
+
frame.body_text_sample,
|
|
539
|
+
frame.body_text,
|
|
540
|
+
frame.text
|
|
541
|
+
];
|
|
542
|
+
return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
|
|
543
|
+
}
|
|
509
544
|
function summarizeRouteInventory(viewport, inventory) {
|
|
510
545
|
const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
|
|
511
546
|
const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
|
|
@@ -535,6 +570,18 @@ function matchText(sample, check) {
|
|
|
535
570
|
}
|
|
536
571
|
return sample.includes(check.text || "");
|
|
537
572
|
}
|
|
573
|
+
function matchesAllowedMessage(message, texts, patterns) {
|
|
574
|
+
const sample = String(message || "");
|
|
575
|
+
if (texts?.some((text) => sample.includes(text))) return true;
|
|
576
|
+
for (const pattern of patterns || []) {
|
|
577
|
+
try {
|
|
578
|
+
if (new RegExp(pattern).test(sample)) return true;
|
|
579
|
+
} catch {
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
538
585
|
function normalizeRoutePath(path) {
|
|
539
586
|
const value = path || "/";
|
|
540
587
|
if (value === "/") return "/";
|
|
@@ -670,6 +717,67 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
670
717
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
671
718
|
};
|
|
672
719
|
}
|
|
720
|
+
if (check.type === "frame_text_visible") {
|
|
721
|
+
const key = selectorKey(check);
|
|
722
|
+
const results = viewports.map((viewport) => {
|
|
723
|
+
const frames = frameEvidenceForSelector(viewport, key);
|
|
724
|
+
const matches = frames.map((frame, index) => ({ index, frame, matched: matchText(frameTextSample(frame), check) })).filter((item) => item.matched);
|
|
725
|
+
return {
|
|
726
|
+
viewport: viewport.name,
|
|
727
|
+
frame_count: frames.length,
|
|
728
|
+
matched_count: matches.length,
|
|
729
|
+
matched: matches.length > 0,
|
|
730
|
+
urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
|
|
731
|
+
samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3)
|
|
732
|
+
};
|
|
733
|
+
});
|
|
734
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
735
|
+
return {
|
|
736
|
+
type: check.type,
|
|
737
|
+
label: checkLabel(check),
|
|
738
|
+
status: failed ? "failed" : "passed",
|
|
739
|
+
evidence: {
|
|
740
|
+
selector: key,
|
|
741
|
+
text: check.text || null,
|
|
742
|
+
pattern: check.pattern || null,
|
|
743
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
744
|
+
},
|
|
745
|
+
message: failed ? `Frame selector ${key} did not contain expected text in ${failed} viewport(s).` : void 0
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
if (check.type === "frame_no_horizontal_overflow") {
|
|
749
|
+
const key = selectorKey(check);
|
|
750
|
+
const maxOverflow = check.max_overflow_px ?? 4;
|
|
751
|
+
const results = viewports.map((viewport) => {
|
|
752
|
+
const frames = frameEvidenceForSelector(viewport, key);
|
|
753
|
+
const frameOverflows = frames.map((frame, index) => ({
|
|
754
|
+
index,
|
|
755
|
+
url: String(frame.url || ""),
|
|
756
|
+
overflow_px: horizontalBoundsOverflowPx(frame),
|
|
757
|
+
offender_count: boundsOffendersForEvidence(frame).length
|
|
758
|
+
}));
|
|
759
|
+
const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
|
|
760
|
+
return {
|
|
761
|
+
viewport: viewport.name,
|
|
762
|
+
frame_count: frames.length,
|
|
763
|
+
max_overflow_px: roundPixels(maxFrameOverflow),
|
|
764
|
+
failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
|
|
765
|
+
frames: frameOverflows.map((frame) => toJsonValue(frame))
|
|
766
|
+
};
|
|
767
|
+
});
|
|
768
|
+
const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
|
|
769
|
+
return {
|
|
770
|
+
type: check.type,
|
|
771
|
+
label: checkLabel(check),
|
|
772
|
+
status: failed ? "failed" : "passed",
|
|
773
|
+
evidence: {
|
|
774
|
+
selector: key,
|
|
775
|
+
max_overflow_px: maxOverflow,
|
|
776
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
777
|
+
},
|
|
778
|
+
message: failed ? `Frame selector ${key} overflow exceeded ${maxOverflow}px or was missing in ${failed} viewport(s).` : void 0
|
|
779
|
+
};
|
|
780
|
+
}
|
|
673
781
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
674
782
|
const key = textKey(check);
|
|
675
783
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -760,14 +868,28 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
760
868
|
};
|
|
761
869
|
}
|
|
762
870
|
if (check.type === "no_fatal_console_errors") {
|
|
763
|
-
const
|
|
871
|
+
const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
|
|
872
|
+
const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
873
|
+
const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
874
|
+
const pageErrors = evidence.page_errors || [];
|
|
875
|
+
const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
876
|
+
const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
877
|
+
const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
|
|
764
878
|
return {
|
|
765
879
|
type: check.type,
|
|
766
880
|
label: checkLabel(check),
|
|
767
881
|
status: fatalCount ? "failed" : "passed",
|
|
768
882
|
evidence: {
|
|
769
|
-
console_fatal_count:
|
|
770
|
-
page_error_count:
|
|
883
|
+
console_fatal_count: unallowedConsoleEvents.length,
|
|
884
|
+
page_error_count: unallowedPageErrors.length,
|
|
885
|
+
total_console_fatal_count: fatalConsoleEvents.length,
|
|
886
|
+
total_page_error_count: pageErrors.length,
|
|
887
|
+
allowed_console_fatal_count: allowedConsoleEvents.length,
|
|
888
|
+
allowed_page_error_count: allowedPageErrors.length,
|
|
889
|
+
allowed_console_texts: check.allowed_console_texts || [],
|
|
890
|
+
allowed_console_patterns: check.allowed_console_patterns || [],
|
|
891
|
+
allowed_page_error_texts: check.allowed_page_error_texts || [],
|
|
892
|
+
allowed_page_error_patterns: check.allowed_page_error_patterns || []
|
|
771
893
|
},
|
|
772
894
|
message: fatalCount ? `${fatalCount} fatal browser error(s) were captured.` : void 0
|
|
773
895
|
};
|
|
@@ -1023,6 +1145,16 @@ function textMatches(sample, check) {
|
|
|
1023
1145
|
}
|
|
1024
1146
|
return String(sample || "").includes(check.text || "");
|
|
1025
1147
|
}
|
|
1148
|
+
function matchesAllowedMessage(message, texts, patterns) {
|
|
1149
|
+
const sample = String(message || "");
|
|
1150
|
+
if ((texts || []).some((text) => sample.includes(text))) return true;
|
|
1151
|
+
for (const pattern of patterns || []) {
|
|
1152
|
+
try {
|
|
1153
|
+
if (new RegExp(pattern).test(sample)) return true;
|
|
1154
|
+
} catch {}
|
|
1155
|
+
}
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1026
1158
|
function textSequenceForCheck(viewport, check) {
|
|
1027
1159
|
const key = check.selector || "";
|
|
1028
1160
|
const sequence = viewport.text_sequences && viewport.text_sequences[key];
|
|
@@ -1045,6 +1177,21 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
1045
1177
|
}
|
|
1046
1178
|
return { matched: true, positions };
|
|
1047
1179
|
}
|
|
1180
|
+
function frameEvidenceForSelector(viewport, selector) {
|
|
1181
|
+
const container = viewport.frames && viewport.frames[selector || ""];
|
|
1182
|
+
if (!container || typeof container !== "object" || Array.isArray(container)) return [];
|
|
1183
|
+
const frames = Array.isArray(container.frames) ? container.frames : [];
|
|
1184
|
+
return frames.filter((frame) => frame && typeof frame === "object" && !Array.isArray(frame));
|
|
1185
|
+
}
|
|
1186
|
+
function frameTextSample(frame) {
|
|
1187
|
+
return [
|
|
1188
|
+
frame && frame.title,
|
|
1189
|
+
frame && frame.text_sample,
|
|
1190
|
+
frame && frame.body_text_sample,
|
|
1191
|
+
frame && frame.body_text,
|
|
1192
|
+
frame && frame.text,
|
|
1193
|
+
].map((part) => String(part || "")).filter(Boolean).join(" ");
|
|
1194
|
+
}
|
|
1048
1195
|
function summarizeRouteInventory(viewport, inventory) {
|
|
1049
1196
|
const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
|
|
1050
1197
|
const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
|
|
@@ -1291,6 +1438,62 @@ function assessProfile(profile, evidence) {
|
|
|
1291
1438
|
});
|
|
1292
1439
|
continue;
|
|
1293
1440
|
}
|
|
1441
|
+
if (check.type === "frame_text_visible") {
|
|
1442
|
+
const selector = check.selector || "";
|
|
1443
|
+
const results = viewports.map((viewport) => {
|
|
1444
|
+
const frames = frameEvidenceForSelector(viewport, selector);
|
|
1445
|
+
const matches = frames
|
|
1446
|
+
.map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
|
|
1447
|
+
.filter((item) => item.matched);
|
|
1448
|
+
return {
|
|
1449
|
+
viewport: viewport.name,
|
|
1450
|
+
frame_count: frames.length,
|
|
1451
|
+
matched_count: matches.length,
|
|
1452
|
+
matched: matches.length > 0,
|
|
1453
|
+
urls: frames.map((frame) => String(frame.url || "")).filter(Boolean).slice(0, 10),
|
|
1454
|
+
samples: matches.map((item) => frameTextSample(item.frame).replace(/\s+/g, " ").trim().slice(0, 240)).slice(0, 3),
|
|
1455
|
+
};
|
|
1456
|
+
});
|
|
1457
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
1458
|
+
checks.push({
|
|
1459
|
+
type: check.type,
|
|
1460
|
+
label: check.label || check.type,
|
|
1461
|
+
status: failed ? "failed" : "passed",
|
|
1462
|
+
evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
|
|
1463
|
+
message: failed ? "Frame selector " + selector + " did not contain expected text in " + failed + " viewport(s)." : undefined,
|
|
1464
|
+
});
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
if (check.type === "frame_no_horizontal_overflow") {
|
|
1468
|
+
const selector = check.selector || "";
|
|
1469
|
+
const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
|
|
1470
|
+
const results = viewports.map((viewport) => {
|
|
1471
|
+
const frames = frameEvidenceForSelector(viewport, selector);
|
|
1472
|
+
const frameOverflows = frames.map((frame, index) => ({
|
|
1473
|
+
index,
|
|
1474
|
+
url: String(frame.url || ""),
|
|
1475
|
+
overflow_px: horizontalBoundsOverflowPx(frame),
|
|
1476
|
+
offender_count: boundsOffendersForEvidence(frame).length,
|
|
1477
|
+
}));
|
|
1478
|
+
const maxFrameOverflow = frameOverflows.reduce((max, frame) => Math.max(max, frame.overflow_px), 0);
|
|
1479
|
+
return {
|
|
1480
|
+
viewport: viewport.name,
|
|
1481
|
+
frame_count: frames.length,
|
|
1482
|
+
max_overflow_px: roundPixels(maxFrameOverflow),
|
|
1483
|
+
failed_frame_count: frameOverflows.filter((frame) => frame.overflow_px > maxOverflow).length,
|
|
1484
|
+
frames: frameOverflows,
|
|
1485
|
+
};
|
|
1486
|
+
});
|
|
1487
|
+
const failed = results.filter((result) => result.frame_count < 1 || result.failed_frame_count > 0).length;
|
|
1488
|
+
checks.push({
|
|
1489
|
+
type: check.type,
|
|
1490
|
+
label: check.label || check.type,
|
|
1491
|
+
status: failed ? "failed" : "passed",
|
|
1492
|
+
evidence: { selector, max_overflow_px: maxOverflow, viewports: results },
|
|
1493
|
+
message: failed ? "Frame selector " + selector + " overflow exceeded " + maxOverflow + "px or was missing in " + failed + " viewport(s)." : undefined,
|
|
1494
|
+
});
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1294
1497
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
1295
1498
|
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
1296
1499
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -1386,12 +1589,29 @@ function assessProfile(profile, evidence) {
|
|
|
1386
1589
|
continue;
|
|
1387
1590
|
}
|
|
1388
1591
|
if (check.type === "no_fatal_console_errors") {
|
|
1389
|
-
const
|
|
1592
|
+
const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
|
|
1593
|
+
const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
1594
|
+
const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
1595
|
+
const pageErrors = evidence.page_errors || [];
|
|
1596
|
+
const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
1597
|
+
const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
1598
|
+
const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
|
|
1390
1599
|
checks.push({
|
|
1391
1600
|
type: check.type,
|
|
1392
1601
|
label: check.label || check.type,
|
|
1393
1602
|
status: fatalCount ? "failed" : "passed",
|
|
1394
|
-
evidence: {
|
|
1603
|
+
evidence: {
|
|
1604
|
+
console_fatal_count: unallowedConsoleEvents.length,
|
|
1605
|
+
page_error_count: unallowedPageErrors.length,
|
|
1606
|
+
total_console_fatal_count: fatalConsoleEvents.length,
|
|
1607
|
+
total_page_error_count: pageErrors.length,
|
|
1608
|
+
allowed_console_fatal_count: allowedConsoleEvents.length,
|
|
1609
|
+
allowed_page_error_count: allowedPageErrors.length,
|
|
1610
|
+
allowed_console_texts: check.allowed_console_texts || [],
|
|
1611
|
+
allowed_console_patterns: check.allowed_console_patterns || [],
|
|
1612
|
+
allowed_page_error_texts: check.allowed_page_error_texts || [],
|
|
1613
|
+
allowed_page_error_patterns: check.allowed_page_error_patterns || [],
|
|
1614
|
+
},
|
|
1395
1615
|
message: fatalCount ? String(fatalCount) + " fatal browser error(s) were captured." : undefined,
|
|
1396
1616
|
});
|
|
1397
1617
|
continue;
|
|
@@ -1688,6 +1908,108 @@ async function selectorTextSequence(selector) {
|
|
|
1688
1908
|
};
|
|
1689
1909
|
}).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
1690
1910
|
}
|
|
1911
|
+
async function frameEvidence(selector) {
|
|
1912
|
+
const result = { selector, count: 0, frame_count: 0, frames: [], errors: [] };
|
|
1913
|
+
let handles = [];
|
|
1914
|
+
try {
|
|
1915
|
+
const locator = page.locator(selector);
|
|
1916
|
+
result.count = await locator.count();
|
|
1917
|
+
handles = await locator.elementHandles();
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
result.errors.push(String(error && error.message ? error.message : error).slice(0, 500));
|
|
1920
|
+
return result;
|
|
1921
|
+
}
|
|
1922
|
+
for (let index = 0; index < handles.length; index += 1) {
|
|
1923
|
+
const handle = handles[index];
|
|
1924
|
+
try {
|
|
1925
|
+
const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame() : null;
|
|
1926
|
+
if (!frame) {
|
|
1927
|
+
result.frames.push({ index, attached: false, error: "content_frame_unavailable" });
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
const snapshot = await frame.evaluate(() => {
|
|
1931
|
+
const body = document.body;
|
|
1932
|
+
const documentElement = document.documentElement;
|
|
1933
|
+
const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
|
|
1934
|
+
const clientWidth = documentElement ? documentElement.clientWidth : window.innerWidth;
|
|
1935
|
+
const clientHeight = documentElement ? documentElement.clientHeight : window.innerHeight;
|
|
1936
|
+
const scrollWidth = documentElement ? documentElement.scrollWidth : 0;
|
|
1937
|
+
const viewportWidth = clientWidth || window.innerWidth;
|
|
1938
|
+
const overflowOffenders = [];
|
|
1939
|
+
function isContainedByHorizontalScroller(element) {
|
|
1940
|
+
let current = element.parentElement;
|
|
1941
|
+
while (current && current !== body && current !== documentElement) {
|
|
1942
|
+
const style = window.getComputedStyle(current);
|
|
1943
|
+
const overflowX = style.overflowX || style.overflow || "";
|
|
1944
|
+
if ((overflowX === "auto" || overflowX === "scroll") && current.scrollWidth > current.clientWidth + 1) {
|
|
1945
|
+
const currentRect = current.getBoundingClientRect();
|
|
1946
|
+
const contained = currentRect.left >= -0.5 && currentRect.right <= viewportWidth + 0.5;
|
|
1947
|
+
if (contained) return true;
|
|
1948
|
+
}
|
|
1949
|
+
current = current.parentElement;
|
|
1950
|
+
}
|
|
1951
|
+
return false;
|
|
1952
|
+
}
|
|
1953
|
+
for (const element of Array.from(body ? body.querySelectorAll("*") : [])) {
|
|
1954
|
+
const rect = element.getBoundingClientRect();
|
|
1955
|
+
if (!rect || rect.width < 1 || rect.height < 1) continue;
|
|
1956
|
+
const style = window.getComputedStyle(element);
|
|
1957
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") continue;
|
|
1958
|
+
const leftOverflow = Math.max(0, -rect.left);
|
|
1959
|
+
const rightOverflow = Math.max(0, rect.right - viewportWidth);
|
|
1960
|
+
const overflow = Math.max(leftOverflow, rightOverflow);
|
|
1961
|
+
if (overflow <= 0.5) continue;
|
|
1962
|
+
if (isContainedByHorizontalScroller(element)) continue;
|
|
1963
|
+
const tag = element.tagName ? element.tagName.toLowerCase() : "element";
|
|
1964
|
+
const id = element.id ? "#" + element.id : "";
|
|
1965
|
+
const className = typeof element.className === "string"
|
|
1966
|
+
? element.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).join(".")
|
|
1967
|
+
: "";
|
|
1968
|
+
overflowOffenders.push({
|
|
1969
|
+
selector: tag + id + (className ? "." + className : ""),
|
|
1970
|
+
tag,
|
|
1971
|
+
text: (element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120),
|
|
1972
|
+
overflow,
|
|
1973
|
+
left_overflow_px: leftOverflow,
|
|
1974
|
+
right_overflow_px: rightOverflow,
|
|
1975
|
+
viewport_width: viewportWidth,
|
|
1976
|
+
rect: {
|
|
1977
|
+
left: rect.left,
|
|
1978
|
+
right: rect.right,
|
|
1979
|
+
width: rect.width,
|
|
1980
|
+
},
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
overflowOffenders.sort((a, b) => b.overflow - a.overflow);
|
|
1984
|
+
const boundsOverflowPx = Math.max(
|
|
1985
|
+
0,
|
|
1986
|
+
scrollWidth - (clientWidth || viewportWidth),
|
|
1987
|
+
...overflowOffenders.map((offender) => offender.overflow),
|
|
1988
|
+
);
|
|
1989
|
+
return {
|
|
1990
|
+
url: location.href,
|
|
1991
|
+
title: document.title,
|
|
1992
|
+
text_length: text.length,
|
|
1993
|
+
text_sample: text.slice(0, 8000),
|
|
1994
|
+
body_text_sample: text.slice(0, 8000),
|
|
1995
|
+
viewport_width: viewportWidth,
|
|
1996
|
+
viewport_height: clientHeight || window.innerHeight,
|
|
1997
|
+
scroll_width: scrollWidth,
|
|
1998
|
+
client_width: clientWidth,
|
|
1999
|
+
overflow_px: Math.max(0, scrollWidth - (clientWidth || viewportWidth)),
|
|
2000
|
+
bounds_overflow_px: Math.round(boundsOverflowPx * 100) / 100,
|
|
2001
|
+
overflow_offender_count: overflowOffenders.length,
|
|
2002
|
+
overflow_offenders: overflowOffenders.slice(0, 10),
|
|
2003
|
+
};
|
|
2004
|
+
});
|
|
2005
|
+
result.frames.push({ index, attached: true, ...snapshot });
|
|
2006
|
+
} catch (error) {
|
|
2007
|
+
result.frames.push({ index, attached: false, error: String(error && error.message ? error.message : error).slice(0, 1000) });
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
result.frame_count = result.frames.filter((frame) => frame && frame.attached !== false && !frame.error).length;
|
|
2011
|
+
return result;
|
|
2012
|
+
}
|
|
1691
2013
|
function inventoryAppPathFromUrl(urlLike) {
|
|
1692
2014
|
const url = new URL(String(urlLike), targetUrl);
|
|
1693
2015
|
const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
|
|
@@ -2092,6 +2414,7 @@ async function captureViewport(viewport) {
|
|
|
2092
2414
|
evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
|
|
2093
2415
|
}));
|
|
2094
2416
|
const selectors = {};
|
|
2417
|
+
const frames = {};
|
|
2095
2418
|
const text_sequences = {};
|
|
2096
2419
|
const text_matches = {};
|
|
2097
2420
|
for (const check of profile.checks || []) {
|
|
@@ -2105,6 +2428,10 @@ async function captureViewport(viewport) {
|
|
|
2105
2428
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
2106
2429
|
text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
|
|
2107
2430
|
}
|
|
2431
|
+
if ((check.type === "frame_text_visible" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
2432
|
+
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
2433
|
+
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
2434
|
+
}
|
|
2108
2435
|
}
|
|
2109
2436
|
const screenshotLabel = profileSlug + "-" + viewport.name;
|
|
2110
2437
|
try {
|
|
@@ -2165,6 +2492,7 @@ async function captureViewport(viewport) {
|
|
|
2165
2492
|
bounds_overflow_px: dom.bounds_overflow_px,
|
|
2166
2493
|
overflow_offenders: dom.overflow_offenders || [],
|
|
2167
2494
|
selectors,
|
|
2495
|
+
frames,
|
|
2168
2496
|
text_sequences,
|
|
2169
2497
|
text_matches,
|
|
2170
2498
|
route_inventory: routeInventory,
|
|
@@ -2200,6 +2528,20 @@ function buildProfileEvidence(currentViewports) {
|
|
|
2200
2528
|
overflow_px: currentViewports.map((viewport) => viewport.overflow_px),
|
|
2201
2529
|
bounds_overflow_px: currentViewports.map((viewport) => viewport.bounds_overflow_px),
|
|
2202
2530
|
overflow_offender_counts: currentViewports.map((viewport) => (viewport.overflow_offenders || []).length),
|
|
2531
|
+
frames: currentViewports
|
|
2532
|
+
.filter((viewport) => viewport.frames)
|
|
2533
|
+
.map((viewport) => ({
|
|
2534
|
+
viewport: viewport.name,
|
|
2535
|
+
selectors: Object.entries(viewport.frames || {}).map(([selector, frameSet]) => ({
|
|
2536
|
+
selector,
|
|
2537
|
+
count: frameSet && frameSet.count,
|
|
2538
|
+
frame_count: frameSet && frameSet.frame_count,
|
|
2539
|
+
max_bounds_overflow_px: Math.max(
|
|
2540
|
+
0,
|
|
2541
|
+
...((frameSet && Array.isArray(frameSet.frames) ? frameSet.frames : [])).map((frame) => horizontalBoundsOverflowPx(frame)),
|
|
2542
|
+
),
|
|
2543
|
+
})),
|
|
2544
|
+
})),
|
|
2203
2545
|
route_inventory: currentViewports
|
|
2204
2546
|
.filter((viewport) => viewport.route_inventory)
|
|
2205
2547
|
.map((viewport) => ({
|
package/dist/profile.d.cts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -77,6 +77,10 @@ interface RiddleProofProfileCheck {
|
|
|
77
77
|
text?: string;
|
|
78
78
|
pattern?: string;
|
|
79
79
|
flags?: string;
|
|
80
|
+
allowed_console_texts?: string[];
|
|
81
|
+
allowed_console_patterns?: string[];
|
|
82
|
+
allowed_page_error_texts?: string[];
|
|
83
|
+
allowed_page_error_patterns?: string[];
|
|
80
84
|
min_count?: number;
|
|
81
85
|
max_overflow_px?: number;
|
|
82
86
|
timeout_ms?: number;
|
|
@@ -139,6 +143,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
139
143
|
count: number;
|
|
140
144
|
visible_count: number;
|
|
141
145
|
}>;
|
|
146
|
+
frames?: Record<string, Record<string, JsonValue>>;
|
|
142
147
|
text_sequences?: Record<string, Record<string, JsonValue>>;
|
|
143
148
|
text_matches?: Record<string, boolean>;
|
|
144
149
|
route_inventory?: Record<string, JsonValue>;
|
package/dist/profile.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_count_at_least", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -77,6 +77,10 @@ interface RiddleProofProfileCheck {
|
|
|
77
77
|
text?: string;
|
|
78
78
|
pattern?: string;
|
|
79
79
|
flags?: string;
|
|
80
|
+
allowed_console_texts?: string[];
|
|
81
|
+
allowed_console_patterns?: string[];
|
|
82
|
+
allowed_page_error_texts?: string[];
|
|
83
|
+
allowed_page_error_patterns?: string[];
|
|
80
84
|
min_count?: number;
|
|
81
85
|
max_overflow_px?: number;
|
|
82
86
|
timeout_ms?: number;
|
|
@@ -139,6 +143,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
139
143
|
count: number;
|
|
140
144
|
visible_count: number;
|
|
141
145
|
}>;
|
|
146
|
+
frames?: Record<string, Record<string, JsonValue>>;
|
|
142
147
|
text_sequences?: Record<string, Record<string, JsonValue>>;
|
|
143
148
|
text_matches?: Record<string, boolean>;
|
|
144
149
|
route_inventory?: Record<string, JsonValue>;
|
package/dist/profile.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveRiddleProofProfileTimeoutSec,
|
|
20
20
|
slugifyRiddleProofProfileName,
|
|
21
21
|
summarizeRiddleProofProfileResult
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-L4FLSU7L.js";
|
|
23
23
|
export {
|
|
24
24
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
25
25
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|
|
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
292
292
|
blocking?: boolean;
|
|
293
293
|
details?: Record<string, unknown>;
|
|
294
294
|
ok: boolean;
|
|
295
|
-
action: "
|
|
295
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
296
296
|
state_path: string;
|
|
297
297
|
stage: any;
|
|
298
298
|
summary: string;
|
|
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
382
382
|
continueWithStage?: WorkflowStage | null;
|
|
383
383
|
blocking?: boolean;
|
|
384
384
|
details?: Record<string, unknown>;
|
|
385
|
-
action: "
|
|
385
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
386
386
|
state_path: string;
|
|
387
387
|
stage: any;
|
|
388
388
|
checkpoint: string;
|
|
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
659
659
|
error?: undefined;
|
|
660
660
|
} | {
|
|
661
661
|
ok: boolean;
|
|
662
|
-
action: "
|
|
662
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
663
663
|
state_path: string;
|
|
664
664
|
stage: any;
|
|
665
665
|
summary: string;
|
|
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
292
292
|
blocking?: boolean;
|
|
293
293
|
details?: Record<string, unknown>;
|
|
294
294
|
ok: boolean;
|
|
295
|
-
action: "
|
|
295
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
296
296
|
state_path: string;
|
|
297
297
|
stage: any;
|
|
298
298
|
summary: string;
|
|
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
382
382
|
continueWithStage?: WorkflowStage | null;
|
|
383
383
|
blocking?: boolean;
|
|
384
384
|
details?: Record<string, unknown>;
|
|
385
|
-
action: "
|
|
385
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
386
386
|
state_path: string;
|
|
387
387
|
stage: any;
|
|
388
388
|
checkpoint: string;
|
|
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
659
659
|
error?: undefined;
|
|
660
660
|
} | {
|
|
661
661
|
ok: boolean;
|
|
662
|
-
action: "
|
|
662
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
663
663
|
state_path: string;
|
|
664
664
|
stage: any;
|
|
665
665
|
summary: string;
|