@riddledc/riddle-proof 0.7.8 → 0.7.10

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
@@ -6799,6 +6799,12 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6799
6799
  "no_mobile_horizontal_overflow",
6800
6800
  "no_fatal_console_errors"
6801
6801
  ];
6802
+ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
6803
+ "click",
6804
+ "wait",
6805
+ "wait_for_selector",
6806
+ "wait_for_text"
6807
+ ];
6802
6808
  var DEFAULT_VIEWPORTS = [
6803
6809
  { name: "desktop", width: 1280, height: 800 }
6804
6810
  ];
@@ -6851,6 +6857,41 @@ function normalizeViewports(value) {
6851
6857
  function isSupportedCheckType(value) {
6852
6858
  return RIDDLE_PROOF_PROFILE_CHECK_TYPES.includes(value);
6853
6859
  }
6860
+ function normalizeSetupActionType(value, index) {
6861
+ const normalized = String(value || "").trim().replace(/-/g, "_");
6862
+ if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
6863
+ return normalized;
6864
+ }
6865
+ throw new Error(`target.setup_actions[${index}].type ${value || "(missing)"} is not supported. Supported actions: ${RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.join(", ")}`);
6866
+ }
6867
+ function normalizeSetupAction(input, index) {
6868
+ if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
6869
+ const type = normalizeSetupActionType(stringValue2(input.type), index);
6870
+ const selector = stringValue2(input.selector);
6871
+ if ((type === "click" || type === "wait_for_selector" || type === "wait_for_text") && !selector) {
6872
+ throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
6873
+ }
6874
+ if (type === "wait_for_text" && !stringValue2(input.text) && !stringValue2(input.pattern)) {
6875
+ throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
6876
+ }
6877
+ return {
6878
+ type,
6879
+ selector,
6880
+ text: stringValue2(input.text),
6881
+ pattern: stringValue2(input.pattern),
6882
+ flags: stringValue2(input.flags),
6883
+ index: numberValue(input.index),
6884
+ ms: numberValue(input.ms) ?? numberValue(input.wait_ms) ?? numberValue(input.waitMs),
6885
+ timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
6886
+ after_ms: numberValue(input.after_ms) ?? numberValue(input.afterMs),
6887
+ continue_on_failure: input.continue_on_failure === true || input.continueOnFailure === true
6888
+ };
6889
+ }
6890
+ function normalizeSetupActions(value) {
6891
+ if (value === void 0) return void 0;
6892
+ if (!Array.isArray(value)) throw new Error("target.setup_actions must be an array.");
6893
+ return value.map(normalizeSetupAction);
6894
+ }
6854
6895
  function normalizeCheck(input, index) {
6855
6896
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
6856
6897
  const type = stringValue2(input.type);
@@ -6918,7 +6959,8 @@ function normalizeRiddleProofProfile(input, options = {}) {
6918
6959
  viewports: options.viewports?.length ? options.viewports : normalizeViewports(targetInput.viewports),
6919
6960
  auth: stringValue2(targetInput.auth) || "none",
6920
6961
  wait_for_selector: stringValue2(targetInput.wait_for_selector) || stringValue2(targetInput.waitForSelector),
6921
- wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs)
6962
+ wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs),
6963
+ setup_actions: normalizeSetupActions(targetInput.setup_actions ?? targetInput.setupActions)
6922
6964
  },
6923
6965
  checks,
6924
6966
  artifacts: Array.isArray(input.artifacts) ? input.artifacts.map((item) => String(item)).filter(Boolean) : ["screenshot", "console", "dom_summary", "proof_json"],
@@ -6936,7 +6978,13 @@ function resolveRiddleProofProfileTargetUrl(profile) {
6936
6978
  throw new Error("profile target URL could not be resolved.");
6937
6979
  }
6938
6980
  function routeForViewport(viewport) {
6939
- return viewport?.route || {
6981
+ if (viewport?.route) {
6982
+ return {
6983
+ ...viewport.route,
6984
+ matched: viewport.route.matched || routePathMatches(viewport.route.observed, viewport.route.expected_path)
6985
+ };
6986
+ }
6987
+ return {
6940
6988
  requested: "",
6941
6989
  observed: "",
6942
6990
  matched: false,
@@ -6962,8 +7010,17 @@ function matchText(sample, check) {
6962
7010
  }
6963
7011
  return sample.includes(check.text || "");
6964
7012
  }
7013
+ function normalizeRoutePath(path7) {
7014
+ const value = path7 || "/";
7015
+ if (value === "/") return "/";
7016
+ return value.replace(/\/+$/, "") || "/";
7017
+ }
7018
+ function routePathMatches(observed, expected) {
7019
+ return normalizeRoutePath(observed) === normalizeRoutePath(expected);
7020
+ }
6965
7021
  function successfulRoute(route) {
6966
- return route.matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
7022
+ const matched = route.matched || routePathMatches(route.observed, route.expected_path);
7023
+ return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
6967
7024
  }
6968
7025
  function assessCheckFromEvidence(check, evidence) {
6969
7026
  const viewports = evidence.viewports || [];
@@ -6981,7 +7038,7 @@ function assessCheckFromEvidence(check, evidence) {
6981
7038
  const failed = viewports.filter((viewport) => !successfulRoute({
6982
7039
  ...viewport.route,
6983
7040
  expected_path: expectedPath,
6984
- matched: viewport.route.observed === expectedPath || viewport.route.matched
7041
+ matched: routePathMatches(viewport.route.observed, expectedPath) || viewport.route.matched
6985
7042
  }));
6986
7043
  return {
6987
7044
  type: check.type,
@@ -7091,6 +7148,48 @@ function assessCheckFromEvidence(check, evidence) {
7091
7148
  message: "Unsupported check type."
7092
7149
  };
7093
7150
  }
7151
+ function assessSetupActionsFromEvidence(profile, evidence) {
7152
+ if (!profile.target.setup_actions?.length) return void 0;
7153
+ const actionCount = profile.target.setup_actions.length;
7154
+ const failed = [];
7155
+ const viewports = evidence.viewports || [];
7156
+ for (const viewport of viewports) {
7157
+ const results = viewport.setup_action_results || [];
7158
+ for (const result of results) {
7159
+ if (result.ok === false) {
7160
+ failed.push({
7161
+ viewport: viewport.name,
7162
+ action: result.action ?? result.type ?? null,
7163
+ selector: result.selector ?? null,
7164
+ reason: result.reason ?? result.error ?? null
7165
+ });
7166
+ }
7167
+ }
7168
+ if (results.length < actionCount && results.every((result) => result.ok !== false)) {
7169
+ failed.push({
7170
+ viewport: viewport.name,
7171
+ action: "setup_actions",
7172
+ selector: null,
7173
+ reason: `missing setup action results: ${results.length}/${actionCount}`
7174
+ });
7175
+ }
7176
+ }
7177
+ return {
7178
+ type: "setup_actions_succeeded",
7179
+ label: "setup actions succeeded",
7180
+ status: failed.length ? "failed" : "passed",
7181
+ evidence: {
7182
+ action_count: actionCount,
7183
+ viewports: viewports.map((viewport) => ({
7184
+ name: viewport.name,
7185
+ ok: (viewport.setup_action_results || []).length >= actionCount && (viewport.setup_action_results || []).every((result) => result.ok !== false),
7186
+ result_count: (viewport.setup_action_results || []).length
7187
+ })),
7188
+ failed
7189
+ },
7190
+ message: failed.length ? `Setup actions failed in ${failed.length} viewport action(s).` : void 0
7191
+ };
7192
+ }
7094
7193
  function profileStatusFromEvidence(evidence, checks) {
7095
7194
  if (!evidence) return "proof_insufficient";
7096
7195
  const viewports = evidence.viewports || [];
@@ -7102,7 +7201,10 @@ function profileStatusFromEvidence(evidence, checks) {
7102
7201
  }
7103
7202
  function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
7104
7203
  const capturedAt = evidence?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
7105
- const checks = evidence ? profile.checks.map((check) => assessCheckFromEvidence(check, evidence)) : [];
7204
+ const checks = evidence ? [
7205
+ assessSetupActionsFromEvidence(profile, evidence),
7206
+ ...profile.checks.map((check) => assessCheckFromEvidence(check, evidence))
7207
+ ].filter((check) => Boolean(check)) : [];
7106
7208
  const status = profileStatusFromEvidence(evidence, checks);
7107
7209
  const firstViewport = evidence?.viewports?.[0];
7108
7210
  const screenshots = (evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => Boolean(label));
@@ -7188,8 +7290,16 @@ function createRiddleProofProfileInsufficientResult(input) {
7188
7290
  }
7189
7291
  function runtimeScriptAssessmentSource() {
7190
7292
  return String.raw`
7293
+ function normalizeRoutePath(path) {
7294
+ const value = path || "/";
7295
+ if (value === "/") return "/";
7296
+ return value.replace(/\/+$/, "") || "/";
7297
+ }
7298
+ function routePathMatches(observed, expected) {
7299
+ return normalizeRoutePath(observed) === normalizeRoutePath(expected);
7300
+ }
7191
7301
  function routeOk(route) {
7192
- return Boolean(route && route.matched && !route.error && (route.http_status == null || route.http_status < 400));
7302
+ return Boolean(route && (route.matched || routePathMatches(route.observed, route.expected_path)) && !route.error && (route.http_status == null || route.http_status < 400));
7193
7303
  }
7194
7304
  function textMatches(sample, check) {
7195
7305
  if (check.pattern) {
@@ -7200,12 +7310,53 @@ function textMatches(sample, check) {
7200
7310
  function assessProfile(profile, evidence) {
7201
7311
  const checks = [];
7202
7312
  const viewports = evidence.viewports || [];
7313
+ if (profile.target && Array.isArray(profile.target.setup_actions) && profile.target.setup_actions.length) {
7314
+ const actionCount = profile.target.setup_actions.length;
7315
+ const failed = [];
7316
+ for (const viewport of viewports) {
7317
+ const results = viewport.setup_action_results || [];
7318
+ for (const result of results) {
7319
+ if (result && result.ok === false) {
7320
+ failed.push({
7321
+ viewport: viewport.name,
7322
+ action: result.action || result.type || null,
7323
+ selector: result.selector || null,
7324
+ reason: result.reason || result.error || null,
7325
+ });
7326
+ }
7327
+ }
7328
+ if (results.length < actionCount && results.every((result) => !result || result.ok !== false)) {
7329
+ failed.push({
7330
+ viewport: viewport.name,
7331
+ action: "setup_actions",
7332
+ selector: null,
7333
+ reason: "missing setup action results: " + results.length + "/" + actionCount,
7334
+ });
7335
+ }
7336
+ }
7337
+ checks.push({
7338
+ type: "setup_actions_succeeded",
7339
+ label: "setup actions succeeded",
7340
+ status: failed.length ? "failed" : "passed",
7341
+ evidence: {
7342
+ action_count: actionCount,
7343
+ viewports: viewports.map((viewport) => ({
7344
+ name: viewport.name,
7345
+ ok: (viewport.setup_action_results || []).length >= actionCount
7346
+ && (viewport.setup_action_results || []).every((result) => !result || result.ok !== false),
7347
+ result_count: (viewport.setup_action_results || []).length,
7348
+ })),
7349
+ failed,
7350
+ },
7351
+ message: failed.length ? "Setup actions failed in " + failed.length + " viewport action(s)." : undefined,
7352
+ });
7353
+ }
7203
7354
  for (const check of profile.checks || []) {
7204
7355
  if (check.type === "route_loaded") {
7205
7356
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
7206
7357
  const failed = viewports.filter((viewport) => {
7207
7358
  const route = { ...(viewport.route || {}), expected_path: expectedPath };
7208
- route.matched = route.observed === expectedPath || route.matched;
7359
+ route.matched = routePathMatches(route.observed, expectedPath) || route.matched;
7209
7360
  return !routeOk(route);
7210
7361
  });
7211
7362
  checks.push({
@@ -7362,6 +7513,105 @@ function textMatches(sample, check) {
7362
7513
  }
7363
7514
  return String(sample || "").includes(check.text || "");
7364
7515
  }
7516
+ function setupActionType(action) {
7517
+ return String(action && action.type ? action.type : "").replace(/-/g, "_");
7518
+ }
7519
+ function setupNumber(value, fallback) {
7520
+ const number = Number(value);
7521
+ return Number.isFinite(number) && number >= 0 ? number : fallback;
7522
+ }
7523
+ function setupTextMatches(sample, action) {
7524
+ if (action.pattern) {
7525
+ try { return new RegExp(action.pattern, action.flags || "").test(sample || ""); } catch { return false; }
7526
+ }
7527
+ return String(sample || "").includes(action.text || "");
7528
+ }
7529
+ async function setupLocatorText(locator, index) {
7530
+ return await locator.nth(index).textContent({ timeout: 1000 }).catch(() => "");
7531
+ }
7532
+ async function setupLocatorVisible(locator, index) {
7533
+ return await locator.nth(index).isVisible({ timeout: 1000 }).catch(() => false);
7534
+ }
7535
+ async function executeSetupAction(action, ordinal) {
7536
+ const type = setupActionType(action);
7537
+ const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null };
7538
+ const timeout = setupNumber(action.timeout_ms, 5000);
7539
+ try {
7540
+ if (type === "wait") {
7541
+ const ms = setupNumber(action.ms, 500);
7542
+ await page.waitForTimeout(ms);
7543
+ return { ...base, ok: true, ms };
7544
+ }
7545
+ if (type === "wait_for_selector") {
7546
+ await page.waitForSelector(action.selector, { state: "visible", timeout });
7547
+ return { ...base, ok: true, timeout_ms: timeout };
7548
+ }
7549
+ if (type === "click") {
7550
+ const locator = page.locator(action.selector);
7551
+ const count = await locator.count();
7552
+ if (!count) return { ...base, reason: "selector_not_found", count };
7553
+ let targetIndex = Number.isInteger(action.index) ? action.index : 0;
7554
+ let matchedText = null;
7555
+ let hiddenMatchIndex = -1;
7556
+ let hiddenMatchedText = null;
7557
+ if (action.text || action.pattern) {
7558
+ targetIndex = -1;
7559
+ for (let index = 0; index < count; index += 1) {
7560
+ const text = await setupLocatorText(locator, index);
7561
+ if (setupTextMatches(text, action)) {
7562
+ const visible = await setupLocatorVisible(locator, index);
7563
+ if (visible) {
7564
+ targetIndex = index;
7565
+ matchedText = text;
7566
+ break;
7567
+ }
7568
+ if (hiddenMatchIndex < 0) {
7569
+ hiddenMatchIndex = index;
7570
+ hiddenMatchedText = text;
7571
+ }
7572
+ }
7573
+ }
7574
+ if (targetIndex < 0 && hiddenMatchIndex >= 0) return { ...base, reason: "matching_element_not_visible", count, target_index: hiddenMatchIndex, text: hiddenMatchedText };
7575
+ if (targetIndex < 0) return { ...base, reason: "text_not_found", count };
7576
+ }
7577
+ if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex };
7578
+ await locator.nth(targetIndex).click({ timeout });
7579
+ return { ...base, ok: true, count, target_index: targetIndex, text: matchedText };
7580
+ }
7581
+ if (type === "wait_for_text") {
7582
+ const locator = page.locator(action.selector);
7583
+ const startedAt = Date.now();
7584
+ let lastText = "";
7585
+ while (Date.now() - startedAt <= timeout) {
7586
+ const count = await locator.count().catch(() => 0);
7587
+ for (let index = 0; index < count; index += 1) {
7588
+ const text = await setupLocatorText(locator, index);
7589
+ lastText = text || lastText;
7590
+ if (setupTextMatches(text, action)) {
7591
+ return { ...base, ok: true, text, target_index: index, timeout_ms: timeout };
7592
+ }
7593
+ }
7594
+ await page.waitForTimeout(100);
7595
+ }
7596
+ return { ...base, reason: "text_not_found", text: lastText, timeout_ms: timeout };
7597
+ }
7598
+ return { ...base, reason: "unsupported_action" };
7599
+ } catch (error) {
7600
+ return { ...base, error: String(error && error.message ? error.message : error).slice(0, 1000) };
7601
+ }
7602
+ }
7603
+ async function executeSetupActions(actions) {
7604
+ const results = [];
7605
+ for (let index = 0; index < (actions || []).length; index += 1) {
7606
+ const action = actions[index] || {};
7607
+ const result = await executeSetupAction(action, index);
7608
+ results.push(result);
7609
+ const afterMs = setupNumber(action.after_ms, 0);
7610
+ if (afterMs) await page.waitForTimeout(afterMs);
7611
+ if (result.ok === false && action.continue_on_failure !== true) break;
7612
+ }
7613
+ return results;
7614
+ }
7365
7615
  function expectedPathFor(check) {
7366
7616
  return check.expected_path || new URL(targetUrl).pathname || "/";
7367
7617
  }
@@ -7396,6 +7646,9 @@ async function captureViewport(viewport) {
7396
7646
  if (!navigationError && profile.target.wait_ms) {
7397
7647
  await page.waitForTimeout(profile.target.wait_ms);
7398
7648
  }
7649
+ const setupActionResults = (!navigationError && !waitError)
7650
+ ? await executeSetupActions(profile.target.setup_actions || [])
7651
+ : [];
7399
7652
  const dom = await page.evaluate(() => {
7400
7653
  const body = document.body;
7401
7654
  const documentElement = document.documentElement;
@@ -7445,7 +7698,7 @@ async function captureViewport(viewport) {
7445
7698
  requested: targetUrl,
7446
7699
  observed: dom.pathname,
7447
7700
  expected_path: expectedPath,
7448
- matched: dom.pathname === expectedPath,
7701
+ matched: routePathMatches(dom.pathname, expectedPath),
7449
7702
  http_status: httpStatus,
7450
7703
  error: navigationError || waitError || undefined,
7451
7704
  },
@@ -7457,6 +7710,7 @@ async function captureViewport(viewport) {
7457
7710
  overflow_px: Math.max(0, (dom.scroll_width || 0) - (dom.client_width || viewport.width)),
7458
7711
  selectors,
7459
7712
  text_matches,
7713
+ setup_action_results: setupActionResults,
7460
7714
  screenshot_label: screenshotLabel,
7461
7715
  navigation_error: navigationError,
7462
7716
  wait_error: waitError,
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  normalizeRiddleProofProfile,
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl
12
- } from "./chunk-7NMAU4DP.js";
12
+ } from "./chunk-A7RJZD4I.js";
13
13
  import {
14
14
  createRiddleApiClient,
15
15
  parseRiddleViewport