@riddledc/riddle-proof 0.7.127 → 0.7.129
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 +3 -1
- package/dist/{chunk-JLINSUKO.js → chunk-TDK6WFH3.js} +216 -8
- package/dist/cli.cjs +216 -8
- package/dist/cli.js +1 -1
- package/dist/index.cjs +216 -8
- package/dist/index.js +1 -1
- package/dist/profile.cjs +216 -8
- package/dist/profile.d.cts +5 -0
- package/dist/profile.d.ts +5 -0
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -7181,6 +7181,37 @@ function jsonValueType(value) {
|
|
|
7181
7181
|
if (typeof value === "string") return "string";
|
|
7182
7182
|
return "object";
|
|
7183
7183
|
}
|
|
7184
|
+
function compactJsonAssertionSample(value, depth = 0) {
|
|
7185
|
+
if (typeof value === "string") return value.length > 240 ? `${value.slice(0, 237)}...` : value;
|
|
7186
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return toJsonValue(value);
|
|
7187
|
+
if (Array.isArray(value)) {
|
|
7188
|
+
if (depth >= 2) return `[array:${value.length}]`;
|
|
7189
|
+
return value.slice(0, 3).map((item) => compactJsonAssertionSample(item, depth + 1));
|
|
7190
|
+
}
|
|
7191
|
+
if (isRecord(value)) {
|
|
7192
|
+
const entries = Object.entries(value).slice(0, 8);
|
|
7193
|
+
if (depth >= 2) return `[object:${Object.keys(value).length} keys]`;
|
|
7194
|
+
return Object.fromEntries(entries.map(([key, child]) => [key, compactJsonAssertionSample(child, depth + 1)]));
|
|
7195
|
+
}
|
|
7196
|
+
return String(value);
|
|
7197
|
+
}
|
|
7198
|
+
function attachJsonAssertionObservedValue(result, value) {
|
|
7199
|
+
const type = jsonValueType(value);
|
|
7200
|
+
if (type === "array" && Array.isArray(value)) {
|
|
7201
|
+
result.observed_length = value.length;
|
|
7202
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
7203
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
7204
|
+
return;
|
|
7205
|
+
}
|
|
7206
|
+
if (type === "object" && isRecord(value)) {
|
|
7207
|
+
const keyCount = Object.keys(value).length;
|
|
7208
|
+
result.observed_key_count = keyCount;
|
|
7209
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
7210
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
7211
|
+
return;
|
|
7212
|
+
}
|
|
7213
|
+
result.observed = toJsonValue(value);
|
|
7214
|
+
}
|
|
7184
7215
|
function deepJsonEqual(left, right) {
|
|
7185
7216
|
if (left === right) return true;
|
|
7186
7217
|
if (typeof left !== typeof right) return false;
|
|
@@ -7278,7 +7309,7 @@ function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
|
|
|
7278
7309
|
exists: resolved.exists,
|
|
7279
7310
|
observed_type: resolved.exists ? jsonValueType(resolved.value) : "missing"
|
|
7280
7311
|
};
|
|
7281
|
-
if (resolved.exists) result
|
|
7312
|
+
if (resolved.exists) attachJsonAssertionObservedValue(result, resolved.value);
|
|
7282
7313
|
if (resolved.error) errors.push(resolved.error);
|
|
7283
7314
|
if (hasOwn(assertion, "exists")) {
|
|
7284
7315
|
result.expected_exists = assertion.exists;
|
|
@@ -8371,6 +8402,10 @@ function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
|
8371
8402
|
ok: false,
|
|
8372
8403
|
exists: assertion.exists === true,
|
|
8373
8404
|
observed: hasOwn(assertion, "observed") ? toJsonValue(assertion.observed) : void 0,
|
|
8405
|
+
observed_sample: hasOwn(assertion, "observed_sample") ? toJsonValue(assertion.observed_sample) : void 0,
|
|
8406
|
+
observed_length: numberValue(assertion.observed_length),
|
|
8407
|
+
observed_key_count: numberValue(assertion.observed_key_count),
|
|
8408
|
+
observed_omitted_count: numberValue(assertion.observed_omitted_count),
|
|
8374
8409
|
observed_type: stringValue2(assertion.observed_type) || "missing",
|
|
8375
8410
|
expected_exists: booleanValue(assertion.expected_exists),
|
|
8376
8411
|
equals: hasOwn(assertion, "equals") ? toJsonValue(assertion.equals) : void 0,
|
|
@@ -8756,6 +8791,49 @@ function matchText(sample, check) {
|
|
|
8756
8791
|
}
|
|
8757
8792
|
return sample.includes(check.text || "");
|
|
8758
8793
|
}
|
|
8794
|
+
function compactTextEvidenceSample(value) {
|
|
8795
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
8796
|
+
}
|
|
8797
|
+
function textSampleAroundMatch(sample, index, length) {
|
|
8798
|
+
if (index < 0) return void 0;
|
|
8799
|
+
const source = String(sample || "");
|
|
8800
|
+
const context = 120;
|
|
8801
|
+
const start = Math.max(0, index - context);
|
|
8802
|
+
const end = Math.min(source.length, index + Math.max(length, 1) + context);
|
|
8803
|
+
const prefix = start > 0 ? "..." : "";
|
|
8804
|
+
const suffix = end < source.length ? "..." : "";
|
|
8805
|
+
const compacted = compactTextEvidenceSample(`${prefix}${source.slice(start, end)}${suffix}`);
|
|
8806
|
+
return compacted ? compacted.slice(0, 240) : void 0;
|
|
8807
|
+
}
|
|
8808
|
+
function textMatchSamples(sample, check) {
|
|
8809
|
+
const source = String(sample || "");
|
|
8810
|
+
if (!source) return [];
|
|
8811
|
+
if (check.pattern) {
|
|
8812
|
+
try {
|
|
8813
|
+
const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
|
|
8814
|
+
const match = new RegExp(check.pattern, flags).exec(source);
|
|
8815
|
+
const sampleText2 = match ? textSampleAroundMatch(source, match.index, match[0]?.length || 1) : void 0;
|
|
8816
|
+
return sampleText2 ? [sampleText2] : [];
|
|
8817
|
+
} catch {
|
|
8818
|
+
return [];
|
|
8819
|
+
}
|
|
8820
|
+
}
|
|
8821
|
+
const text = check.text || "";
|
|
8822
|
+
if (!text) return [];
|
|
8823
|
+
const index = source.indexOf(text);
|
|
8824
|
+
const sampleText = textSampleAroundMatch(source, index, text.length);
|
|
8825
|
+
return sampleText ? [sampleText] : [];
|
|
8826
|
+
}
|
|
8827
|
+
function textCheckFailureSamples(viewport, check) {
|
|
8828
|
+
const key = textKey(check);
|
|
8829
|
+
const captured = viewport.text_match_samples?.[key] || [];
|
|
8830
|
+
const capturedSamples = captured.map((sample) => compactTextEvidenceSample(sample).slice(0, 240)).filter(Boolean);
|
|
8831
|
+
if (capturedSamples.length) return capturedSamples.slice(0, 3);
|
|
8832
|
+
const matchedSamples = textMatchSamples(viewport.body_text_sample || "", check);
|
|
8833
|
+
if (matchedSamples.length) return matchedSamples.slice(0, 3);
|
|
8834
|
+
const fallback = compactTextEvidenceSample(viewport.body_text_sample || "").slice(0, 240);
|
|
8835
|
+
return fallback ? [fallback] : [];
|
|
8836
|
+
}
|
|
8759
8837
|
function allowedMessageSample(input) {
|
|
8760
8838
|
if (!isRecord(input)) return String(input || "");
|
|
8761
8839
|
const parts = [
|
|
@@ -9200,10 +9278,17 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
9200
9278
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
9201
9279
|
const key = textKey(check);
|
|
9202
9280
|
const expectedVisible = check.type === "text_visible";
|
|
9203
|
-
const
|
|
9281
|
+
const results = viewports.map((viewport) => {
|
|
9204
9282
|
const fromEvidence = viewport.text_matches?.[key];
|
|
9205
|
-
|
|
9283
|
+
const matched = typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
|
|
9284
|
+
const failedAgainstExpectation = matched !== expectedVisible;
|
|
9285
|
+
return {
|
|
9286
|
+
viewport: viewport.name,
|
|
9287
|
+
matched,
|
|
9288
|
+
samples: failedAgainstExpectation ? textCheckFailureSamples(viewport, check) : []
|
|
9289
|
+
};
|
|
9206
9290
|
});
|
|
9291
|
+
const matches = results.map((result) => result.matched);
|
|
9207
9292
|
const failed = matches.filter((matched) => matched !== expectedVisible).length;
|
|
9208
9293
|
return {
|
|
9209
9294
|
type: check.type,
|
|
@@ -9212,7 +9297,8 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
9212
9297
|
evidence: {
|
|
9213
9298
|
text: check.text || null,
|
|
9214
9299
|
pattern: check.pattern || null,
|
|
9215
|
-
matches
|
|
9300
|
+
matches,
|
|
9301
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
9216
9302
|
},
|
|
9217
9303
|
message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
|
|
9218
9304
|
};
|
|
@@ -9783,6 +9869,48 @@ function textMatches(sample, check) {
|
|
|
9783
9869
|
}
|
|
9784
9870
|
return String(sample || "").includes(check.text || "");
|
|
9785
9871
|
}
|
|
9872
|
+
function compactTextEvidenceSample(value) {
|
|
9873
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
9874
|
+
}
|
|
9875
|
+
function textSampleAroundMatch(sample, index, length) {
|
|
9876
|
+
if (index < 0) return undefined;
|
|
9877
|
+
const source = String(sample || "");
|
|
9878
|
+
const context = 120;
|
|
9879
|
+
const start = Math.max(0, index - context);
|
|
9880
|
+
const end = Math.min(source.length, index + Math.max(length, 1) + context);
|
|
9881
|
+
const prefix = start > 0 ? "..." : "";
|
|
9882
|
+
const suffix = end < source.length ? "..." : "";
|
|
9883
|
+
const compacted = compactTextEvidenceSample(prefix + source.slice(start, end) + suffix);
|
|
9884
|
+
return compacted ? compacted.slice(0, 240) : undefined;
|
|
9885
|
+
}
|
|
9886
|
+
function textMatchSamples(sample, check) {
|
|
9887
|
+
const source = String(sample || "");
|
|
9888
|
+
if (!source) return [];
|
|
9889
|
+
if (check.pattern) {
|
|
9890
|
+
try {
|
|
9891
|
+
const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
|
|
9892
|
+
const match = new RegExp(check.pattern, flags).exec(source);
|
|
9893
|
+
const sampleText = match ? textSampleAroundMatch(source, match.index, match[0] ? match[0].length : 1) : undefined;
|
|
9894
|
+
return sampleText ? [sampleText] : [];
|
|
9895
|
+
} catch { return []; }
|
|
9896
|
+
}
|
|
9897
|
+
const text = check.text || "";
|
|
9898
|
+
if (!text) return [];
|
|
9899
|
+
const sampleText = textSampleAroundMatch(source, source.indexOf(text), text.length);
|
|
9900
|
+
return sampleText ? [sampleText] : [];
|
|
9901
|
+
}
|
|
9902
|
+
function textCheckFailureSamples(viewport, check) {
|
|
9903
|
+
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
9904
|
+
const captured = viewport && viewport.text_match_samples && Array.isArray(viewport.text_match_samples[key]) ? viewport.text_match_samples[key] : [];
|
|
9905
|
+
const capturedSamples = captured
|
|
9906
|
+
.map((sample) => compactTextEvidenceSample(sample).slice(0, 240))
|
|
9907
|
+
.filter(Boolean);
|
|
9908
|
+
if (capturedSamples.length) return capturedSamples.slice(0, 3);
|
|
9909
|
+
const matchedSamples = textMatchSamples(viewport && viewport.body_text_sample || "", check);
|
|
9910
|
+
if (matchedSamples.length) return matchedSamples.slice(0, 3);
|
|
9911
|
+
const fallback = compactTextEvidenceSample(viewport && viewport.body_text_sample || "").slice(0, 240);
|
|
9912
|
+
return fallback ? [fallback] : [];
|
|
9913
|
+
}
|
|
9786
9914
|
function allowedMessageSample(input) {
|
|
9787
9915
|
if (!input || typeof input !== "object" || Array.isArray(input)) return String(input || "");
|
|
9788
9916
|
const parts = [
|
|
@@ -9985,6 +10113,10 @@ function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
|
9985
10113
|
ok: false,
|
|
9986
10114
|
exists: assertion.exists === true,
|
|
9987
10115
|
observed: Object.hasOwn(assertion, "observed") ? assertion.observed : undefined,
|
|
10116
|
+
observed_sample: Object.hasOwn(assertion, "observed_sample") ? assertion.observed_sample : undefined,
|
|
10117
|
+
observed_length: typeof assertion.observed_length === "number" && Number.isFinite(assertion.observed_length) ? assertion.observed_length : undefined,
|
|
10118
|
+
observed_key_count: typeof assertion.observed_key_count === "number" && Number.isFinite(assertion.observed_key_count) ? assertion.observed_key_count : undefined,
|
|
10119
|
+
observed_omitted_count: typeof assertion.observed_omitted_count === "number" && Number.isFinite(assertion.observed_omitted_count) ? assertion.observed_omitted_count : undefined,
|
|
9988
10120
|
observed_type: typeof assertion.observed_type === "string" && assertion.observed_type ? assertion.observed_type : "missing",
|
|
9989
10121
|
expected_exists: typeof assertion.expected_exists === "boolean" ? assertion.expected_exists : undefined,
|
|
9990
10122
|
equals: Object.hasOwn(assertion, "equals") ? assertion.equals : undefined,
|
|
@@ -10856,13 +10988,22 @@ function assessProfile(profile, evidence) {
|
|
|
10856
10988
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
10857
10989
|
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
10858
10990
|
const expectedVisible = check.type === "text_visible";
|
|
10859
|
-
const
|
|
10991
|
+
const results = checkViewports.map((viewport) => {
|
|
10992
|
+
const matched = viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check);
|
|
10993
|
+
const failedAgainstExpectation = matched !== expectedVisible;
|
|
10994
|
+
return {
|
|
10995
|
+
viewport: viewport.name,
|
|
10996
|
+
matched,
|
|
10997
|
+
samples: failedAgainstExpectation ? textCheckFailureSamples(viewport, check) : [],
|
|
10998
|
+
};
|
|
10999
|
+
});
|
|
11000
|
+
const matches = results.map((result) => result.matched);
|
|
10860
11001
|
const failed = matches.filter((matched) => matched !== expectedVisible).length;
|
|
10861
11002
|
checks.push({
|
|
10862
11003
|
type: check.type,
|
|
10863
11004
|
label: check.label || check.type,
|
|
10864
11005
|
status: failed ? "failed" : "passed",
|
|
10865
|
-
evidence: { text: check.text, pattern: check.pattern, matches },
|
|
11006
|
+
evidence: { text: check.text, pattern: check.pattern, matches, viewports: results },
|
|
10866
11007
|
message: failed ? "Text assertion failed in " + failed + " viewport(s)." : undefined,
|
|
10867
11008
|
});
|
|
10868
11009
|
continue;
|
|
@@ -11182,6 +11323,36 @@ function textMatches(sample, check) {
|
|
|
11182
11323
|
}
|
|
11183
11324
|
return String(sample || "").includes(check.text || "");
|
|
11184
11325
|
}
|
|
11326
|
+
function compactTextEvidenceSample(value) {
|
|
11327
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
11328
|
+
}
|
|
11329
|
+
function textSampleAroundMatch(sample, index, length) {
|
|
11330
|
+
if (index < 0) return undefined;
|
|
11331
|
+
const source = String(sample || "");
|
|
11332
|
+
const context = 120;
|
|
11333
|
+
const start = Math.max(0, index - context);
|
|
11334
|
+
const end = Math.min(source.length, index + Math.max(length, 1) + context);
|
|
11335
|
+
const prefix = start > 0 ? "..." : "";
|
|
11336
|
+
const suffix = end < source.length ? "..." : "";
|
|
11337
|
+
const compacted = compactTextEvidenceSample(prefix + source.slice(start, end) + suffix);
|
|
11338
|
+
return compacted ? compacted.slice(0, 240) : undefined;
|
|
11339
|
+
}
|
|
11340
|
+
function textMatchSamples(sample, check) {
|
|
11341
|
+
const source = String(sample || "");
|
|
11342
|
+
if (!source) return [];
|
|
11343
|
+
if (check.pattern) {
|
|
11344
|
+
try {
|
|
11345
|
+
const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
|
|
11346
|
+
const match = new RegExp(check.pattern, flags).exec(source);
|
|
11347
|
+
const sampleText = match ? textSampleAroundMatch(source, match.index, match[0] ? match[0].length : 1) : undefined;
|
|
11348
|
+
return sampleText ? [sampleText] : [];
|
|
11349
|
+
} catch { return []; }
|
|
11350
|
+
}
|
|
11351
|
+
const text = check.text || "";
|
|
11352
|
+
if (!text) return [];
|
|
11353
|
+
const sampleText = textSampleAroundMatch(source, source.indexOf(text), text.length);
|
|
11354
|
+
return sampleText ? [sampleText] : [];
|
|
11355
|
+
}
|
|
11185
11356
|
function profileCheckAppliesToViewport(check, viewport) {
|
|
11186
11357
|
if (!Array.isArray(check.viewports) || !check.viewports.length) return true;
|
|
11187
11358
|
return Boolean(viewport && viewport.name && check.viewports.includes(viewport.name));
|
|
@@ -12192,6 +12363,38 @@ function jsonProbeValueType(value) {
|
|
|
12192
12363
|
if (typeof value === "string") return "string";
|
|
12193
12364
|
return "object";
|
|
12194
12365
|
}
|
|
12366
|
+
function compactJsonProbeSample(value, depth) {
|
|
12367
|
+
const level = typeof depth === "number" ? depth : 0;
|
|
12368
|
+
if (typeof value === "string") return value.length > 240 ? value.slice(0, 237) + "..." : value;
|
|
12369
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
12370
|
+
if (Array.isArray(value)) {
|
|
12371
|
+
if (level >= 2) return "[array:" + value.length + "]";
|
|
12372
|
+
return value.slice(0, 3).map((item) => compactJsonProbeSample(item, level + 1));
|
|
12373
|
+
}
|
|
12374
|
+
if (value && typeof value === "object") {
|
|
12375
|
+
const entries = Object.entries(value);
|
|
12376
|
+
if (level >= 2) return "[object:" + entries.length + " keys]";
|
|
12377
|
+
return Object.fromEntries(entries.slice(0, 8).map(([key, child]) => [key, compactJsonProbeSample(child, level + 1)]));
|
|
12378
|
+
}
|
|
12379
|
+
return String(value);
|
|
12380
|
+
}
|
|
12381
|
+
function attachJsonProbeObservedValue(result, value) {
|
|
12382
|
+
const type = jsonProbeValueType(value);
|
|
12383
|
+
if (type === "array" && Array.isArray(value)) {
|
|
12384
|
+
result.observed_length = value.length;
|
|
12385
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
12386
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
12387
|
+
return;
|
|
12388
|
+
}
|
|
12389
|
+
if (type === "object" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
12390
|
+
const keyCount = Object.keys(value).length;
|
|
12391
|
+
result.observed_key_count = keyCount;
|
|
12392
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
12393
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
12394
|
+
return;
|
|
12395
|
+
}
|
|
12396
|
+
result.observed = value;
|
|
12397
|
+
}
|
|
12195
12398
|
function jsonProbeDeepEqual(left, right) {
|
|
12196
12399
|
if (left === right) return true;
|
|
12197
12400
|
if (typeof left !== typeof right) return false;
|
|
@@ -12288,7 +12491,7 @@ function evaluateJsonProbeAssertion(root, assertion) {
|
|
|
12288
12491
|
exists: resolved.exists,
|
|
12289
12492
|
observed_type: resolved.exists ? jsonProbeValueType(resolved.value) : "missing",
|
|
12290
12493
|
};
|
|
12291
|
-
if (resolved.exists) result
|
|
12494
|
+
if (resolved.exists) attachJsonProbeObservedValue(result, resolved.value);
|
|
12292
12495
|
if (resolved.error) errors.push(resolved.error);
|
|
12293
12496
|
if (Object.hasOwn(assertion, "exists")) {
|
|
12294
12497
|
result.expected_exists = assertion.exists;
|
|
@@ -13101,6 +13304,7 @@ async function captureViewport(viewport) {
|
|
|
13101
13304
|
const frames = {};
|
|
13102
13305
|
const text_sequences = {};
|
|
13103
13306
|
const text_matches = {};
|
|
13307
|
+
const text_match_samples = {};
|
|
13104
13308
|
const http_statuses = {};
|
|
13105
13309
|
const link_statuses = {};
|
|
13106
13310
|
for (const check of profile.checks || []) {
|
|
@@ -13122,7 +13326,10 @@ async function captureViewport(viewport) {
|
|
|
13122
13326
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
13123
13327
|
}
|
|
13124
13328
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
13125
|
-
|
|
13329
|
+
const key = textKey(check);
|
|
13330
|
+
const sample = dom.body_text || dom.body_text_sample || "";
|
|
13331
|
+
text_matches[key] = textMatches(sample, check);
|
|
13332
|
+
text_match_samples[key] = textMatchSamples(sample, check);
|
|
13126
13333
|
}
|
|
13127
13334
|
if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
13128
13335
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
@@ -13199,6 +13406,7 @@ async function captureViewport(viewport) {
|
|
|
13199
13406
|
frames,
|
|
13200
13407
|
text_sequences,
|
|
13201
13408
|
text_matches,
|
|
13409
|
+
text_match_samples,
|
|
13202
13410
|
http_statuses,
|
|
13203
13411
|
link_statuses,
|
|
13204
13412
|
route_inventory: routeInventory,
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -8977,6 +8977,37 @@ function jsonValueType(value) {
|
|
|
8977
8977
|
if (typeof value === "string") return "string";
|
|
8978
8978
|
return "object";
|
|
8979
8979
|
}
|
|
8980
|
+
function compactJsonAssertionSample(value, depth = 0) {
|
|
8981
|
+
if (typeof value === "string") return value.length > 240 ? `${value.slice(0, 237)}...` : value;
|
|
8982
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return toJsonValue(value);
|
|
8983
|
+
if (Array.isArray(value)) {
|
|
8984
|
+
if (depth >= 2) return `[array:${value.length}]`;
|
|
8985
|
+
return value.slice(0, 3).map((item) => compactJsonAssertionSample(item, depth + 1));
|
|
8986
|
+
}
|
|
8987
|
+
if (isRecord2(value)) {
|
|
8988
|
+
const entries = Object.entries(value).slice(0, 8);
|
|
8989
|
+
if (depth >= 2) return `[object:${Object.keys(value).length} keys]`;
|
|
8990
|
+
return Object.fromEntries(entries.map(([key, child]) => [key, compactJsonAssertionSample(child, depth + 1)]));
|
|
8991
|
+
}
|
|
8992
|
+
return String(value);
|
|
8993
|
+
}
|
|
8994
|
+
function attachJsonAssertionObservedValue(result, value) {
|
|
8995
|
+
const type = jsonValueType(value);
|
|
8996
|
+
if (type === "array" && Array.isArray(value)) {
|
|
8997
|
+
result.observed_length = value.length;
|
|
8998
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
8999
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
9000
|
+
return;
|
|
9001
|
+
}
|
|
9002
|
+
if (type === "object" && isRecord2(value)) {
|
|
9003
|
+
const keyCount = Object.keys(value).length;
|
|
9004
|
+
result.observed_key_count = keyCount;
|
|
9005
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
9006
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
9007
|
+
return;
|
|
9008
|
+
}
|
|
9009
|
+
result.observed = toJsonValue(value);
|
|
9010
|
+
}
|
|
8980
9011
|
function deepJsonEqual(left, right) {
|
|
8981
9012
|
if (left === right) return true;
|
|
8982
9013
|
if (typeof left !== typeof right) return false;
|
|
@@ -9074,7 +9105,7 @@ function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
|
|
|
9074
9105
|
exists: resolved.exists,
|
|
9075
9106
|
observed_type: resolved.exists ? jsonValueType(resolved.value) : "missing"
|
|
9076
9107
|
};
|
|
9077
|
-
if (resolved.exists) result
|
|
9108
|
+
if (resolved.exists) attachJsonAssertionObservedValue(result, resolved.value);
|
|
9078
9109
|
if (resolved.error) errors.push(resolved.error);
|
|
9079
9110
|
if (hasOwn(assertion, "exists")) {
|
|
9080
9111
|
result.expected_exists = assertion.exists;
|
|
@@ -10167,6 +10198,10 @@ function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
|
10167
10198
|
ok: false,
|
|
10168
10199
|
exists: assertion.exists === true,
|
|
10169
10200
|
observed: hasOwn(assertion, "observed") ? toJsonValue(assertion.observed) : void 0,
|
|
10201
|
+
observed_sample: hasOwn(assertion, "observed_sample") ? toJsonValue(assertion.observed_sample) : void 0,
|
|
10202
|
+
observed_length: numberValue3(assertion.observed_length),
|
|
10203
|
+
observed_key_count: numberValue3(assertion.observed_key_count),
|
|
10204
|
+
observed_omitted_count: numberValue3(assertion.observed_omitted_count),
|
|
10170
10205
|
observed_type: stringValue5(assertion.observed_type) || "missing",
|
|
10171
10206
|
expected_exists: booleanValue(assertion.expected_exists),
|
|
10172
10207
|
equals: hasOwn(assertion, "equals") ? toJsonValue(assertion.equals) : void 0,
|
|
@@ -10552,6 +10587,49 @@ function matchText(sample, check) {
|
|
|
10552
10587
|
}
|
|
10553
10588
|
return sample.includes(check.text || "");
|
|
10554
10589
|
}
|
|
10590
|
+
function compactTextEvidenceSample(value) {
|
|
10591
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
10592
|
+
}
|
|
10593
|
+
function textSampleAroundMatch(sample, index, length) {
|
|
10594
|
+
if (index < 0) return void 0;
|
|
10595
|
+
const source = String(sample || "");
|
|
10596
|
+
const context = 120;
|
|
10597
|
+
const start = Math.max(0, index - context);
|
|
10598
|
+
const end = Math.min(source.length, index + Math.max(length, 1) + context);
|
|
10599
|
+
const prefix = start > 0 ? "..." : "";
|
|
10600
|
+
const suffix = end < source.length ? "..." : "";
|
|
10601
|
+
const compacted = compactTextEvidenceSample(`${prefix}${source.slice(start, end)}${suffix}`);
|
|
10602
|
+
return compacted ? compacted.slice(0, 240) : void 0;
|
|
10603
|
+
}
|
|
10604
|
+
function textMatchSamples(sample, check) {
|
|
10605
|
+
const source = String(sample || "");
|
|
10606
|
+
if (!source) return [];
|
|
10607
|
+
if (check.pattern) {
|
|
10608
|
+
try {
|
|
10609
|
+
const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
|
|
10610
|
+
const match = new RegExp(check.pattern, flags).exec(source);
|
|
10611
|
+
const sampleText2 = match ? textSampleAroundMatch(source, match.index, match[0]?.length || 1) : void 0;
|
|
10612
|
+
return sampleText2 ? [sampleText2] : [];
|
|
10613
|
+
} catch {
|
|
10614
|
+
return [];
|
|
10615
|
+
}
|
|
10616
|
+
}
|
|
10617
|
+
const text = check.text || "";
|
|
10618
|
+
if (!text) return [];
|
|
10619
|
+
const index = source.indexOf(text);
|
|
10620
|
+
const sampleText = textSampleAroundMatch(source, index, text.length);
|
|
10621
|
+
return sampleText ? [sampleText] : [];
|
|
10622
|
+
}
|
|
10623
|
+
function textCheckFailureSamples(viewport, check) {
|
|
10624
|
+
const key = textKey(check);
|
|
10625
|
+
const captured = viewport.text_match_samples?.[key] || [];
|
|
10626
|
+
const capturedSamples = captured.map((sample) => compactTextEvidenceSample(sample).slice(0, 240)).filter(Boolean);
|
|
10627
|
+
if (capturedSamples.length) return capturedSamples.slice(0, 3);
|
|
10628
|
+
const matchedSamples = textMatchSamples(viewport.body_text_sample || "", check);
|
|
10629
|
+
if (matchedSamples.length) return matchedSamples.slice(0, 3);
|
|
10630
|
+
const fallback = compactTextEvidenceSample(viewport.body_text_sample || "").slice(0, 240);
|
|
10631
|
+
return fallback ? [fallback] : [];
|
|
10632
|
+
}
|
|
10555
10633
|
function allowedMessageSample(input) {
|
|
10556
10634
|
if (!isRecord2(input)) return String(input || "");
|
|
10557
10635
|
const parts = [
|
|
@@ -10996,10 +11074,17 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
10996
11074
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
10997
11075
|
const key = textKey(check);
|
|
10998
11076
|
const expectedVisible = check.type === "text_visible";
|
|
10999
|
-
const
|
|
11077
|
+
const results = viewports.map((viewport) => {
|
|
11000
11078
|
const fromEvidence = viewport.text_matches?.[key];
|
|
11001
|
-
|
|
11079
|
+
const matched = typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
|
|
11080
|
+
const failedAgainstExpectation = matched !== expectedVisible;
|
|
11081
|
+
return {
|
|
11082
|
+
viewport: viewport.name,
|
|
11083
|
+
matched,
|
|
11084
|
+
samples: failedAgainstExpectation ? textCheckFailureSamples(viewport, check) : []
|
|
11085
|
+
};
|
|
11002
11086
|
});
|
|
11087
|
+
const matches = results.map((result) => result.matched);
|
|
11003
11088
|
const failed = matches.filter((matched) => matched !== expectedVisible).length;
|
|
11004
11089
|
return {
|
|
11005
11090
|
type: check.type,
|
|
@@ -11008,7 +11093,8 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
11008
11093
|
evidence: {
|
|
11009
11094
|
text: check.text || null,
|
|
11010
11095
|
pattern: check.pattern || null,
|
|
11011
|
-
matches
|
|
11096
|
+
matches,
|
|
11097
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
11012
11098
|
},
|
|
11013
11099
|
message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
|
|
11014
11100
|
};
|
|
@@ -11595,6 +11681,48 @@ function textMatches(sample, check) {
|
|
|
11595
11681
|
}
|
|
11596
11682
|
return String(sample || "").includes(check.text || "");
|
|
11597
11683
|
}
|
|
11684
|
+
function compactTextEvidenceSample(value) {
|
|
11685
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
11686
|
+
}
|
|
11687
|
+
function textSampleAroundMatch(sample, index, length) {
|
|
11688
|
+
if (index < 0) return undefined;
|
|
11689
|
+
const source = String(sample || "");
|
|
11690
|
+
const context = 120;
|
|
11691
|
+
const start = Math.max(0, index - context);
|
|
11692
|
+
const end = Math.min(source.length, index + Math.max(length, 1) + context);
|
|
11693
|
+
const prefix = start > 0 ? "..." : "";
|
|
11694
|
+
const suffix = end < source.length ? "..." : "";
|
|
11695
|
+
const compacted = compactTextEvidenceSample(prefix + source.slice(start, end) + suffix);
|
|
11696
|
+
return compacted ? compacted.slice(0, 240) : undefined;
|
|
11697
|
+
}
|
|
11698
|
+
function textMatchSamples(sample, check) {
|
|
11699
|
+
const source = String(sample || "");
|
|
11700
|
+
if (!source) return [];
|
|
11701
|
+
if (check.pattern) {
|
|
11702
|
+
try {
|
|
11703
|
+
const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
|
|
11704
|
+
const match = new RegExp(check.pattern, flags).exec(source);
|
|
11705
|
+
const sampleText = match ? textSampleAroundMatch(source, match.index, match[0] ? match[0].length : 1) : undefined;
|
|
11706
|
+
return sampleText ? [sampleText] : [];
|
|
11707
|
+
} catch { return []; }
|
|
11708
|
+
}
|
|
11709
|
+
const text = check.text || "";
|
|
11710
|
+
if (!text) return [];
|
|
11711
|
+
const sampleText = textSampleAroundMatch(source, source.indexOf(text), text.length);
|
|
11712
|
+
return sampleText ? [sampleText] : [];
|
|
11713
|
+
}
|
|
11714
|
+
function textCheckFailureSamples(viewport, check) {
|
|
11715
|
+
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
11716
|
+
const captured = viewport && viewport.text_match_samples && Array.isArray(viewport.text_match_samples[key]) ? viewport.text_match_samples[key] : [];
|
|
11717
|
+
const capturedSamples = captured
|
|
11718
|
+
.map((sample) => compactTextEvidenceSample(sample).slice(0, 240))
|
|
11719
|
+
.filter(Boolean);
|
|
11720
|
+
if (capturedSamples.length) return capturedSamples.slice(0, 3);
|
|
11721
|
+
const matchedSamples = textMatchSamples(viewport && viewport.body_text_sample || "", check);
|
|
11722
|
+
if (matchedSamples.length) return matchedSamples.slice(0, 3);
|
|
11723
|
+
const fallback = compactTextEvidenceSample(viewport && viewport.body_text_sample || "").slice(0, 240);
|
|
11724
|
+
return fallback ? [fallback] : [];
|
|
11725
|
+
}
|
|
11598
11726
|
function allowedMessageSample(input) {
|
|
11599
11727
|
if (!input || typeof input !== "object" || Array.isArray(input)) return String(input || "");
|
|
11600
11728
|
const parts = [
|
|
@@ -11797,6 +11925,10 @@ function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
|
11797
11925
|
ok: false,
|
|
11798
11926
|
exists: assertion.exists === true,
|
|
11799
11927
|
observed: Object.hasOwn(assertion, "observed") ? assertion.observed : undefined,
|
|
11928
|
+
observed_sample: Object.hasOwn(assertion, "observed_sample") ? assertion.observed_sample : undefined,
|
|
11929
|
+
observed_length: typeof assertion.observed_length === "number" && Number.isFinite(assertion.observed_length) ? assertion.observed_length : undefined,
|
|
11930
|
+
observed_key_count: typeof assertion.observed_key_count === "number" && Number.isFinite(assertion.observed_key_count) ? assertion.observed_key_count : undefined,
|
|
11931
|
+
observed_omitted_count: typeof assertion.observed_omitted_count === "number" && Number.isFinite(assertion.observed_omitted_count) ? assertion.observed_omitted_count : undefined,
|
|
11800
11932
|
observed_type: typeof assertion.observed_type === "string" && assertion.observed_type ? assertion.observed_type : "missing",
|
|
11801
11933
|
expected_exists: typeof assertion.expected_exists === "boolean" ? assertion.expected_exists : undefined,
|
|
11802
11934
|
equals: Object.hasOwn(assertion, "equals") ? assertion.equals : undefined,
|
|
@@ -12668,13 +12800,22 @@ function assessProfile(profile, evidence) {
|
|
|
12668
12800
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
12669
12801
|
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
12670
12802
|
const expectedVisible = check.type === "text_visible";
|
|
12671
|
-
const
|
|
12803
|
+
const results = checkViewports.map((viewport) => {
|
|
12804
|
+
const matched = viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check);
|
|
12805
|
+
const failedAgainstExpectation = matched !== expectedVisible;
|
|
12806
|
+
return {
|
|
12807
|
+
viewport: viewport.name,
|
|
12808
|
+
matched,
|
|
12809
|
+
samples: failedAgainstExpectation ? textCheckFailureSamples(viewport, check) : [],
|
|
12810
|
+
};
|
|
12811
|
+
});
|
|
12812
|
+
const matches = results.map((result) => result.matched);
|
|
12672
12813
|
const failed = matches.filter((matched) => matched !== expectedVisible).length;
|
|
12673
12814
|
checks.push({
|
|
12674
12815
|
type: check.type,
|
|
12675
12816
|
label: check.label || check.type,
|
|
12676
12817
|
status: failed ? "failed" : "passed",
|
|
12677
|
-
evidence: { text: check.text, pattern: check.pattern, matches },
|
|
12818
|
+
evidence: { text: check.text, pattern: check.pattern, matches, viewports: results },
|
|
12678
12819
|
message: failed ? "Text assertion failed in " + failed + " viewport(s)." : undefined,
|
|
12679
12820
|
});
|
|
12680
12821
|
continue;
|
|
@@ -12994,6 +13135,36 @@ function textMatches(sample, check) {
|
|
|
12994
13135
|
}
|
|
12995
13136
|
return String(sample || "").includes(check.text || "");
|
|
12996
13137
|
}
|
|
13138
|
+
function compactTextEvidenceSample(value) {
|
|
13139
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
13140
|
+
}
|
|
13141
|
+
function textSampleAroundMatch(sample, index, length) {
|
|
13142
|
+
if (index < 0) return undefined;
|
|
13143
|
+
const source = String(sample || "");
|
|
13144
|
+
const context = 120;
|
|
13145
|
+
const start = Math.max(0, index - context);
|
|
13146
|
+
const end = Math.min(source.length, index + Math.max(length, 1) + context);
|
|
13147
|
+
const prefix = start > 0 ? "..." : "";
|
|
13148
|
+
const suffix = end < source.length ? "..." : "";
|
|
13149
|
+
const compacted = compactTextEvidenceSample(prefix + source.slice(start, end) + suffix);
|
|
13150
|
+
return compacted ? compacted.slice(0, 240) : undefined;
|
|
13151
|
+
}
|
|
13152
|
+
function textMatchSamples(sample, check) {
|
|
13153
|
+
const source = String(sample || "");
|
|
13154
|
+
if (!source) return [];
|
|
13155
|
+
if (check.pattern) {
|
|
13156
|
+
try {
|
|
13157
|
+
const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
|
|
13158
|
+
const match = new RegExp(check.pattern, flags).exec(source);
|
|
13159
|
+
const sampleText = match ? textSampleAroundMatch(source, match.index, match[0] ? match[0].length : 1) : undefined;
|
|
13160
|
+
return sampleText ? [sampleText] : [];
|
|
13161
|
+
} catch { return []; }
|
|
13162
|
+
}
|
|
13163
|
+
const text = check.text || "";
|
|
13164
|
+
if (!text) return [];
|
|
13165
|
+
const sampleText = textSampleAroundMatch(source, source.indexOf(text), text.length);
|
|
13166
|
+
return sampleText ? [sampleText] : [];
|
|
13167
|
+
}
|
|
12997
13168
|
function profileCheckAppliesToViewport(check, viewport) {
|
|
12998
13169
|
if (!Array.isArray(check.viewports) || !check.viewports.length) return true;
|
|
12999
13170
|
return Boolean(viewport && viewport.name && check.viewports.includes(viewport.name));
|
|
@@ -14004,6 +14175,38 @@ function jsonProbeValueType(value) {
|
|
|
14004
14175
|
if (typeof value === "string") return "string";
|
|
14005
14176
|
return "object";
|
|
14006
14177
|
}
|
|
14178
|
+
function compactJsonProbeSample(value, depth) {
|
|
14179
|
+
const level = typeof depth === "number" ? depth : 0;
|
|
14180
|
+
if (typeof value === "string") return value.length > 240 ? value.slice(0, 237) + "..." : value;
|
|
14181
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
14182
|
+
if (Array.isArray(value)) {
|
|
14183
|
+
if (level >= 2) return "[array:" + value.length + "]";
|
|
14184
|
+
return value.slice(0, 3).map((item) => compactJsonProbeSample(item, level + 1));
|
|
14185
|
+
}
|
|
14186
|
+
if (value && typeof value === "object") {
|
|
14187
|
+
const entries = Object.entries(value);
|
|
14188
|
+
if (level >= 2) return "[object:" + entries.length + " keys]";
|
|
14189
|
+
return Object.fromEntries(entries.slice(0, 8).map(([key, child]) => [key, compactJsonProbeSample(child, level + 1)]));
|
|
14190
|
+
}
|
|
14191
|
+
return String(value);
|
|
14192
|
+
}
|
|
14193
|
+
function attachJsonProbeObservedValue(result, value) {
|
|
14194
|
+
const type = jsonProbeValueType(value);
|
|
14195
|
+
if (type === "array" && Array.isArray(value)) {
|
|
14196
|
+
result.observed_length = value.length;
|
|
14197
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
14198
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
14199
|
+
return;
|
|
14200
|
+
}
|
|
14201
|
+
if (type === "object" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
14202
|
+
const keyCount = Object.keys(value).length;
|
|
14203
|
+
result.observed_key_count = keyCount;
|
|
14204
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
14205
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
14206
|
+
return;
|
|
14207
|
+
}
|
|
14208
|
+
result.observed = value;
|
|
14209
|
+
}
|
|
14007
14210
|
function jsonProbeDeepEqual(left, right) {
|
|
14008
14211
|
if (left === right) return true;
|
|
14009
14212
|
if (typeof left !== typeof right) return false;
|
|
@@ -14100,7 +14303,7 @@ function evaluateJsonProbeAssertion(root, assertion) {
|
|
|
14100
14303
|
exists: resolved.exists,
|
|
14101
14304
|
observed_type: resolved.exists ? jsonProbeValueType(resolved.value) : "missing",
|
|
14102
14305
|
};
|
|
14103
|
-
if (resolved.exists) result
|
|
14306
|
+
if (resolved.exists) attachJsonProbeObservedValue(result, resolved.value);
|
|
14104
14307
|
if (resolved.error) errors.push(resolved.error);
|
|
14105
14308
|
if (Object.hasOwn(assertion, "exists")) {
|
|
14106
14309
|
result.expected_exists = assertion.exists;
|
|
@@ -14913,6 +15116,7 @@ async function captureViewport(viewport) {
|
|
|
14913
15116
|
const frames = {};
|
|
14914
15117
|
const text_sequences = {};
|
|
14915
15118
|
const text_matches = {};
|
|
15119
|
+
const text_match_samples = {};
|
|
14916
15120
|
const http_statuses = {};
|
|
14917
15121
|
const link_statuses = {};
|
|
14918
15122
|
for (const check of profile.checks || []) {
|
|
@@ -14934,7 +15138,10 @@ async function captureViewport(viewport) {
|
|
|
14934
15138
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
14935
15139
|
}
|
|
14936
15140
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
14937
|
-
|
|
15141
|
+
const key = textKey(check);
|
|
15142
|
+
const sample = dom.body_text || dom.body_text_sample || "";
|
|
15143
|
+
text_matches[key] = textMatches(sample, check);
|
|
15144
|
+
text_match_samples[key] = textMatchSamples(sample, check);
|
|
14938
15145
|
}
|
|
14939
15146
|
if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
|
|
14940
15147
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
@@ -15011,6 +15218,7 @@ async function captureViewport(viewport) {
|
|
|
15011
15218
|
frames,
|
|
15012
15219
|
text_sequences,
|
|
15013
15220
|
text_matches,
|
|
15221
|
+
text_match_samples,
|
|
15014
15222
|
http_statuses,
|
|
15015
15223
|
link_statuses,
|
|
15016
15224
|
route_inventory: routeInventory,
|