@riddledc/riddle-proof 0.7.149 → 0.7.151
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 +20 -0
- package/dist/{chunk-QXQCG3WB.js → chunk-QJJ3ISMK.js} +194 -2
- package/dist/cli.cjs +267 -5
- package/dist/cli.js +75 -4
- package/dist/index.cjs +194 -2
- package/dist/index.js +1 -1
- package/dist/profile.cjs +194 -2
- package/dist/profile.d.cts +4 -1
- package/dist/profile.d.ts +4 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -605,6 +605,26 @@ The check records visible text for the selector in each viewport and reports
|
|
|
605
605
|
matched counts plus short samples, which makes generated-command and evidence
|
|
606
606
|
card audits easier to diagnose than global `text_visible` checks.
|
|
607
607
|
|
|
608
|
+
Use `observe_within` when the proof needs to catch a short-lived user-visible
|
|
609
|
+
state after setup actions, such as a combo badge, damage flash, transient
|
|
610
|
+
particle count, toast, or canvas-adjacent HUD update:
|
|
611
|
+
|
|
612
|
+
```json
|
|
613
|
+
{
|
|
614
|
+
"type": "observe_within",
|
|
615
|
+
"selector": ".result-state",
|
|
616
|
+
"pattern": "Photon.*active",
|
|
617
|
+
"timeout_ms": 1500
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
With `selector` plus `text` or `pattern`, the runner polls visible selector
|
|
622
|
+
text until it matches. With only `selector`, it polls for a visible matching
|
|
623
|
+
element. With only `text` or `pattern`, it polls the rendered page body. The
|
|
624
|
+
proof evidence records per-viewport match status, elapsed time, attempts,
|
|
625
|
+
selector counts when applicable, and a compact sample. `within_ms` is accepted
|
|
626
|
+
as an alias for `timeout_ms`; the default timeout is `2000`.
|
|
627
|
+
|
|
608
628
|
Use `http_status` when the contract belongs to the fetched response itself:
|
|
609
629
|
status code, content type, byte size, or raw body fragments from a markdown,
|
|
610
630
|
JSON, YAML, robots, sitemap, or other machine-readable endpoint:
|
|
@@ -26,6 +26,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
26
26
|
"selector_text_visible",
|
|
27
27
|
"selector_text_absent",
|
|
28
28
|
"selector_text_order",
|
|
29
|
+
"observe_within",
|
|
29
30
|
"frame_text_visible",
|
|
30
31
|
"frame_url_equals",
|
|
31
32
|
"frame_url_matches",
|
|
@@ -1348,7 +1349,7 @@ function normalizeCheck(input, index) {
|
|
|
1348
1349
|
throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
|
|
1349
1350
|
}
|
|
1350
1351
|
const isDialogCountCheck = isDialogCountCheckType(type);
|
|
1351
|
-
if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue(input.selector)) {
|
|
1352
|
+
if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent" || type === "observe_within" && !stringValue(input.text) && !stringValue(input.pattern)) && !stringValue(input.selector)) {
|
|
1352
1353
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
1353
1354
|
}
|
|
1354
1355
|
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
@@ -1489,7 +1490,7 @@ function normalizeCheck(input, index) {
|
|
|
1489
1490
|
allowed_content_types: allowedContentTypes,
|
|
1490
1491
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
1491
1492
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
1492
|
-
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
1493
|
+
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs) ?? numberValue(input.within_ms) ?? numberValue(input.withinMs),
|
|
1493
1494
|
run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
|
|
1494
1495
|
run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
|
|
1495
1496
|
run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
|
|
@@ -2007,6 +2008,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2007
2008
|
function textKey(check) {
|
|
2008
2009
|
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
2009
2010
|
}
|
|
2011
|
+
function observeWithinTimeoutMs(check) {
|
|
2012
|
+
const raw = check.timeout_ms;
|
|
2013
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
|
|
2014
|
+
return 2e3;
|
|
2015
|
+
}
|
|
2016
|
+
function observeWithinKey(check) {
|
|
2017
|
+
const target = check.selector ? `selector:${check.selector}` : "page";
|
|
2018
|
+
const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
|
|
2019
|
+
return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
|
|
2020
|
+
}
|
|
2010
2021
|
function textSequenceForCheck(viewport, check) {
|
|
2011
2022
|
const key = selectorKey(check);
|
|
2012
2023
|
const sequence = viewport.text_sequences?.[key];
|
|
@@ -2513,6 +2524,40 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
2513
2524
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
2514
2525
|
};
|
|
2515
2526
|
}
|
|
2527
|
+
if (check.type === "observe_within") {
|
|
2528
|
+
const key = observeWithinKey(check);
|
|
2529
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
2530
|
+
const results = viewports.map((viewport) => {
|
|
2531
|
+
const observation = viewport.observations?.[key];
|
|
2532
|
+
const matched = observation?.matched === true;
|
|
2533
|
+
return {
|
|
2534
|
+
viewport: viewport.name,
|
|
2535
|
+
matched,
|
|
2536
|
+
elapsed_ms: numberValue(observation?.elapsed_ms) ?? null,
|
|
2537
|
+
timeout_ms: numberValue(observation?.timeout_ms) ?? timeoutMs,
|
|
2538
|
+
attempts: numberValue(observation?.attempts) ?? null,
|
|
2539
|
+
selector_count: numberValue(observation?.selector_count) ?? null,
|
|
2540
|
+
visible_count: numberValue(observation?.visible_count) ?? null,
|
|
2541
|
+
matched_count: numberValue(observation?.matched_count) ?? null,
|
|
2542
|
+
sample: stringValue(observation?.sample) ?? null,
|
|
2543
|
+
error: stringValue(observation?.error) ?? null
|
|
2544
|
+
};
|
|
2545
|
+
});
|
|
2546
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
2547
|
+
return {
|
|
2548
|
+
type: check.type,
|
|
2549
|
+
label: checkLabel(check),
|
|
2550
|
+
status: failed ? "failed" : "passed",
|
|
2551
|
+
evidence: {
|
|
2552
|
+
selector: check.selector || null,
|
|
2553
|
+
text: check.text || null,
|
|
2554
|
+
pattern: check.pattern || null,
|
|
2555
|
+
timeout_ms: timeoutMs,
|
|
2556
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
2557
|
+
},
|
|
2558
|
+
message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2516
2561
|
if (check.type === "frame_text_visible") {
|
|
2517
2562
|
const key = selectorKey(check);
|
|
2518
2563
|
const results = viewports.map((viewport) => {
|
|
@@ -4422,6 +4467,36 @@ function assessProfile(profile, evidence) {
|
|
|
4422
4467
|
});
|
|
4423
4468
|
continue;
|
|
4424
4469
|
}
|
|
4470
|
+
if (check.type === "observe_within") {
|
|
4471
|
+
const key = observeWithinKey(check);
|
|
4472
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
4473
|
+
const results = checkViewports.map((viewport) => {
|
|
4474
|
+
const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
|
|
4475
|
+
? viewport.observations[key]
|
|
4476
|
+
: {};
|
|
4477
|
+
return {
|
|
4478
|
+
viewport: viewport.name,
|
|
4479
|
+
matched: observation.matched === true,
|
|
4480
|
+
elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
|
|
4481
|
+
timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
|
|
4482
|
+
attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
|
|
4483
|
+
selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
|
|
4484
|
+
visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
|
|
4485
|
+
matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
|
|
4486
|
+
sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
|
|
4487
|
+
error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
|
|
4488
|
+
};
|
|
4489
|
+
});
|
|
4490
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
4491
|
+
checks.push({
|
|
4492
|
+
type: check.type,
|
|
4493
|
+
label: check.label || check.type,
|
|
4494
|
+
status: failed ? "failed" : "passed",
|
|
4495
|
+
evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
|
|
4496
|
+
message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
|
|
4497
|
+
});
|
|
4498
|
+
continue;
|
|
4499
|
+
}
|
|
4425
4500
|
if (check.type === "frame_text_visible") {
|
|
4426
4501
|
const selector = check.selector || "";
|
|
4427
4502
|
const results = checkViewports.map((viewport) => {
|
|
@@ -4854,6 +4929,19 @@ function ensureDialogHandler() {
|
|
|
4854
4929
|
function textKey(check) {
|
|
4855
4930
|
return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
4856
4931
|
}
|
|
4932
|
+
function observeWithinTimeoutMs(check) {
|
|
4933
|
+
const raw = Number(check && check.timeout_ms);
|
|
4934
|
+
return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
|
|
4935
|
+
}
|
|
4936
|
+
function observeWithinKey(check) {
|
|
4937
|
+
const target = check && check.selector ? "selector:" + check.selector : "page";
|
|
4938
|
+
const expectation = check && check.pattern
|
|
4939
|
+
? "pattern:" + check.pattern + "/" + (check.flags || "")
|
|
4940
|
+
: check && check.text
|
|
4941
|
+
? "text:" + check.text
|
|
4942
|
+
: "visible";
|
|
4943
|
+
return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
|
|
4944
|
+
}
|
|
4857
4945
|
function textMatches(sample, check) {
|
|
4858
4946
|
if (check.pattern) {
|
|
4859
4947
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -6169,6 +6257,104 @@ async function selectorTextSequence(selector) {
|
|
|
6169
6257
|
};
|
|
6170
6258
|
}).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], match_texts: [], visible_match_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
6171
6259
|
}
|
|
6260
|
+
async function observeWithinSnapshot(check) {
|
|
6261
|
+
const payload = {
|
|
6262
|
+
selector: check.selector || "",
|
|
6263
|
+
text: check.text || "",
|
|
6264
|
+
pattern: check.pattern || "",
|
|
6265
|
+
flags: check.flags || "",
|
|
6266
|
+
wants_text: Boolean(check.text || check.pattern),
|
|
6267
|
+
};
|
|
6268
|
+
if (payload.selector) {
|
|
6269
|
+
return page.locator(payload.selector).evaluateAll((elements, input) => {
|
|
6270
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
6271
|
+
const matchText = (value) => {
|
|
6272
|
+
const source = compact(value);
|
|
6273
|
+
if (input.pattern) {
|
|
6274
|
+
try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
|
|
6275
|
+
}
|
|
6276
|
+
return source.includes(input.text || "");
|
|
6277
|
+
};
|
|
6278
|
+
const isVisible = (element) => {
|
|
6279
|
+
const style = window.getComputedStyle(element);
|
|
6280
|
+
const rect = element.getBoundingClientRect();
|
|
6281
|
+
return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
6282
|
+
};
|
|
6283
|
+
const rows = elements.map((element, index) => {
|
|
6284
|
+
const text = compact(element.innerText || element.textContent || "");
|
|
6285
|
+
const visible = isVisible(element);
|
|
6286
|
+
return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
|
|
6287
|
+
});
|
|
6288
|
+
const visibleRows = rows.filter((row) => row.visible);
|
|
6289
|
+
const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
|
|
6290
|
+
const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
|
|
6291
|
+
return {
|
|
6292
|
+
selector: input.selector,
|
|
6293
|
+
text: input.text || null,
|
|
6294
|
+
pattern: input.pattern || null,
|
|
6295
|
+
selector_count: rows.length,
|
|
6296
|
+
visible_count: visibleRows.length,
|
|
6297
|
+
matched_count: matches.length,
|
|
6298
|
+
matched: matches.length > 0,
|
|
6299
|
+
sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
|
|
6300
|
+
};
|
|
6301
|
+
}, payload).catch((error) => ({
|
|
6302
|
+
selector: payload.selector,
|
|
6303
|
+
text: payload.text || null,
|
|
6304
|
+
pattern: payload.pattern || null,
|
|
6305
|
+
selector_count: 0,
|
|
6306
|
+
visible_count: 0,
|
|
6307
|
+
matched_count: 0,
|
|
6308
|
+
matched: false,
|
|
6309
|
+
sample: null,
|
|
6310
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
6311
|
+
}));
|
|
6312
|
+
}
|
|
6313
|
+
return page.evaluate((input) => {
|
|
6314
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
6315
|
+
const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
|
|
6316
|
+
let matched = false;
|
|
6317
|
+
if (input.pattern) {
|
|
6318
|
+
try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
|
|
6319
|
+
} else {
|
|
6320
|
+
matched = sample.includes(input.text || "");
|
|
6321
|
+
}
|
|
6322
|
+
return {
|
|
6323
|
+
selector: null,
|
|
6324
|
+
text: input.text || null,
|
|
6325
|
+
pattern: input.pattern || null,
|
|
6326
|
+
matched,
|
|
6327
|
+
matched_count: matched ? 1 : 0,
|
|
6328
|
+
sample: sample.slice(0, 240),
|
|
6329
|
+
};
|
|
6330
|
+
}, payload).catch((error) => ({
|
|
6331
|
+
selector: null,
|
|
6332
|
+
text: payload.text || null,
|
|
6333
|
+
pattern: payload.pattern || null,
|
|
6334
|
+
matched: false,
|
|
6335
|
+
matched_count: 0,
|
|
6336
|
+
sample: null,
|
|
6337
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
6338
|
+
}));
|
|
6339
|
+
}
|
|
6340
|
+
async function observeWithin(check) {
|
|
6341
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
6342
|
+
const startedAt = Date.now();
|
|
6343
|
+
let attempts = 0;
|
|
6344
|
+
let last = null;
|
|
6345
|
+
while (true) {
|
|
6346
|
+
attempts += 1;
|
|
6347
|
+
last = await observeWithinSnapshot(check);
|
|
6348
|
+
const elapsedMs = Date.now() - startedAt;
|
|
6349
|
+
if (last && last.matched === true) {
|
|
6350
|
+
return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
6351
|
+
}
|
|
6352
|
+
if (elapsedMs >= timeoutMs) {
|
|
6353
|
+
return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
6354
|
+
}
|
|
6355
|
+
await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6172
6358
|
function linkProbeMaxLinks(check) {
|
|
6173
6359
|
const value = Number(check.max_links || check.maxLinks || check.limit || 100);
|
|
6174
6360
|
return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
|
|
@@ -7221,6 +7407,7 @@ async function captureViewport(viewport) {
|
|
|
7221
7407
|
const text_matches = {};
|
|
7222
7408
|
const text_match_samples = {};
|
|
7223
7409
|
const text_case_insensitive_samples = {};
|
|
7410
|
+
const observations = {};
|
|
7224
7411
|
const http_statuses = {};
|
|
7225
7412
|
const link_statuses = {};
|
|
7226
7413
|
for (const check of profile.checks || []) {
|
|
@@ -7241,6 +7428,10 @@ async function captureViewport(viewport) {
|
|
|
7241
7428
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
7242
7429
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
7243
7430
|
}
|
|
7431
|
+
if (check.type === "observe_within") {
|
|
7432
|
+
const key = observeWithinKey(check);
|
|
7433
|
+
observations[key] = observations[key] || await observeWithin(check);
|
|
7434
|
+
}
|
|
7244
7435
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
7245
7436
|
const key = textKey(check);
|
|
7246
7437
|
const sample = dom.body_text || dom.body_text_sample || "";
|
|
@@ -7334,6 +7525,7 @@ async function captureViewport(viewport) {
|
|
|
7334
7525
|
text_matches,
|
|
7335
7526
|
text_match_samples,
|
|
7336
7527
|
text_case_insensitive_samples,
|
|
7528
|
+
observations,
|
|
7337
7529
|
http_statuses,
|
|
7338
7530
|
link_statuses,
|
|
7339
7531
|
route_inventory: routeInventory,
|