@riddledc/riddle-proof 0.7.33 → 0.7.35
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 +25 -7
- package/dist/{chunk-COUZXKGM.js → chunk-VCINQE5O.js} +173 -5
- package/dist/cli.cjs +173 -5
- package/dist/cli.js +1 -1
- package/dist/index.cjs +173 -5
- package/dist/index.js +1 -1
- package/dist/profile.cjs +173 -5
- package/dist/profile.d.cts +3 -1
- package/dist/profile.d.ts +3 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,6 +202,21 @@ clears `local`, `session`, or `both` browser storage scopes, defaults to
|
|
|
202
202
|
profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
|
|
203
203
|
still overrides the profile value for one-off runs.
|
|
204
204
|
|
|
205
|
+
Use `selector_text_order` when a table, list, or card group must show visible
|
|
206
|
+
items in a specific order after setup actions such as sorting or filtering:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"type": "selector_text_order",
|
|
211
|
+
"selector": ".game-table tbody tr",
|
|
212
|
+
"expected_texts": ["AAA Alpha", "MMM Middle", "ZZZ Omega"]
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The check records the visible text sequence for the selector and passes when
|
|
217
|
+
the expected texts appear in that order as a subsequence. This is less brittle
|
|
218
|
+
than matching one large body-text regex when only row or card order matters.
|
|
219
|
+
|
|
205
220
|
Use the `route_inventory` check for source-page route coverage audits where a
|
|
206
221
|
navigation surface must expose a known set of routes and each route must load
|
|
207
222
|
both directly and through real link clicks:
|
|
@@ -220,14 +235,17 @@ both directly and through real link clicks:
|
|
|
220
235
|
}
|
|
221
236
|
```
|
|
222
237
|
|
|
223
|
-
The check records discovered source links,
|
|
224
|
-
|
|
225
|
-
source-surface failures. It
|
|
226
|
-
|
|
227
|
-
source page across all configured
|
|
228
|
-
`
|
|
238
|
+
The check records discovered source links, unique source-link counts, duplicate
|
|
239
|
+
source-link counts, missing/unexpected routes, direct route health, real
|
|
240
|
+
clickthrough health, wrong-path failures, and stale source-surface failures. It
|
|
241
|
+
runs direct/clickthrough sweeps on the first viewport by default and leaves
|
|
242
|
+
ordinary profile overflow checks to cover the source page across all configured
|
|
243
|
+
viewports. Set `run_direct_routes: false`, `run_clickthroughs: false`,
|
|
244
|
+
`allow_unexpected_routes: true`, `require_unique_routes: false`, or
|
|
229
245
|
`save_route_screenshots: true` when a profile needs a narrower or more
|
|
230
|
-
artifact-heavy audit.
|
|
246
|
+
artifact-heavy audit. `require_unique_routes: false` is useful when a navigation
|
|
247
|
+
surface intentionally links to the same route from multiple cards or anchors,
|
|
248
|
+
while still proving the unique expected route set.
|
|
231
249
|
|
|
232
250
|
The result uses `riddle-proof.profile-result.v1` and separates product failures
|
|
233
251
|
from weak proof and environment blockers:
|
|
@@ -14,6 +14,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
14
14
|
"route_loaded",
|
|
15
15
|
"selector_visible",
|
|
16
16
|
"selector_count_at_least",
|
|
17
|
+
"selector_text_order",
|
|
17
18
|
"text_visible",
|
|
18
19
|
"text_absent",
|
|
19
20
|
"route_inventory",
|
|
@@ -293,6 +294,15 @@ function normalizeRouteInventoryRoutes(value, index) {
|
|
|
293
294
|
}
|
|
294
295
|
return routes;
|
|
295
296
|
}
|
|
297
|
+
function normalizeExpectedTexts(value, index) {
|
|
298
|
+
if (value === void 0) return void 0;
|
|
299
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
300
|
+
throw new Error(`checks[${index}] selector_text_order expected_texts must be a non-empty array.`);
|
|
301
|
+
}
|
|
302
|
+
const texts = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
303
|
+
if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
|
|
304
|
+
return texts;
|
|
305
|
+
}
|
|
296
306
|
function normalizeCheck(input, index) {
|
|
297
307
|
if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
298
308
|
const type = stringValue(input.type);
|
|
@@ -309,6 +319,11 @@ function normalizeCheck(input, index) {
|
|
|
309
319
|
if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
|
|
310
320
|
throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
|
|
311
321
|
}
|
|
322
|
+
const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
|
|
323
|
+
if (type === "selector_text_order") {
|
|
324
|
+
if (!stringValue(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
|
|
325
|
+
if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
|
|
326
|
+
}
|
|
312
327
|
const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
|
|
313
328
|
if (type === "route_inventory" && !expectedRoutes?.length) {
|
|
314
329
|
throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
|
|
@@ -319,6 +334,7 @@ function normalizeCheck(input, index) {
|
|
|
319
334
|
expected_path: stringValue(input.expected_path),
|
|
320
335
|
expected_routes: expectedRoutes,
|
|
321
336
|
selector: stringValue(input.selector),
|
|
337
|
+
expected_texts: expectedTexts,
|
|
322
338
|
link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
|
|
323
339
|
source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
|
|
324
340
|
route_path_prefix: stringValue(input.route_path_prefix) || stringValue(input.routePathPrefix),
|
|
@@ -424,6 +440,28 @@ function selectorKey(check) {
|
|
|
424
440
|
function textKey(check) {
|
|
425
441
|
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
426
442
|
}
|
|
443
|
+
function textSequenceForCheck(viewport, check) {
|
|
444
|
+
const key = selectorKey(check);
|
|
445
|
+
const sequence = viewport.text_sequences?.[key];
|
|
446
|
+
if (isRecord(sequence)) {
|
|
447
|
+
const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
|
|
448
|
+
const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
|
|
449
|
+
const candidates = visibleTexts.length ? visibleTexts : texts;
|
|
450
|
+
return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
451
|
+
}
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
function textOrderMatch(texts, expectedTexts) {
|
|
455
|
+
const positions = [];
|
|
456
|
+
let startAt = 0;
|
|
457
|
+
for (const expected of expectedTexts) {
|
|
458
|
+
const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
|
|
459
|
+
if (index < 0) return { matched: false, positions };
|
|
460
|
+
positions.push(index);
|
|
461
|
+
startAt = index + 1;
|
|
462
|
+
}
|
|
463
|
+
return { matched: true, positions };
|
|
464
|
+
}
|
|
427
465
|
function matchText(sample, check) {
|
|
428
466
|
if (check.pattern) {
|
|
429
467
|
try {
|
|
@@ -543,6 +581,32 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
543
581
|
message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
|
|
544
582
|
};
|
|
545
583
|
}
|
|
584
|
+
if (check.type === "selector_text_order") {
|
|
585
|
+
const key = selectorKey(check);
|
|
586
|
+
const expectedTexts = check.expected_texts || [];
|
|
587
|
+
const results = viewports.map((viewport) => {
|
|
588
|
+
const texts = textSequenceForCheck(viewport, check);
|
|
589
|
+
const match = textOrderMatch(texts, expectedTexts);
|
|
590
|
+
return {
|
|
591
|
+
viewport: viewport.name,
|
|
592
|
+
matched: match.matched,
|
|
593
|
+
matched_positions: match.positions,
|
|
594
|
+
visible_texts: texts.slice(0, 20)
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
598
|
+
return {
|
|
599
|
+
type: check.type,
|
|
600
|
+
label: checkLabel(check),
|
|
601
|
+
status: failed ? "failed" : "passed",
|
|
602
|
+
evidence: {
|
|
603
|
+
selector: key,
|
|
604
|
+
expected_texts: expectedTexts,
|
|
605
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
606
|
+
},
|
|
607
|
+
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
608
|
+
};
|
|
609
|
+
}
|
|
546
610
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
547
611
|
const key = textKey(check);
|
|
548
612
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -578,14 +642,23 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
578
642
|
const first = inventories[0]?.inventory;
|
|
579
643
|
const directRoutes = Array.isArray(first?.direct_routes) ? first.direct_routes : [];
|
|
580
644
|
const clickthroughs = Array.isArray(first?.clickthroughs) ? first.clickthroughs : [];
|
|
645
|
+
const sourceLinkCount = numberValue(first?.source_link_count) ?? numberValue(first?.home_game_link_count) ?? null;
|
|
646
|
+
const sourceUniqueLinkCount = numberValue(first?.source_unique_link_count) ?? numberValue(first?.home_unique_game_link_count) ?? null;
|
|
647
|
+
const duplicateSourceLinks = Array.isArray(first?.duplicate_source_link_paths) ? first.duplicate_source_link_paths.map((path) => String(path)) : [];
|
|
648
|
+
const duplicateSourceLinkCount = numberValue(first?.duplicate_source_link_count) ?? (sourceLinkCount !== null && sourceUniqueLinkCount !== null ? Math.max(0, sourceLinkCount - sourceUniqueLinkCount) : null);
|
|
581
649
|
return {
|
|
582
650
|
type: check.type,
|
|
583
651
|
label: checkLabel(check),
|
|
584
652
|
status: failures.length ? "failed" : "passed",
|
|
585
653
|
evidence: {
|
|
586
654
|
expected_count: check.expected_routes?.length || 0,
|
|
587
|
-
|
|
588
|
-
|
|
655
|
+
source_link_count: sourceLinkCount,
|
|
656
|
+
source_unique_link_count: sourceUniqueLinkCount,
|
|
657
|
+
duplicate_source_link_count: duplicateSourceLinkCount,
|
|
658
|
+
duplicate_source_links: duplicateSourceLinks,
|
|
659
|
+
duplicates_allowed: check.require_unique_routes === false,
|
|
660
|
+
homepage_link_count: numberValue(first?.home_game_link_count) ?? sourceLinkCount,
|
|
661
|
+
homepage_unique_link_count: numberValue(first?.home_unique_game_link_count) ?? sourceUniqueLinkCount,
|
|
589
662
|
direct_route_count: directRoutes.length,
|
|
590
663
|
clickthrough_count: clickthroughs.length,
|
|
591
664
|
failures
|
|
@@ -884,6 +957,28 @@ function textMatches(sample, check) {
|
|
|
884
957
|
}
|
|
885
958
|
return String(sample || "").includes(check.text || "");
|
|
886
959
|
}
|
|
960
|
+
function textSequenceForCheck(viewport, check) {
|
|
961
|
+
const key = check.selector || "";
|
|
962
|
+
const sequence = viewport.text_sequences && viewport.text_sequences[key];
|
|
963
|
+
if (sequence && typeof sequence === "object") {
|
|
964
|
+
const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
|
|
965
|
+
const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
|
|
966
|
+
const candidates = visibleTexts.length ? visibleTexts : texts;
|
|
967
|
+
return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
968
|
+
}
|
|
969
|
+
return [];
|
|
970
|
+
}
|
|
971
|
+
function textOrderMatch(texts, expectedTexts) {
|
|
972
|
+
const positions = [];
|
|
973
|
+
let startAt = 0;
|
|
974
|
+
for (const expected of expectedTexts || []) {
|
|
975
|
+
const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
|
|
976
|
+
if (index < 0) return { matched: false, positions };
|
|
977
|
+
positions.push(index);
|
|
978
|
+
startAt = index + 1;
|
|
979
|
+
}
|
|
980
|
+
return { matched: true, positions };
|
|
981
|
+
}
|
|
887
982
|
function numberValue(value) {
|
|
888
983
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
889
984
|
}
|
|
@@ -1088,6 +1183,29 @@ function assessProfile(profile, evidence) {
|
|
|
1088
1183
|
});
|
|
1089
1184
|
continue;
|
|
1090
1185
|
}
|
|
1186
|
+
if (check.type === "selector_text_order") {
|
|
1187
|
+
const selector = check.selector || "";
|
|
1188
|
+
const expectedTexts = check.expected_texts || [];
|
|
1189
|
+
const results = viewports.map((viewport) => {
|
|
1190
|
+
const texts = textSequenceForCheck(viewport, check);
|
|
1191
|
+
const match = textOrderMatch(texts, expectedTexts);
|
|
1192
|
+
return {
|
|
1193
|
+
viewport: viewport.name,
|
|
1194
|
+
matched: match.matched,
|
|
1195
|
+
matched_positions: match.positions,
|
|
1196
|
+
visible_texts: texts.slice(0, 20),
|
|
1197
|
+
};
|
|
1198
|
+
});
|
|
1199
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
1200
|
+
checks.push({
|
|
1201
|
+
type: check.type,
|
|
1202
|
+
label: check.label || check.type,
|
|
1203
|
+
status: failed ? "failed" : "passed",
|
|
1204
|
+
evidence: { selector, expected_texts: expectedTexts, viewports: results },
|
|
1205
|
+
message: failed ? "Selector " + selector + " text order failed in " + failed + " viewport(s)." : undefined,
|
|
1206
|
+
});
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1091
1209
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
1092
1210
|
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
1093
1211
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -1125,14 +1243,23 @@ function assessProfile(profile, evidence) {
|
|
|
1125
1243
|
const first = inventories[0].inventory || {};
|
|
1126
1244
|
const directRoutes = Array.isArray(first.direct_routes) ? first.direct_routes : [];
|
|
1127
1245
|
const clickthroughs = Array.isArray(first.clickthroughs) ? first.clickthroughs : [];
|
|
1246
|
+
const sourceLinkCount = typeof first.source_link_count === "number" ? first.source_link_count : typeof first.home_game_link_count === "number" ? first.home_game_link_count : null;
|
|
1247
|
+
const sourceUniqueLinkCount = typeof first.source_unique_link_count === "number" ? first.source_unique_link_count : typeof first.home_unique_game_link_count === "number" ? first.home_unique_game_link_count : null;
|
|
1248
|
+
const duplicateSourceLinks = Array.isArray(first.duplicate_source_link_paths) ? first.duplicate_source_link_paths.map((path) => String(path)) : [];
|
|
1249
|
+
const duplicateSourceLinkCount = typeof first.duplicate_source_link_count === "number" ? first.duplicate_source_link_count : sourceLinkCount !== null && sourceUniqueLinkCount !== null ? Math.max(0, sourceLinkCount - sourceUniqueLinkCount) : null;
|
|
1128
1250
|
checks.push({
|
|
1129
1251
|
type: check.type,
|
|
1130
1252
|
label: check.label || check.type,
|
|
1131
1253
|
status: failures.length ? "failed" : "passed",
|
|
1132
1254
|
evidence: {
|
|
1133
1255
|
expected_count: (check.expected_routes || []).length,
|
|
1134
|
-
|
|
1135
|
-
|
|
1256
|
+
source_link_count: sourceLinkCount,
|
|
1257
|
+
source_unique_link_count: sourceUniqueLinkCount,
|
|
1258
|
+
duplicate_source_link_count: duplicateSourceLinkCount,
|
|
1259
|
+
duplicate_source_links: duplicateSourceLinks,
|
|
1260
|
+
duplicates_allowed: check.require_unique_routes === false,
|
|
1261
|
+
homepage_link_count: typeof first.home_game_link_count === "number" ? first.home_game_link_count : sourceLinkCount,
|
|
1262
|
+
homepage_unique_link_count: typeof first.home_unique_game_link_count === "number" ? first.home_unique_game_link_count : sourceUniqueLinkCount,
|
|
1136
1263
|
direct_route_count: directRoutes.length,
|
|
1137
1264
|
clickthrough_count: clickthroughs.length,
|
|
1138
1265
|
failures,
|
|
@@ -1452,6 +1579,27 @@ async function selectorStats(selector) {
|
|
|
1452
1579
|
return { count: elements.length, visible_count: elements.filter(isVisible).length };
|
|
1453
1580
|
}).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
1454
1581
|
}
|
|
1582
|
+
async function selectorTextSequence(selector) {
|
|
1583
|
+
return page.locator(selector).evaluateAll((elements) => {
|
|
1584
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
|
|
1585
|
+
const isVisible = (element) => {
|
|
1586
|
+
const style = window.getComputedStyle(element);
|
|
1587
|
+
const rect = element.getBoundingClientRect();
|
|
1588
|
+
return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
1589
|
+
};
|
|
1590
|
+
const rows = elements.map((element, index) => ({
|
|
1591
|
+
index,
|
|
1592
|
+
text: compact(element.innerText || element.textContent || ""),
|
|
1593
|
+
visible: isVisible(element),
|
|
1594
|
+
}));
|
|
1595
|
+
return {
|
|
1596
|
+
count: rows.length,
|
|
1597
|
+
visible_count: rows.filter((row) => row.visible).length,
|
|
1598
|
+
texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
|
|
1599
|
+
visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
|
|
1600
|
+
};
|
|
1601
|
+
}).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
1602
|
+
}
|
|
1455
1603
|
function inventoryAppPathFromUrl(urlLike) {
|
|
1456
1604
|
const url = new URL(String(urlLike), targetUrl);
|
|
1457
1605
|
const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
|
|
@@ -1651,8 +1799,9 @@ async function collectRouteInventory(check, viewport) {
|
|
|
1651
1799
|
const homeLinkPaths = homeLinks.map((link) => link.app_path);
|
|
1652
1800
|
const uniqueHomeLinkPaths = Array.from(new Set(homeLinkPaths));
|
|
1653
1801
|
const duplicateHomeLinkPaths = homeLinkPaths.filter((path, index) => homeLinkPaths.indexOf(path) !== index);
|
|
1802
|
+
const duplicateHomeLinkPathSet = Array.from(new Set(duplicateHomeLinkPaths));
|
|
1654
1803
|
if (check.require_unique_routes !== false && duplicateHomeLinkPaths.length) {
|
|
1655
|
-
failures.push({ code: "duplicate_source_links", paths:
|
|
1804
|
+
failures.push({ code: "duplicate_source_links", paths: duplicateHomeLinkPathSet });
|
|
1656
1805
|
}
|
|
1657
1806
|
for (const route of expectedRoutes) {
|
|
1658
1807
|
if (!homeLinkPaths.includes(normalizeRoutePath(route.path))) {
|
|
@@ -1728,6 +1877,11 @@ async function collectRouteInventory(check, viewport) {
|
|
|
1728
1877
|
expected_routes: expectedRoutes,
|
|
1729
1878
|
link_selector: check.link_selector || "a[href]",
|
|
1730
1879
|
source_selector: check.source_selector || null,
|
|
1880
|
+
source_link_count: homeLinkPaths.length,
|
|
1881
|
+
source_unique_link_count: uniqueHomeLinkPaths.length,
|
|
1882
|
+
duplicate_source_link_count: duplicateHomeLinkPaths.length,
|
|
1883
|
+
duplicate_source_link_paths: duplicateHomeLinkPathSet,
|
|
1884
|
+
duplicates_allowed: check.require_unique_routes === false,
|
|
1731
1885
|
home_game_link_count: homeLinkPaths.length,
|
|
1732
1886
|
home_unique_game_link_count: uniqueHomeLinkPaths.length,
|
|
1733
1887
|
home_links: homeLinks,
|
|
@@ -1843,11 +1997,16 @@ async function captureViewport(viewport) {
|
|
|
1843
1997
|
evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
|
|
1844
1998
|
}));
|
|
1845
1999
|
const selectors = {};
|
|
2000
|
+
const text_sequences = {};
|
|
1846
2001
|
const text_matches = {};
|
|
1847
2002
|
for (const check of profile.checks || []) {
|
|
1848
2003
|
if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
|
|
1849
2004
|
selectors[check.selector] = await selectorStats(check.selector);
|
|
1850
2005
|
}
|
|
2006
|
+
if (check.type === "selector_text_order" && check.selector) {
|
|
2007
|
+
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
2008
|
+
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
2009
|
+
}
|
|
1851
2010
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
1852
2011
|
text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
|
|
1853
2012
|
}
|
|
@@ -1871,6 +2030,11 @@ async function captureViewport(viewport) {
|
|
|
1871
2030
|
expected_routes: routeInventoryCheck.expected_routes || [],
|
|
1872
2031
|
link_selector: routeInventoryCheck.link_selector || "a[href]",
|
|
1873
2032
|
source_selector: routeInventoryCheck.source_selector || null,
|
|
2033
|
+
source_link_count: 0,
|
|
2034
|
+
source_unique_link_count: 0,
|
|
2035
|
+
duplicate_source_link_count: 0,
|
|
2036
|
+
duplicate_source_link_paths: [],
|
|
2037
|
+
duplicates_allowed: routeInventoryCheck.require_unique_routes === false,
|
|
1874
2038
|
home_game_link_count: 0,
|
|
1875
2039
|
home_unique_game_link_count: 0,
|
|
1876
2040
|
home_links: [],
|
|
@@ -1906,6 +2070,7 @@ async function captureViewport(viewport) {
|
|
|
1906
2070
|
bounds_overflow_px: dom.bounds_overflow_px,
|
|
1907
2071
|
overflow_offenders: dom.overflow_offenders || [],
|
|
1908
2072
|
selectors,
|
|
2073
|
+
text_sequences,
|
|
1909
2074
|
text_matches,
|
|
1910
2075
|
route_inventory: routeInventory,
|
|
1911
2076
|
setup_action_results: setupActionResults,
|
|
@@ -1945,6 +2110,9 @@ function buildProfileEvidence(currentViewports) {
|
|
|
1945
2110
|
.map((viewport) => ({
|
|
1946
2111
|
viewport: viewport.name,
|
|
1947
2112
|
expected_count: (viewport.route_inventory.expected_routes || []).length,
|
|
2113
|
+
source_link_count: viewport.route_inventory.source_link_count == null ? viewport.route_inventory.home_game_link_count : viewport.route_inventory.source_link_count,
|
|
2114
|
+
source_unique_link_count: viewport.route_inventory.source_unique_link_count == null ? viewport.route_inventory.home_unique_game_link_count : viewport.route_inventory.source_unique_link_count,
|
|
2115
|
+
duplicate_source_link_count: viewport.route_inventory.duplicate_source_link_count,
|
|
1948
2116
|
home_unique_game_link_count: viewport.route_inventory.home_unique_game_link_count,
|
|
1949
2117
|
direct_route_count: (viewport.route_inventory.direct_routes || []).length,
|
|
1950
2118
|
clickthrough_count: (viewport.route_inventory.clickthroughs || []).length,
|
package/dist/cli.cjs
CHANGED
|
@@ -6887,6 +6887,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
6887
6887
|
"route_loaded",
|
|
6888
6888
|
"selector_visible",
|
|
6889
6889
|
"selector_count_at_least",
|
|
6890
|
+
"selector_text_order",
|
|
6890
6891
|
"text_visible",
|
|
6891
6892
|
"text_absent",
|
|
6892
6893
|
"route_inventory",
|
|
@@ -7166,6 +7167,15 @@ function normalizeRouteInventoryRoutes(value, index) {
|
|
|
7166
7167
|
}
|
|
7167
7168
|
return routes;
|
|
7168
7169
|
}
|
|
7170
|
+
function normalizeExpectedTexts(value, index) {
|
|
7171
|
+
if (value === void 0) return void 0;
|
|
7172
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
7173
|
+
throw new Error(`checks[${index}] selector_text_order expected_texts must be a non-empty array.`);
|
|
7174
|
+
}
|
|
7175
|
+
const texts = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
7176
|
+
if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
|
|
7177
|
+
return texts;
|
|
7178
|
+
}
|
|
7169
7179
|
function normalizeCheck(input, index) {
|
|
7170
7180
|
if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
7171
7181
|
const type = stringValue2(input.type);
|
|
@@ -7182,6 +7192,11 @@ function normalizeCheck(input, index) {
|
|
|
7182
7192
|
if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
|
|
7183
7193
|
throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
|
|
7184
7194
|
}
|
|
7195
|
+
const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
|
|
7196
|
+
if (type === "selector_text_order") {
|
|
7197
|
+
if (!stringValue2(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
|
|
7198
|
+
if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
|
|
7199
|
+
}
|
|
7185
7200
|
const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
|
|
7186
7201
|
if (type === "route_inventory" && !expectedRoutes?.length) {
|
|
7187
7202
|
throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
|
|
@@ -7192,6 +7207,7 @@ function normalizeCheck(input, index) {
|
|
|
7192
7207
|
expected_path: stringValue2(input.expected_path),
|
|
7193
7208
|
expected_routes: expectedRoutes,
|
|
7194
7209
|
selector: stringValue2(input.selector),
|
|
7210
|
+
expected_texts: expectedTexts,
|
|
7195
7211
|
link_selector: stringValue2(input.link_selector) || stringValue2(input.linkSelector),
|
|
7196
7212
|
source_selector: stringValue2(input.source_selector) || stringValue2(input.sourceSelector),
|
|
7197
7213
|
route_path_prefix: stringValue2(input.route_path_prefix) || stringValue2(input.routePathPrefix),
|
|
@@ -7297,6 +7313,28 @@ function selectorKey(check) {
|
|
|
7297
7313
|
function textKey(check) {
|
|
7298
7314
|
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
7299
7315
|
}
|
|
7316
|
+
function textSequenceForCheck(viewport, check) {
|
|
7317
|
+
const key = selectorKey(check);
|
|
7318
|
+
const sequence = viewport.text_sequences?.[key];
|
|
7319
|
+
if (isRecord(sequence)) {
|
|
7320
|
+
const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
|
|
7321
|
+
const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
|
|
7322
|
+
const candidates = visibleTexts.length ? visibleTexts : texts;
|
|
7323
|
+
return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
7324
|
+
}
|
|
7325
|
+
return [];
|
|
7326
|
+
}
|
|
7327
|
+
function textOrderMatch(texts, expectedTexts) {
|
|
7328
|
+
const positions = [];
|
|
7329
|
+
let startAt = 0;
|
|
7330
|
+
for (const expected of expectedTexts) {
|
|
7331
|
+
const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
|
|
7332
|
+
if (index < 0) return { matched: false, positions };
|
|
7333
|
+
positions.push(index);
|
|
7334
|
+
startAt = index + 1;
|
|
7335
|
+
}
|
|
7336
|
+
return { matched: true, positions };
|
|
7337
|
+
}
|
|
7300
7338
|
function matchText(sample, check) {
|
|
7301
7339
|
if (check.pattern) {
|
|
7302
7340
|
try {
|
|
@@ -7416,6 +7454,32 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
7416
7454
|
message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
|
|
7417
7455
|
};
|
|
7418
7456
|
}
|
|
7457
|
+
if (check.type === "selector_text_order") {
|
|
7458
|
+
const key = selectorKey(check);
|
|
7459
|
+
const expectedTexts = check.expected_texts || [];
|
|
7460
|
+
const results = viewports.map((viewport) => {
|
|
7461
|
+
const texts = textSequenceForCheck(viewport, check);
|
|
7462
|
+
const match = textOrderMatch(texts, expectedTexts);
|
|
7463
|
+
return {
|
|
7464
|
+
viewport: viewport.name,
|
|
7465
|
+
matched: match.matched,
|
|
7466
|
+
matched_positions: match.positions,
|
|
7467
|
+
visible_texts: texts.slice(0, 20)
|
|
7468
|
+
};
|
|
7469
|
+
});
|
|
7470
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
7471
|
+
return {
|
|
7472
|
+
type: check.type,
|
|
7473
|
+
label: checkLabel(check),
|
|
7474
|
+
status: failed ? "failed" : "passed",
|
|
7475
|
+
evidence: {
|
|
7476
|
+
selector: key,
|
|
7477
|
+
expected_texts: expectedTexts,
|
|
7478
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
7479
|
+
},
|
|
7480
|
+
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
7481
|
+
};
|
|
7482
|
+
}
|
|
7419
7483
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
7420
7484
|
const key = textKey(check);
|
|
7421
7485
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -7451,14 +7515,23 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
7451
7515
|
const first = inventories[0]?.inventory;
|
|
7452
7516
|
const directRoutes = Array.isArray(first?.direct_routes) ? first.direct_routes : [];
|
|
7453
7517
|
const clickthroughs = Array.isArray(first?.clickthroughs) ? first.clickthroughs : [];
|
|
7518
|
+
const sourceLinkCount = numberValue(first?.source_link_count) ?? numberValue(first?.home_game_link_count) ?? null;
|
|
7519
|
+
const sourceUniqueLinkCount = numberValue(first?.source_unique_link_count) ?? numberValue(first?.home_unique_game_link_count) ?? null;
|
|
7520
|
+
const duplicateSourceLinks = Array.isArray(first?.duplicate_source_link_paths) ? first.duplicate_source_link_paths.map((path7) => String(path7)) : [];
|
|
7521
|
+
const duplicateSourceLinkCount = numberValue(first?.duplicate_source_link_count) ?? (sourceLinkCount !== null && sourceUniqueLinkCount !== null ? Math.max(0, sourceLinkCount - sourceUniqueLinkCount) : null);
|
|
7454
7522
|
return {
|
|
7455
7523
|
type: check.type,
|
|
7456
7524
|
label: checkLabel(check),
|
|
7457
7525
|
status: failures.length ? "failed" : "passed",
|
|
7458
7526
|
evidence: {
|
|
7459
7527
|
expected_count: check.expected_routes?.length || 0,
|
|
7460
|
-
|
|
7461
|
-
|
|
7528
|
+
source_link_count: sourceLinkCount,
|
|
7529
|
+
source_unique_link_count: sourceUniqueLinkCount,
|
|
7530
|
+
duplicate_source_link_count: duplicateSourceLinkCount,
|
|
7531
|
+
duplicate_source_links: duplicateSourceLinks,
|
|
7532
|
+
duplicates_allowed: check.require_unique_routes === false,
|
|
7533
|
+
homepage_link_count: numberValue(first?.home_game_link_count) ?? sourceLinkCount,
|
|
7534
|
+
homepage_unique_link_count: numberValue(first?.home_unique_game_link_count) ?? sourceUniqueLinkCount,
|
|
7462
7535
|
direct_route_count: directRoutes.length,
|
|
7463
7536
|
clickthrough_count: clickthroughs.length,
|
|
7464
7537
|
failures
|
|
@@ -7741,6 +7814,28 @@ function textMatches(sample, check) {
|
|
|
7741
7814
|
}
|
|
7742
7815
|
return String(sample || "").includes(check.text || "");
|
|
7743
7816
|
}
|
|
7817
|
+
function textSequenceForCheck(viewport, check) {
|
|
7818
|
+
const key = check.selector || "";
|
|
7819
|
+
const sequence = viewport.text_sequences && viewport.text_sequences[key];
|
|
7820
|
+
if (sequence && typeof sequence === "object") {
|
|
7821
|
+
const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
|
|
7822
|
+
const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
|
|
7823
|
+
const candidates = visibleTexts.length ? visibleTexts : texts;
|
|
7824
|
+
return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
7825
|
+
}
|
|
7826
|
+
return [];
|
|
7827
|
+
}
|
|
7828
|
+
function textOrderMatch(texts, expectedTexts) {
|
|
7829
|
+
const positions = [];
|
|
7830
|
+
let startAt = 0;
|
|
7831
|
+
for (const expected of expectedTexts || []) {
|
|
7832
|
+
const index = texts.findIndex((text, offset) => offset >= startAt && text.includes(expected));
|
|
7833
|
+
if (index < 0) return { matched: false, positions };
|
|
7834
|
+
positions.push(index);
|
|
7835
|
+
startAt = index + 1;
|
|
7836
|
+
}
|
|
7837
|
+
return { matched: true, positions };
|
|
7838
|
+
}
|
|
7744
7839
|
function numberValue(value) {
|
|
7745
7840
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
7746
7841
|
}
|
|
@@ -7945,6 +8040,29 @@ function assessProfile(profile, evidence) {
|
|
|
7945
8040
|
});
|
|
7946
8041
|
continue;
|
|
7947
8042
|
}
|
|
8043
|
+
if (check.type === "selector_text_order") {
|
|
8044
|
+
const selector = check.selector || "";
|
|
8045
|
+
const expectedTexts = check.expected_texts || [];
|
|
8046
|
+
const results = viewports.map((viewport) => {
|
|
8047
|
+
const texts = textSequenceForCheck(viewport, check);
|
|
8048
|
+
const match = textOrderMatch(texts, expectedTexts);
|
|
8049
|
+
return {
|
|
8050
|
+
viewport: viewport.name,
|
|
8051
|
+
matched: match.matched,
|
|
8052
|
+
matched_positions: match.positions,
|
|
8053
|
+
visible_texts: texts.slice(0, 20),
|
|
8054
|
+
};
|
|
8055
|
+
});
|
|
8056
|
+
const failed = results.filter((result) => !result.matched).length;
|
|
8057
|
+
checks.push({
|
|
8058
|
+
type: check.type,
|
|
8059
|
+
label: check.label || check.type,
|
|
8060
|
+
status: failed ? "failed" : "passed",
|
|
8061
|
+
evidence: { selector, expected_texts: expectedTexts, viewports: results },
|
|
8062
|
+
message: failed ? "Selector " + selector + " text order failed in " + failed + " viewport(s)." : undefined,
|
|
8063
|
+
});
|
|
8064
|
+
continue;
|
|
8065
|
+
}
|
|
7948
8066
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
7949
8067
|
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
7950
8068
|
const expectedVisible = check.type === "text_visible";
|
|
@@ -7982,14 +8100,23 @@ function assessProfile(profile, evidence) {
|
|
|
7982
8100
|
const first = inventories[0].inventory || {};
|
|
7983
8101
|
const directRoutes = Array.isArray(first.direct_routes) ? first.direct_routes : [];
|
|
7984
8102
|
const clickthroughs = Array.isArray(first.clickthroughs) ? first.clickthroughs : [];
|
|
8103
|
+
const sourceLinkCount = typeof first.source_link_count === "number" ? first.source_link_count : typeof first.home_game_link_count === "number" ? first.home_game_link_count : null;
|
|
8104
|
+
const sourceUniqueLinkCount = typeof first.source_unique_link_count === "number" ? first.source_unique_link_count : typeof first.home_unique_game_link_count === "number" ? first.home_unique_game_link_count : null;
|
|
8105
|
+
const duplicateSourceLinks = Array.isArray(first.duplicate_source_link_paths) ? first.duplicate_source_link_paths.map((path) => String(path)) : [];
|
|
8106
|
+
const duplicateSourceLinkCount = typeof first.duplicate_source_link_count === "number" ? first.duplicate_source_link_count : sourceLinkCount !== null && sourceUniqueLinkCount !== null ? Math.max(0, sourceLinkCount - sourceUniqueLinkCount) : null;
|
|
7985
8107
|
checks.push({
|
|
7986
8108
|
type: check.type,
|
|
7987
8109
|
label: check.label || check.type,
|
|
7988
8110
|
status: failures.length ? "failed" : "passed",
|
|
7989
8111
|
evidence: {
|
|
7990
8112
|
expected_count: (check.expected_routes || []).length,
|
|
7991
|
-
|
|
7992
|
-
|
|
8113
|
+
source_link_count: sourceLinkCount,
|
|
8114
|
+
source_unique_link_count: sourceUniqueLinkCount,
|
|
8115
|
+
duplicate_source_link_count: duplicateSourceLinkCount,
|
|
8116
|
+
duplicate_source_links: duplicateSourceLinks,
|
|
8117
|
+
duplicates_allowed: check.require_unique_routes === false,
|
|
8118
|
+
homepage_link_count: typeof first.home_game_link_count === "number" ? first.home_game_link_count : sourceLinkCount,
|
|
8119
|
+
homepage_unique_link_count: typeof first.home_unique_game_link_count === "number" ? first.home_unique_game_link_count : sourceUniqueLinkCount,
|
|
7993
8120
|
direct_route_count: directRoutes.length,
|
|
7994
8121
|
clickthrough_count: clickthroughs.length,
|
|
7995
8122
|
failures,
|
|
@@ -8309,6 +8436,27 @@ async function selectorStats(selector) {
|
|
|
8309
8436
|
return { count: elements.length, visible_count: elements.filter(isVisible).length };
|
|
8310
8437
|
}).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
8311
8438
|
}
|
|
8439
|
+
async function selectorTextSequence(selector) {
|
|
8440
|
+
return page.locator(selector).evaluateAll((elements) => {
|
|
8441
|
+
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
|
|
8442
|
+
const isVisible = (element) => {
|
|
8443
|
+
const style = window.getComputedStyle(element);
|
|
8444
|
+
const rect = element.getBoundingClientRect();
|
|
8445
|
+
return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
8446
|
+
};
|
|
8447
|
+
const rows = elements.map((element, index) => ({
|
|
8448
|
+
index,
|
|
8449
|
+
text: compact(element.innerText || element.textContent || ""),
|
|
8450
|
+
visible: isVisible(element),
|
|
8451
|
+
}));
|
|
8452
|
+
return {
|
|
8453
|
+
count: rows.length,
|
|
8454
|
+
visible_count: rows.filter((row) => row.visible).length,
|
|
8455
|
+
texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
|
|
8456
|
+
visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
|
|
8457
|
+
};
|
|
8458
|
+
}).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
8459
|
+
}
|
|
8312
8460
|
function inventoryAppPathFromUrl(urlLike) {
|
|
8313
8461
|
const url = new URL(String(urlLike), targetUrl);
|
|
8314
8462
|
const mountPrefix = previewMountPrefix(new URL(targetUrl).pathname);
|
|
@@ -8508,8 +8656,9 @@ async function collectRouteInventory(check, viewport) {
|
|
|
8508
8656
|
const homeLinkPaths = homeLinks.map((link) => link.app_path);
|
|
8509
8657
|
const uniqueHomeLinkPaths = Array.from(new Set(homeLinkPaths));
|
|
8510
8658
|
const duplicateHomeLinkPaths = homeLinkPaths.filter((path, index) => homeLinkPaths.indexOf(path) !== index);
|
|
8659
|
+
const duplicateHomeLinkPathSet = Array.from(new Set(duplicateHomeLinkPaths));
|
|
8511
8660
|
if (check.require_unique_routes !== false && duplicateHomeLinkPaths.length) {
|
|
8512
|
-
failures.push({ code: "duplicate_source_links", paths:
|
|
8661
|
+
failures.push({ code: "duplicate_source_links", paths: duplicateHomeLinkPathSet });
|
|
8513
8662
|
}
|
|
8514
8663
|
for (const route of expectedRoutes) {
|
|
8515
8664
|
if (!homeLinkPaths.includes(normalizeRoutePath(route.path))) {
|
|
@@ -8585,6 +8734,11 @@ async function collectRouteInventory(check, viewport) {
|
|
|
8585
8734
|
expected_routes: expectedRoutes,
|
|
8586
8735
|
link_selector: check.link_selector || "a[href]",
|
|
8587
8736
|
source_selector: check.source_selector || null,
|
|
8737
|
+
source_link_count: homeLinkPaths.length,
|
|
8738
|
+
source_unique_link_count: uniqueHomeLinkPaths.length,
|
|
8739
|
+
duplicate_source_link_count: duplicateHomeLinkPaths.length,
|
|
8740
|
+
duplicate_source_link_paths: duplicateHomeLinkPathSet,
|
|
8741
|
+
duplicates_allowed: check.require_unique_routes === false,
|
|
8588
8742
|
home_game_link_count: homeLinkPaths.length,
|
|
8589
8743
|
home_unique_game_link_count: uniqueHomeLinkPaths.length,
|
|
8590
8744
|
home_links: homeLinks,
|
|
@@ -8700,11 +8854,16 @@ async function captureViewport(viewport) {
|
|
|
8700
8854
|
evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
|
|
8701
8855
|
}));
|
|
8702
8856
|
const selectors = {};
|
|
8857
|
+
const text_sequences = {};
|
|
8703
8858
|
const text_matches = {};
|
|
8704
8859
|
for (const check of profile.checks || []) {
|
|
8705
8860
|
if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
|
|
8706
8861
|
selectors[check.selector] = await selectorStats(check.selector);
|
|
8707
8862
|
}
|
|
8863
|
+
if (check.type === "selector_text_order" && check.selector) {
|
|
8864
|
+
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
8865
|
+
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
8866
|
+
}
|
|
8708
8867
|
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
8709
8868
|
text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
|
|
8710
8869
|
}
|
|
@@ -8728,6 +8887,11 @@ async function captureViewport(viewport) {
|
|
|
8728
8887
|
expected_routes: routeInventoryCheck.expected_routes || [],
|
|
8729
8888
|
link_selector: routeInventoryCheck.link_selector || "a[href]",
|
|
8730
8889
|
source_selector: routeInventoryCheck.source_selector || null,
|
|
8890
|
+
source_link_count: 0,
|
|
8891
|
+
source_unique_link_count: 0,
|
|
8892
|
+
duplicate_source_link_count: 0,
|
|
8893
|
+
duplicate_source_link_paths: [],
|
|
8894
|
+
duplicates_allowed: routeInventoryCheck.require_unique_routes === false,
|
|
8731
8895
|
home_game_link_count: 0,
|
|
8732
8896
|
home_unique_game_link_count: 0,
|
|
8733
8897
|
home_links: [],
|
|
@@ -8763,6 +8927,7 @@ async function captureViewport(viewport) {
|
|
|
8763
8927
|
bounds_overflow_px: dom.bounds_overflow_px,
|
|
8764
8928
|
overflow_offenders: dom.overflow_offenders || [],
|
|
8765
8929
|
selectors,
|
|
8930
|
+
text_sequences,
|
|
8766
8931
|
text_matches,
|
|
8767
8932
|
route_inventory: routeInventory,
|
|
8768
8933
|
setup_action_results: setupActionResults,
|
|
@@ -8802,6 +8967,9 @@ function buildProfileEvidence(currentViewports) {
|
|
|
8802
8967
|
.map((viewport) => ({
|
|
8803
8968
|
viewport: viewport.name,
|
|
8804
8969
|
expected_count: (viewport.route_inventory.expected_routes || []).length,
|
|
8970
|
+
source_link_count: viewport.route_inventory.source_link_count == null ? viewport.route_inventory.home_game_link_count : viewport.route_inventory.source_link_count,
|
|
8971
|
+
source_unique_link_count: viewport.route_inventory.source_unique_link_count == null ? viewport.route_inventory.home_unique_game_link_count : viewport.route_inventory.source_unique_link_count,
|
|
8972
|
+
duplicate_source_link_count: viewport.route_inventory.duplicate_source_link_count,
|
|
8805
8973
|
home_unique_game_link_count: viewport.route_inventory.home_unique_game_link_count,
|
|
8806
8974
|
direct_route_count: (viewport.route_inventory.direct_routes || []).length,
|
|
8807
8975
|
clickthrough_count: (viewport.route_inventory.clickthroughs || []).length,
|