@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/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.observed = toJsonValue(resolved.value);
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 matches = viewports.map((viewport) => {
9281
+ const results = viewports.map((viewport) => {
9204
9282
  const fromEvidence = viewport.text_matches?.[key];
9205
- return typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
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 matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
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.observed = resolved.value;
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
- text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
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
@@ -12,7 +12,7 @@ import {
12
12
  profileStatusExitCode,
13
13
  resolveRiddleProofProfileTargetUrl,
14
14
  resolveRiddleProofProfileTimeoutSec
15
- } from "./chunk-JLINSUKO.js";
15
+ } from "./chunk-TDK6WFH3.js";
16
16
  import {
17
17
  createRiddleApiClient,
18
18
  parseRiddleViewport
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.observed = toJsonValue(resolved.value);
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 matches = viewports.map((viewport) => {
11077
+ const results = viewports.map((viewport) => {
11000
11078
  const fromEvidence = viewport.text_matches?.[key];
11001
- return typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
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 matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
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.observed = resolved.value;
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
- text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
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,