@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 +16 -4
- package/dist/basic-gameplay.cjs +4 -0
- package/dist/basic-gameplay.d.cts +1 -1
- package/dist/basic-gameplay.d.ts +1 -1
- package/dist/basic-gameplay.js +1 -1
- package/dist/{chunk-AHW7SL3W.js → chunk-5A4SL6CX.js} +4 -0
- package/dist/{chunk-7NMAU4DP.js → chunk-NUSUGO2P.js} +221 -2
- package/dist/cli.cjs +220 -2
- package/dist/cli.js +1 -1
- package/dist/index.cjs +226 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/profile.cjs +222 -2
- package/dist/profile.d.cts +17 -1
- package/dist/profile.d.ts +17 -1
- package/dist/profile.js +3 -1
- package/dist/proof-run-engine.d.cts +3 -3
- package/dist/proof-run-engine.d.ts +3 -3
- package/examples/profiles/page-content-basic.json +4 -1
- package/package.json +1 -1
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
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
package/dist/basic-gameplay.cjs
CHANGED
|
@@ -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 & {});
|
package/dist/basic-gameplay.d.ts
CHANGED
|
@@ -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 & {});
|
package/dist/basic-gameplay.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
extractBasicGameplayEvidence,
|
|
17
17
|
resolveBasicGameplayProgressionCheckWithArtifactScreenshots,
|
|
18
18
|
sanitizeBasicGameplayJsonString
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-5A4SL6CX.js";
|
|
20
20
|
export {
|
|
21
21
|
BASIC_GAMEPLAY_ACTION_TYPES,
|
|
22
22
|
BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES,
|
|
@@ -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 ?
|
|
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 ?
|
|
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,
|