@riddledc/riddle-proof 0.7.149 → 0.7.150
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 +202 -2
- package/dist/cli.js +9 -1
- package/dist/index.cjs +194 -2
- package/dist/index.js +1 -1
- package/dist/profile.cjs +194 -2
- package/dist/profile.d.cts +2 -1
- package/dist/profile.d.ts +2 -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,
|
package/dist/cli.cjs
CHANGED
|
@@ -6975,6 +6975,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
6975
6975
|
"selector_text_visible",
|
|
6976
6976
|
"selector_text_absent",
|
|
6977
6977
|
"selector_text_order",
|
|
6978
|
+
"observe_within",
|
|
6978
6979
|
"frame_text_visible",
|
|
6979
6980
|
"frame_url_equals",
|
|
6980
6981
|
"frame_url_matches",
|
|
@@ -8297,7 +8298,7 @@ function normalizeCheck(input, index) {
|
|
|
8297
8298
|
throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
|
|
8298
8299
|
}
|
|
8299
8300
|
const isDialogCountCheck = isDialogCountCheckType(type);
|
|
8300
|
-
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") && !stringValue2(input.selector)) {
|
|
8301
|
+
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" && !stringValue2(input.text) && !stringValue2(input.pattern)) && !stringValue2(input.selector)) {
|
|
8301
8302
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
8302
8303
|
}
|
|
8303
8304
|
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue2(input.selector)) {
|
|
@@ -8438,7 +8439,7 @@ function normalizeCheck(input, index) {
|
|
|
8438
8439
|
allowed_content_types: allowedContentTypes,
|
|
8439
8440
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
8440
8441
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
8441
|
-
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
8442
|
+
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs) ?? numberValue(input.within_ms) ?? numberValue(input.withinMs),
|
|
8442
8443
|
run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
|
|
8443
8444
|
run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
|
|
8444
8445
|
run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
|
|
@@ -8956,6 +8957,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
8956
8957
|
function textKey(check) {
|
|
8957
8958
|
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
8958
8959
|
}
|
|
8960
|
+
function observeWithinTimeoutMs(check) {
|
|
8961
|
+
const raw = check.timeout_ms;
|
|
8962
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
|
|
8963
|
+
return 2e3;
|
|
8964
|
+
}
|
|
8965
|
+
function observeWithinKey(check) {
|
|
8966
|
+
const target = check.selector ? `selector:${check.selector}` : "page";
|
|
8967
|
+
const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
|
|
8968
|
+
return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
|
|
8969
|
+
}
|
|
8959
8970
|
function textSequenceForCheck(viewport, check) {
|
|
8960
8971
|
const key = selectorKey(check);
|
|
8961
8972
|
const sequence = viewport.text_sequences?.[key];
|
|
@@ -9462,6 +9473,40 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
9462
9473
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
9463
9474
|
};
|
|
9464
9475
|
}
|
|
9476
|
+
if (check.type === "observe_within") {
|
|
9477
|
+
const key = observeWithinKey(check);
|
|
9478
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
9479
|
+
const results = viewports.map((viewport) => {
|
|
9480
|
+
const observation = viewport.observations?.[key];
|
|
9481
|
+
const matched = observation?.matched === true;
|
|
9482
|
+
return {
|
|
9483
|
+
viewport: viewport.name,
|
|
9484
|
+
matched,
|
|
9485
|
+
elapsed_ms: numberValue(observation?.elapsed_ms) ?? null,
|
|
9486
|
+
timeout_ms: numberValue(observation?.timeout_ms) ?? timeoutMs,
|
|
9487
|
+
attempts: numberValue(observation?.attempts) ?? null,
|
|
9488
|
+
selector_count: numberValue(observation?.selector_count) ?? null,
|
|
9489
|
+
visible_count: numberValue(observation?.visible_count) ?? null,
|
|
9490
|
+
matched_count: numberValue(observation?.matched_count) ?? null,
|
|
9491
|
+
sample: stringValue2(observation?.sample) ?? null,
|
|
9492
|
+
error: stringValue2(observation?.error) ?? null
|
|
9493
|
+
};
|
|
9494
|
+
});
|
|
9495
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
9496
|
+
return {
|
|
9497
|
+
type: check.type,
|
|
9498
|
+
label: checkLabel(check),
|
|
9499
|
+
status: failed ? "failed" : "passed",
|
|
9500
|
+
evidence: {
|
|
9501
|
+
selector: check.selector || null,
|
|
9502
|
+
text: check.text || null,
|
|
9503
|
+
pattern: check.pattern || null,
|
|
9504
|
+
timeout_ms: timeoutMs,
|
|
9505
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
9506
|
+
},
|
|
9507
|
+
message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
|
|
9508
|
+
};
|
|
9509
|
+
}
|
|
9465
9510
|
if (check.type === "frame_text_visible") {
|
|
9466
9511
|
const key = selectorKey(check);
|
|
9467
9512
|
const results = viewports.map((viewport) => {
|
|
@@ -11355,6 +11400,36 @@ function assessProfile(profile, evidence) {
|
|
|
11355
11400
|
});
|
|
11356
11401
|
continue;
|
|
11357
11402
|
}
|
|
11403
|
+
if (check.type === "observe_within") {
|
|
11404
|
+
const key = observeWithinKey(check);
|
|
11405
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
11406
|
+
const results = checkViewports.map((viewport) => {
|
|
11407
|
+
const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
|
|
11408
|
+
? viewport.observations[key]
|
|
11409
|
+
: {};
|
|
11410
|
+
return {
|
|
11411
|
+
viewport: viewport.name,
|
|
11412
|
+
matched: observation.matched === true,
|
|
11413
|
+
elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
|
|
11414
|
+
timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
|
|
11415
|
+
attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
|
|
11416
|
+
selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
|
|
11417
|
+
visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
|
|
11418
|
+
matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
|
|
11419
|
+
sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
|
|
11420
|
+
error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
|
|
11421
|
+
};
|
|
11422
|
+
});
|
|
11423
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
11424
|
+
checks.push({
|
|
11425
|
+
type: check.type,
|
|
11426
|
+
label: check.label || check.type,
|
|
11427
|
+
status: failed ? "failed" : "passed",
|
|
11428
|
+
evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
|
|
11429
|
+
message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
|
|
11430
|
+
});
|
|
11431
|
+
continue;
|
|
11432
|
+
}
|
|
11358
11433
|
if (check.type === "frame_text_visible") {
|
|
11359
11434
|
const selector = check.selector || "";
|
|
11360
11435
|
const results = checkViewports.map((viewport) => {
|
|
@@ -11787,6 +11862,19 @@ function ensureDialogHandler() {
|
|
|
11787
11862
|
function textKey(check) {
|
|
11788
11863
|
return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
11789
11864
|
}
|
|
11865
|
+
function observeWithinTimeoutMs(check) {
|
|
11866
|
+
const raw = Number(check && check.timeout_ms);
|
|
11867
|
+
return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
|
|
11868
|
+
}
|
|
11869
|
+
function observeWithinKey(check) {
|
|
11870
|
+
const target = check && check.selector ? "selector:" + check.selector : "page";
|
|
11871
|
+
const expectation = check && check.pattern
|
|
11872
|
+
? "pattern:" + check.pattern + "/" + (check.flags || "")
|
|
11873
|
+
: check && check.text
|
|
11874
|
+
? "text:" + check.text
|
|
11875
|
+
: "visible";
|
|
11876
|
+
return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
|
|
11877
|
+
}
|
|
11790
11878
|
function textMatches(sample, check) {
|
|
11791
11879
|
if (check.pattern) {
|
|
11792
11880
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -13102,6 +13190,104 @@ async function selectorTextSequence(selector) {
|
|
|
13102
13190
|
};
|
|
13103
13191
|
}).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) }));
|
|
13104
13192
|
}
|
|
13193
|
+
async function observeWithinSnapshot(check) {
|
|
13194
|
+
const payload = {
|
|
13195
|
+
selector: check.selector || "",
|
|
13196
|
+
text: check.text || "",
|
|
13197
|
+
pattern: check.pattern || "",
|
|
13198
|
+
flags: check.flags || "",
|
|
13199
|
+
wants_text: Boolean(check.text || check.pattern),
|
|
13200
|
+
};
|
|
13201
|
+
if (payload.selector) {
|
|
13202
|
+
return page.locator(payload.selector).evaluateAll((elements, input) => {
|
|
13203
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
13204
|
+
const matchText = (value) => {
|
|
13205
|
+
const source = compact(value);
|
|
13206
|
+
if (input.pattern) {
|
|
13207
|
+
try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
|
|
13208
|
+
}
|
|
13209
|
+
return source.includes(input.text || "");
|
|
13210
|
+
};
|
|
13211
|
+
const isVisible = (element) => {
|
|
13212
|
+
const style = window.getComputedStyle(element);
|
|
13213
|
+
const rect = element.getBoundingClientRect();
|
|
13214
|
+
return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
13215
|
+
};
|
|
13216
|
+
const rows = elements.map((element, index) => {
|
|
13217
|
+
const text = compact(element.innerText || element.textContent || "");
|
|
13218
|
+
const visible = isVisible(element);
|
|
13219
|
+
return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
|
|
13220
|
+
});
|
|
13221
|
+
const visibleRows = rows.filter((row) => row.visible);
|
|
13222
|
+
const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
|
|
13223
|
+
const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
|
|
13224
|
+
return {
|
|
13225
|
+
selector: input.selector,
|
|
13226
|
+
text: input.text || null,
|
|
13227
|
+
pattern: input.pattern || null,
|
|
13228
|
+
selector_count: rows.length,
|
|
13229
|
+
visible_count: visibleRows.length,
|
|
13230
|
+
matched_count: matches.length,
|
|
13231
|
+
matched: matches.length > 0,
|
|
13232
|
+
sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
|
|
13233
|
+
};
|
|
13234
|
+
}, payload).catch((error) => ({
|
|
13235
|
+
selector: payload.selector,
|
|
13236
|
+
text: payload.text || null,
|
|
13237
|
+
pattern: payload.pattern || null,
|
|
13238
|
+
selector_count: 0,
|
|
13239
|
+
visible_count: 0,
|
|
13240
|
+
matched_count: 0,
|
|
13241
|
+
matched: false,
|
|
13242
|
+
sample: null,
|
|
13243
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
13244
|
+
}));
|
|
13245
|
+
}
|
|
13246
|
+
return page.evaluate((input) => {
|
|
13247
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
13248
|
+
const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
|
|
13249
|
+
let matched = false;
|
|
13250
|
+
if (input.pattern) {
|
|
13251
|
+
try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
|
|
13252
|
+
} else {
|
|
13253
|
+
matched = sample.includes(input.text || "");
|
|
13254
|
+
}
|
|
13255
|
+
return {
|
|
13256
|
+
selector: null,
|
|
13257
|
+
text: input.text || null,
|
|
13258
|
+
pattern: input.pattern || null,
|
|
13259
|
+
matched,
|
|
13260
|
+
matched_count: matched ? 1 : 0,
|
|
13261
|
+
sample: sample.slice(0, 240),
|
|
13262
|
+
};
|
|
13263
|
+
}, payload).catch((error) => ({
|
|
13264
|
+
selector: null,
|
|
13265
|
+
text: payload.text || null,
|
|
13266
|
+
pattern: payload.pattern || null,
|
|
13267
|
+
matched: false,
|
|
13268
|
+
matched_count: 0,
|
|
13269
|
+
sample: null,
|
|
13270
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
13271
|
+
}));
|
|
13272
|
+
}
|
|
13273
|
+
async function observeWithin(check) {
|
|
13274
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
13275
|
+
const startedAt = Date.now();
|
|
13276
|
+
let attempts = 0;
|
|
13277
|
+
let last = null;
|
|
13278
|
+
while (true) {
|
|
13279
|
+
attempts += 1;
|
|
13280
|
+
last = await observeWithinSnapshot(check);
|
|
13281
|
+
const elapsedMs = Date.now() - startedAt;
|
|
13282
|
+
if (last && last.matched === true) {
|
|
13283
|
+
return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
13284
|
+
}
|
|
13285
|
+
if (elapsedMs >= timeoutMs) {
|
|
13286
|
+
return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
13287
|
+
}
|
|
13288
|
+
await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
|
|
13289
|
+
}
|
|
13290
|
+
}
|
|
13105
13291
|
function linkProbeMaxLinks(check) {
|
|
13106
13292
|
const value = Number(check.max_links || check.maxLinks || check.limit || 100);
|
|
13107
13293
|
return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
|
|
@@ -14154,6 +14340,7 @@ async function captureViewport(viewport) {
|
|
|
14154
14340
|
const text_matches = {};
|
|
14155
14341
|
const text_match_samples = {};
|
|
14156
14342
|
const text_case_insensitive_samples = {};
|
|
14343
|
+
const observations = {};
|
|
14157
14344
|
const http_statuses = {};
|
|
14158
14345
|
const link_statuses = {};
|
|
14159
14346
|
for (const check of profile.checks || []) {
|
|
@@ -14174,6 +14361,10 @@ async function captureViewport(viewport) {
|
|
|
14174
14361
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
14175
14362
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
14176
14363
|
}
|
|
14364
|
+
if (check.type === "observe_within") {
|
|
14365
|
+
const key = observeWithinKey(check);
|
|
14366
|
+
observations[key] = observations[key] || await observeWithin(check);
|
|
14367
|
+
}
|
|
14177
14368
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
14178
14369
|
const key = textKey(check);
|
|
14179
14370
|
const sample = dom.body_text || dom.body_text_sample || "";
|
|
@@ -14267,6 +14458,7 @@ async function captureViewport(viewport) {
|
|
|
14267
14458
|
text_matches,
|
|
14268
14459
|
text_match_samples,
|
|
14269
14460
|
text_case_insensitive_samples,
|
|
14461
|
+
observations,
|
|
14270
14462
|
http_statuses,
|
|
14271
14463
|
link_statuses,
|
|
14272
14464
|
route_inventory: routeInventory,
|
|
@@ -14949,6 +15141,14 @@ function profileCheckMarkdownTarget(check) {
|
|
|
14949
15141
|
if (check.type === "selector_text_order") {
|
|
14950
15142
|
return selector ? `${markdownInlineCode(selector)} text order` : void 0;
|
|
14951
15143
|
}
|
|
15144
|
+
if (check.type === "observe_within") {
|
|
15145
|
+
const textTarget = profileCheckTextTarget(evidence);
|
|
15146
|
+
const timeoutMs = cliFiniteNumber(evidence.timeout_ms);
|
|
15147
|
+
const withinLabel = timeoutMs === void 0 ? "within timeout" : `within ${timeoutMs}ms`;
|
|
15148
|
+
if (selector && textTarget) return `${markdownInlineCode(selector)} observes ${textTarget} ${withinLabel}`;
|
|
15149
|
+
if (selector) return `${markdownInlineCode(selector)} visible ${withinLabel}`;
|
|
15150
|
+
return textTarget ? `${textTarget} ${withinLabel}` : withinLabel;
|
|
15151
|
+
}
|
|
14952
15152
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
14953
15153
|
return profileCheckTextTarget(evidence);
|
|
14954
15154
|
}
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
profileStatusExitCode,
|
|
14
14
|
resolveRiddleProofProfileTargetUrl,
|
|
15
15
|
resolveRiddleProofProfileTimeoutSec
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-QJJ3ISMK.js";
|
|
17
17
|
import {
|
|
18
18
|
createRiddleApiClient,
|
|
19
19
|
parseRiddleViewport
|
|
@@ -507,6 +507,14 @@ function profileCheckMarkdownTarget(check) {
|
|
|
507
507
|
if (check.type === "selector_text_order") {
|
|
508
508
|
return selector ? `${markdownInlineCode(selector)} text order` : void 0;
|
|
509
509
|
}
|
|
510
|
+
if (check.type === "observe_within") {
|
|
511
|
+
const textTarget = profileCheckTextTarget(evidence);
|
|
512
|
+
const timeoutMs = cliFiniteNumber(evidence.timeout_ms);
|
|
513
|
+
const withinLabel = timeoutMs === void 0 ? "within timeout" : `within ${timeoutMs}ms`;
|
|
514
|
+
if (selector && textTarget) return `${markdownInlineCode(selector)} observes ${textTarget} ${withinLabel}`;
|
|
515
|
+
if (selector) return `${markdownInlineCode(selector)} visible ${withinLabel}`;
|
|
516
|
+
return textTarget ? `${textTarget} ${withinLabel}` : withinLabel;
|
|
517
|
+
}
|
|
510
518
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
511
519
|
return profileCheckTextTarget(evidence);
|
|
512
520
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -8759,6 +8759,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
8759
8759
|
"selector_text_visible",
|
|
8760
8760
|
"selector_text_absent",
|
|
8761
8761
|
"selector_text_order",
|
|
8762
|
+
"observe_within",
|
|
8762
8763
|
"frame_text_visible",
|
|
8763
8764
|
"frame_url_equals",
|
|
8764
8765
|
"frame_url_matches",
|
|
@@ -10081,7 +10082,7 @@ function normalizeCheck(input, index) {
|
|
|
10081
10082
|
throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
|
|
10082
10083
|
}
|
|
10083
10084
|
const isDialogCountCheck = isDialogCountCheckType(type);
|
|
10084
|
-
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") && !stringValue5(input.selector)) {
|
|
10085
|
+
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" && !stringValue5(input.text) && !stringValue5(input.pattern)) && !stringValue5(input.selector)) {
|
|
10085
10086
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
10086
10087
|
}
|
|
10087
10088
|
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue5(input.selector)) {
|
|
@@ -10222,7 +10223,7 @@ function normalizeCheck(input, index) {
|
|
|
10222
10223
|
allowed_content_types: allowedContentTypes,
|
|
10223
10224
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
10224
10225
|
max_overflow_px: numberValue3(input.max_overflow_px),
|
|
10225
|
-
timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs),
|
|
10226
|
+
timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs) ?? numberValue3(input.within_ms) ?? numberValue3(input.withinMs),
|
|
10226
10227
|
run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
|
|
10227
10228
|
run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
|
|
10228
10229
|
run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
|
|
@@ -10740,6 +10741,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
10740
10741
|
function textKey(check) {
|
|
10741
10742
|
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
10742
10743
|
}
|
|
10744
|
+
function observeWithinTimeoutMs(check) {
|
|
10745
|
+
const raw = check.timeout_ms;
|
|
10746
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
|
|
10747
|
+
return 2e3;
|
|
10748
|
+
}
|
|
10749
|
+
function observeWithinKey(check) {
|
|
10750
|
+
const target = check.selector ? `selector:${check.selector}` : "page";
|
|
10751
|
+
const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
|
|
10752
|
+
return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
|
|
10753
|
+
}
|
|
10743
10754
|
function textSequenceForCheck(viewport, check) {
|
|
10744
10755
|
const key = selectorKey(check);
|
|
10745
10756
|
const sequence = viewport.text_sequences?.[key];
|
|
@@ -11246,6 +11257,40 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
11246
11257
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
11247
11258
|
};
|
|
11248
11259
|
}
|
|
11260
|
+
if (check.type === "observe_within") {
|
|
11261
|
+
const key = observeWithinKey(check);
|
|
11262
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
11263
|
+
const results = viewports.map((viewport) => {
|
|
11264
|
+
const observation = viewport.observations?.[key];
|
|
11265
|
+
const matched = observation?.matched === true;
|
|
11266
|
+
return {
|
|
11267
|
+
viewport: viewport.name,
|
|
11268
|
+
matched,
|
|
11269
|
+
elapsed_ms: numberValue3(observation?.elapsed_ms) ?? null,
|
|
11270
|
+
timeout_ms: numberValue3(observation?.timeout_ms) ?? timeoutMs,
|
|
11271
|
+
attempts: numberValue3(observation?.attempts) ?? null,
|
|
11272
|
+
selector_count: numberValue3(observation?.selector_count) ?? null,
|
|
11273
|
+
visible_count: numberValue3(observation?.visible_count) ?? null,
|
|
11274
|
+
matched_count: numberValue3(observation?.matched_count) ?? null,
|
|
11275
|
+
sample: stringValue5(observation?.sample) ?? null,
|
|
11276
|
+
error: stringValue5(observation?.error) ?? null
|
|
11277
|
+
};
|
|
11278
|
+
});
|
|
11279
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
11280
|
+
return {
|
|
11281
|
+
type: check.type,
|
|
11282
|
+
label: checkLabel(check),
|
|
11283
|
+
status: failed ? "failed" : "passed",
|
|
11284
|
+
evidence: {
|
|
11285
|
+
selector: check.selector || null,
|
|
11286
|
+
text: check.text || null,
|
|
11287
|
+
pattern: check.pattern || null,
|
|
11288
|
+
timeout_ms: timeoutMs,
|
|
11289
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
11290
|
+
},
|
|
11291
|
+
message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
|
|
11292
|
+
};
|
|
11293
|
+
}
|
|
11249
11294
|
if (check.type === "frame_text_visible") {
|
|
11250
11295
|
const key = selectorKey(check);
|
|
11251
11296
|
const results = viewports.map((viewport) => {
|
|
@@ -13155,6 +13200,36 @@ function assessProfile(profile, evidence) {
|
|
|
13155
13200
|
});
|
|
13156
13201
|
continue;
|
|
13157
13202
|
}
|
|
13203
|
+
if (check.type === "observe_within") {
|
|
13204
|
+
const key = observeWithinKey(check);
|
|
13205
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
13206
|
+
const results = checkViewports.map((viewport) => {
|
|
13207
|
+
const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
|
|
13208
|
+
? viewport.observations[key]
|
|
13209
|
+
: {};
|
|
13210
|
+
return {
|
|
13211
|
+
viewport: viewport.name,
|
|
13212
|
+
matched: observation.matched === true,
|
|
13213
|
+
elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
|
|
13214
|
+
timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
|
|
13215
|
+
attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
|
|
13216
|
+
selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
|
|
13217
|
+
visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
|
|
13218
|
+
matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
|
|
13219
|
+
sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
|
|
13220
|
+
error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
|
|
13221
|
+
};
|
|
13222
|
+
});
|
|
13223
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
13224
|
+
checks.push({
|
|
13225
|
+
type: check.type,
|
|
13226
|
+
label: check.label || check.type,
|
|
13227
|
+
status: failed ? "failed" : "passed",
|
|
13228
|
+
evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
|
|
13229
|
+
message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
|
|
13230
|
+
});
|
|
13231
|
+
continue;
|
|
13232
|
+
}
|
|
13158
13233
|
if (check.type === "frame_text_visible") {
|
|
13159
13234
|
const selector = check.selector || "";
|
|
13160
13235
|
const results = checkViewports.map((viewport) => {
|
|
@@ -13587,6 +13662,19 @@ function ensureDialogHandler() {
|
|
|
13587
13662
|
function textKey(check) {
|
|
13588
13663
|
return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
13589
13664
|
}
|
|
13665
|
+
function observeWithinTimeoutMs(check) {
|
|
13666
|
+
const raw = Number(check && check.timeout_ms);
|
|
13667
|
+
return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
|
|
13668
|
+
}
|
|
13669
|
+
function observeWithinKey(check) {
|
|
13670
|
+
const target = check && check.selector ? "selector:" + check.selector : "page";
|
|
13671
|
+
const expectation = check && check.pattern
|
|
13672
|
+
? "pattern:" + check.pattern + "/" + (check.flags || "")
|
|
13673
|
+
: check && check.text
|
|
13674
|
+
? "text:" + check.text
|
|
13675
|
+
: "visible";
|
|
13676
|
+
return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
|
|
13677
|
+
}
|
|
13590
13678
|
function textMatches(sample, check) {
|
|
13591
13679
|
if (check.pattern) {
|
|
13592
13680
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -14902,6 +14990,104 @@ async function selectorTextSequence(selector) {
|
|
|
14902
14990
|
};
|
|
14903
14991
|
}).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) }));
|
|
14904
14992
|
}
|
|
14993
|
+
async function observeWithinSnapshot(check) {
|
|
14994
|
+
const payload = {
|
|
14995
|
+
selector: check.selector || "",
|
|
14996
|
+
text: check.text || "",
|
|
14997
|
+
pattern: check.pattern || "",
|
|
14998
|
+
flags: check.flags || "",
|
|
14999
|
+
wants_text: Boolean(check.text || check.pattern),
|
|
15000
|
+
};
|
|
15001
|
+
if (payload.selector) {
|
|
15002
|
+
return page.locator(payload.selector).evaluateAll((elements, input) => {
|
|
15003
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
15004
|
+
const matchText = (value) => {
|
|
15005
|
+
const source = compact(value);
|
|
15006
|
+
if (input.pattern) {
|
|
15007
|
+
try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
|
|
15008
|
+
}
|
|
15009
|
+
return source.includes(input.text || "");
|
|
15010
|
+
};
|
|
15011
|
+
const isVisible = (element) => {
|
|
15012
|
+
const style = window.getComputedStyle(element);
|
|
15013
|
+
const rect = element.getBoundingClientRect();
|
|
15014
|
+
return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
15015
|
+
};
|
|
15016
|
+
const rows = elements.map((element, index) => {
|
|
15017
|
+
const text = compact(element.innerText || element.textContent || "");
|
|
15018
|
+
const visible = isVisible(element);
|
|
15019
|
+
return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
|
|
15020
|
+
});
|
|
15021
|
+
const visibleRows = rows.filter((row) => row.visible);
|
|
15022
|
+
const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
|
|
15023
|
+
const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
|
|
15024
|
+
return {
|
|
15025
|
+
selector: input.selector,
|
|
15026
|
+
text: input.text || null,
|
|
15027
|
+
pattern: input.pattern || null,
|
|
15028
|
+
selector_count: rows.length,
|
|
15029
|
+
visible_count: visibleRows.length,
|
|
15030
|
+
matched_count: matches.length,
|
|
15031
|
+
matched: matches.length > 0,
|
|
15032
|
+
sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
|
|
15033
|
+
};
|
|
15034
|
+
}, payload).catch((error) => ({
|
|
15035
|
+
selector: payload.selector,
|
|
15036
|
+
text: payload.text || null,
|
|
15037
|
+
pattern: payload.pattern || null,
|
|
15038
|
+
selector_count: 0,
|
|
15039
|
+
visible_count: 0,
|
|
15040
|
+
matched_count: 0,
|
|
15041
|
+
matched: false,
|
|
15042
|
+
sample: null,
|
|
15043
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
15044
|
+
}));
|
|
15045
|
+
}
|
|
15046
|
+
return page.evaluate((input) => {
|
|
15047
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
15048
|
+
const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
|
|
15049
|
+
let matched = false;
|
|
15050
|
+
if (input.pattern) {
|
|
15051
|
+
try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
|
|
15052
|
+
} else {
|
|
15053
|
+
matched = sample.includes(input.text || "");
|
|
15054
|
+
}
|
|
15055
|
+
return {
|
|
15056
|
+
selector: null,
|
|
15057
|
+
text: input.text || null,
|
|
15058
|
+
pattern: input.pattern || null,
|
|
15059
|
+
matched,
|
|
15060
|
+
matched_count: matched ? 1 : 0,
|
|
15061
|
+
sample: sample.slice(0, 240),
|
|
15062
|
+
};
|
|
15063
|
+
}, payload).catch((error) => ({
|
|
15064
|
+
selector: null,
|
|
15065
|
+
text: payload.text || null,
|
|
15066
|
+
pattern: payload.pattern || null,
|
|
15067
|
+
matched: false,
|
|
15068
|
+
matched_count: 0,
|
|
15069
|
+
sample: null,
|
|
15070
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
15071
|
+
}));
|
|
15072
|
+
}
|
|
15073
|
+
async function observeWithin(check) {
|
|
15074
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
15075
|
+
const startedAt = Date.now();
|
|
15076
|
+
let attempts = 0;
|
|
15077
|
+
let last = null;
|
|
15078
|
+
while (true) {
|
|
15079
|
+
attempts += 1;
|
|
15080
|
+
last = await observeWithinSnapshot(check);
|
|
15081
|
+
const elapsedMs = Date.now() - startedAt;
|
|
15082
|
+
if (last && last.matched === true) {
|
|
15083
|
+
return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
15084
|
+
}
|
|
15085
|
+
if (elapsedMs >= timeoutMs) {
|
|
15086
|
+
return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
15087
|
+
}
|
|
15088
|
+
await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
|
|
15089
|
+
}
|
|
15090
|
+
}
|
|
14905
15091
|
function linkProbeMaxLinks(check) {
|
|
14906
15092
|
const value = Number(check.max_links || check.maxLinks || check.limit || 100);
|
|
14907
15093
|
return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
|
|
@@ -15954,6 +16140,7 @@ async function captureViewport(viewport) {
|
|
|
15954
16140
|
const text_matches = {};
|
|
15955
16141
|
const text_match_samples = {};
|
|
15956
16142
|
const text_case_insensitive_samples = {};
|
|
16143
|
+
const observations = {};
|
|
15957
16144
|
const http_statuses = {};
|
|
15958
16145
|
const link_statuses = {};
|
|
15959
16146
|
for (const check of profile.checks || []) {
|
|
@@ -15974,6 +16161,10 @@ async function captureViewport(viewport) {
|
|
|
15974
16161
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
15975
16162
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
15976
16163
|
}
|
|
16164
|
+
if (check.type === "observe_within") {
|
|
16165
|
+
const key = observeWithinKey(check);
|
|
16166
|
+
observations[key] = observations[key] || await observeWithin(check);
|
|
16167
|
+
}
|
|
15977
16168
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
15978
16169
|
const key = textKey(check);
|
|
15979
16170
|
const sample = dom.body_text || dom.body_text_sample || "";
|
|
@@ -16067,6 +16258,7 @@ async function captureViewport(viewport) {
|
|
|
16067
16258
|
text_matches,
|
|
16068
16259
|
text_match_samples,
|
|
16069
16260
|
text_case_insensitive_samples,
|
|
16261
|
+
observations,
|
|
16070
16262
|
http_statuses,
|
|
16071
16263
|
link_statuses,
|
|
16072
16264
|
route_inventory: routeInventory,
|
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
resolveRiddleProofProfileTimeoutSec,
|
|
63
63
|
slugifyRiddleProofProfileName,
|
|
64
64
|
summarizeRiddleProofProfileResult
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-QJJ3ISMK.js";
|
|
66
66
|
import {
|
|
67
67
|
DEFAULT_RIDDLE_API_BASE_URL,
|
|
68
68
|
DEFAULT_RIDDLE_API_KEY_FILE,
|
package/dist/profile.cjs
CHANGED
|
@@ -73,6 +73,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
73
73
|
"selector_text_visible",
|
|
74
74
|
"selector_text_absent",
|
|
75
75
|
"selector_text_order",
|
|
76
|
+
"observe_within",
|
|
76
77
|
"frame_text_visible",
|
|
77
78
|
"frame_url_equals",
|
|
78
79
|
"frame_url_matches",
|
|
@@ -1395,7 +1396,7 @@ function normalizeCheck(input, index) {
|
|
|
1395
1396
|
throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
|
|
1396
1397
|
}
|
|
1397
1398
|
const isDialogCountCheck = isDialogCountCheckType(type);
|
|
1398
|
-
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)) {
|
|
1399
|
+
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)) {
|
|
1399
1400
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
1400
1401
|
}
|
|
1401
1402
|
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
@@ -1536,7 +1537,7 @@ function normalizeCheck(input, index) {
|
|
|
1536
1537
|
allowed_content_types: allowedContentTypes,
|
|
1537
1538
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
1538
1539
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
1539
|
-
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
1540
|
+
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs) ?? numberValue(input.within_ms) ?? numberValue(input.withinMs),
|
|
1540
1541
|
run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
|
|
1541
1542
|
run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
|
|
1542
1543
|
run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
|
|
@@ -2054,6 +2055,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2054
2055
|
function textKey(check) {
|
|
2055
2056
|
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
2056
2057
|
}
|
|
2058
|
+
function observeWithinTimeoutMs(check) {
|
|
2059
|
+
const raw = check.timeout_ms;
|
|
2060
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
|
|
2061
|
+
return 2e3;
|
|
2062
|
+
}
|
|
2063
|
+
function observeWithinKey(check) {
|
|
2064
|
+
const target = check.selector ? `selector:${check.selector}` : "page";
|
|
2065
|
+
const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
|
|
2066
|
+
return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
|
|
2067
|
+
}
|
|
2057
2068
|
function textSequenceForCheck(viewport, check) {
|
|
2058
2069
|
const key = selectorKey(check);
|
|
2059
2070
|
const sequence = viewport.text_sequences?.[key];
|
|
@@ -2560,6 +2571,40 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
2560
2571
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
2561
2572
|
};
|
|
2562
2573
|
}
|
|
2574
|
+
if (check.type === "observe_within") {
|
|
2575
|
+
const key = observeWithinKey(check);
|
|
2576
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
2577
|
+
const results = viewports.map((viewport) => {
|
|
2578
|
+
const observation = viewport.observations?.[key];
|
|
2579
|
+
const matched = observation?.matched === true;
|
|
2580
|
+
return {
|
|
2581
|
+
viewport: viewport.name,
|
|
2582
|
+
matched,
|
|
2583
|
+
elapsed_ms: numberValue(observation?.elapsed_ms) ?? null,
|
|
2584
|
+
timeout_ms: numberValue(observation?.timeout_ms) ?? timeoutMs,
|
|
2585
|
+
attempts: numberValue(observation?.attempts) ?? null,
|
|
2586
|
+
selector_count: numberValue(observation?.selector_count) ?? null,
|
|
2587
|
+
visible_count: numberValue(observation?.visible_count) ?? null,
|
|
2588
|
+
matched_count: numberValue(observation?.matched_count) ?? null,
|
|
2589
|
+
sample: stringValue(observation?.sample) ?? null,
|
|
2590
|
+
error: stringValue(observation?.error) ?? null
|
|
2591
|
+
};
|
|
2592
|
+
});
|
|
2593
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
2594
|
+
return {
|
|
2595
|
+
type: check.type,
|
|
2596
|
+
label: checkLabel(check),
|
|
2597
|
+
status: failed ? "failed" : "passed",
|
|
2598
|
+
evidence: {
|
|
2599
|
+
selector: check.selector || null,
|
|
2600
|
+
text: check.text || null,
|
|
2601
|
+
pattern: check.pattern || null,
|
|
2602
|
+
timeout_ms: timeoutMs,
|
|
2603
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
2604
|
+
},
|
|
2605
|
+
message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2563
2608
|
if (check.type === "frame_text_visible") {
|
|
2564
2609
|
const key = selectorKey(check);
|
|
2565
2610
|
const results = viewports.map((viewport) => {
|
|
@@ -4469,6 +4514,36 @@ function assessProfile(profile, evidence) {
|
|
|
4469
4514
|
});
|
|
4470
4515
|
continue;
|
|
4471
4516
|
}
|
|
4517
|
+
if (check.type === "observe_within") {
|
|
4518
|
+
const key = observeWithinKey(check);
|
|
4519
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
4520
|
+
const results = checkViewports.map((viewport) => {
|
|
4521
|
+
const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
|
|
4522
|
+
? viewport.observations[key]
|
|
4523
|
+
: {};
|
|
4524
|
+
return {
|
|
4525
|
+
viewport: viewport.name,
|
|
4526
|
+
matched: observation.matched === true,
|
|
4527
|
+
elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
|
|
4528
|
+
timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
|
|
4529
|
+
attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
|
|
4530
|
+
selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
|
|
4531
|
+
visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
|
|
4532
|
+
matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
|
|
4533
|
+
sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
|
|
4534
|
+
error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
|
|
4535
|
+
};
|
|
4536
|
+
});
|
|
4537
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
4538
|
+
checks.push({
|
|
4539
|
+
type: check.type,
|
|
4540
|
+
label: check.label || check.type,
|
|
4541
|
+
status: failed ? "failed" : "passed",
|
|
4542
|
+
evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
|
|
4543
|
+
message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
|
|
4544
|
+
});
|
|
4545
|
+
continue;
|
|
4546
|
+
}
|
|
4472
4547
|
if (check.type === "frame_text_visible") {
|
|
4473
4548
|
const selector = check.selector || "";
|
|
4474
4549
|
const results = checkViewports.map((viewport) => {
|
|
@@ -4901,6 +4976,19 @@ function ensureDialogHandler() {
|
|
|
4901
4976
|
function textKey(check) {
|
|
4902
4977
|
return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
4903
4978
|
}
|
|
4979
|
+
function observeWithinTimeoutMs(check) {
|
|
4980
|
+
const raw = Number(check && check.timeout_ms);
|
|
4981
|
+
return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
|
|
4982
|
+
}
|
|
4983
|
+
function observeWithinKey(check) {
|
|
4984
|
+
const target = check && check.selector ? "selector:" + check.selector : "page";
|
|
4985
|
+
const expectation = check && check.pattern
|
|
4986
|
+
? "pattern:" + check.pattern + "/" + (check.flags || "")
|
|
4987
|
+
: check && check.text
|
|
4988
|
+
? "text:" + check.text
|
|
4989
|
+
: "visible";
|
|
4990
|
+
return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
|
|
4991
|
+
}
|
|
4904
4992
|
function textMatches(sample, check) {
|
|
4905
4993
|
if (check.pattern) {
|
|
4906
4994
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -6216,6 +6304,104 @@ async function selectorTextSequence(selector) {
|
|
|
6216
6304
|
};
|
|
6217
6305
|
}).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) }));
|
|
6218
6306
|
}
|
|
6307
|
+
async function observeWithinSnapshot(check) {
|
|
6308
|
+
const payload = {
|
|
6309
|
+
selector: check.selector || "",
|
|
6310
|
+
text: check.text || "",
|
|
6311
|
+
pattern: check.pattern || "",
|
|
6312
|
+
flags: check.flags || "",
|
|
6313
|
+
wants_text: Boolean(check.text || check.pattern),
|
|
6314
|
+
};
|
|
6315
|
+
if (payload.selector) {
|
|
6316
|
+
return page.locator(payload.selector).evaluateAll((elements, input) => {
|
|
6317
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
6318
|
+
const matchText = (value) => {
|
|
6319
|
+
const source = compact(value);
|
|
6320
|
+
if (input.pattern) {
|
|
6321
|
+
try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
|
|
6322
|
+
}
|
|
6323
|
+
return source.includes(input.text || "");
|
|
6324
|
+
};
|
|
6325
|
+
const isVisible = (element) => {
|
|
6326
|
+
const style = window.getComputedStyle(element);
|
|
6327
|
+
const rect = element.getBoundingClientRect();
|
|
6328
|
+
return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
6329
|
+
};
|
|
6330
|
+
const rows = elements.map((element, index) => {
|
|
6331
|
+
const text = compact(element.innerText || element.textContent || "");
|
|
6332
|
+
const visible = isVisible(element);
|
|
6333
|
+
return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
|
|
6334
|
+
});
|
|
6335
|
+
const visibleRows = rows.filter((row) => row.visible);
|
|
6336
|
+
const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
|
|
6337
|
+
const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
|
|
6338
|
+
return {
|
|
6339
|
+
selector: input.selector,
|
|
6340
|
+
text: input.text || null,
|
|
6341
|
+
pattern: input.pattern || null,
|
|
6342
|
+
selector_count: rows.length,
|
|
6343
|
+
visible_count: visibleRows.length,
|
|
6344
|
+
matched_count: matches.length,
|
|
6345
|
+
matched: matches.length > 0,
|
|
6346
|
+
sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
|
|
6347
|
+
};
|
|
6348
|
+
}, payload).catch((error) => ({
|
|
6349
|
+
selector: payload.selector,
|
|
6350
|
+
text: payload.text || null,
|
|
6351
|
+
pattern: payload.pattern || null,
|
|
6352
|
+
selector_count: 0,
|
|
6353
|
+
visible_count: 0,
|
|
6354
|
+
matched_count: 0,
|
|
6355
|
+
matched: false,
|
|
6356
|
+
sample: null,
|
|
6357
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
6358
|
+
}));
|
|
6359
|
+
}
|
|
6360
|
+
return page.evaluate((input) => {
|
|
6361
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
6362
|
+
const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
|
|
6363
|
+
let matched = false;
|
|
6364
|
+
if (input.pattern) {
|
|
6365
|
+
try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
|
|
6366
|
+
} else {
|
|
6367
|
+
matched = sample.includes(input.text || "");
|
|
6368
|
+
}
|
|
6369
|
+
return {
|
|
6370
|
+
selector: null,
|
|
6371
|
+
text: input.text || null,
|
|
6372
|
+
pattern: input.pattern || null,
|
|
6373
|
+
matched,
|
|
6374
|
+
matched_count: matched ? 1 : 0,
|
|
6375
|
+
sample: sample.slice(0, 240),
|
|
6376
|
+
};
|
|
6377
|
+
}, payload).catch((error) => ({
|
|
6378
|
+
selector: null,
|
|
6379
|
+
text: payload.text || null,
|
|
6380
|
+
pattern: payload.pattern || null,
|
|
6381
|
+
matched: false,
|
|
6382
|
+
matched_count: 0,
|
|
6383
|
+
sample: null,
|
|
6384
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
6385
|
+
}));
|
|
6386
|
+
}
|
|
6387
|
+
async function observeWithin(check) {
|
|
6388
|
+
const timeoutMs = observeWithinTimeoutMs(check);
|
|
6389
|
+
const startedAt = Date.now();
|
|
6390
|
+
let attempts = 0;
|
|
6391
|
+
let last = null;
|
|
6392
|
+
while (true) {
|
|
6393
|
+
attempts += 1;
|
|
6394
|
+
last = await observeWithinSnapshot(check);
|
|
6395
|
+
const elapsedMs = Date.now() - startedAt;
|
|
6396
|
+
if (last && last.matched === true) {
|
|
6397
|
+
return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
6398
|
+
}
|
|
6399
|
+
if (elapsedMs >= timeoutMs) {
|
|
6400
|
+
return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
|
|
6401
|
+
}
|
|
6402
|
+
await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6219
6405
|
function linkProbeMaxLinks(check) {
|
|
6220
6406
|
const value = Number(check.max_links || check.maxLinks || check.limit || 100);
|
|
6221
6407
|
return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
|
|
@@ -7268,6 +7454,7 @@ async function captureViewport(viewport) {
|
|
|
7268
7454
|
const text_matches = {};
|
|
7269
7455
|
const text_match_samples = {};
|
|
7270
7456
|
const text_case_insensitive_samples = {};
|
|
7457
|
+
const observations = {};
|
|
7271
7458
|
const http_statuses = {};
|
|
7272
7459
|
const link_statuses = {};
|
|
7273
7460
|
for (const check of profile.checks || []) {
|
|
@@ -7288,6 +7475,10 @@ async function captureViewport(viewport) {
|
|
|
7288
7475
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
7289
7476
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
7290
7477
|
}
|
|
7478
|
+
if (check.type === "observe_within") {
|
|
7479
|
+
const key = observeWithinKey(check);
|
|
7480
|
+
observations[key] = observations[key] || await observeWithin(check);
|
|
7481
|
+
}
|
|
7291
7482
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
7292
7483
|
const key = textKey(check);
|
|
7293
7484
|
const sample = dom.body_text || dom.body_text_sample || "";
|
|
@@ -7381,6 +7572,7 @@ async function captureViewport(viewport) {
|
|
|
7381
7572
|
text_matches,
|
|
7382
7573
|
text_match_samples,
|
|
7383
7574
|
text_case_insensitive_samples,
|
|
7575
|
+
observations,
|
|
7384
7576
|
http_statuses,
|
|
7385
7577
|
link_statuses,
|
|
7386
7578
|
route_inventory: routeInventory,
|
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", "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", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
|
|
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", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "observe_within", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "set_range_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_eval", "window_call", "window_call_until"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -319,6 +319,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
319
319
|
text_matches?: Record<string, boolean>;
|
|
320
320
|
text_match_samples?: Record<string, string[]>;
|
|
321
321
|
text_case_insensitive_samples?: Record<string, string[]>;
|
|
322
|
+
observations?: Record<string, Record<string, JsonValue>>;
|
|
322
323
|
http_statuses?: Record<string, Record<string, JsonValue>>;
|
|
323
324
|
link_statuses?: Record<string, Record<string, JsonValue>>;
|
|
324
325
|
route_inventory?: Record<string, JsonValue>;
|
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", "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", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
|
|
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", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "observe_within", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "set_range_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_eval", "window_call", "window_call_until"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -319,6 +319,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
319
319
|
text_matches?: Record<string, boolean>;
|
|
320
320
|
text_match_samples?: Record<string, string[]>;
|
|
321
321
|
text_case_insensitive_samples?: Record<string, string[]>;
|
|
322
|
+
observations?: Record<string, Record<string, JsonValue>>;
|
|
322
323
|
http_statuses?: Record<string, Record<string, JsonValue>>;
|
|
323
324
|
link_statuses?: Record<string, Record<string, JsonValue>>;
|
|
324
325
|
route_inventory?: Record<string, JsonValue>;
|
package/dist/profile.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
resolveRiddleProofProfileTimeoutSec,
|
|
24
24
|
slugifyRiddleProofProfileName,
|
|
25
25
|
summarizeRiddleProofProfileResult
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-QJJ3ISMK.js";
|
|
27
27
|
export {
|
|
28
28
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
29
29
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|