@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/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
|
|
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:
|
|
7600
|
-
page_error_count:
|
|
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
|
|
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: {
|
|
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