@riddledc/riddle-proof 0.7.97 → 0.7.98

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.
@@ -29,6 +29,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
29
29
  "frame_no_horizontal_overflow",
30
30
  "text_visible",
31
31
  "text_absent",
32
+ "http_status",
32
33
  "link_status",
33
34
  "artifact_link_status",
34
35
  "route_inventory",
@@ -778,16 +779,27 @@ function normalizeCheck(input, index) {
778
779
  if (type === "route_inventory" && !expectedRoutes?.length) {
779
780
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
780
781
  }
782
+ const isHttpStatusCheck = type === "http_status";
781
783
  const isLinkStatusCheck = type === "link_status" || type === "artifact_link_status";
782
- const expectedStatus = isLinkStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
783
- const allowedStatuses = isLinkStatusCheck ? normalizeHttpStatuses(
784
+ const isStatusCheck = isHttpStatusCheck || isLinkStatusCheck;
785
+ const requestUrl = stringFromOwn(input, "url", "endpoint_url", "endpointUrl", "endpoint", "request_url", "requestUrl", "path");
786
+ if (isHttpStatusCheck && !requestUrl) {
787
+ throw new Error(`checks[${index}] http_status requires url.`);
788
+ }
789
+ const method = stringFromOwn(input, "method", "http_method", "httpMethod")?.toUpperCase();
790
+ if (isHttpStatusCheck && method && !/^[A-Z]+$/.test(method)) {
791
+ throw new Error(`checks[${index}] http_status method must contain only letters.`);
792
+ }
793
+ const hasBodyJson = hasOwn(input, "body_json") || hasOwn(input, "bodyJson") || hasOwn(input, "json");
794
+ const expectedStatus = isStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
795
+ const allowedStatuses = isStatusCheck ? normalizeHttpStatuses(
784
796
  input.allowed_statuses ?? input.allowedStatuses ?? input.expected_statuses ?? input.expectedStatuses,
785
797
  `checks[${index}] allowed_statuses`
786
798
  ) : void 0;
787
799
  const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
788
- const minBytes = isLinkStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
789
- const expectedContentType = isLinkStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
790
- const allowedContentTypes = isLinkStatusCheck ? normalizeStringList(
800
+ const minBytes = isStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
801
+ const expectedContentType = isStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
802
+ const allowedContentTypes = isStatusCheck ? normalizeStringList(
791
803
  input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
792
804
  `checks[${index}] allowed_content_types`
793
805
  ) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
@@ -808,6 +820,11 @@ function normalizeCheck(input, index) {
808
820
  expected_url: expectedUrl,
809
821
  expected_routes: expectedRoutes,
810
822
  selector: stringValue(input.selector),
823
+ url: isHttpStatusCheck ? requestUrl : void 0,
824
+ method: isHttpStatusCheck ? method || "GET" : void 0,
825
+ headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
826
+ body: isHttpStatusCheck ? stringValue(input.body) : void 0,
827
+ body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
811
828
  expected_texts: expectedTexts,
812
829
  link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
813
830
  source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
@@ -831,7 +848,7 @@ function normalizeCheck(input, index) {
831
848
  max_links: maxLinks,
832
849
  same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
833
850
  dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
834
- require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
851
+ require_nonzero_bytes: isStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
835
852
  min_bytes: minBytes,
836
853
  allowed_content_types: allowedContentTypes,
837
854
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
@@ -930,14 +947,32 @@ function selectorKey(check) {
930
947
  function linkStatusSelector(check) {
931
948
  return check.selector || check.link_selector || "a[href]";
932
949
  }
933
- function linkStatusAllowedStatuses(check) {
950
+ function httpStatusRequestUrl(check, baseUrl) {
951
+ const rawUrl = check.url || "";
952
+ if (!rawUrl) return "";
953
+ try {
954
+ return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
955
+ } catch {
956
+ return rawUrl;
957
+ }
958
+ }
959
+ function httpStatusMethod(check) {
960
+ return (check.method || "GET").toUpperCase();
961
+ }
962
+ function httpStatusKey(check, baseUrl) {
963
+ return `${httpStatusMethod(check)} ${httpStatusRequestUrl(check, baseUrl)}`;
964
+ }
965
+ function httpStatusAllowedStatuses(check) {
934
966
  if (check.allowed_statuses?.length) return check.allowed_statuses;
935
967
  if (check.expected_status !== void 0) return [check.expected_status];
936
968
  return void 0;
937
969
  }
938
- function linkStatusIsAllowed(status, check) {
970
+ function linkStatusAllowedStatuses(check) {
971
+ return httpStatusAllowedStatuses(check);
972
+ }
973
+ function httpStatusIsAllowed(status, check) {
939
974
  if (status === void 0) return false;
940
- const allowed = linkStatusAllowedStatuses(check);
975
+ const allowed = httpStatusAllowedStatuses(check);
941
976
  return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
942
977
  }
943
978
  function linkStatusObservedBytes(result) {
@@ -963,7 +998,7 @@ function linkStatusContentTypeOk(result, check) {
963
998
  }
964
999
  function linkStatusResultOk(result, check) {
965
1000
  const status = numberValue(result.status);
966
- if (!linkStatusIsAllowed(status, check)) return false;
1001
+ if (!httpStatusIsAllowed(status, check)) return false;
967
1002
  if (stringValue(result.error)) return false;
968
1003
  if (result.ok === false) return false;
969
1004
  if (!linkStatusContentTypeOk(result, check)) return false;
@@ -977,6 +1012,54 @@ function linkStatusResultOk(result, check) {
977
1012
  }
978
1013
  return true;
979
1014
  }
1015
+ function httpStatusEvidenceForCheck(viewport, check) {
1016
+ const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
1017
+ return isRecord(evidence) ? evidence : void 0;
1018
+ }
1019
+ function summarizeHttpStatusEvidence(viewport, check) {
1020
+ const key = httpStatusKey(check, viewport.url);
1021
+ const statusEvidence = httpStatusEvidenceForCheck(viewport, check);
1022
+ if (!statusEvidence) {
1023
+ return {
1024
+ viewport: viewport.name,
1025
+ key,
1026
+ url: httpStatusRequestUrl(check, viewport.url),
1027
+ method: httpStatusMethod(check),
1028
+ ok: false,
1029
+ status: null,
1030
+ failures: [{ code: "http_status_evidence_missing" }]
1031
+ };
1032
+ }
1033
+ const failures = [];
1034
+ if (!linkStatusResultOk(statusEvidence, check)) {
1035
+ failures.push({
1036
+ code: "http_status_failed",
1037
+ url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
1038
+ status: numberValue(statusEvidence.status) ?? null,
1039
+ method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
1040
+ error: stringValue(statusEvidence.error) ?? null,
1041
+ content_type: stringValue(statusEvidence.content_type) ?? null,
1042
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1043
+ allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
1044
+ min_bytes: check.min_bytes ?? null,
1045
+ allowed_content_types: check.allowed_content_types ?? null
1046
+ });
1047
+ }
1048
+ return {
1049
+ viewport: viewport.name,
1050
+ key,
1051
+ url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
1052
+ method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
1053
+ status: numberValue(statusEvidence.status) ?? null,
1054
+ status_text: stringValue(statusEvidence.status_text) ?? null,
1055
+ ok: failures.length === 0,
1056
+ error: stringValue(statusEvidence.error) ?? null,
1057
+ content_type: stringValue(statusEvidence.content_type) ?? null,
1058
+ content_length: numberValue(statusEvidence.content_length) ?? null,
1059
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1060
+ failures
1061
+ };
1062
+ }
980
1063
  function linkStatusEvidenceForCheck(viewport, check) {
981
1064
  const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
982
1065
  return isRecord(evidence) ? evidence : void 0;
@@ -1567,6 +1650,28 @@ function assessCheckFromEvidence(check, evidence) {
1567
1650
  message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
1568
1651
  };
1569
1652
  }
1653
+ if (check.type === "http_status") {
1654
+ const url = httpStatusRequestUrl(check, evidence.target_url);
1655
+ const method = httpStatusMethod(check);
1656
+ const summaries = viewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
1657
+ const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
1658
+ return {
1659
+ type: check.type,
1660
+ label: checkLabel(check),
1661
+ status: failed.length ? "failed" : "passed",
1662
+ evidence: {
1663
+ url,
1664
+ method,
1665
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
1666
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
1667
+ min_bytes: check.min_bytes ?? null,
1668
+ allowed_content_types: check.allowed_content_types ?? null,
1669
+ viewports: summaries.map((summary) => toJsonValue(summary)),
1670
+ failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
1671
+ },
1672
+ message: failed.length ? `HTTP status failed in ${failed.length} viewport(s).` : void 0
1673
+ };
1674
+ }
1570
1675
  if (check.type === "link_status" || check.type === "artifact_link_status") {
1571
1676
  const selector = linkStatusSelector(check);
1572
1677
  const summaries = viewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
@@ -2176,16 +2281,37 @@ function textOrderMatch(texts, expectedTexts) {
2176
2281
  function linkStatusSelector(check) {
2177
2282
  return check.selector || check.link_selector || "a[href]";
2178
2283
  }
2179
- function linkStatusAllowedStatuses(check) {
2284
+ function httpStatusRequestUrl(check, baseUrl) {
2285
+ const rawUrl = check.url || "";
2286
+ if (!rawUrl) return "";
2287
+ try {
2288
+ return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
2289
+ } catch {
2290
+ return rawUrl;
2291
+ }
2292
+ }
2293
+ function httpStatusMethod(check) {
2294
+ return String(check.method || "GET").toUpperCase();
2295
+ }
2296
+ function httpStatusKey(check, baseUrl) {
2297
+ return httpStatusMethod(check) + " " + httpStatusRequestUrl(check, baseUrl);
2298
+ }
2299
+ function httpStatusAllowedStatuses(check) {
2180
2300
  if (Array.isArray(check.allowed_statuses) && check.allowed_statuses.length) return check.allowed_statuses;
2181
2301
  if (typeof check.expected_status === "number" && Number.isFinite(check.expected_status)) return [check.expected_status];
2182
2302
  return undefined;
2183
2303
  }
2184
- function linkStatusIsAllowed(status, check) {
2304
+ function linkStatusAllowedStatuses(check) {
2305
+ return httpStatusAllowedStatuses(check);
2306
+ }
2307
+ function httpStatusIsAllowed(status, check) {
2185
2308
  if (typeof status !== "number" || !Number.isFinite(status)) return false;
2186
- const allowed = linkStatusAllowedStatuses(check);
2309
+ const allowed = httpStatusAllowedStatuses(check);
2187
2310
  return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
2188
2311
  }
2312
+ function linkStatusIsAllowed(status, check) {
2313
+ return httpStatusIsAllowed(status, check);
2314
+ }
2189
2315
  function linkStatusObservedBytes(result) {
2190
2316
  const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
2191
2317
  const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
@@ -2210,7 +2336,7 @@ function linkStatusContentTypeOk(result, check) {
2210
2336
  }
2211
2337
  function linkStatusResultOk(result, check) {
2212
2338
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
2213
- if (!linkStatusIsAllowed(result.status, check)) return false;
2339
+ if (!httpStatusIsAllowed(result.status, check)) return false;
2214
2340
  if (typeof result.error === "string" && result.error.trim()) return false;
2215
2341
  if (result.ok === false) return false;
2216
2342
  if (!linkStatusContentTypeOk(result, check)) return false;
@@ -2224,6 +2350,50 @@ function linkStatusResultOk(result, check) {
2224
2350
  }
2225
2351
  return true;
2226
2352
  }
2353
+ function summarizeHttpStatusEvidence(viewport, check) {
2354
+ const key = httpStatusKey(check, viewport && viewport.url);
2355
+ const statusEvidence = viewport && viewport.http_statuses && viewport.http_statuses[key];
2356
+ if (!statusEvidence || typeof statusEvidence !== "object" || Array.isArray(statusEvidence)) {
2357
+ return {
2358
+ viewport: viewport && viewport.name,
2359
+ key,
2360
+ url: httpStatusRequestUrl(check, viewport && viewport.url),
2361
+ method: httpStatusMethod(check),
2362
+ ok: false,
2363
+ status: null,
2364
+ failures: [{ code: "http_status_evidence_missing" }],
2365
+ };
2366
+ }
2367
+ const failures = [];
2368
+ if (!linkStatusResultOk(statusEvidence, check)) {
2369
+ failures.push({
2370
+ code: "http_status_failed",
2371
+ url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
2372
+ status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
2373
+ method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
2374
+ error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
2375
+ content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
2376
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
2377
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
2378
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
2379
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
2380
+ });
2381
+ }
2382
+ return {
2383
+ viewport: viewport && viewport.name,
2384
+ key,
2385
+ url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
2386
+ method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
2387
+ status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
2388
+ status_text: typeof statusEvidence.status_text === "string" ? statusEvidence.status_text : null,
2389
+ ok: failures.length === 0,
2390
+ error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
2391
+ content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
2392
+ content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
2393
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
2394
+ failures,
2395
+ };
2396
+ }
2227
2397
  function summarizeLinkStatusEvidence(viewport, check) {
2228
2398
  const selector = linkStatusSelector(check);
2229
2399
  const linkEvidence = viewport && viewport.link_statuses && viewport.link_statuses[selector];
@@ -2979,6 +3149,31 @@ function assessProfile(profile, evidence) {
2979
3149
  });
2980
3150
  continue;
2981
3151
  }
3152
+ if (check.type === "http_status") {
3153
+ const url = httpStatusRequestUrl(check, evidence.target_url);
3154
+ const method = httpStatusMethod(check);
3155
+ const summaries = checkViewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
3156
+ const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
3157
+ checks.push({
3158
+ type: check.type,
3159
+ label: check.label || check.type,
3160
+ status: failed.length ? "failed" : "passed",
3161
+ evidence: {
3162
+ url,
3163
+ method,
3164
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
3165
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
3166
+ min_bytes: check.min_bytes ?? null,
3167
+ allowed_content_types: check.allowed_content_types ?? null,
3168
+ viewports: summaries,
3169
+ failures: failed.flatMap((summary) => Array.isArray(summary.failures)
3170
+ ? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
3171
+ : []),
3172
+ },
3173
+ message: failed.length ? "HTTP status failed in " + failed.length + " viewport(s)." : undefined,
3174
+ });
3175
+ continue;
3176
+ }
2982
3177
  if (check.type === "link_status" || check.type === "artifact_link_status") {
2983
3178
  const selector = linkStatusSelector(check);
2984
3179
  const summaries = checkViewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
@@ -4176,6 +4371,65 @@ function linkProbeResponseFields(response, method) {
4176
4371
  content_length: contentLength,
4177
4372
  };
4178
4373
  }
4374
+ async function collectHttpStatus(check) {
4375
+ const url = httpStatusRequestUrl(check, page.url() || targetUrl);
4376
+ const method = httpStatusMethod(check);
4377
+ const headers = check.headers && typeof check.headers === "object" && !Array.isArray(check.headers)
4378
+ ? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim()))
4379
+ : {};
4380
+ let body;
4381
+ if (check.body_json !== undefined) {
4382
+ body = JSON.stringify(check.body_json);
4383
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
4384
+ } else if (typeof check.body === "string") {
4385
+ body = check.body;
4386
+ }
4387
+ const options = {
4388
+ method,
4389
+ redirect: "follow",
4390
+ cache: "no-store",
4391
+ headers,
4392
+ };
4393
+ if (body !== undefined && method !== "GET" && method !== "HEAD") options.body = body;
4394
+ const result = {
4395
+ version: "riddle-proof.http-status.v1",
4396
+ url,
4397
+ method,
4398
+ status: null,
4399
+ ok: false,
4400
+ error: null,
4401
+ request_body_bytes: typeof body === "string" ? body.length : 0,
4402
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
4403
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
4404
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
4405
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
4406
+ };
4407
+ try {
4408
+ const response = await fetch(url, options);
4409
+ Object.assign(result, linkProbeResponseFields(response, method));
4410
+ result.url = url;
4411
+ result.status_text = response.statusText || "";
4412
+ const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes));
4413
+ if (shouldReadBody) {
4414
+ try {
4415
+ const buffer = await response.arrayBuffer();
4416
+ result.bytes = buffer.byteLength;
4417
+ } catch (error) {
4418
+ result.error = String(error && error.message ? error.message : error).slice(0, 500);
4419
+ }
4420
+ }
4421
+ result.ok = linkProbeAllowed(result.status, check)
4422
+ && linkProbeContentTypeAllowed(result, check)
4423
+ && (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
4424
+ && (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
4425
+ && !result.error;
4426
+ return result;
4427
+ } catch (error) {
4428
+ result.error = String(error && error.message ? error.message : error).slice(0, 500);
4429
+ result.ok = false;
4430
+ return result;
4431
+ }
4432
+ }
4179
4433
  async function probeLinkStatus(candidate, check) {
4180
4434
  const requireNonzeroBytes = check.require_nonzero_bytes === true;
4181
4435
  const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
@@ -4868,6 +5122,7 @@ async function captureViewport(viewport) {
4868
5122
  const frames = {};
4869
5123
  const text_sequences = {};
4870
5124
  const text_matches = {};
5125
+ const http_statuses = {};
4871
5126
  const link_statuses = {};
4872
5127
  for (const check of profile.checks || []) {
4873
5128
  if (!profileCheckAppliesToViewport(check, viewport)) continue;
@@ -4894,6 +5149,10 @@ async function captureViewport(viewport) {
4894
5149
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
4895
5150
  frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
4896
5151
  }
5152
+ if (check.type === "http_status") {
5153
+ const key = httpStatusKey(check, page.url() || targetUrl);
5154
+ http_statuses[key] = http_statuses[key] || await collectHttpStatus(check);
5155
+ }
4897
5156
  if (check.type === "link_status" || check.type === "artifact_link_status") {
4898
5157
  const selector = linkStatusSelector(check);
4899
5158
  link_statuses[selector] = link_statuses[selector] || await collectLinkStatus(check);
@@ -4961,6 +5220,7 @@ async function captureViewport(viewport) {
4961
5220
  frames,
4962
5221
  text_sequences,
4963
5222
  text_matches,
5223
+ http_statuses,
4964
5224
  link_statuses,
4965
5225
  route_inventory: routeInventory,
4966
5226
  setup_action_results: setupActionResults,
@@ -5010,6 +5270,19 @@ function buildProfileEvidence(currentViewports) {
5010
5270
  ),
5011
5271
  })),
5012
5272
  })),
5273
+ http_status: currentViewports
5274
+ .filter((viewport) => viewport.http_statuses && Object.keys(viewport.http_statuses).length)
5275
+ .map((viewport) => ({
5276
+ viewport: viewport.name,
5277
+ requests: Object.entries(viewport.http_statuses || {}).map(([key, statusSet]) => ({
5278
+ key,
5279
+ url: statusSet && statusSet.url,
5280
+ method: statusSet && statusSet.method,
5281
+ status: statusSet && statusSet.status,
5282
+ ok: statusSet && statusSet.ok === true,
5283
+ error: statusSet && statusSet.error,
5284
+ })),
5285
+ })),
5013
5286
  link_status: currentViewports
5014
5287
  .filter((viewport) => viewport.link_statuses && Object.keys(viewport.link_statuses).length)
5015
5288
  .map((viewport) => ({