@riddledc/riddle-proof 0.7.54 → 0.7.56
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 +19 -0
- package/dist/{chunk-H3DOYKAQ.js → chunk-2JFDTCIC.js} +138 -0
- package/dist/cli.cjs +140 -1
- package/dist/cli.js +3 -2
- package/dist/index.cjs +138 -0
- package/dist/index.js +1 -1
- package/dist/profile.cjs +138 -0
- package/dist/profile.d.cts +3 -1
- package/dist/profile.d.ts +3 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -174,6 +174,9 @@ riddle-proof-loop run-profile \
|
|
|
174
174
|
Hosted profile runs emit Riddle poll progress to stderr while waiting. Use
|
|
175
175
|
`--quiet` to suppress progress lines, or `--progress-every-ms` to tune the
|
|
176
176
|
heartbeat cadence for long route-inventory or workflow profiles.
|
|
177
|
+
Strict Riddle script validation remains on by default; use `--strict=false`
|
|
178
|
+
only for trusted profile-generated scripts when the validator blocks a large
|
|
179
|
+
workflow profile.
|
|
177
180
|
|
|
178
181
|
The package includes a generic starter profile at
|
|
179
182
|
`examples/profiles/page-content-basic.json`; copy that shape into a repository
|
|
@@ -303,6 +306,22 @@ These checks are useful for audit/no-diff profiles where the product should
|
|
|
303
306
|
show a fallback state and avoid rendering stale loaders, duplicate rows, or
|
|
304
307
|
missing-resource iframes.
|
|
305
308
|
|
|
309
|
+
Use `url_search_param_equals` and `url_search_param_absent` when the final URL
|
|
310
|
+
is part of the contract, such as deep-link recovery that must drop a stale
|
|
311
|
+
local identifier while preserving shareable query state:
|
|
312
|
+
|
|
313
|
+
```json
|
|
314
|
+
[
|
|
315
|
+
{ "type": "url_search_param_absent", "param": "seq" },
|
|
316
|
+
{ "type": "url_search_param_equals", "param": "song", "expected_value": "monkberry-moon-delight-tab" },
|
|
317
|
+
{ "type": "url_search_param_equals", "param": "view", "expected_value": "trainer" }
|
|
318
|
+
]
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The check uses the captured browser URL for each viewport after setup actions
|
|
322
|
+
and waits have completed. `search_param` and `key` are accepted as aliases for
|
|
323
|
+
`param`; `value` is accepted as an alias for `expected_value`.
|
|
324
|
+
|
|
306
325
|
Use `selector_text_order` when a table, list, or card group must show visible
|
|
307
326
|
items in a specific order after setup actions such as sorting or filtering:
|
|
308
327
|
|
|
@@ -12,6 +12,8 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
|
|
|
12
12
|
];
|
|
13
13
|
var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
14
14
|
"route_loaded",
|
|
15
|
+
"url_search_param_equals",
|
|
16
|
+
"url_search_param_absent",
|
|
15
17
|
"selector_visible",
|
|
16
18
|
"selector_absent",
|
|
17
19
|
"selector_count_at_least",
|
|
@@ -488,6 +490,13 @@ function normalizeCheck(input, index) {
|
|
|
488
490
|
if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
489
491
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
490
492
|
}
|
|
493
|
+
if ((type === "url_search_param_equals" || type === "url_search_param_absent") && !stringValue(input.param) && !stringValue(input.search_param) && !stringValue(input.searchParam) && !stringValue(input.key)) {
|
|
494
|
+
throw new Error(`checks[${index}] ${type} requires param.`);
|
|
495
|
+
}
|
|
496
|
+
const expectedValue = stringFromOwn(input, "expected_value", "expectedValue", "value");
|
|
497
|
+
if (type === "url_search_param_equals" && expectedValue === void 0) {
|
|
498
|
+
throw new Error(`checks[${index}] url_search_param_equals requires expected_value.`);
|
|
499
|
+
}
|
|
491
500
|
if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
|
|
492
501
|
throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
|
|
493
502
|
}
|
|
@@ -508,6 +517,8 @@ function normalizeCheck(input, index) {
|
|
|
508
517
|
type,
|
|
509
518
|
label: stringValue(input.label),
|
|
510
519
|
expected_path: stringValue(input.expected_path),
|
|
520
|
+
param: stringValue(input.param) || stringValue(input.search_param) || stringValue(input.searchParam) || stringValue(input.key),
|
|
521
|
+
expected_value: expectedValue,
|
|
511
522
|
expected_routes: expectedRoutes,
|
|
512
523
|
selector: stringValue(input.selector),
|
|
513
524
|
expected_texts: expectedTexts,
|
|
@@ -760,6 +771,32 @@ function successfulRoute(route, targetUrl) {
|
|
|
760
771
|
const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
|
|
761
772
|
return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
|
|
762
773
|
}
|
|
774
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
775
|
+
if (!value) return void 0;
|
|
776
|
+
try {
|
|
777
|
+
return targetUrl ? new URL(value, targetUrl) : new URL(value);
|
|
778
|
+
} catch {
|
|
779
|
+
try {
|
|
780
|
+
return new URL(value);
|
|
781
|
+
} catch {
|
|
782
|
+
return void 0;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
787
|
+
return parseEvidenceUrl(viewport.url, targetUrl) || parseEvidenceUrl(viewport.route?.observed, targetUrl) || parseEvidenceUrl(viewport.route?.requested, targetUrl) || parseEvidenceUrl(targetUrl, void 0);
|
|
788
|
+
}
|
|
789
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
790
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
791
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
792
|
+
return {
|
|
793
|
+
viewport: viewport.name,
|
|
794
|
+
url: url?.href || null,
|
|
795
|
+
value: values.length ? values[0] : null,
|
|
796
|
+
values,
|
|
797
|
+
present: values.length > 0
|
|
798
|
+
};
|
|
799
|
+
}
|
|
763
800
|
function viewportsForCheck(check, viewports) {
|
|
764
801
|
if (!check.viewports?.length) return viewports;
|
|
765
802
|
const names = new Set(check.viewports);
|
|
@@ -797,6 +834,44 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
797
834
|
message: failed.length ? `Route did not load as ${expectedPath} in ${failed.length} viewport(s).` : void 0
|
|
798
835
|
};
|
|
799
836
|
}
|
|
837
|
+
if (check.type === "url_search_param_equals") {
|
|
838
|
+
const param = check.param || "";
|
|
839
|
+
const expectedValue = check.expected_value ?? "";
|
|
840
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
841
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
842
|
+
return {
|
|
843
|
+
type: check.type,
|
|
844
|
+
label: checkLabel(check),
|
|
845
|
+
status: failed.length ? "failed" : "passed",
|
|
846
|
+
evidence: {
|
|
847
|
+
param,
|
|
848
|
+
expected_value: expectedValue,
|
|
849
|
+
observed_values: observations.map((observation) => observation.value),
|
|
850
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
851
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
852
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
853
|
+
},
|
|
854
|
+
message: failed.length ? `URL search param ${param} did not equal ${JSON.stringify(expectedValue)} in ${failed.length} viewport(s).` : void 0
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
if (check.type === "url_search_param_absent") {
|
|
858
|
+
const param = check.param || "";
|
|
859
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
860
|
+
const failed = observations.filter((observation) => observation.present);
|
|
861
|
+
return {
|
|
862
|
+
type: check.type,
|
|
863
|
+
label: checkLabel(check),
|
|
864
|
+
status: failed.length ? "failed" : "passed",
|
|
865
|
+
evidence: {
|
|
866
|
+
param,
|
|
867
|
+
observed_values: observations.map((observation) => observation.value),
|
|
868
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
869
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
870
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
871
|
+
},
|
|
872
|
+
message: failed.length ? `URL search param ${param} was present in ${failed.length} viewport(s).` : void 0
|
|
873
|
+
};
|
|
874
|
+
}
|
|
800
875
|
if (check.type === "selector_visible") {
|
|
801
876
|
const key = selectorKey(check);
|
|
802
877
|
const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.visible_count || 0) < 1);
|
|
@@ -1332,6 +1407,29 @@ function routePathMatches(observed, expected, targetUrl) {
|
|
|
1332
1407
|
function routeOk(route, targetUrl) {
|
|
1333
1408
|
return Boolean(route && (route.matched || routePathMatches(route.observed, route.expected_path, targetUrl)) && !route.error && (route.http_status == null || route.http_status < 400));
|
|
1334
1409
|
}
|
|
1410
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
1411
|
+
if (!value) return null;
|
|
1412
|
+
try { return targetUrl ? new URL(value, targetUrl) : new URL(value); } catch {}
|
|
1413
|
+
try { return new URL(value); } catch {}
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
1417
|
+
return parseEvidenceUrl(viewport && viewport.url, targetUrl)
|
|
1418
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.observed, targetUrl)
|
|
1419
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.requested, targetUrl)
|
|
1420
|
+
|| parseEvidenceUrl(targetUrl, null);
|
|
1421
|
+
}
|
|
1422
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
1423
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
1424
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
1425
|
+
return {
|
|
1426
|
+
viewport: viewport && viewport.name,
|
|
1427
|
+
url: url ? url.href : null,
|
|
1428
|
+
value: values.length ? values[0] : null,
|
|
1429
|
+
values,
|
|
1430
|
+
present: values.length > 0,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1335
1433
|
function textMatches(sample, check) {
|
|
1336
1434
|
if (check.pattern) {
|
|
1337
1435
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -1636,6 +1734,46 @@ function assessProfile(profile, evidence) {
|
|
|
1636
1734
|
});
|
|
1637
1735
|
continue;
|
|
1638
1736
|
}
|
|
1737
|
+
if (check.type === "url_search_param_equals") {
|
|
1738
|
+
const param = check.param || "";
|
|
1739
|
+
const expectedValue = check.expected_value == null ? "" : String(check.expected_value);
|
|
1740
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
1741
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
1742
|
+
checks.push({
|
|
1743
|
+
type: check.type,
|
|
1744
|
+
label: check.label || check.type,
|
|
1745
|
+
status: failed.length ? "failed" : "passed",
|
|
1746
|
+
evidence: {
|
|
1747
|
+
param,
|
|
1748
|
+
expected_value: expectedValue,
|
|
1749
|
+
observed_values: observations.map((observation) => observation.value),
|
|
1750
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
1751
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
1752
|
+
viewports: observations,
|
|
1753
|
+
},
|
|
1754
|
+
message: failed.length ? "URL search param " + param + " did not equal " + JSON.stringify(expectedValue) + " in " + failed.length + " viewport(s)." : undefined,
|
|
1755
|
+
});
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
if (check.type === "url_search_param_absent") {
|
|
1759
|
+
const param = check.param || "";
|
|
1760
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
1761
|
+
const failed = observations.filter((observation) => observation.present);
|
|
1762
|
+
checks.push({
|
|
1763
|
+
type: check.type,
|
|
1764
|
+
label: check.label || check.type,
|
|
1765
|
+
status: failed.length ? "failed" : "passed",
|
|
1766
|
+
evidence: {
|
|
1767
|
+
param,
|
|
1768
|
+
observed_values: observations.map((observation) => observation.value),
|
|
1769
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
1770
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
1771
|
+
viewports: observations,
|
|
1772
|
+
},
|
|
1773
|
+
message: failed.length ? "URL search param " + param + " was present in " + failed.length + " viewport(s)." : undefined,
|
|
1774
|
+
});
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1639
1777
|
if (check.type === "selector_visible") {
|
|
1640
1778
|
const selector = check.selector || "";
|
|
1641
1779
|
const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
|
package/dist/cli.cjs
CHANGED
|
@@ -6885,6 +6885,8 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
|
|
|
6885
6885
|
];
|
|
6886
6886
|
var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
6887
6887
|
"route_loaded",
|
|
6888
|
+
"url_search_param_equals",
|
|
6889
|
+
"url_search_param_absent",
|
|
6888
6890
|
"selector_visible",
|
|
6889
6891
|
"selector_absent",
|
|
6890
6892
|
"selector_count_at_least",
|
|
@@ -7361,6 +7363,13 @@ function normalizeCheck(input, index) {
|
|
|
7361
7363
|
if ((type === "text_visible" || type === "text_absent") && !stringValue2(input.text) && !stringValue2(input.pattern)) {
|
|
7362
7364
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
7363
7365
|
}
|
|
7366
|
+
if ((type === "url_search_param_equals" || type === "url_search_param_absent") && !stringValue2(input.param) && !stringValue2(input.search_param) && !stringValue2(input.searchParam) && !stringValue2(input.key)) {
|
|
7367
|
+
throw new Error(`checks[${index}] ${type} requires param.`);
|
|
7368
|
+
}
|
|
7369
|
+
const expectedValue = stringFromOwn(input, "expected_value", "expectedValue", "value");
|
|
7370
|
+
if (type === "url_search_param_equals" && expectedValue === void 0) {
|
|
7371
|
+
throw new Error(`checks[${index}] url_search_param_equals requires expected_value.`);
|
|
7372
|
+
}
|
|
7364
7373
|
if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
|
|
7365
7374
|
throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
|
|
7366
7375
|
}
|
|
@@ -7381,6 +7390,8 @@ function normalizeCheck(input, index) {
|
|
|
7381
7390
|
type,
|
|
7382
7391
|
label: stringValue2(input.label),
|
|
7383
7392
|
expected_path: stringValue2(input.expected_path),
|
|
7393
|
+
param: stringValue2(input.param) || stringValue2(input.search_param) || stringValue2(input.searchParam) || stringValue2(input.key),
|
|
7394
|
+
expected_value: expectedValue,
|
|
7384
7395
|
expected_routes: expectedRoutes,
|
|
7385
7396
|
selector: stringValue2(input.selector),
|
|
7386
7397
|
expected_texts: expectedTexts,
|
|
@@ -7633,6 +7644,32 @@ function successfulRoute(route, targetUrl) {
|
|
|
7633
7644
|
const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
|
|
7634
7645
|
return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
|
|
7635
7646
|
}
|
|
7647
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
7648
|
+
if (!value) return void 0;
|
|
7649
|
+
try {
|
|
7650
|
+
return targetUrl ? new URL(value, targetUrl) : new URL(value);
|
|
7651
|
+
} catch {
|
|
7652
|
+
try {
|
|
7653
|
+
return new URL(value);
|
|
7654
|
+
} catch {
|
|
7655
|
+
return void 0;
|
|
7656
|
+
}
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7659
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
7660
|
+
return parseEvidenceUrl(viewport.url, targetUrl) || parseEvidenceUrl(viewport.route?.observed, targetUrl) || parseEvidenceUrl(viewport.route?.requested, targetUrl) || parseEvidenceUrl(targetUrl, void 0);
|
|
7661
|
+
}
|
|
7662
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
7663
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
7664
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
7665
|
+
return {
|
|
7666
|
+
viewport: viewport.name,
|
|
7667
|
+
url: url?.href || null,
|
|
7668
|
+
value: values.length ? values[0] : null,
|
|
7669
|
+
values,
|
|
7670
|
+
present: values.length > 0
|
|
7671
|
+
};
|
|
7672
|
+
}
|
|
7636
7673
|
function viewportsForCheck(check, viewports) {
|
|
7637
7674
|
if (!check.viewports?.length) return viewports;
|
|
7638
7675
|
const names = new Set(check.viewports);
|
|
@@ -7670,6 +7707,44 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
7670
7707
|
message: failed.length ? `Route did not load as ${expectedPath} in ${failed.length} viewport(s).` : void 0
|
|
7671
7708
|
};
|
|
7672
7709
|
}
|
|
7710
|
+
if (check.type === "url_search_param_equals") {
|
|
7711
|
+
const param = check.param || "";
|
|
7712
|
+
const expectedValue = check.expected_value ?? "";
|
|
7713
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
7714
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
7715
|
+
return {
|
|
7716
|
+
type: check.type,
|
|
7717
|
+
label: checkLabel(check),
|
|
7718
|
+
status: failed.length ? "failed" : "passed",
|
|
7719
|
+
evidence: {
|
|
7720
|
+
param,
|
|
7721
|
+
expected_value: expectedValue,
|
|
7722
|
+
observed_values: observations.map((observation) => observation.value),
|
|
7723
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
7724
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
7725
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
7726
|
+
},
|
|
7727
|
+
message: failed.length ? `URL search param ${param} did not equal ${JSON.stringify(expectedValue)} in ${failed.length} viewport(s).` : void 0
|
|
7728
|
+
};
|
|
7729
|
+
}
|
|
7730
|
+
if (check.type === "url_search_param_absent") {
|
|
7731
|
+
const param = check.param || "";
|
|
7732
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
7733
|
+
const failed = observations.filter((observation) => observation.present);
|
|
7734
|
+
return {
|
|
7735
|
+
type: check.type,
|
|
7736
|
+
label: checkLabel(check),
|
|
7737
|
+
status: failed.length ? "failed" : "passed",
|
|
7738
|
+
evidence: {
|
|
7739
|
+
param,
|
|
7740
|
+
observed_values: observations.map((observation) => observation.value),
|
|
7741
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
7742
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
7743
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
7744
|
+
},
|
|
7745
|
+
message: failed.length ? `URL search param ${param} was present in ${failed.length} viewport(s).` : void 0
|
|
7746
|
+
};
|
|
7747
|
+
}
|
|
7673
7748
|
if (check.type === "selector_visible") {
|
|
7674
7749
|
const key = selectorKey(check);
|
|
7675
7750
|
const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.visible_count || 0) < 1);
|
|
@@ -8189,6 +8264,29 @@ function routePathMatches(observed, expected, targetUrl) {
|
|
|
8189
8264
|
function routeOk(route, targetUrl) {
|
|
8190
8265
|
return Boolean(route && (route.matched || routePathMatches(route.observed, route.expected_path, targetUrl)) && !route.error && (route.http_status == null || route.http_status < 400));
|
|
8191
8266
|
}
|
|
8267
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
8268
|
+
if (!value) return null;
|
|
8269
|
+
try { return targetUrl ? new URL(value, targetUrl) : new URL(value); } catch {}
|
|
8270
|
+
try { return new URL(value); } catch {}
|
|
8271
|
+
return null;
|
|
8272
|
+
}
|
|
8273
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
8274
|
+
return parseEvidenceUrl(viewport && viewport.url, targetUrl)
|
|
8275
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.observed, targetUrl)
|
|
8276
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.requested, targetUrl)
|
|
8277
|
+
|| parseEvidenceUrl(targetUrl, null);
|
|
8278
|
+
}
|
|
8279
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
8280
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
8281
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
8282
|
+
return {
|
|
8283
|
+
viewport: viewport && viewport.name,
|
|
8284
|
+
url: url ? url.href : null,
|
|
8285
|
+
value: values.length ? values[0] : null,
|
|
8286
|
+
values,
|
|
8287
|
+
present: values.length > 0,
|
|
8288
|
+
};
|
|
8289
|
+
}
|
|
8192
8290
|
function textMatches(sample, check) {
|
|
8193
8291
|
if (check.pattern) {
|
|
8194
8292
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -8493,6 +8591,46 @@ function assessProfile(profile, evidence) {
|
|
|
8493
8591
|
});
|
|
8494
8592
|
continue;
|
|
8495
8593
|
}
|
|
8594
|
+
if (check.type === "url_search_param_equals") {
|
|
8595
|
+
const param = check.param || "";
|
|
8596
|
+
const expectedValue = check.expected_value == null ? "" : String(check.expected_value);
|
|
8597
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
8598
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
8599
|
+
checks.push({
|
|
8600
|
+
type: check.type,
|
|
8601
|
+
label: check.label || check.type,
|
|
8602
|
+
status: failed.length ? "failed" : "passed",
|
|
8603
|
+
evidence: {
|
|
8604
|
+
param,
|
|
8605
|
+
expected_value: expectedValue,
|
|
8606
|
+
observed_values: observations.map((observation) => observation.value),
|
|
8607
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
8608
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
8609
|
+
viewports: observations,
|
|
8610
|
+
},
|
|
8611
|
+
message: failed.length ? "URL search param " + param + " did not equal " + JSON.stringify(expectedValue) + " in " + failed.length + " viewport(s)." : undefined,
|
|
8612
|
+
});
|
|
8613
|
+
continue;
|
|
8614
|
+
}
|
|
8615
|
+
if (check.type === "url_search_param_absent") {
|
|
8616
|
+
const param = check.param || "";
|
|
8617
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
8618
|
+
const failed = observations.filter((observation) => observation.present);
|
|
8619
|
+
checks.push({
|
|
8620
|
+
type: check.type,
|
|
8621
|
+
label: check.label || check.type,
|
|
8622
|
+
status: failed.length ? "failed" : "passed",
|
|
8623
|
+
evidence: {
|
|
8624
|
+
param,
|
|
8625
|
+
observed_values: observations.map((observation) => observation.value),
|
|
8626
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
8627
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
8628
|
+
viewports: observations,
|
|
8629
|
+
},
|
|
8630
|
+
message: failed.length ? "URL search param " + param + " was present in " + failed.length + " viewport(s)." : undefined,
|
|
8631
|
+
});
|
|
8632
|
+
continue;
|
|
8633
|
+
}
|
|
8496
8634
|
if (check.type === "selector_visible") {
|
|
8497
8635
|
const selector = check.selector || "";
|
|
8498
8636
|
const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
|
|
@@ -10138,7 +10276,7 @@ function usage() {
|
|
|
10138
10276
|
" riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
|
|
10139
10277
|
" riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
|
|
10140
10278
|
" riddle-proof-loop status --state-path <path>",
|
|
10141
|
-
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--output <dir>] [--quiet]",
|
|
10279
|
+
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false] [--output <dir>] [--quiet]",
|
|
10142
10280
|
" riddle-proof-loop riddle-preview-deploy <build-dir> <label>",
|
|
10143
10281
|
" riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
|
|
10144
10282
|
" riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720] [--strict true|false]",
|
|
@@ -10496,6 +10634,7 @@ async function runProfileForCli(profile, options) {
|
|
|
10496
10634
|
profile,
|
|
10497
10635
|
optionString(options, "timeout") ? Number(optionString(options, "timeout")) : void 0
|
|
10498
10636
|
),
|
|
10637
|
+
strict: optionBoolean(options, "strict"),
|
|
10499
10638
|
sync: options.sync === true ? true : void 0
|
|
10500
10639
|
});
|
|
10501
10640
|
} catch (error) {
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
profileStatusExitCode,
|
|
11
11
|
resolveRiddleProofProfileTargetUrl,
|
|
12
12
|
resolveRiddleProofProfileTimeoutSec
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2JFDTCIC.js";
|
|
14
14
|
import {
|
|
15
15
|
createRiddleApiClient,
|
|
16
16
|
parseRiddleViewport
|
|
@@ -44,7 +44,7 @@ function usage() {
|
|
|
44
44
|
" riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
|
|
45
45
|
" riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
|
|
46
46
|
" riddle-proof-loop status --state-path <path>",
|
|
47
|
-
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--output <dir>] [--quiet]",
|
|
47
|
+
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false] [--output <dir>] [--quiet]",
|
|
48
48
|
" riddle-proof-loop riddle-preview-deploy <build-dir> <label>",
|
|
49
49
|
" riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
|
|
50
50
|
" riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720] [--strict true|false]",
|
|
@@ -402,6 +402,7 @@ async function runProfileForCli(profile, options) {
|
|
|
402
402
|
profile,
|
|
403
403
|
optionString(options, "timeout") ? Number(optionString(options, "timeout")) : void 0
|
|
404
404
|
),
|
|
405
|
+
strict: optionBoolean(options, "strict"),
|
|
405
406
|
sync: options.sync === true ? true : void 0
|
|
406
407
|
});
|
|
407
408
|
} catch (error) {
|
package/dist/index.cjs
CHANGED
|
@@ -8726,6 +8726,8 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
|
|
|
8726
8726
|
];
|
|
8727
8727
|
var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
8728
8728
|
"route_loaded",
|
|
8729
|
+
"url_search_param_equals",
|
|
8730
|
+
"url_search_param_absent",
|
|
8729
8731
|
"selector_visible",
|
|
8730
8732
|
"selector_absent",
|
|
8731
8733
|
"selector_count_at_least",
|
|
@@ -9202,6 +9204,13 @@ function normalizeCheck(input, index) {
|
|
|
9202
9204
|
if ((type === "text_visible" || type === "text_absent") && !stringValue5(input.text) && !stringValue5(input.pattern)) {
|
|
9203
9205
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
9204
9206
|
}
|
|
9207
|
+
if ((type === "url_search_param_equals" || type === "url_search_param_absent") && !stringValue5(input.param) && !stringValue5(input.search_param) && !stringValue5(input.searchParam) && !stringValue5(input.key)) {
|
|
9208
|
+
throw new Error(`checks[${index}] ${type} requires param.`);
|
|
9209
|
+
}
|
|
9210
|
+
const expectedValue = stringFromOwn(input, "expected_value", "expectedValue", "value");
|
|
9211
|
+
if (type === "url_search_param_equals" && expectedValue === void 0) {
|
|
9212
|
+
throw new Error(`checks[${index}] url_search_param_equals requires expected_value.`);
|
|
9213
|
+
}
|
|
9205
9214
|
if (type === "selector_count_at_least" && numberValue3(input.min_count) === void 0) {
|
|
9206
9215
|
throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
|
|
9207
9216
|
}
|
|
@@ -9222,6 +9231,8 @@ function normalizeCheck(input, index) {
|
|
|
9222
9231
|
type,
|
|
9223
9232
|
label: stringValue5(input.label),
|
|
9224
9233
|
expected_path: stringValue5(input.expected_path),
|
|
9234
|
+
param: stringValue5(input.param) || stringValue5(input.search_param) || stringValue5(input.searchParam) || stringValue5(input.key),
|
|
9235
|
+
expected_value: expectedValue,
|
|
9225
9236
|
expected_routes: expectedRoutes,
|
|
9226
9237
|
selector: stringValue5(input.selector),
|
|
9227
9238
|
expected_texts: expectedTexts,
|
|
@@ -9474,6 +9485,32 @@ function successfulRoute(route, targetUrl) {
|
|
|
9474
9485
|
const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
|
|
9475
9486
|
return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
|
|
9476
9487
|
}
|
|
9488
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
9489
|
+
if (!value) return void 0;
|
|
9490
|
+
try {
|
|
9491
|
+
return targetUrl ? new URL(value, targetUrl) : new URL(value);
|
|
9492
|
+
} catch {
|
|
9493
|
+
try {
|
|
9494
|
+
return new URL(value);
|
|
9495
|
+
} catch {
|
|
9496
|
+
return void 0;
|
|
9497
|
+
}
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9500
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
9501
|
+
return parseEvidenceUrl(viewport.url, targetUrl) || parseEvidenceUrl(viewport.route?.observed, targetUrl) || parseEvidenceUrl(viewport.route?.requested, targetUrl) || parseEvidenceUrl(targetUrl, void 0);
|
|
9502
|
+
}
|
|
9503
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
9504
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
9505
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
9506
|
+
return {
|
|
9507
|
+
viewport: viewport.name,
|
|
9508
|
+
url: url?.href || null,
|
|
9509
|
+
value: values.length ? values[0] : null,
|
|
9510
|
+
values,
|
|
9511
|
+
present: values.length > 0
|
|
9512
|
+
};
|
|
9513
|
+
}
|
|
9477
9514
|
function viewportsForCheck(check, viewports) {
|
|
9478
9515
|
if (!check.viewports?.length) return viewports;
|
|
9479
9516
|
const names = new Set(check.viewports);
|
|
@@ -9511,6 +9548,44 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
9511
9548
|
message: failed.length ? `Route did not load as ${expectedPath} in ${failed.length} viewport(s).` : void 0
|
|
9512
9549
|
};
|
|
9513
9550
|
}
|
|
9551
|
+
if (check.type === "url_search_param_equals") {
|
|
9552
|
+
const param = check.param || "";
|
|
9553
|
+
const expectedValue = check.expected_value ?? "";
|
|
9554
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
9555
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
9556
|
+
return {
|
|
9557
|
+
type: check.type,
|
|
9558
|
+
label: checkLabel(check),
|
|
9559
|
+
status: failed.length ? "failed" : "passed",
|
|
9560
|
+
evidence: {
|
|
9561
|
+
param,
|
|
9562
|
+
expected_value: expectedValue,
|
|
9563
|
+
observed_values: observations.map((observation) => observation.value),
|
|
9564
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
9565
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
9566
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
9567
|
+
},
|
|
9568
|
+
message: failed.length ? `URL search param ${param} did not equal ${JSON.stringify(expectedValue)} in ${failed.length} viewport(s).` : void 0
|
|
9569
|
+
};
|
|
9570
|
+
}
|
|
9571
|
+
if (check.type === "url_search_param_absent") {
|
|
9572
|
+
const param = check.param || "";
|
|
9573
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
9574
|
+
const failed = observations.filter((observation) => observation.present);
|
|
9575
|
+
return {
|
|
9576
|
+
type: check.type,
|
|
9577
|
+
label: checkLabel(check),
|
|
9578
|
+
status: failed.length ? "failed" : "passed",
|
|
9579
|
+
evidence: {
|
|
9580
|
+
param,
|
|
9581
|
+
observed_values: observations.map((observation) => observation.value),
|
|
9582
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
9583
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
9584
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
9585
|
+
},
|
|
9586
|
+
message: failed.length ? `URL search param ${param} was present in ${failed.length} viewport(s).` : void 0
|
|
9587
|
+
};
|
|
9588
|
+
}
|
|
9514
9589
|
if (check.type === "selector_visible") {
|
|
9515
9590
|
const key = selectorKey(check);
|
|
9516
9591
|
const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.visible_count || 0) < 1);
|
|
@@ -10046,6 +10121,29 @@ function routePathMatches(observed, expected, targetUrl) {
|
|
|
10046
10121
|
function routeOk(route, targetUrl) {
|
|
10047
10122
|
return Boolean(route && (route.matched || routePathMatches(route.observed, route.expected_path, targetUrl)) && !route.error && (route.http_status == null || route.http_status < 400));
|
|
10048
10123
|
}
|
|
10124
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
10125
|
+
if (!value) return null;
|
|
10126
|
+
try { return targetUrl ? new URL(value, targetUrl) : new URL(value); } catch {}
|
|
10127
|
+
try { return new URL(value); } catch {}
|
|
10128
|
+
return null;
|
|
10129
|
+
}
|
|
10130
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
10131
|
+
return parseEvidenceUrl(viewport && viewport.url, targetUrl)
|
|
10132
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.observed, targetUrl)
|
|
10133
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.requested, targetUrl)
|
|
10134
|
+
|| parseEvidenceUrl(targetUrl, null);
|
|
10135
|
+
}
|
|
10136
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
10137
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
10138
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
10139
|
+
return {
|
|
10140
|
+
viewport: viewport && viewport.name,
|
|
10141
|
+
url: url ? url.href : null,
|
|
10142
|
+
value: values.length ? values[0] : null,
|
|
10143
|
+
values,
|
|
10144
|
+
present: values.length > 0,
|
|
10145
|
+
};
|
|
10146
|
+
}
|
|
10049
10147
|
function textMatches(sample, check) {
|
|
10050
10148
|
if (check.pattern) {
|
|
10051
10149
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -10350,6 +10448,46 @@ function assessProfile(profile, evidence) {
|
|
|
10350
10448
|
});
|
|
10351
10449
|
continue;
|
|
10352
10450
|
}
|
|
10451
|
+
if (check.type === "url_search_param_equals") {
|
|
10452
|
+
const param = check.param || "";
|
|
10453
|
+
const expectedValue = check.expected_value == null ? "" : String(check.expected_value);
|
|
10454
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
10455
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
10456
|
+
checks.push({
|
|
10457
|
+
type: check.type,
|
|
10458
|
+
label: check.label || check.type,
|
|
10459
|
+
status: failed.length ? "failed" : "passed",
|
|
10460
|
+
evidence: {
|
|
10461
|
+
param,
|
|
10462
|
+
expected_value: expectedValue,
|
|
10463
|
+
observed_values: observations.map((observation) => observation.value),
|
|
10464
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
10465
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
10466
|
+
viewports: observations,
|
|
10467
|
+
},
|
|
10468
|
+
message: failed.length ? "URL search param " + param + " did not equal " + JSON.stringify(expectedValue) + " in " + failed.length + " viewport(s)." : undefined,
|
|
10469
|
+
});
|
|
10470
|
+
continue;
|
|
10471
|
+
}
|
|
10472
|
+
if (check.type === "url_search_param_absent") {
|
|
10473
|
+
const param = check.param || "";
|
|
10474
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
10475
|
+
const failed = observations.filter((observation) => observation.present);
|
|
10476
|
+
checks.push({
|
|
10477
|
+
type: check.type,
|
|
10478
|
+
label: check.label || check.type,
|
|
10479
|
+
status: failed.length ? "failed" : "passed",
|
|
10480
|
+
evidence: {
|
|
10481
|
+
param,
|
|
10482
|
+
observed_values: observations.map((observation) => observation.value),
|
|
10483
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
10484
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
10485
|
+
viewports: observations,
|
|
10486
|
+
},
|
|
10487
|
+
message: failed.length ? "URL search param " + param + " was present in " + failed.length + " viewport(s)." : undefined,
|
|
10488
|
+
});
|
|
10489
|
+
continue;
|
|
10490
|
+
}
|
|
10353
10491
|
if (check.type === "selector_visible") {
|
|
10354
10492
|
const selector = check.selector || "";
|
|
10355
10493
|
const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
resolveRiddleProofProfileTimeoutSec,
|
|
59
59
|
slugifyRiddleProofProfileName,
|
|
60
60
|
summarizeRiddleProofProfileResult
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-2JFDTCIC.js";
|
|
62
62
|
import {
|
|
63
63
|
DEFAULT_RIDDLE_API_BASE_URL,
|
|
64
64
|
DEFAULT_RIDDLE_API_KEY_FILE,
|
package/dist/profile.cjs
CHANGED
|
@@ -55,6 +55,8 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
|
|
|
55
55
|
];
|
|
56
56
|
var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
57
57
|
"route_loaded",
|
|
58
|
+
"url_search_param_equals",
|
|
59
|
+
"url_search_param_absent",
|
|
58
60
|
"selector_visible",
|
|
59
61
|
"selector_absent",
|
|
60
62
|
"selector_count_at_least",
|
|
@@ -531,6 +533,13 @@ function normalizeCheck(input, index) {
|
|
|
531
533
|
if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
532
534
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
533
535
|
}
|
|
536
|
+
if ((type === "url_search_param_equals" || type === "url_search_param_absent") && !stringValue(input.param) && !stringValue(input.search_param) && !stringValue(input.searchParam) && !stringValue(input.key)) {
|
|
537
|
+
throw new Error(`checks[${index}] ${type} requires param.`);
|
|
538
|
+
}
|
|
539
|
+
const expectedValue = stringFromOwn(input, "expected_value", "expectedValue", "value");
|
|
540
|
+
if (type === "url_search_param_equals" && expectedValue === void 0) {
|
|
541
|
+
throw new Error(`checks[${index}] url_search_param_equals requires expected_value.`);
|
|
542
|
+
}
|
|
534
543
|
if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
|
|
535
544
|
throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
|
|
536
545
|
}
|
|
@@ -551,6 +560,8 @@ function normalizeCheck(input, index) {
|
|
|
551
560
|
type,
|
|
552
561
|
label: stringValue(input.label),
|
|
553
562
|
expected_path: stringValue(input.expected_path),
|
|
563
|
+
param: stringValue(input.param) || stringValue(input.search_param) || stringValue(input.searchParam) || stringValue(input.key),
|
|
564
|
+
expected_value: expectedValue,
|
|
554
565
|
expected_routes: expectedRoutes,
|
|
555
566
|
selector: stringValue(input.selector),
|
|
556
567
|
expected_texts: expectedTexts,
|
|
@@ -803,6 +814,32 @@ function successfulRoute(route, targetUrl) {
|
|
|
803
814
|
const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
|
|
804
815
|
return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
|
|
805
816
|
}
|
|
817
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
818
|
+
if (!value) return void 0;
|
|
819
|
+
try {
|
|
820
|
+
return targetUrl ? new URL(value, targetUrl) : new URL(value);
|
|
821
|
+
} catch {
|
|
822
|
+
try {
|
|
823
|
+
return new URL(value);
|
|
824
|
+
} catch {
|
|
825
|
+
return void 0;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
830
|
+
return parseEvidenceUrl(viewport.url, targetUrl) || parseEvidenceUrl(viewport.route?.observed, targetUrl) || parseEvidenceUrl(viewport.route?.requested, targetUrl) || parseEvidenceUrl(targetUrl, void 0);
|
|
831
|
+
}
|
|
832
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
833
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
834
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
835
|
+
return {
|
|
836
|
+
viewport: viewport.name,
|
|
837
|
+
url: url?.href || null,
|
|
838
|
+
value: values.length ? values[0] : null,
|
|
839
|
+
values,
|
|
840
|
+
present: values.length > 0
|
|
841
|
+
};
|
|
842
|
+
}
|
|
806
843
|
function viewportsForCheck(check, viewports) {
|
|
807
844
|
if (!check.viewports?.length) return viewports;
|
|
808
845
|
const names = new Set(check.viewports);
|
|
@@ -840,6 +877,44 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
840
877
|
message: failed.length ? `Route did not load as ${expectedPath} in ${failed.length} viewport(s).` : void 0
|
|
841
878
|
};
|
|
842
879
|
}
|
|
880
|
+
if (check.type === "url_search_param_equals") {
|
|
881
|
+
const param = check.param || "";
|
|
882
|
+
const expectedValue = check.expected_value ?? "";
|
|
883
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
884
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
885
|
+
return {
|
|
886
|
+
type: check.type,
|
|
887
|
+
label: checkLabel(check),
|
|
888
|
+
status: failed.length ? "failed" : "passed",
|
|
889
|
+
evidence: {
|
|
890
|
+
param,
|
|
891
|
+
expected_value: expectedValue,
|
|
892
|
+
observed_values: observations.map((observation) => observation.value),
|
|
893
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
894
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
895
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
896
|
+
},
|
|
897
|
+
message: failed.length ? `URL search param ${param} did not equal ${JSON.stringify(expectedValue)} in ${failed.length} viewport(s).` : void 0
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
if (check.type === "url_search_param_absent") {
|
|
901
|
+
const param = check.param || "";
|
|
902
|
+
const observations = viewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
903
|
+
const failed = observations.filter((observation) => observation.present);
|
|
904
|
+
return {
|
|
905
|
+
type: check.type,
|
|
906
|
+
label: checkLabel(check),
|
|
907
|
+
status: failed.length ? "failed" : "passed",
|
|
908
|
+
evidence: {
|
|
909
|
+
param,
|
|
910
|
+
observed_values: observations.map((observation) => observation.value),
|
|
911
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
912
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
913
|
+
viewports: observations.map((observation) => toJsonValue(observation))
|
|
914
|
+
},
|
|
915
|
+
message: failed.length ? `URL search param ${param} was present in ${failed.length} viewport(s).` : void 0
|
|
916
|
+
};
|
|
917
|
+
}
|
|
843
918
|
if (check.type === "selector_visible") {
|
|
844
919
|
const key = selectorKey(check);
|
|
845
920
|
const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.visible_count || 0) < 1);
|
|
@@ -1375,6 +1450,29 @@ function routePathMatches(observed, expected, targetUrl) {
|
|
|
1375
1450
|
function routeOk(route, targetUrl) {
|
|
1376
1451
|
return Boolean(route && (route.matched || routePathMatches(route.observed, route.expected_path, targetUrl)) && !route.error && (route.http_status == null || route.http_status < 400));
|
|
1377
1452
|
}
|
|
1453
|
+
function parseEvidenceUrl(value, targetUrl) {
|
|
1454
|
+
if (!value) return null;
|
|
1455
|
+
try { return targetUrl ? new URL(value, targetUrl) : new URL(value); } catch {}
|
|
1456
|
+
try { return new URL(value); } catch {}
|
|
1457
|
+
return null;
|
|
1458
|
+
}
|
|
1459
|
+
function observedUrlForViewport(viewport, targetUrl) {
|
|
1460
|
+
return parseEvidenceUrl(viewport && viewport.url, targetUrl)
|
|
1461
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.observed, targetUrl)
|
|
1462
|
+
|| parseEvidenceUrl(viewport && viewport.route && viewport.route.requested, targetUrl)
|
|
1463
|
+
|| parseEvidenceUrl(targetUrl, null);
|
|
1464
|
+
}
|
|
1465
|
+
function searchParamObservation(viewport, targetUrl, param) {
|
|
1466
|
+
const url = observedUrlForViewport(viewport, targetUrl);
|
|
1467
|
+
const values = url ? url.searchParams.getAll(param) : [];
|
|
1468
|
+
return {
|
|
1469
|
+
viewport: viewport && viewport.name,
|
|
1470
|
+
url: url ? url.href : null,
|
|
1471
|
+
value: values.length ? values[0] : null,
|
|
1472
|
+
values,
|
|
1473
|
+
present: values.length > 0,
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1378
1476
|
function textMatches(sample, check) {
|
|
1379
1477
|
if (check.pattern) {
|
|
1380
1478
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -1679,6 +1777,46 @@ function assessProfile(profile, evidence) {
|
|
|
1679
1777
|
});
|
|
1680
1778
|
continue;
|
|
1681
1779
|
}
|
|
1780
|
+
if (check.type === "url_search_param_equals") {
|
|
1781
|
+
const param = check.param || "";
|
|
1782
|
+
const expectedValue = check.expected_value == null ? "" : String(check.expected_value);
|
|
1783
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
1784
|
+
const failed = observations.filter((observation) => observation.value !== expectedValue);
|
|
1785
|
+
checks.push({
|
|
1786
|
+
type: check.type,
|
|
1787
|
+
label: check.label || check.type,
|
|
1788
|
+
status: failed.length ? "failed" : "passed",
|
|
1789
|
+
evidence: {
|
|
1790
|
+
param,
|
|
1791
|
+
expected_value: expectedValue,
|
|
1792
|
+
observed_values: observations.map((observation) => observation.value),
|
|
1793
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
1794
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
1795
|
+
viewports: observations,
|
|
1796
|
+
},
|
|
1797
|
+
message: failed.length ? "URL search param " + param + " did not equal " + JSON.stringify(expectedValue) + " in " + failed.length + " viewport(s)." : undefined,
|
|
1798
|
+
});
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
if (check.type === "url_search_param_absent") {
|
|
1802
|
+
const param = check.param || "";
|
|
1803
|
+
const observations = checkViewports.map((viewport) => searchParamObservation(viewport, evidence.target_url, param));
|
|
1804
|
+
const failed = observations.filter((observation) => observation.present);
|
|
1805
|
+
checks.push({
|
|
1806
|
+
type: check.type,
|
|
1807
|
+
label: check.label || check.type,
|
|
1808
|
+
status: failed.length ? "failed" : "passed",
|
|
1809
|
+
evidence: {
|
|
1810
|
+
param,
|
|
1811
|
+
observed_values: observations.map((observation) => observation.value),
|
|
1812
|
+
observed_all_values: observations.map((observation) => observation.values),
|
|
1813
|
+
observed_urls: observations.map((observation) => observation.url),
|
|
1814
|
+
viewports: observations,
|
|
1815
|
+
},
|
|
1816
|
+
message: failed.length ? "URL search param " + param + " was present in " + failed.length + " viewport(s)." : undefined,
|
|
1817
|
+
});
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1682
1820
|
if (check.type === "selector_visible") {
|
|
1683
1821
|
const selector = check.selector || "";
|
|
1684
1822
|
const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
|
package/dist/profile.d.cts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text", "window_call"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -88,6 +88,8 @@ interface RiddleProofProfileCheck {
|
|
|
88
88
|
type: RiddleProofProfileCheckType;
|
|
89
89
|
label?: string;
|
|
90
90
|
expected_path?: string;
|
|
91
|
+
param?: string;
|
|
92
|
+
expected_value?: string;
|
|
91
93
|
expected_routes?: RiddleProofProfileRouteInventoryRoute[];
|
|
92
94
|
selector?: string;
|
|
93
95
|
expected_texts?: string[];
|
package/dist/profile.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text", "window_call"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -88,6 +88,8 @@ interface RiddleProofProfileCheck {
|
|
|
88
88
|
type: RiddleProofProfileCheckType;
|
|
89
89
|
label?: string;
|
|
90
90
|
expected_path?: string;
|
|
91
|
+
param?: string;
|
|
92
|
+
expected_value?: string;
|
|
91
93
|
expected_routes?: RiddleProofProfileRouteInventoryRoute[];
|
|
92
94
|
selector?: string;
|
|
93
95
|
expected_texts?: string[];
|
package/dist/profile.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveRiddleProofProfileTimeoutSec,
|
|
20
20
|
slugifyRiddleProofProfileName,
|
|
21
21
|
summarizeRiddleProofProfileResult
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-2JFDTCIC.js";
|
|
23
23
|
export {
|
|
24
24
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
25
25
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|