@riddledc/riddle-proof 0.7.43 → 0.7.45

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/index.cjs CHANGED
@@ -8967,6 +8967,15 @@ function normalizeNetworkMock(input, index) {
8967
8967
  const payload = normalizeNetworkMockResponsePayload(input, `target.network_mocks[${index}]`);
8968
8968
  const responsesInput = input.responses ?? input.sequence;
8969
8969
  const responses = normalizeNetworkMockResponses(responsesInput, index, payload);
8970
+ const requestBodyContains = normalizeStringList(
8971
+ input.request_body_contains ?? input.requestBodyContains ?? input.body_contains ?? input.bodyContains,
8972
+ `target.network_mocks[${index}].request_body_contains`
8973
+ );
8974
+ const requestBodyPatterns = normalizeStringList(
8975
+ input.request_body_patterns ?? input.requestBodyPatterns ?? input.body_patterns ?? input.bodyPatterns,
8976
+ `target.network_mocks[${index}].request_body_patterns`
8977
+ );
8978
+ validateRegexPatterns(requestBodyPatterns, `target.network_mocks[${index}].request_body_patterns`);
8970
8979
  const requiredHitCount = numberValue3(
8971
8980
  input.required_hit_count ?? input.requiredHitCount ?? input.required_hits ?? input.requiredHits ?? input.min_hits ?? input.minHits
8972
8981
  );
@@ -8981,7 +8990,10 @@ function normalizeNetworkMock(input, index) {
8981
8990
  responses,
8982
8991
  repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
8983
8992
  required_hit_count: requiredHitCount,
8984
- required: input.required === false ? false : true
8993
+ required: input.required === false ? false : true,
8994
+ capture_request_body: input.capture_request_body === true || input.captureRequestBody === true || Boolean(requestBodyContains?.length) || Boolean(requestBodyPatterns?.length),
8995
+ request_body_contains: requestBodyContains,
8996
+ request_body_patterns: requestBodyPatterns
8985
8997
  };
8986
8998
  }
8987
8999
  function normalizeNetworkMockResponsePayload(input, label, defaults = {}) {
@@ -9064,6 +9076,16 @@ function normalizeStringList(value, label) {
9064
9076
  if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
9065
9077
  return values;
9066
9078
  }
9079
+ function validateRegexPatterns(patterns, label) {
9080
+ for (const pattern of patterns || []) {
9081
+ try {
9082
+ new RegExp(pattern);
9083
+ } catch (error) {
9084
+ const message = error instanceof Error ? error.message : String(error);
9085
+ throw new Error(`${label} contains invalid regex ${JSON.stringify(pattern)}: ${message}`);
9086
+ }
9087
+ }
9088
+ }
9067
9089
  function normalizeCheck(input, index) {
9068
9090
  if (!isRecord2(input)) throw new Error(`checks[${index}] must be an object.`);
9069
9091
  const type = stringValue5(input.type);
@@ -9116,6 +9138,7 @@ function normalizeCheck(input, index) {
9116
9138
  text: stringValue5(input.text),
9117
9139
  pattern: stringValue5(input.pattern),
9118
9140
  flags: stringValue5(input.flags),
9141
+ viewports: normalizeStringList(input.viewports ?? input.viewport_names ?? input.viewportNames, `checks[${index}] viewports`),
9119
9142
  allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
9120
9143
  allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
9121
9144
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
@@ -9354,15 +9377,22 @@ function successfulRoute(route, targetUrl) {
9354
9377
  const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
9355
9378
  return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
9356
9379
  }
9380
+ function viewportsForCheck(check, viewports) {
9381
+ if (!check.viewports?.length) return viewports;
9382
+ const names = new Set(check.viewports);
9383
+ return viewports.filter((viewport) => names.has(viewport.name));
9384
+ }
9357
9385
  function assessCheckFromEvidence(check, evidence) {
9358
- const viewports = evidence.viewports || [];
9386
+ const viewports = viewportsForCheck(check, evidence.viewports || []);
9359
9387
  if (!viewports.length) {
9360
9388
  return {
9361
9389
  type: check.type,
9362
9390
  label: checkLabel(check),
9363
9391
  status: "failed",
9364
- evidence: {},
9365
- message: "No viewport evidence was captured."
9392
+ evidence: {
9393
+ expected_viewports: check.viewports || []
9394
+ },
9395
+ message: check.viewports?.length ? `No matching viewport evidence was captured for ${check.viewports.join(", ")}.` : "No viewport evidence was captured."
9366
9396
  };
9367
9397
  }
9368
9398
  if (check.type === "route_loaded") {
@@ -9726,6 +9756,17 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
9726
9756
  reason: event.reason ?? event.error ?? "mock_failed"
9727
9757
  });
9728
9758
  }
9759
+ if (event.request_body_matches === false) {
9760
+ failed.push({
9761
+ label: event.label ?? null,
9762
+ url: event.url ?? null,
9763
+ method: event.method ?? null,
9764
+ reason: "request_body_mismatch",
9765
+ request_body_failures: event.request_body_failures ?? [],
9766
+ request_body_length: event.request_body_length ?? null,
9767
+ request_body_sample: event.request_body_sample ?? null
9768
+ });
9769
+ }
9729
9770
  }
9730
9771
  return {
9731
9772
  type: "network_mocks_succeeded",
@@ -9967,6 +10008,11 @@ function frameTextSample(frame) {
9967
10008
  frame && frame.text,
9968
10009
  ].map((part) => String(part || "")).filter(Boolean).join(" ");
9969
10010
  }
10011
+ function viewportsForCheck(check, viewports) {
10012
+ if (!Array.isArray(check.viewports) || !check.viewports.length) return viewports;
10013
+ const names = new Set(check.viewports);
10014
+ return viewports.filter((viewport) => names.has(viewport.name));
10015
+ }
9970
10016
  function summarizeRouteInventory(viewport, inventory) {
9971
10017
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
9972
10018
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -10093,6 +10139,17 @@ function assessProfile(profile, evidence) {
10093
10139
  reason: event.reason || event.error || "mock_failed",
10094
10140
  });
10095
10141
  }
10142
+ if (event && event.request_body_matches === false) {
10143
+ failed.push({
10144
+ label: event.label || null,
10145
+ url: event.url || null,
10146
+ method: event.method || null,
10147
+ reason: "request_body_mismatch",
10148
+ request_body_failures: event.request_body_failures || [],
10149
+ request_body_length: event.request_body_length || null,
10150
+ request_body_sample: event.request_body_sample || null,
10151
+ });
10152
+ }
10096
10153
  }
10097
10154
  const hitsByLabel = {};
10098
10155
  const requiredHitsByLabel = {};
@@ -10157,9 +10214,22 @@ function assessProfile(profile, evidence) {
10157
10214
  });
10158
10215
  }
10159
10216
  for (const check of profile.checks || []) {
10217
+ const checkViewports = viewportsForCheck(check, viewports);
10218
+ if (!checkViewports.length) {
10219
+ checks.push({
10220
+ type: check.type,
10221
+ label: check.label || check.type,
10222
+ status: "failed",
10223
+ evidence: { expected_viewports: check.viewports || [] },
10224
+ message: Array.isArray(check.viewports) && check.viewports.length
10225
+ ? "No matching viewport evidence was captured for " + check.viewports.join(", ") + "."
10226
+ : "No viewport evidence was captured.",
10227
+ });
10228
+ continue;
10229
+ }
10160
10230
  if (check.type === "route_loaded") {
10161
10231
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
10162
- const failed = viewports.filter((viewport) => {
10232
+ const failed = checkViewports.filter((viewport) => {
10163
10233
  const route = { ...(viewport.route || {}), expected_path: expectedPath };
10164
10234
  route.matched = routePathMatches(route.observed, expectedPath, evidence.target_url) || route.matched;
10165
10235
  return !routeOk(route, evidence.target_url);
@@ -10170,8 +10240,8 @@ function assessProfile(profile, evidence) {
10170
10240
  status: failed.length ? "failed" : "passed",
10171
10241
  evidence: {
10172
10242
  expected_path: expectedPath,
10173
- observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
10174
- http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
10243
+ observed_paths: checkViewports.map((viewport) => viewport.route && viewport.route.observed),
10244
+ http_statuses: checkViewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
10175
10245
  },
10176
10246
  message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
10177
10247
  });
@@ -10179,24 +10249,24 @@ function assessProfile(profile, evidence) {
10179
10249
  }
10180
10250
  if (check.type === "selector_visible") {
10181
10251
  const selector = check.selector || "";
10182
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
10252
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
10183
10253
  checks.push({
10184
10254
  type: check.type,
10185
10255
  label: check.label || check.type,
10186
10256
  status: failed.length ? "failed" : "passed",
10187
- evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
10257
+ evidence: { selector, visible_counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
10188
10258
  message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
10189
10259
  });
10190
10260
  continue;
10191
10261
  }
10192
10262
  if (check.type === "selector_absent") {
10193
10263
  const selector = check.selector || "";
10194
- const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
10264
+ const failed = checkViewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
10195
10265
  checks.push({
10196
10266
  type: check.type,
10197
10267
  label: check.label || check.type,
10198
10268
  status: failed.length ? "failed" : "passed",
10199
- evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10269
+ evidence: { selector, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10200
10270
  message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
10201
10271
  });
10202
10272
  continue;
@@ -10204,12 +10274,12 @@ function assessProfile(profile, evidence) {
10204
10274
  if (check.type === "selector_count_at_least") {
10205
10275
  const selector = check.selector || "";
10206
10276
  const minCount = check.min_count == null ? 1 : check.min_count;
10207
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
10277
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
10208
10278
  checks.push({
10209
10279
  type: check.type,
10210
10280
  label: check.label || check.type,
10211
10281
  status: failed.length ? "failed" : "passed",
10212
- evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10282
+ evidence: { selector, min_count: minCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10213
10283
  message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
10214
10284
  });
10215
10285
  continue;
@@ -10217,12 +10287,12 @@ function assessProfile(profile, evidence) {
10217
10287
  if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
10218
10288
  const selector = check.selector || "";
10219
10289
  const expectedCount = check.expected_count == null ? 0 : check.expected_count;
10220
- const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
10290
+ const failed = checkViewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
10221
10291
  checks.push({
10222
10292
  type: check.type,
10223
10293
  label: check.label || check.type,
10224
10294
  status: failed.length ? "failed" : "passed",
10225
- evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10295
+ evidence: { selector, expected_count: expectedCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10226
10296
  message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
10227
10297
  });
10228
10298
  continue;
@@ -10230,7 +10300,7 @@ function assessProfile(profile, evidence) {
10230
10300
  if (check.type === "selector_text_order") {
10231
10301
  const selector = check.selector || "";
10232
10302
  const expectedTexts = check.expected_texts || [];
10233
- const results = viewports.map((viewport) => {
10303
+ const results = checkViewports.map((viewport) => {
10234
10304
  const texts = textSequenceForCheck(viewport, check);
10235
10305
  const match = textOrderMatch(texts, expectedTexts);
10236
10306
  return {
@@ -10252,7 +10322,7 @@ function assessProfile(profile, evidence) {
10252
10322
  }
10253
10323
  if (check.type === "frame_text_visible") {
10254
10324
  const selector = check.selector || "";
10255
- const results = viewports.map((viewport) => {
10325
+ const results = checkViewports.map((viewport) => {
10256
10326
  const frames = frameEvidenceForSelector(viewport, selector);
10257
10327
  const matches = frames
10258
10328
  .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
@@ -10279,7 +10349,7 @@ function assessProfile(profile, evidence) {
10279
10349
  if (check.type === "frame_no_horizontal_overflow") {
10280
10350
  const selector = check.selector || "";
10281
10351
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
10282
- const results = viewports.map((viewport) => {
10352
+ const results = checkViewports.map((viewport) => {
10283
10353
  const frames = frameEvidenceForSelector(viewport, selector);
10284
10354
  const frameOverflows = frames.map((frame, index) => ({
10285
10355
  index,
@@ -10309,7 +10379,7 @@ function assessProfile(profile, evidence) {
10309
10379
  if (check.type === "text_visible" || check.type === "text_absent") {
10310
10380
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
10311
10381
  const expectedVisible = check.type === "text_visible";
10312
- const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
10382
+ const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
10313
10383
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
10314
10384
  checks.push({
10315
10385
  type: check.type,
@@ -10321,7 +10391,7 @@ function assessProfile(profile, evidence) {
10321
10391
  continue;
10322
10392
  }
10323
10393
  if (check.type === "route_inventory") {
10324
- const inventories = viewports
10394
+ const inventories = checkViewports
10325
10395
  .map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory }))
10326
10396
  .filter((item) => item.inventory && typeof item.inventory === "object");
10327
10397
  if (!inventories.length) {
@@ -10373,7 +10443,7 @@ function assessProfile(profile, evidence) {
10373
10443
  }
10374
10444
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
10375
10445
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
10376
- const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
10446
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? checkViewports.filter((viewport) => viewport.width <= 820) : checkViewports;
10377
10447
  if (!applicable.length) {
10378
10448
  checks.push({
10379
10449
  type: check.type,
@@ -10528,6 +10598,52 @@ function compactSetupResultText(value) {
10528
10598
  if (text.length <= 500) return text;
10529
10599
  return text.slice(0, 500) + "... (" + text.length + " chars)";
10530
10600
  }
10601
+ function compactNetworkMockRequestBody(value) {
10602
+ const text = String(value || "").replace(/\s+/g, " ").trim();
10603
+ if (text.length <= 1000) return text;
10604
+ return text.slice(0, 1000) + "... (" + text.length + " chars)";
10605
+ }
10606
+ function networkMockStringList(value) {
10607
+ return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
10608
+ }
10609
+ function networkMockShouldCaptureRequestBody(mock) {
10610
+ return mock && (
10611
+ mock.capture_request_body === true
10612
+ || networkMockStringList(mock.request_body_contains).length > 0
10613
+ || networkMockStringList(mock.request_body_patterns).length > 0
10614
+ );
10615
+ }
10616
+ function networkMockRequestBodyFailures(body, mock) {
10617
+ const failures = [];
10618
+ const rawBody = String(body || "");
10619
+ const compactBody = compactNetworkMockRequestBody(rawBody);
10620
+ for (const expected of networkMockStringList(mock.request_body_contains)) {
10621
+ if (!rawBody.includes(expected) && !compactBody.includes(expected)) {
10622
+ failures.push({
10623
+ type: "request_body_missing_text",
10624
+ text: String(expected).slice(0, 200),
10625
+ });
10626
+ }
10627
+ }
10628
+ for (const pattern of networkMockStringList(mock.request_body_patterns)) {
10629
+ try {
10630
+ const regex = new RegExp(pattern);
10631
+ if (!regex.test(rawBody) && !regex.test(compactBody)) {
10632
+ failures.push({
10633
+ type: "request_body_pattern_not_matched",
10634
+ pattern: String(pattern).slice(0, 200),
10635
+ });
10636
+ }
10637
+ } catch (error) {
10638
+ failures.push({
10639
+ type: "request_body_invalid_pattern",
10640
+ pattern: String(pattern).slice(0, 200),
10641
+ error: String(error && error.message ? error.message : error).slice(0, 500),
10642
+ });
10643
+ }
10644
+ }
10645
+ return failures;
10646
+ }
10531
10647
  async function setupLocatorVisible(locator, index) {
10532
10648
  return await locator.nth(index).isVisible({ timeout: 1000 }).catch(() => false);
10533
10649
  }
@@ -10556,7 +10672,11 @@ async function registerNetworkMocks(mocks) {
10556
10672
  body = JSON.stringify(response.body_json);
10557
10673
  contentType = response.content_type || headers["content-type"] || headers["Content-Type"] || "application/json";
10558
10674
  }
10559
- networkMockEvents.push({
10675
+ const shouldCaptureRequestBody = networkMockShouldCaptureRequestBody(mock);
10676
+ const requestBody = shouldCaptureRequestBody && request.postData ? request.postData() || "" : "";
10677
+ const requestBodyFailures = shouldCaptureRequestBody ? networkMockRequestBodyFailures(requestBody, mock) : [];
10678
+ const status = response.status || mock.status || 200;
10679
+ const event = {
10560
10680
  ok: true,
10561
10681
  label: mock.label,
10562
10682
  response_label: response.label || null,
@@ -10566,10 +10686,17 @@ async function registerNetworkMocks(mocks) {
10566
10686
  sequence_cycle: responseIndex !== null && mock.repeat_responses === true && hitIndex >= responses.length,
10567
10687
  url: request.url(),
10568
10688
  method,
10569
- status: response.status || mock.status || 200,
10570
- });
10689
+ status,
10690
+ };
10691
+ if (shouldCaptureRequestBody) {
10692
+ event.request_body_matches = requestBodyFailures.length === 0;
10693
+ event.request_body_failures = requestBodyFailures;
10694
+ event.request_body_length = requestBody.length;
10695
+ event.request_body_sample = compactNetworkMockRequestBody(requestBody);
10696
+ }
10697
+ networkMockEvents.push(event);
10571
10698
  await route.fulfill({
10572
- status: response.status || mock.status || 200,
10699
+ status,
10573
10700
  headers,
10574
10701
  contentType,
10575
10702
  body,
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-ZB5DSQF2.js";
61
+ } from "./chunk-IJGMTTVP.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,