@riddledc/riddle-proof 0.7.7 → 0.7.9

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/README.md CHANGED
@@ -108,7 +108,11 @@ or as a stronger proof base before a change loop.
108
108
  { "name": "mobile", "width": 390, "height": 844 },
109
109
  { "name": "desktop", "width": 1440, "height": 1000 }
110
110
  ],
111
- "auth": "none"
111
+ "auth": "none",
112
+ "setup_actions": [
113
+ { "type": "click", "selector": "[data-testid='show-plans']" },
114
+ { "type": "wait_for_text", "selector": "body", "text": "Start building" }
115
+ ]
112
116
  },
113
117
  "checks": [
114
118
  { "type": "route_loaded", "expected_path": "/pricing" },
@@ -141,6 +145,13 @@ The package includes a generic starter profile at
141
145
  profile directory and replace the selector/text checks with app-specific
142
146
  invariants.
143
147
 
148
+ `target.setup_actions` is optional. Use it when the meaningful proof surface
149
+ appears only after a picker, tab, login stub, transport control, or other
150
+ bounded interaction. Supported setup actions are `click`, `wait`,
151
+ `wait_for_selector`, and `wait_for_text`; a failed setup action is recorded as
152
+ a failed `setup_actions_succeeded` check so the profile cannot pass without
153
+ reaching the intended state.
154
+
144
155
  The result uses `riddle-proof.profile-result.v1` and separates product failures
145
156
  from weak proof and environment blockers:
146
157
 
@@ -301,9 +312,10 @@ const catches = createBasicGameplayCatchRecords(assessment, evidence);
301
312
  ```
302
313
 
303
314
  The package owns generic contracts such as `state_path`, `state_call`,
304
- `property_path`, `number_unchanged`, held-key/window-call/evaluate action type
305
- constants, and JSON-safe text compaction. Site-specific manifests, selectors,
306
- and deterministic game scripts should stay in the caller.
315
+ `property_path`, `number_unchanged`, held-key, canvas-click,
316
+ canvas-pointer-down/move/up, window-call/evaluate action type constants, and
317
+ JSON-safe text compaction. Site-specific manifests, selectors, and
318
+ deterministic game scripts should stay in the caller.
307
319
 
308
320
  ### Server Preview Usage
309
321
 
@@ -51,6 +51,10 @@ var BASIC_GAMEPLAY_ACTION_TYPES = [
51
51
  "repeat",
52
52
  "click",
53
53
  "click-by-text",
54
+ "canvas-click",
55
+ "canvas-pointer-down",
56
+ "canvas-pointer-move",
57
+ "canvas-pointer-up",
54
58
  "wait-for-text",
55
59
  "window-call",
56
60
  "evaluate"
@@ -36,7 +36,7 @@ interface BasicGameplayActionResult {
36
36
  action?: string;
37
37
  [key: string]: unknown;
38
38
  }
39
- declare const BASIC_GAMEPLAY_ACTION_TYPES: readonly ["wait", "key", "key-down", "key-up", "hold-key", "repeat", "click", "click-by-text", "wait-for-text", "window-call", "evaluate"];
39
+ declare const BASIC_GAMEPLAY_ACTION_TYPES: readonly ["wait", "key", "key-down", "key-up", "hold-key", "repeat", "click", "click-by-text", "canvas-click", "canvas-pointer-down", "canvas-pointer-move", "canvas-pointer-up", "wait-for-text", "window-call", "evaluate"];
40
40
  declare const BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES: readonly ["selector_count_increases", "selector_count_at_least", "selector_absent", "selector_text_matches", "number_increases", "number_decreases", "number_at_least", "number_gte", "number_unchanged", "number_stays_equal", "number_equals", "canvas_hash_changes", "screenshot_hash_changes", "visual_hash_changes", "state_changes"];
41
41
  type BasicGameplayActionType = typeof BASIC_GAMEPLAY_ACTION_TYPES[number] | (string & {});
42
42
  type BasicGameplayProgressCheckType = typeof BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES[number] | (string & {});
@@ -36,7 +36,7 @@ interface BasicGameplayActionResult {
36
36
  action?: string;
37
37
  [key: string]: unknown;
38
38
  }
39
- declare const BASIC_GAMEPLAY_ACTION_TYPES: readonly ["wait", "key", "key-down", "key-up", "hold-key", "repeat", "click", "click-by-text", "wait-for-text", "window-call", "evaluate"];
39
+ declare const BASIC_GAMEPLAY_ACTION_TYPES: readonly ["wait", "key", "key-down", "key-up", "hold-key", "repeat", "click", "click-by-text", "canvas-click", "canvas-pointer-down", "canvas-pointer-move", "canvas-pointer-up", "wait-for-text", "window-call", "evaluate"];
40
40
  declare const BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES: readonly ["selector_count_increases", "selector_count_at_least", "selector_absent", "selector_text_matches", "number_increases", "number_decreases", "number_at_least", "number_gte", "number_unchanged", "number_stays_equal", "number_equals", "canvas_hash_changes", "screenshot_hash_changes", "visual_hash_changes", "state_changes"];
41
41
  type BasicGameplayActionType = typeof BASIC_GAMEPLAY_ACTION_TYPES[number] | (string & {});
42
42
  type BasicGameplayProgressCheckType = typeof BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES[number] | (string & {});
@@ -16,7 +16,7 @@ import {
16
16
  extractBasicGameplayEvidence,
17
17
  resolveBasicGameplayProgressionCheckWithArtifactScreenshots,
18
18
  sanitizeBasicGameplayJsonString
19
- } from "./chunk-AHW7SL3W.js";
19
+ } from "./chunk-5A4SL6CX.js";
20
20
  export {
21
21
  BASIC_GAMEPLAY_ACTION_TYPES,
22
22
  BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES,
@@ -11,6 +11,10 @@ var BASIC_GAMEPLAY_ACTION_TYPES = [
11
11
  "repeat",
12
12
  "click",
13
13
  "click-by-text",
14
+ "canvas-click",
15
+ "canvas-pointer-down",
16
+ "canvas-pointer-move",
17
+ "canvas-pointer-up",
14
18
  "wait-for-text",
15
19
  "window-call",
16
20
  "evaluate"
@@ -20,6 +20,12 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
20
20
  "no_mobile_horizontal_overflow",
21
21
  "no_fatal_console_errors"
22
22
  ];
23
+ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
24
+ "click",
25
+ "wait",
26
+ "wait_for_selector",
27
+ "wait_for_text"
28
+ ];
23
29
  var DEFAULT_VIEWPORTS = [
24
30
  { name: "desktop", width: 1280, height: 800 }
25
31
  ];
@@ -72,6 +78,41 @@ function normalizeViewports(value) {
72
78
  function isSupportedCheckType(value) {
73
79
  return RIDDLE_PROOF_PROFILE_CHECK_TYPES.includes(value);
74
80
  }
81
+ function normalizeSetupActionType(value, index) {
82
+ const normalized = String(value || "").trim().replace(/-/g, "_");
83
+ if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
84
+ return normalized;
85
+ }
86
+ throw new Error(`target.setup_actions[${index}].type ${value || "(missing)"} is not supported. Supported actions: ${RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.join(", ")}`);
87
+ }
88
+ function normalizeSetupAction(input, index) {
89
+ if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
90
+ const type = normalizeSetupActionType(stringValue(input.type), index);
91
+ const selector = stringValue(input.selector);
92
+ if ((type === "click" || type === "wait_for_selector" || type === "wait_for_text") && !selector) {
93
+ throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
94
+ }
95
+ if (type === "wait_for_text" && !stringValue(input.text) && !stringValue(input.pattern)) {
96
+ throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
97
+ }
98
+ return {
99
+ type,
100
+ selector,
101
+ text: stringValue(input.text),
102
+ pattern: stringValue(input.pattern),
103
+ flags: stringValue(input.flags),
104
+ index: numberValue(input.index),
105
+ ms: numberValue(input.ms) ?? numberValue(input.wait_ms) ?? numberValue(input.waitMs),
106
+ timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
107
+ after_ms: numberValue(input.after_ms) ?? numberValue(input.afterMs),
108
+ continue_on_failure: input.continue_on_failure === true || input.continueOnFailure === true
109
+ };
110
+ }
111
+ function normalizeSetupActions(value) {
112
+ if (value === void 0) return void 0;
113
+ if (!Array.isArray(value)) throw new Error("target.setup_actions must be an array.");
114
+ return value.map(normalizeSetupAction);
115
+ }
75
116
  function normalizeCheck(input, index) {
76
117
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
77
118
  const type = stringValue(input.type);
@@ -139,7 +180,8 @@ function normalizeRiddleProofProfile(input, options = {}) {
139
180
  viewports: options.viewports?.length ? options.viewports : normalizeViewports(targetInput.viewports),
140
181
  auth: stringValue(targetInput.auth) || "none",
141
182
  wait_for_selector: stringValue(targetInput.wait_for_selector) || stringValue(targetInput.waitForSelector),
142
- wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs)
183
+ wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs),
184
+ setup_actions: normalizeSetupActions(targetInput.setup_actions ?? targetInput.setupActions)
143
185
  },
144
186
  checks,
145
187
  artifacts: Array.isArray(input.artifacts) ? input.artifacts.map((item) => String(item)).filter(Boolean) : ["screenshot", "console", "dom_summary", "proof_json"],
@@ -312,6 +354,48 @@ function assessCheckFromEvidence(check, evidence) {
312
354
  message: "Unsupported check type."
313
355
  };
314
356
  }
357
+ function assessSetupActionsFromEvidence(profile, evidence) {
358
+ if (!profile.target.setup_actions?.length) return void 0;
359
+ const actionCount = profile.target.setup_actions.length;
360
+ const failed = [];
361
+ const viewports = evidence.viewports || [];
362
+ for (const viewport of viewports) {
363
+ const results = viewport.setup_action_results || [];
364
+ for (const result of results) {
365
+ if (result.ok === false) {
366
+ failed.push({
367
+ viewport: viewport.name,
368
+ action: result.action ?? result.type ?? null,
369
+ selector: result.selector ?? null,
370
+ reason: result.reason ?? result.error ?? null
371
+ });
372
+ }
373
+ }
374
+ if (results.length < actionCount && results.every((result) => result.ok !== false)) {
375
+ failed.push({
376
+ viewport: viewport.name,
377
+ action: "setup_actions",
378
+ selector: null,
379
+ reason: `missing setup action results: ${results.length}/${actionCount}`
380
+ });
381
+ }
382
+ }
383
+ return {
384
+ type: "setup_actions_succeeded",
385
+ label: "setup actions succeeded",
386
+ status: failed.length ? "failed" : "passed",
387
+ evidence: {
388
+ action_count: actionCount,
389
+ viewports: viewports.map((viewport) => ({
390
+ name: viewport.name,
391
+ ok: (viewport.setup_action_results || []).length >= actionCount && (viewport.setup_action_results || []).every((result) => result.ok !== false),
392
+ result_count: (viewport.setup_action_results || []).length
393
+ })),
394
+ failed
395
+ },
396
+ message: failed.length ? `Setup actions failed in ${failed.length} viewport action(s).` : void 0
397
+ };
398
+ }
315
399
  function profileStatusFromEvidence(evidence, checks) {
316
400
  if (!evidence) return "proof_insufficient";
317
401
  const viewports = evidence.viewports || [];
@@ -323,7 +407,10 @@ function profileStatusFromEvidence(evidence, checks) {
323
407
  }
324
408
  function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
325
409
  const capturedAt = evidence?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
326
- const checks = evidence ? profile.checks.map((check) => assessCheckFromEvidence(check, evidence)) : [];
410
+ const checks = evidence ? [
411
+ assessSetupActionsFromEvidence(profile, evidence),
412
+ ...profile.checks.map((check) => assessCheckFromEvidence(check, evidence))
413
+ ].filter((check) => Boolean(check)) : [];
327
414
  const status = profileStatusFromEvidence(evidence, checks);
328
415
  const firstViewport = evidence?.viewports?.[0];
329
416
  const screenshots = (evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => Boolean(label));
@@ -437,6 +524,47 @@ function textMatches(sample, check) {
437
524
  function assessProfile(profile, evidence) {
438
525
  const checks = [];
439
526
  const viewports = evidence.viewports || [];
527
+ if (profile.target && Array.isArray(profile.target.setup_actions) && profile.target.setup_actions.length) {
528
+ const actionCount = profile.target.setup_actions.length;
529
+ const failed = [];
530
+ for (const viewport of viewports) {
531
+ const results = viewport.setup_action_results || [];
532
+ for (const result of results) {
533
+ if (result && result.ok === false) {
534
+ failed.push({
535
+ viewport: viewport.name,
536
+ action: result.action || result.type || null,
537
+ selector: result.selector || null,
538
+ reason: result.reason || result.error || null,
539
+ });
540
+ }
541
+ }
542
+ if (results.length < actionCount && results.every((result) => !result || result.ok !== false)) {
543
+ failed.push({
544
+ viewport: viewport.name,
545
+ action: "setup_actions",
546
+ selector: null,
547
+ reason: "missing setup action results: " + results.length + "/" + actionCount,
548
+ });
549
+ }
550
+ }
551
+ checks.push({
552
+ type: "setup_actions_succeeded",
553
+ label: "setup actions succeeded",
554
+ status: failed.length ? "failed" : "passed",
555
+ evidence: {
556
+ action_count: actionCount,
557
+ viewports: viewports.map((viewport) => ({
558
+ name: viewport.name,
559
+ ok: (viewport.setup_action_results || []).length >= actionCount
560
+ && (viewport.setup_action_results || []).every((result) => !result || result.ok !== false),
561
+ result_count: (viewport.setup_action_results || []).length,
562
+ })),
563
+ failed,
564
+ },
565
+ message: failed.length ? "Setup actions failed in " + failed.length + " viewport action(s)." : undefined,
566
+ });
567
+ }
440
568
  for (const check of profile.checks || []) {
441
569
  if (check.type === "route_loaded") {
442
570
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
@@ -599,6 +727,92 @@ function textMatches(sample, check) {
599
727
  }
600
728
  return String(sample || "").includes(check.text || "");
601
729
  }
730
+ function setupActionType(action) {
731
+ return String(action && action.type ? action.type : "").replace(/-/g, "_");
732
+ }
733
+ function setupNumber(value, fallback) {
734
+ const number = Number(value);
735
+ return Number.isFinite(number) && number >= 0 ? number : fallback;
736
+ }
737
+ function setupTextMatches(sample, action) {
738
+ if (action.pattern) {
739
+ try { return new RegExp(action.pattern, action.flags || "").test(sample || ""); } catch { return false; }
740
+ }
741
+ return String(sample || "").includes(action.text || "");
742
+ }
743
+ async function setupLocatorText(locator, index) {
744
+ return await locator.nth(index).textContent({ timeout: 1000 }).catch(() => "");
745
+ }
746
+ async function executeSetupAction(action, ordinal) {
747
+ const type = setupActionType(action);
748
+ const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null };
749
+ const timeout = setupNumber(action.timeout_ms, 5000);
750
+ try {
751
+ if (type === "wait") {
752
+ const ms = setupNumber(action.ms, 500);
753
+ await page.waitForTimeout(ms);
754
+ return { ...base, ok: true, ms };
755
+ }
756
+ if (type === "wait_for_selector") {
757
+ await page.waitForSelector(action.selector, { state: "visible", timeout });
758
+ return { ...base, ok: true, timeout_ms: timeout };
759
+ }
760
+ if (type === "click") {
761
+ const locator = page.locator(action.selector);
762
+ const count = await locator.count();
763
+ if (!count) return { ...base, reason: "selector_not_found", count };
764
+ let targetIndex = Number.isInteger(action.index) ? action.index : 0;
765
+ let matchedText = null;
766
+ if (action.text || action.pattern) {
767
+ targetIndex = -1;
768
+ for (let index = 0; index < count; index += 1) {
769
+ const text = await setupLocatorText(locator, index);
770
+ if (setupTextMatches(text, action)) {
771
+ targetIndex = index;
772
+ matchedText = text;
773
+ break;
774
+ }
775
+ }
776
+ if (targetIndex < 0) return { ...base, reason: "text_not_found", count };
777
+ }
778
+ if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex };
779
+ await locator.nth(targetIndex).click({ timeout });
780
+ return { ...base, ok: true, count, target_index: targetIndex, text: matchedText };
781
+ }
782
+ if (type === "wait_for_text") {
783
+ const locator = page.locator(action.selector);
784
+ const startedAt = Date.now();
785
+ let lastText = "";
786
+ while (Date.now() - startedAt <= timeout) {
787
+ const count = await locator.count().catch(() => 0);
788
+ for (let index = 0; index < count; index += 1) {
789
+ const text = await setupLocatorText(locator, index);
790
+ lastText = text || lastText;
791
+ if (setupTextMatches(text, action)) {
792
+ return { ...base, ok: true, text, target_index: index, timeout_ms: timeout };
793
+ }
794
+ }
795
+ await page.waitForTimeout(100);
796
+ }
797
+ return { ...base, reason: "text_not_found", text: lastText, timeout_ms: timeout };
798
+ }
799
+ return { ...base, reason: "unsupported_action" };
800
+ } catch (error) {
801
+ return { ...base, error: String(error && error.message ? error.message : error).slice(0, 1000) };
802
+ }
803
+ }
804
+ async function executeSetupActions(actions) {
805
+ const results = [];
806
+ for (let index = 0; index < (actions || []).length; index += 1) {
807
+ const action = actions[index] || {};
808
+ const result = await executeSetupAction(action, index);
809
+ results.push(result);
810
+ const afterMs = setupNumber(action.after_ms, 0);
811
+ if (afterMs) await page.waitForTimeout(afterMs);
812
+ if (result.ok === false && action.continue_on_failure !== true) break;
813
+ }
814
+ return results;
815
+ }
602
816
  function expectedPathFor(check) {
603
817
  return check.expected_path || new URL(targetUrl).pathname || "/";
604
818
  }
@@ -633,6 +847,9 @@ async function captureViewport(viewport) {
633
847
  if (!navigationError && profile.target.wait_ms) {
634
848
  await page.waitForTimeout(profile.target.wait_ms);
635
849
  }
850
+ const setupActionResults = (!navigationError && !waitError)
851
+ ? await executeSetupActions(profile.target.setup_actions || [])
852
+ : [];
636
853
  const dom = await page.evaluate(() => {
637
854
  const body = document.body;
638
855
  const documentElement = document.documentElement;
@@ -694,6 +911,7 @@ async function captureViewport(viewport) {
694
911
  overflow_px: Math.max(0, (dom.scroll_width || 0) - (dom.client_width || viewport.width)),
695
912
  selectors,
696
913
  text_matches,
914
+ setup_action_results: setupActionResults,
697
915
  screenshot_label: screenshotLabel,
698
916
  navigation_error: navigationError,
699
917
  wait_error: waitError,
@@ -793,6 +1011,7 @@ export {
793
1011
  RIDDLE_PROOF_PROFILE_RESULT_VERSION,
794
1012
  RIDDLE_PROOF_PROFILE_STATUSES,
795
1013
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
1014
+ RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES,
796
1015
  slugifyRiddleProofProfileName,
797
1016
  normalizeRiddleProofProfile,
798
1017
  resolveRiddleProofProfileTargetUrl,
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"],
@@ -7091,6 +7133,48 @@ function assessCheckFromEvidence(check, evidence) {
7091
7133
  message: "Unsupported check type."
7092
7134
  };
7093
7135
  }
7136
+ function assessSetupActionsFromEvidence(profile, evidence) {
7137
+ if (!profile.target.setup_actions?.length) return void 0;
7138
+ const actionCount = profile.target.setup_actions.length;
7139
+ const failed = [];
7140
+ const viewports = evidence.viewports || [];
7141
+ for (const viewport of viewports) {
7142
+ const results = viewport.setup_action_results || [];
7143
+ for (const result of results) {
7144
+ if (result.ok === false) {
7145
+ failed.push({
7146
+ viewport: viewport.name,
7147
+ action: result.action ?? result.type ?? null,
7148
+ selector: result.selector ?? null,
7149
+ reason: result.reason ?? result.error ?? null
7150
+ });
7151
+ }
7152
+ }
7153
+ if (results.length < actionCount && results.every((result) => result.ok !== false)) {
7154
+ failed.push({
7155
+ viewport: viewport.name,
7156
+ action: "setup_actions",
7157
+ selector: null,
7158
+ reason: `missing setup action results: ${results.length}/${actionCount}`
7159
+ });
7160
+ }
7161
+ }
7162
+ return {
7163
+ type: "setup_actions_succeeded",
7164
+ label: "setup actions succeeded",
7165
+ status: failed.length ? "failed" : "passed",
7166
+ evidence: {
7167
+ action_count: actionCount,
7168
+ viewports: viewports.map((viewport) => ({
7169
+ name: viewport.name,
7170
+ ok: (viewport.setup_action_results || []).length >= actionCount && (viewport.setup_action_results || []).every((result) => result.ok !== false),
7171
+ result_count: (viewport.setup_action_results || []).length
7172
+ })),
7173
+ failed
7174
+ },
7175
+ message: failed.length ? `Setup actions failed in ${failed.length} viewport action(s).` : void 0
7176
+ };
7177
+ }
7094
7178
  function profileStatusFromEvidence(evidence, checks) {
7095
7179
  if (!evidence) return "proof_insufficient";
7096
7180
  const viewports = evidence.viewports || [];
@@ -7102,7 +7186,10 @@ function profileStatusFromEvidence(evidence, checks) {
7102
7186
  }
7103
7187
  function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
7104
7188
  const capturedAt = evidence?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
7105
- const checks = evidence ? profile.checks.map((check) => assessCheckFromEvidence(check, evidence)) : [];
7189
+ const checks = evidence ? [
7190
+ assessSetupActionsFromEvidence(profile, evidence),
7191
+ ...profile.checks.map((check) => assessCheckFromEvidence(check, evidence))
7192
+ ].filter((check) => Boolean(check)) : [];
7106
7193
  const status = profileStatusFromEvidence(evidence, checks);
7107
7194
  const firstViewport = evidence?.viewports?.[0];
7108
7195
  const screenshots = (evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => Boolean(label));
@@ -7200,6 +7287,47 @@ function textMatches(sample, check) {
7200
7287
  function assessProfile(profile, evidence) {
7201
7288
  const checks = [];
7202
7289
  const viewports = evidence.viewports || [];
7290
+ if (profile.target && Array.isArray(profile.target.setup_actions) && profile.target.setup_actions.length) {
7291
+ const actionCount = profile.target.setup_actions.length;
7292
+ const failed = [];
7293
+ for (const viewport of viewports) {
7294
+ const results = viewport.setup_action_results || [];
7295
+ for (const result of results) {
7296
+ if (result && result.ok === false) {
7297
+ failed.push({
7298
+ viewport: viewport.name,
7299
+ action: result.action || result.type || null,
7300
+ selector: result.selector || null,
7301
+ reason: result.reason || result.error || null,
7302
+ });
7303
+ }
7304
+ }
7305
+ if (results.length < actionCount && results.every((result) => !result || result.ok !== false)) {
7306
+ failed.push({
7307
+ viewport: viewport.name,
7308
+ action: "setup_actions",
7309
+ selector: null,
7310
+ reason: "missing setup action results: " + results.length + "/" + actionCount,
7311
+ });
7312
+ }
7313
+ }
7314
+ checks.push({
7315
+ type: "setup_actions_succeeded",
7316
+ label: "setup actions succeeded",
7317
+ status: failed.length ? "failed" : "passed",
7318
+ evidence: {
7319
+ action_count: actionCount,
7320
+ viewports: viewports.map((viewport) => ({
7321
+ name: viewport.name,
7322
+ ok: (viewport.setup_action_results || []).length >= actionCount
7323
+ && (viewport.setup_action_results || []).every((result) => !result || result.ok !== false),
7324
+ result_count: (viewport.setup_action_results || []).length,
7325
+ })),
7326
+ failed,
7327
+ },
7328
+ message: failed.length ? "Setup actions failed in " + failed.length + " viewport action(s)." : undefined,
7329
+ });
7330
+ }
7203
7331
  for (const check of profile.checks || []) {
7204
7332
  if (check.type === "route_loaded") {
7205
7333
  const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
@@ -7362,6 +7490,92 @@ function textMatches(sample, check) {
7362
7490
  }
7363
7491
  return String(sample || "").includes(check.text || "");
7364
7492
  }
7493
+ function setupActionType(action) {
7494
+ return String(action && action.type ? action.type : "").replace(/-/g, "_");
7495
+ }
7496
+ function setupNumber(value, fallback) {
7497
+ const number = Number(value);
7498
+ return Number.isFinite(number) && number >= 0 ? number : fallback;
7499
+ }
7500
+ function setupTextMatches(sample, action) {
7501
+ if (action.pattern) {
7502
+ try { return new RegExp(action.pattern, action.flags || "").test(sample || ""); } catch { return false; }
7503
+ }
7504
+ return String(sample || "").includes(action.text || "");
7505
+ }
7506
+ async function setupLocatorText(locator, index) {
7507
+ return await locator.nth(index).textContent({ timeout: 1000 }).catch(() => "");
7508
+ }
7509
+ async function executeSetupAction(action, ordinal) {
7510
+ const type = setupActionType(action);
7511
+ const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null };
7512
+ const timeout = setupNumber(action.timeout_ms, 5000);
7513
+ try {
7514
+ if (type === "wait") {
7515
+ const ms = setupNumber(action.ms, 500);
7516
+ await page.waitForTimeout(ms);
7517
+ return { ...base, ok: true, ms };
7518
+ }
7519
+ if (type === "wait_for_selector") {
7520
+ await page.waitForSelector(action.selector, { state: "visible", timeout });
7521
+ return { ...base, ok: true, timeout_ms: timeout };
7522
+ }
7523
+ if (type === "click") {
7524
+ const locator = page.locator(action.selector);
7525
+ const count = await locator.count();
7526
+ if (!count) return { ...base, reason: "selector_not_found", count };
7527
+ let targetIndex = Number.isInteger(action.index) ? action.index : 0;
7528
+ let matchedText = null;
7529
+ if (action.text || action.pattern) {
7530
+ targetIndex = -1;
7531
+ for (let index = 0; index < count; index += 1) {
7532
+ const text = await setupLocatorText(locator, index);
7533
+ if (setupTextMatches(text, action)) {
7534
+ targetIndex = index;
7535
+ matchedText = text;
7536
+ break;
7537
+ }
7538
+ }
7539
+ if (targetIndex < 0) return { ...base, reason: "text_not_found", count };
7540
+ }
7541
+ if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex };
7542
+ await locator.nth(targetIndex).click({ timeout });
7543
+ return { ...base, ok: true, count, target_index: targetIndex, text: matchedText };
7544
+ }
7545
+ if (type === "wait_for_text") {
7546
+ const locator = page.locator(action.selector);
7547
+ const startedAt = Date.now();
7548
+ let lastText = "";
7549
+ while (Date.now() - startedAt <= timeout) {
7550
+ const count = await locator.count().catch(() => 0);
7551
+ for (let index = 0; index < count; index += 1) {
7552
+ const text = await setupLocatorText(locator, index);
7553
+ lastText = text || lastText;
7554
+ if (setupTextMatches(text, action)) {
7555
+ return { ...base, ok: true, text, target_index: index, timeout_ms: timeout };
7556
+ }
7557
+ }
7558
+ await page.waitForTimeout(100);
7559
+ }
7560
+ return { ...base, reason: "text_not_found", text: lastText, timeout_ms: timeout };
7561
+ }
7562
+ return { ...base, reason: "unsupported_action" };
7563
+ } catch (error) {
7564
+ return { ...base, error: String(error && error.message ? error.message : error).slice(0, 1000) };
7565
+ }
7566
+ }
7567
+ async function executeSetupActions(actions) {
7568
+ const results = [];
7569
+ for (let index = 0; index < (actions || []).length; index += 1) {
7570
+ const action = actions[index] || {};
7571
+ const result = await executeSetupAction(action, index);
7572
+ results.push(result);
7573
+ const afterMs = setupNumber(action.after_ms, 0);
7574
+ if (afterMs) await page.waitForTimeout(afterMs);
7575
+ if (result.ok === false && action.continue_on_failure !== true) break;
7576
+ }
7577
+ return results;
7578
+ }
7365
7579
  function expectedPathFor(check) {
7366
7580
  return check.expected_path || new URL(targetUrl).pathname || "/";
7367
7581
  }
@@ -7396,6 +7610,9 @@ async function captureViewport(viewport) {
7396
7610
  if (!navigationError && profile.target.wait_ms) {
7397
7611
  await page.waitForTimeout(profile.target.wait_ms);
7398
7612
  }
7613
+ const setupActionResults = (!navigationError && !waitError)
7614
+ ? await executeSetupActions(profile.target.setup_actions || [])
7615
+ : [];
7399
7616
  const dom = await page.evaluate(() => {
7400
7617
  const body = document.body;
7401
7618
  const documentElement = document.documentElement;
@@ -7457,6 +7674,7 @@ async function captureViewport(viewport) {
7457
7674
  overflow_px: Math.max(0, (dom.scroll_width || 0) - (dom.client_width || viewport.width)),
7458
7675
  selectors,
7459
7676
  text_matches,
7677
+ setup_action_results: setupActionResults,
7460
7678
  screenshot_label: screenshotLabel,
7461
7679
  navigation_error: navigationError,
7462
7680
  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-NUSUGO2P.js";
13
13
  import {
14
14
  createRiddleApiClient,
15
15
  parseRiddleViewport