@riddledc/riddle-proof 0.7.148 → 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-WAJA6TJV.js → chunk-QJJ3ISMK.js} +238 -2
- package/dist/cli.cjs +271 -3
- package/dist/cli.js +34 -2
- package/dist/index.cjs +238 -2
- package/dist/index.js +1 -1
- package/dist/profile.cjs +238 -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",
|
|
@@ -497,6 +498,22 @@ function profileSetupWindowEvalReceipts(results) {
|
|
|
497
498
|
return receipt;
|
|
498
499
|
});
|
|
499
500
|
}
|
|
501
|
+
function profileSetupRangeValueReceipts(results) {
|
|
502
|
+
return results.filter((result) => profileSetupResultAction(result) === "set_range_value").map((result) => ({
|
|
503
|
+
ordinal: result.ordinal ?? null,
|
|
504
|
+
ok: result.ok !== false,
|
|
505
|
+
selector: result.selector ?? null,
|
|
506
|
+
frame_selector: result.frame_selector ?? null,
|
|
507
|
+
requested_value: result.requested_value ?? null,
|
|
508
|
+
actual_value: result.actual_value ?? null,
|
|
509
|
+
before_value: result.before_value ?? null,
|
|
510
|
+
value_as_number: result.value_as_number ?? null,
|
|
511
|
+
min: result.min ?? null,
|
|
512
|
+
max: result.max ?? null,
|
|
513
|
+
step: result.step ?? null,
|
|
514
|
+
reason: result.reason ?? result.error ?? null
|
|
515
|
+
}));
|
|
516
|
+
}
|
|
500
517
|
function sampleProfileSetupSummaryItems(items, limit) {
|
|
501
518
|
if (items.length <= limit) return items;
|
|
502
519
|
const firstCount = Math.floor(limit / 2);
|
|
@@ -539,6 +556,8 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
|
|
|
539
556
|
const windowEvalStoredTotal = windowEvalReceipts.filter((result) => typeof result.return_stored_to === "string" && result.return_stored_to.trim()).length;
|
|
540
557
|
const windowEvalCapturedTotal = windowEvalReceipts.filter((result) => result.return_captured === true).length;
|
|
541
558
|
const sampledWindowEvalReceipts = sampleProfileSetupSummaryItems(windowEvalReceipts, 8);
|
|
559
|
+
const rangeValueReceipts = profileSetupRangeValueReceipts(results);
|
|
560
|
+
const sampledRangeValueReceipts = sampleProfileSetupSummaryItems(rangeValueReceipts, 8);
|
|
542
561
|
const clickedItems = results.filter((result) => profileSetupResultAction(result) === "click" && result.ok !== false).map((result) => {
|
|
543
562
|
const clickCount = typeof result.click_count === "number" && Number.isFinite(result.click_count) && result.click_count > 1 ? result.click_count : void 0;
|
|
544
563
|
return {
|
|
@@ -587,6 +606,9 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
|
|
|
587
606
|
window_eval_captured_total: windowEvalCapturedTotal,
|
|
588
607
|
window_eval_truncated: windowEvalReceipts.length > sampledWindowEvalReceipts.length,
|
|
589
608
|
window_eval: sampledWindowEvalReceipts,
|
|
609
|
+
set_range_value_total: rangeValueReceipts.length,
|
|
610
|
+
set_range_value_truncated: rangeValueReceipts.length > sampledRangeValueReceipts.length,
|
|
611
|
+
set_range_value: sampledRangeValueReceipts,
|
|
590
612
|
clicked,
|
|
591
613
|
text_samples,
|
|
592
614
|
failed: failed.map((result) => ({
|
|
@@ -1327,7 +1349,7 @@ function normalizeCheck(input, index) {
|
|
|
1327
1349
|
throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
|
|
1328
1350
|
}
|
|
1329
1351
|
const isDialogCountCheck = isDialogCountCheckType(type);
|
|
1330
|
-
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)) {
|
|
1331
1353
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
1332
1354
|
}
|
|
1333
1355
|
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
@@ -1468,7 +1490,7 @@ function normalizeCheck(input, index) {
|
|
|
1468
1490
|
allowed_content_types: allowedContentTypes,
|
|
1469
1491
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
1470
1492
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
1471
|
-
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),
|
|
1472
1494
|
run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
|
|
1473
1495
|
run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
|
|
1474
1496
|
run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
|
|
@@ -1986,6 +2008,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
1986
2008
|
function textKey(check) {
|
|
1987
2009
|
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
1988
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
|
+
}
|
|
1989
2021
|
function textSequenceForCheck(viewport, check) {
|
|
1990
2022
|
const key = selectorKey(check);
|
|
1991
2023
|
const sequence = viewport.text_sequences?.[key];
|
|
@@ -2492,6 +2524,40 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
2492
2524
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
2493
2525
|
};
|
|
2494
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
|
+
}
|
|
2495
2561
|
if (check.type === "frame_text_visible") {
|
|
2496
2562
|
const key = selectorKey(check);
|
|
2497
2563
|
const results = viewports.map((viewport) => {
|
|
@@ -3896,6 +3962,24 @@ function profileSetupWindowEvalReceipts(results) {
|
|
|
3896
3962
|
return receipt;
|
|
3897
3963
|
});
|
|
3898
3964
|
}
|
|
3965
|
+
function profileSetupRangeValueReceipts(results) {
|
|
3966
|
+
return (results || [])
|
|
3967
|
+
.filter((result) => result && profileSetupResultAction(result) === "set_range_value")
|
|
3968
|
+
.map((result) => ({
|
|
3969
|
+
ordinal: result.ordinal ?? null,
|
|
3970
|
+
ok: result.ok !== false,
|
|
3971
|
+
selector: result.selector ?? null,
|
|
3972
|
+
frame_selector: result.frame_selector ?? null,
|
|
3973
|
+
requested_value: result.requested_value ?? null,
|
|
3974
|
+
actual_value: result.actual_value ?? null,
|
|
3975
|
+
before_value: result.before_value ?? null,
|
|
3976
|
+
value_as_number: result.value_as_number ?? null,
|
|
3977
|
+
min: result.min ?? null,
|
|
3978
|
+
max: result.max ?? null,
|
|
3979
|
+
step: result.step ?? null,
|
|
3980
|
+
reason: result.reason || result.error || null,
|
|
3981
|
+
}));
|
|
3982
|
+
}
|
|
3899
3983
|
function sampleProfileSetupSummaryItems(items, limit) {
|
|
3900
3984
|
if ((items || []).length <= limit) return items || [];
|
|
3901
3985
|
const firstCount = Math.floor(limit / 2);
|
|
@@ -3952,6 +4036,8 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
|
|
|
3952
4036
|
const windowEvalStoredTotal = windowEvalReceipts.filter((result) => typeof result.return_stored_to === "string" && result.return_stored_to.trim()).length;
|
|
3953
4037
|
const windowEvalCapturedTotal = windowEvalReceipts.filter((result) => result.return_captured === true).length;
|
|
3954
4038
|
const sampledWindowEvalReceipts = sampleProfileSetupSummaryItems(windowEvalReceipts, 8);
|
|
4039
|
+
const rangeValueReceipts = profileSetupRangeValueReceipts(results);
|
|
4040
|
+
const sampledRangeValueReceipts = sampleProfileSetupSummaryItems(rangeValueReceipts, 8);
|
|
3955
4041
|
const clickedItems = results
|
|
3956
4042
|
.filter((result) => result && profileSetupResultAction(result) === "click" && result.ok !== false)
|
|
3957
4043
|
.map((result) => {
|
|
@@ -4010,6 +4096,9 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
|
|
|
4010
4096
|
window_eval_captured_total: windowEvalCapturedTotal,
|
|
4011
4097
|
window_eval_truncated: windowEvalReceipts.length > sampledWindowEvalReceipts.length,
|
|
4012
4098
|
window_eval: sampledWindowEvalReceipts,
|
|
4099
|
+
set_range_value_total: rangeValueReceipts.length,
|
|
4100
|
+
set_range_value_truncated: rangeValueReceipts.length > sampledRangeValueReceipts.length,
|
|
4101
|
+
set_range_value: sampledRangeValueReceipts,
|
|
4013
4102
|
clicked,
|
|
4014
4103
|
text_samples: textSamples,
|
|
4015
4104
|
failed: failed.map((result) => ({
|
|
@@ -4378,6 +4467,36 @@ function assessProfile(profile, evidence) {
|
|
|
4378
4467
|
});
|
|
4379
4468
|
continue;
|
|
4380
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
|
+
}
|
|
4381
4500
|
if (check.type === "frame_text_visible") {
|
|
4382
4501
|
const selector = check.selector || "";
|
|
4383
4502
|
const results = checkViewports.map((viewport) => {
|
|
@@ -4810,6 +4929,19 @@ function ensureDialogHandler() {
|
|
|
4810
4929
|
function textKey(check) {
|
|
4811
4930
|
return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
4812
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
|
+
}
|
|
4813
4945
|
function textMatches(sample, check) {
|
|
4814
4946
|
if (check.pattern) {
|
|
4815
4947
|
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
@@ -6125,6 +6257,104 @@ async function selectorTextSequence(selector) {
|
|
|
6125
6257
|
};
|
|
6126
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) }));
|
|
6127
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
|
+
}
|
|
6128
6358
|
function linkProbeMaxLinks(check) {
|
|
6129
6359
|
const value = Number(check.max_links || check.maxLinks || check.limit || 100);
|
|
6130
6360
|
return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
|
|
@@ -7177,6 +7407,7 @@ async function captureViewport(viewport) {
|
|
|
7177
7407
|
const text_matches = {};
|
|
7178
7408
|
const text_match_samples = {};
|
|
7179
7409
|
const text_case_insensitive_samples = {};
|
|
7410
|
+
const observations = {};
|
|
7180
7411
|
const http_statuses = {};
|
|
7181
7412
|
const link_statuses = {};
|
|
7182
7413
|
for (const check of profile.checks || []) {
|
|
@@ -7197,6 +7428,10 @@ async function captureViewport(viewport) {
|
|
|
7197
7428
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
7198
7429
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
7199
7430
|
}
|
|
7431
|
+
if (check.type === "observe_within") {
|
|
7432
|
+
const key = observeWithinKey(check);
|
|
7433
|
+
observations[key] = observations[key] || await observeWithin(check);
|
|
7434
|
+
}
|
|
7200
7435
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
7201
7436
|
const key = textKey(check);
|
|
7202
7437
|
const sample = dom.body_text || dom.body_text_sample || "";
|
|
@@ -7290,6 +7525,7 @@ async function captureViewport(viewport) {
|
|
|
7290
7525
|
text_matches,
|
|
7291
7526
|
text_match_samples,
|
|
7292
7527
|
text_case_insensitive_samples,
|
|
7528
|
+
observations,
|
|
7293
7529
|
http_statuses,
|
|
7294
7530
|
link_statuses,
|
|
7295
7531
|
route_inventory: routeInventory,
|