@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/cli.cjs CHANGED
@@ -7126,6 +7126,15 @@ function normalizeNetworkMock(input, index) {
7126
7126
  const payload = normalizeNetworkMockResponsePayload(input, `target.network_mocks[${index}]`);
7127
7127
  const responsesInput = input.responses ?? input.sequence;
7128
7128
  const responses = normalizeNetworkMockResponses(responsesInput, index, payload);
7129
+ const requestBodyContains = normalizeStringList(
7130
+ input.request_body_contains ?? input.requestBodyContains ?? input.body_contains ?? input.bodyContains,
7131
+ `target.network_mocks[${index}].request_body_contains`
7132
+ );
7133
+ const requestBodyPatterns = normalizeStringList(
7134
+ input.request_body_patterns ?? input.requestBodyPatterns ?? input.body_patterns ?? input.bodyPatterns,
7135
+ `target.network_mocks[${index}].request_body_patterns`
7136
+ );
7137
+ validateRegexPatterns(requestBodyPatterns, `target.network_mocks[${index}].request_body_patterns`);
7129
7138
  const requiredHitCount = numberValue(
7130
7139
  input.required_hit_count ?? input.requiredHitCount ?? input.required_hits ?? input.requiredHits ?? input.min_hits ?? input.minHits
7131
7140
  );
@@ -7140,7 +7149,10 @@ function normalizeNetworkMock(input, index) {
7140
7149
  responses,
7141
7150
  repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
7142
7151
  required_hit_count: requiredHitCount,
7143
- required: input.required === false ? false : true
7152
+ required: input.required === false ? false : true,
7153
+ capture_request_body: input.capture_request_body === true || input.captureRequestBody === true || Boolean(requestBodyContains?.length) || Boolean(requestBodyPatterns?.length),
7154
+ request_body_contains: requestBodyContains,
7155
+ request_body_patterns: requestBodyPatterns
7144
7156
  };
7145
7157
  }
7146
7158
  function normalizeNetworkMockResponsePayload(input, label, defaults = {}) {
@@ -7223,6 +7235,16 @@ function normalizeStringList(value, label) {
7223
7235
  if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
7224
7236
  return values;
7225
7237
  }
7238
+ function validateRegexPatterns(patterns, label) {
7239
+ for (const pattern of patterns || []) {
7240
+ try {
7241
+ new RegExp(pattern);
7242
+ } catch (error) {
7243
+ const message = error instanceof Error ? error.message : String(error);
7244
+ throw new Error(`${label} contains invalid regex ${JSON.stringify(pattern)}: ${message}`);
7245
+ }
7246
+ }
7247
+ }
7226
7248
  function normalizeCheck(input, index) {
7227
7249
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
7228
7250
  const type = stringValue2(input.type);
@@ -7275,6 +7297,7 @@ function normalizeCheck(input, index) {
7275
7297
  text: stringValue2(input.text),
7276
7298
  pattern: stringValue2(input.pattern),
7277
7299
  flags: stringValue2(input.flags),
7300
+ viewports: normalizeStringList(input.viewports ?? input.viewport_names ?? input.viewportNames, `checks[${index}] viewports`),
7278
7301
  allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
7279
7302
  allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
7280
7303
  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`),
@@ -7513,15 +7536,22 @@ function successfulRoute(route, targetUrl) {
7513
7536
  const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
7514
7537
  return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
7515
7538
  }
7539
+ function viewportsForCheck(check, viewports) {
7540
+ if (!check.viewports?.length) return viewports;
7541
+ const names = new Set(check.viewports);
7542
+ return viewports.filter((viewport) => names.has(viewport.name));
7543
+ }
7516
7544
  function assessCheckFromEvidence(check, evidence) {
7517
- const viewports = evidence.viewports || [];
7545
+ const viewports = viewportsForCheck(check, evidence.viewports || []);
7518
7546
  if (!viewports.length) {
7519
7547
  return {
7520
7548
  type: check.type,
7521
7549
  label: checkLabel(check),
7522
7550
  status: "failed",
7523
- evidence: {},
7524
- message: "No viewport evidence was captured."
7551
+ evidence: {
7552
+ expected_viewports: check.viewports || []
7553
+ },
7554
+ message: check.viewports?.length ? `No matching viewport evidence was captured for ${check.viewports.join(", ")}.` : "No viewport evidence was captured."
7525
7555
  };
7526
7556
  }
7527
7557
  if (check.type === "route_loaded") {
@@ -7885,6 +7915,17 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
7885
7915
  reason: event.reason ?? event.error ?? "mock_failed"
7886
7916
  });
7887
7917
  }
7918
+ if (event.request_body_matches === false) {
7919
+ failed.push({
7920
+ label: event.label ?? null,
7921
+ url: event.url ?? null,
7922
+ method: event.method ?? null,
7923
+ reason: "request_body_mismatch",
7924
+ request_body_failures: event.request_body_failures ?? [],
7925
+ request_body_length: event.request_body_length ?? null,
7926
+ request_body_sample: event.request_body_sample ?? null
7927
+ });
7928
+ }
7888
7929
  }
7889
7930
  return {
7890
7931
  type: "network_mocks_succeeded",
@@ -8110,6 +8151,11 @@ function frameTextSample(frame) {
8110
8151
  frame && frame.text,
8111
8152
  ].map((part) => String(part || "")).filter(Boolean).join(" ");
8112
8153
  }
8154
+ function viewportsForCheck(check, viewports) {
8155
+ if (!Array.isArray(check.viewports) || !check.viewports.length) return viewports;
8156
+ const names = new Set(check.viewports);
8157
+ return viewports.filter((viewport) => names.has(viewport.name));
8158
+ }
8113
8159
  function summarizeRouteInventory(viewport, inventory) {
8114
8160
  const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
8115
8161
  const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
@@ -8236,6 +8282,17 @@ function assessProfile(profile, evidence) {
8236
8282
  reason: event.reason || event.error || "mock_failed",
8237
8283
  });
8238
8284
  }
8285
+ if (event && event.request_body_matches === false) {
8286
+ failed.push({
8287
+ label: event.label || null,
8288
+ url: event.url || null,
8289
+ method: event.method || null,
8290
+ reason: "request_body_mismatch",
8291
+ request_body_failures: event.request_body_failures || [],
8292
+ request_body_length: event.request_body_length || null,
8293
+ request_body_sample: event.request_body_sample || null,
8294
+ });
8295
+ }
8239
8296
  }
8240
8297
  const hitsByLabel = {};
8241
8298
  const requiredHitsByLabel = {};
@@ -8300,9 +8357,22 @@ function assessProfile(profile, evidence) {
8300
8357
  });
8301
8358
  }
8302
8359
  for (const check of profile.checks || []) {
8360
+ const checkViewports = viewportsForCheck(check, viewports);
8361
+ if (!checkViewports.length) {
8362
+ checks.push({
8363
+ type: check.type,
8364
+ label: check.label || check.type,
8365
+ status: "failed",
8366
+ evidence: { expected_viewports: check.viewports || [] },
8367
+ message: Array.isArray(check.viewports) && check.viewports.length
8368
+ ? "No matching viewport evidence was captured for " + check.viewports.join(", ") + "."
8369
+ : "No viewport evidence was captured.",
8370
+ });
8371
+ continue;
8372
+ }
8303
8373
  if (check.type === "route_loaded") {
8304
8374
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
8305
- const failed = viewports.filter((viewport) => {
8375
+ const failed = checkViewports.filter((viewport) => {
8306
8376
  const route = { ...(viewport.route || {}), expected_path: expectedPath };
8307
8377
  route.matched = routePathMatches(route.observed, expectedPath, evidence.target_url) || route.matched;
8308
8378
  return !routeOk(route, evidence.target_url);
@@ -8313,8 +8383,8 @@ function assessProfile(profile, evidence) {
8313
8383
  status: failed.length ? "failed" : "passed",
8314
8384
  evidence: {
8315
8385
  expected_path: expectedPath,
8316
- observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
8317
- http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
8386
+ observed_paths: checkViewports.map((viewport) => viewport.route && viewport.route.observed),
8387
+ http_statuses: checkViewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
8318
8388
  },
8319
8389
  message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
8320
8390
  });
@@ -8322,24 +8392,24 @@ function assessProfile(profile, evidence) {
8322
8392
  }
8323
8393
  if (check.type === "selector_visible") {
8324
8394
  const selector = check.selector || "";
8325
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
8395
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
8326
8396
  checks.push({
8327
8397
  type: check.type,
8328
8398
  label: check.label || check.type,
8329
8399
  status: failed.length ? "failed" : "passed",
8330
- evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
8400
+ evidence: { selector, visible_counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
8331
8401
  message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
8332
8402
  });
8333
8403
  continue;
8334
8404
  }
8335
8405
  if (check.type === "selector_absent") {
8336
8406
  const selector = check.selector || "";
8337
- const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
8407
+ const failed = checkViewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
8338
8408
  checks.push({
8339
8409
  type: check.type,
8340
8410
  label: check.label || check.type,
8341
8411
  status: failed.length ? "failed" : "passed",
8342
- evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8412
+ evidence: { selector, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8343
8413
  message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
8344
8414
  });
8345
8415
  continue;
@@ -8347,12 +8417,12 @@ function assessProfile(profile, evidence) {
8347
8417
  if (check.type === "selector_count_at_least") {
8348
8418
  const selector = check.selector || "";
8349
8419
  const minCount = check.min_count == null ? 1 : check.min_count;
8350
- const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
8420
+ const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
8351
8421
  checks.push({
8352
8422
  type: check.type,
8353
8423
  label: check.label || check.type,
8354
8424
  status: failed.length ? "failed" : "passed",
8355
- evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8425
+ evidence: { selector, min_count: minCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8356
8426
  message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
8357
8427
  });
8358
8428
  continue;
@@ -8360,12 +8430,12 @@ function assessProfile(profile, evidence) {
8360
8430
  if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
8361
8431
  const selector = check.selector || "";
8362
8432
  const expectedCount = check.expected_count == null ? 0 : check.expected_count;
8363
- const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
8433
+ const failed = checkViewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
8364
8434
  checks.push({
8365
8435
  type: check.type,
8366
8436
  label: check.label || check.type,
8367
8437
  status: failed.length ? "failed" : "passed",
8368
- evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8438
+ evidence: { selector, expected_count: expectedCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8369
8439
  message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
8370
8440
  });
8371
8441
  continue;
@@ -8373,7 +8443,7 @@ function assessProfile(profile, evidence) {
8373
8443
  if (check.type === "selector_text_order") {
8374
8444
  const selector = check.selector || "";
8375
8445
  const expectedTexts = check.expected_texts || [];
8376
- const results = viewports.map((viewport) => {
8446
+ const results = checkViewports.map((viewport) => {
8377
8447
  const texts = textSequenceForCheck(viewport, check);
8378
8448
  const match = textOrderMatch(texts, expectedTexts);
8379
8449
  return {
@@ -8395,7 +8465,7 @@ function assessProfile(profile, evidence) {
8395
8465
  }
8396
8466
  if (check.type === "frame_text_visible") {
8397
8467
  const selector = check.selector || "";
8398
- const results = viewports.map((viewport) => {
8468
+ const results = checkViewports.map((viewport) => {
8399
8469
  const frames = frameEvidenceForSelector(viewport, selector);
8400
8470
  const matches = frames
8401
8471
  .map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
@@ -8422,7 +8492,7 @@ function assessProfile(profile, evidence) {
8422
8492
  if (check.type === "frame_no_horizontal_overflow") {
8423
8493
  const selector = check.selector || "";
8424
8494
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
8425
- const results = viewports.map((viewport) => {
8495
+ const results = checkViewports.map((viewport) => {
8426
8496
  const frames = frameEvidenceForSelector(viewport, selector);
8427
8497
  const frameOverflows = frames.map((frame, index) => ({
8428
8498
  index,
@@ -8452,7 +8522,7 @@ function assessProfile(profile, evidence) {
8452
8522
  if (check.type === "text_visible" || check.type === "text_absent") {
8453
8523
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
8454
8524
  const expectedVisible = check.type === "text_visible";
8455
- const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
8525
+ const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
8456
8526
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
8457
8527
  checks.push({
8458
8528
  type: check.type,
@@ -8464,7 +8534,7 @@ function assessProfile(profile, evidence) {
8464
8534
  continue;
8465
8535
  }
8466
8536
  if (check.type === "route_inventory") {
8467
- const inventories = viewports
8537
+ const inventories = checkViewports
8468
8538
  .map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory }))
8469
8539
  .filter((item) => item.inventory && typeof item.inventory === "object");
8470
8540
  if (!inventories.length) {
@@ -8516,7 +8586,7 @@ function assessProfile(profile, evidence) {
8516
8586
  }
8517
8587
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
8518
8588
  const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
8519
- const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
8589
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? checkViewports.filter((viewport) => viewport.width <= 820) : checkViewports;
8520
8590
  if (!applicable.length) {
8521
8591
  checks.push({
8522
8592
  type: check.type,
@@ -8671,6 +8741,52 @@ function compactSetupResultText(value) {
8671
8741
  if (text.length <= 500) return text;
8672
8742
  return text.slice(0, 500) + "... (" + text.length + " chars)";
8673
8743
  }
8744
+ function compactNetworkMockRequestBody(value) {
8745
+ const text = String(value || "").replace(/\s+/g, " ").trim();
8746
+ if (text.length <= 1000) return text;
8747
+ return text.slice(0, 1000) + "... (" + text.length + " chars)";
8748
+ }
8749
+ function networkMockStringList(value) {
8750
+ return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
8751
+ }
8752
+ function networkMockShouldCaptureRequestBody(mock) {
8753
+ return mock && (
8754
+ mock.capture_request_body === true
8755
+ || networkMockStringList(mock.request_body_contains).length > 0
8756
+ || networkMockStringList(mock.request_body_patterns).length > 0
8757
+ );
8758
+ }
8759
+ function networkMockRequestBodyFailures(body, mock) {
8760
+ const failures = [];
8761
+ const rawBody = String(body || "");
8762
+ const compactBody = compactNetworkMockRequestBody(rawBody);
8763
+ for (const expected of networkMockStringList(mock.request_body_contains)) {
8764
+ if (!rawBody.includes(expected) && !compactBody.includes(expected)) {
8765
+ failures.push({
8766
+ type: "request_body_missing_text",
8767
+ text: String(expected).slice(0, 200),
8768
+ });
8769
+ }
8770
+ }
8771
+ for (const pattern of networkMockStringList(mock.request_body_patterns)) {
8772
+ try {
8773
+ const regex = new RegExp(pattern);
8774
+ if (!regex.test(rawBody) && !regex.test(compactBody)) {
8775
+ failures.push({
8776
+ type: "request_body_pattern_not_matched",
8777
+ pattern: String(pattern).slice(0, 200),
8778
+ });
8779
+ }
8780
+ } catch (error) {
8781
+ failures.push({
8782
+ type: "request_body_invalid_pattern",
8783
+ pattern: String(pattern).slice(0, 200),
8784
+ error: String(error && error.message ? error.message : error).slice(0, 500),
8785
+ });
8786
+ }
8787
+ }
8788
+ return failures;
8789
+ }
8674
8790
  async function setupLocatorVisible(locator, index) {
8675
8791
  return await locator.nth(index).isVisible({ timeout: 1000 }).catch(() => false);
8676
8792
  }
@@ -8699,7 +8815,11 @@ async function registerNetworkMocks(mocks) {
8699
8815
  body = JSON.stringify(response.body_json);
8700
8816
  contentType = response.content_type || headers["content-type"] || headers["Content-Type"] || "application/json";
8701
8817
  }
8702
- networkMockEvents.push({
8818
+ const shouldCaptureRequestBody = networkMockShouldCaptureRequestBody(mock);
8819
+ const requestBody = shouldCaptureRequestBody && request.postData ? request.postData() || "" : "";
8820
+ const requestBodyFailures = shouldCaptureRequestBody ? networkMockRequestBodyFailures(requestBody, mock) : [];
8821
+ const status = response.status || mock.status || 200;
8822
+ const event = {
8703
8823
  ok: true,
8704
8824
  label: mock.label,
8705
8825
  response_label: response.label || null,
@@ -8709,10 +8829,17 @@ async function registerNetworkMocks(mocks) {
8709
8829
  sequence_cycle: responseIndex !== null && mock.repeat_responses === true && hitIndex >= responses.length,
8710
8830
  url: request.url(),
8711
8831
  method,
8712
- status: response.status || mock.status || 200,
8713
- });
8832
+ status,
8833
+ };
8834
+ if (shouldCaptureRequestBody) {
8835
+ event.request_body_matches = requestBodyFailures.length === 0;
8836
+ event.request_body_failures = requestBodyFailures;
8837
+ event.request_body_length = requestBody.length;
8838
+ event.request_body_sample = compactNetworkMockRequestBody(requestBody);
8839
+ }
8840
+ networkMockEvents.push(event);
8714
8841
  await route.fulfill({
8715
- status: response.status || mock.status || 200,
8842
+ status,
8716
8843
  headers,
8717
8844
  contentType,
8718
8845
  body,
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-ZB5DSQF2.js";
13
+ } from "./chunk-IJGMTTVP.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport