@riddledc/riddle-proof 0.7.38 → 0.7.40

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
@@ -7119,24 +7119,57 @@ function normalizeNetworkMock(input, index) {
7119
7119
  if (!isRecord(input)) throw new Error(`target.network_mocks[${index}] must be an object.`);
7120
7120
  const url = stringValue2(input.url) || stringValue2(input.glob) || stringValue2(input.pattern);
7121
7121
  if (!url) throw new Error(`target.network_mocks[${index}] requires url.`);
7122
- const status = numberValue(input.status) ?? 200;
7123
- if (!Number.isInteger(status) || status < 100 || status > 599) {
7124
- throw new Error(`target.network_mocks[${index}].status must be an HTTP status code.`);
7122
+ const payload = normalizeNetworkMockResponsePayload(input, `target.network_mocks[${index}]`);
7123
+ const responsesInput = input.responses ?? input.sequence;
7124
+ const responses = normalizeNetworkMockResponses(responsesInput, index, payload);
7125
+ const requiredHitCount = numberValue(
7126
+ input.required_hit_count ?? input.requiredHitCount ?? input.required_hits ?? input.requiredHits ?? input.min_hits ?? input.minHits
7127
+ );
7128
+ if (requiredHitCount !== void 0 && (!Number.isInteger(requiredHitCount) || requiredHitCount < 1)) {
7129
+ throw new Error(`target.network_mocks[${index}].required_hit_count must be a positive integer.`);
7125
7130
  }
7126
- const body = stringValue2(input.body) ?? stringValue2(input.body_text) ?? stringValue2(input.bodyText);
7127
- const hasJsonBody = Object.prototype.hasOwnProperty.call(input, "body_json") || Object.prototype.hasOwnProperty.call(input, "bodyJson") || Object.prototype.hasOwnProperty.call(input, "json");
7128
7131
  return {
7132
+ ...payload,
7129
7133
  label: normalizeName(input.label || input.name, `network-mock-${index + 1}`),
7130
7134
  url,
7131
7135
  method: stringValue2(input.method)?.toUpperCase(),
7136
+ responses,
7137
+ required_hit_count: requiredHitCount,
7138
+ required: input.required === false ? false : true
7139
+ };
7140
+ }
7141
+ function normalizeNetworkMockResponsePayload(input, label, defaults = {}) {
7142
+ const status = numberValue(input.status) ?? defaults.status ?? 200;
7143
+ if (!Number.isInteger(status) || status < 100 || status > 599) {
7144
+ throw new Error(`${label}.status must be an HTTP status code.`);
7145
+ }
7146
+ const body = stringValue2(input.body) ?? stringValue2(input.body_text) ?? stringValue2(input.bodyText) ?? defaults.body;
7147
+ const hasJsonBody = Object.prototype.hasOwnProperty.call(input, "body_json") || Object.prototype.hasOwnProperty.call(input, "bodyJson") || Object.prototype.hasOwnProperty.call(input, "json");
7148
+ return {
7149
+ label: stringValue2(input.label) || stringValue2(input.name) || defaults.label,
7132
7150
  status,
7133
- content_type: stringValue2(input.content_type) || stringValue2(input.contentType),
7134
- headers: stringRecord(input.headers),
7151
+ content_type: stringValue2(input.content_type) || stringValue2(input.contentType) || defaults.content_type,
7152
+ headers: stringRecord(input.headers) || defaults.headers,
7135
7153
  body,
7136
- body_json: hasJsonBody ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
7137
- required: input.required === false ? false : true
7154
+ body_json: hasJsonBody ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : defaults.body_json
7138
7155
  };
7139
7156
  }
7157
+ function normalizeNetworkMockResponses(value, mockIndex, defaults) {
7158
+ if (value === void 0) return void 0;
7159
+ if (!Array.isArray(value)) throw new Error(`target.network_mocks[${mockIndex}].responses must be an array.`);
7160
+ if (!value.length) throw new Error(`target.network_mocks[${mockIndex}].responses must not be empty.`);
7161
+ const responseDefaults = { ...defaults, label: void 0 };
7162
+ return value.map((response, responseIndex) => {
7163
+ if (!isRecord(response)) {
7164
+ throw new Error(`target.network_mocks[${mockIndex}].responses[${responseIndex}] must be an object.`);
7165
+ }
7166
+ return normalizeNetworkMockResponsePayload(
7167
+ response,
7168
+ `target.network_mocks[${mockIndex}].responses[${responseIndex}]`,
7169
+ responseDefaults
7170
+ );
7171
+ });
7172
+ }
7140
7173
  function normalizeNetworkMocks(value) {
7141
7174
  if (value === void 0) return void 0;
7142
7175
  if (!Array.isArray(value)) throw new Error("target.network_mocks must be an array.");
@@ -7178,6 +7211,13 @@ function normalizeExpectedTexts(value, index) {
7178
7211
  if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
7179
7212
  return texts;
7180
7213
  }
7214
+ function normalizeStringList(value, label) {
7215
+ if (value === void 0) return void 0;
7216
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
7217
+ const values = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
7218
+ if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
7219
+ return values;
7220
+ }
7181
7221
  function normalizeCheck(input, index) {
7182
7222
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
7183
7223
  const type = stringValue2(input.type);
@@ -7226,6 +7266,10 @@ function normalizeCheck(input, index) {
7226
7266
  text: stringValue2(input.text),
7227
7267
  pattern: stringValue2(input.pattern),
7228
7268
  flags: stringValue2(input.flags),
7269
+ allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
7270
+ allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
7271
+ 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`),
7272
+ allowed_page_error_patterns: normalizeStringList(input.allowed_page_error_patterns ?? input.allowedPageErrorPatterns ?? input.allow_page_error_patterns ?? input.allowPageErrorPatterns, `checks[${index}] allowed_page_error_patterns`),
7229
7273
  min_count: numberValue(input.min_count),
7230
7274
  max_overflow_px: numberValue(input.max_overflow_px),
7231
7275
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
@@ -7389,6 +7433,18 @@ function matchText(sample, check) {
7389
7433
  }
7390
7434
  return sample.includes(check.text || "");
7391
7435
  }
7436
+ function matchesAllowedMessage(message, texts, patterns) {
7437
+ const sample = String(message || "");
7438
+ if (texts?.some((text) => sample.includes(text))) return true;
7439
+ for (const pattern of patterns || []) {
7440
+ try {
7441
+ if (new RegExp(pattern).test(sample)) return true;
7442
+ } catch {
7443
+ continue;
7444
+ }
7445
+ }
7446
+ return false;
7447
+ }
7392
7448
  function normalizeRoutePath(path7) {
7393
7449
  const value = path7 || "/";
7394
7450
  if (value === "/") return "/";
@@ -7675,14 +7731,28 @@ function assessCheckFromEvidence(check, evidence) {
7675
7731
  };
7676
7732
  }
7677
7733
  if (check.type === "no_fatal_console_errors") {
7678
- const fatalCount = (evidence.console?.fatal_count || 0) + (evidence.page_errors?.length || 0);
7734
+ const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
7735
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
7736
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
7737
+ const pageErrors = evidence.page_errors || [];
7738
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7739
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
7740
+ const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
7679
7741
  return {
7680
7742
  type: check.type,
7681
7743
  label: checkLabel(check),
7682
7744
  status: fatalCount ? "failed" : "passed",
7683
7745
  evidence: {
7684
- console_fatal_count: evidence.console?.fatal_count || 0,
7685
- page_error_count: evidence.page_errors?.length || 0
7746
+ console_fatal_count: unallowedConsoleEvents.length,
7747
+ page_error_count: unallowedPageErrors.length,
7748
+ total_console_fatal_count: fatalConsoleEvents.length,
7749
+ total_page_error_count: pageErrors.length,
7750
+ allowed_console_fatal_count: allowedConsoleEvents.length,
7751
+ allowed_page_error_count: allowedPageErrors.length,
7752
+ allowed_console_texts: check.allowed_console_texts || [],
7753
+ allowed_console_patterns: check.allowed_console_patterns || [],
7754
+ allowed_page_error_texts: check.allowed_page_error_texts || [],
7755
+ allowed_page_error_patterns: check.allowed_page_error_patterns || []
7686
7756
  },
7687
7757
  message: fatalCount ? `${fatalCount} fatal browser error(s) were captured.` : void 0
7688
7758
  };
@@ -7745,12 +7815,15 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
7745
7815
  const requiredMocks = mocks.filter((mock) => mock.required !== false);
7746
7816
  for (const mock of requiredMocks) {
7747
7817
  const hits = events.filter((event) => event.label === mock.label && event.ok !== false).length;
7748
- if (hits < 1) {
7818
+ const requiredHitCount = requiredNetworkMockHitCount(mock);
7819
+ if (hits < requiredHitCount) {
7749
7820
  failed.push({
7750
7821
  label: mock.label,
7751
7822
  url: mock.url,
7752
7823
  method: mock.method || null,
7753
- reason: "required_mock_not_hit"
7824
+ reason: hits < 1 ? "required_mock_not_hit" : "required_mock_hit_count_not_met",
7825
+ required_hit_count: requiredHitCount,
7826
+ hit_count: hits
7754
7827
  });
7755
7828
  }
7756
7829
  }
@@ -7776,11 +7849,21 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
7776
7849
  mock.label,
7777
7850
  events.filter((event) => event.label === mock.label && event.ok !== false).length
7778
7851
  ])),
7852
+ required_hits_by_label: Object.fromEntries(requiredMocks.map((mock) => [
7853
+ mock.label,
7854
+ requiredNetworkMockHitCount(mock)
7855
+ ])),
7779
7856
  failed
7780
7857
  },
7781
7858
  message: failed.length ? `Network mocks failed or were not hit for ${failed.length} mock(s).` : void 0
7782
7859
  };
7783
7860
  }
7861
+ function requiredNetworkMockHitCount(mock) {
7862
+ if (mock.required === false) return 0;
7863
+ if (mock.required_hit_count !== void 0) return mock.required_hit_count;
7864
+ if (Array.isArray(mock.responses) && mock.responses.length) return mock.responses.length;
7865
+ return 1;
7866
+ }
7784
7867
  function profileStatusFromEvidence(profile, evidence, checks) {
7785
7868
  if (!evidence) return "proof_insufficient";
7786
7869
  const viewports = evidence.viewports || [];
@@ -7922,6 +8005,16 @@ function textMatches(sample, check) {
7922
8005
  }
7923
8006
  return String(sample || "").includes(check.text || "");
7924
8007
  }
8008
+ function matchesAllowedMessage(message, texts, patterns) {
8009
+ const sample = String(message || "");
8010
+ if ((texts || []).some((text) => sample.includes(text))) return true;
8011
+ for (const pattern of patterns || []) {
8012
+ try {
8013
+ if (new RegExp(pattern).test(sample)) return true;
8014
+ } catch {}
8015
+ }
8016
+ return false;
8017
+ }
7925
8018
  function textSequenceForCheck(viewport, check) {
7926
8019
  const key = check.selector || "";
7927
8020
  const sequence = viewport.text_sequences && viewport.text_sequences[key];
@@ -8049,6 +8142,12 @@ function horizontalBoundsOverflowPx(value) {
8049
8142
  }
8050
8143
  return roundPixels(max);
8051
8144
  }
8145
+ function requiredNetworkMockHitCount(mock) {
8146
+ if (!mock || mock.required === false) return 0;
8147
+ if (Number.isInteger(mock.required_hit_count) && mock.required_hit_count > 0) return mock.required_hit_count;
8148
+ if (Array.isArray(mock.responses) && mock.responses.length) return mock.responses.length;
8149
+ return 1;
8150
+ }
8052
8151
  function assessProfile(profile, evidence) {
8053
8152
  const checks = [];
8054
8153
  const viewports = evidence.viewports || [];
@@ -8058,12 +8157,15 @@ function assessProfile(profile, evidence) {
8058
8157
  const failed = [];
8059
8158
  for (const mock of requiredMocks) {
8060
8159
  const hits = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
8061
- if (hits < 1) {
8160
+ const requiredHitCount = requiredNetworkMockHitCount(mock);
8161
+ if (hits < requiredHitCount) {
8062
8162
  failed.push({
8063
8163
  label: mock.label,
8064
8164
  url: mock.url,
8065
8165
  method: mock.method || null,
8066
- reason: "required_mock_not_hit",
8166
+ reason: hits < 1 ? "required_mock_not_hit" : "required_mock_hit_count_not_met",
8167
+ required_hit_count: requiredHitCount,
8168
+ hit_count: hits,
8067
8169
  });
8068
8170
  }
8069
8171
  }
@@ -8078,8 +8180,10 @@ function assessProfile(profile, evidence) {
8078
8180
  }
8079
8181
  }
8080
8182
  const hitsByLabel = {};
8183
+ const requiredHitsByLabel = {};
8081
8184
  for (const mock of profile.target.network_mocks) {
8082
8185
  hitsByLabel[mock.label] = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
8186
+ if (mock && mock.required !== false) requiredHitsByLabel[mock.label] = requiredNetworkMockHitCount(mock);
8083
8187
  }
8084
8188
  checks.push({
8085
8189
  type: "network_mocks_succeeded",
@@ -8090,6 +8194,7 @@ function assessProfile(profile, evidence) {
8090
8194
  required_count: requiredMocks.length,
8091
8195
  hit_count: events.filter((event) => event && event.ok !== false).length,
8092
8196
  hits_by_label: hitsByLabel,
8197
+ required_hits_by_label: requiredHitsByLabel,
8093
8198
  failed,
8094
8199
  },
8095
8200
  message: failed.length ? "Network mocks failed or were not hit for " + failed.length + " mock(s)." : undefined,
@@ -8356,12 +8461,29 @@ function assessProfile(profile, evidence) {
8356
8461
  continue;
8357
8462
  }
8358
8463
  if (check.type === "no_fatal_console_errors") {
8359
- const fatalCount = ((evidence.console && evidence.console.fatal_count) || 0) + ((evidence.page_errors || []).length);
8464
+ const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
8465
+ const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
8466
+ const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
8467
+ const pageErrors = evidence.page_errors || [];
8468
+ const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8469
+ const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
8470
+ const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
8360
8471
  checks.push({
8361
8472
  type: check.type,
8362
8473
  label: check.label || check.type,
8363
8474
  status: fatalCount ? "failed" : "passed",
8364
- evidence: { console_fatal_count: (evidence.console && evidence.console.fatal_count) || 0, page_error_count: (evidence.page_errors || []).length },
8475
+ evidence: {
8476
+ console_fatal_count: unallowedConsoleEvents.length,
8477
+ page_error_count: unallowedPageErrors.length,
8478
+ total_console_fatal_count: fatalConsoleEvents.length,
8479
+ total_page_error_count: pageErrors.length,
8480
+ allowed_console_fatal_count: allowedConsoleEvents.length,
8481
+ allowed_page_error_count: allowedPageErrors.length,
8482
+ allowed_console_texts: check.allowed_console_texts || [],
8483
+ allowed_console_patterns: check.allowed_console_patterns || [],
8484
+ allowed_page_error_texts: check.allowed_page_error_texts || [],
8485
+ allowed_page_error_patterns: check.allowed_page_error_patterns || [],
8486
+ },
8365
8487
  message: fatalCount ? String(fatalCount) + " fatal browser error(s) were captured." : undefined,
8366
8488
  });
8367
8489
  continue;
@@ -8471,6 +8593,7 @@ async function setupLocatorVisible(locator, index) {
8471
8593
  }
8472
8594
  async function registerNetworkMocks(mocks) {
8473
8595
  for (const mock of mocks || []) {
8596
+ let hitCount = 0;
8474
8597
  await page.route(mock.url, async (route) => {
8475
8598
  const request = route.request();
8476
8599
  const method = request.method ? request.method() : "";
@@ -8479,22 +8602,31 @@ async function registerNetworkMocks(mocks) {
8479
8602
  return;
8480
8603
  }
8481
8604
  try {
8482
- const headers = { ...(mock.headers || {}) };
8483
- let body = mock.body || "";
8484
- let contentType = mock.content_type || headers["content-type"] || headers["Content-Type"] || "text/plain";
8485
- if (mock.body_json !== undefined) {
8486
- body = JSON.stringify(mock.body_json);
8487
- contentType = mock.content_type || headers["content-type"] || headers["Content-Type"] || "application/json";
8605
+ const responses = Array.isArray(mock.responses) ? mock.responses : [];
8606
+ const hitIndex = hitCount;
8607
+ hitCount += 1;
8608
+ const responseIndex = responses.length ? Math.min(hitIndex, responses.length - 1) : null;
8609
+ const response = responseIndex === null ? mock : responses[responseIndex];
8610
+ const headers = { ...(response.headers || mock.headers || {}) };
8611
+ let body = response.body || "";
8612
+ let contentType = response.content_type || headers["content-type"] || headers["Content-Type"] || "text/plain";
8613
+ if (response.body_json !== undefined) {
8614
+ body = JSON.stringify(response.body_json);
8615
+ contentType = response.content_type || headers["content-type"] || headers["Content-Type"] || "application/json";
8488
8616
  }
8489
8617
  networkMockEvents.push({
8490
8618
  ok: true,
8491
8619
  label: mock.label,
8620
+ response_label: response.label || null,
8621
+ hit_index: hitIndex,
8622
+ response_index: responseIndex,
8623
+ sequence_reused: responseIndex !== null && hitIndex >= responses.length,
8492
8624
  url: request.url(),
8493
8625
  method,
8494
- status: mock.status || 200,
8626
+ status: response.status || mock.status || 200,
8495
8627
  });
8496
8628
  await route.fulfill({
8497
- status: mock.status || 200,
8629
+ status: response.status || mock.status || 200,
8498
8630
  headers,
8499
8631
  contentType,
8500
8632
  body,
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-ZB7AFRN3.js";
13
+ } from "./chunk-RAVOICNN.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport