Add deepFindAll to the shared browser helper, so counting probes stop being hand-rolled
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
deepFind returns the FIRST match and stops. Every count-based assertion needs
all of them -- how many rows, anchors, hints, canvases -- and because this
file offered no such helper, each ad-hoc probe hand-rolled its own shadow
walker. Across one session of verification work those produced four false
results:
a naive walker double-visited through nested roots: 48 cards for 24;
a single-level querySelector found 0 anchors where there were 155;
an .rd-row count included a detail panel sharing that class, so a correct
filter read as broken;
a bare "<script" prefix match counted the word scriptUrl in a comment and
flagged a valid file.
Two of those nearly became bug reports against working code. A missing helper
does not stop anyone writing the check; it moves the check somewhere nobody
reviews.
The seen-Set dedupe is the load-bearing part: a node reachable through two
roots must count once, which is the first failure above.
Backward compatible by construction -- the third parameter is APPENDED, so
callbacks taking (deepFind, arg) are untouched. Verified rather than assumed:
a (deepFind, arg) callback still resolves; deepFindAll returns 24 cards with
returned === distinct; share-roundtrip-smoke (a real consumer) still passes;
check:a11y-button-names and check:parse both pass.
This commit is contained in:
parent
35dbb5d557
commit
2015378fcd
1 changed files with 38 additions and 2 deletions
|
|
@ -13,7 +13,12 @@
|
|||
* DEEP_FIND_SRC page-side `deepFind(root, sel)` source: querySelector that
|
||||
* recurses into every nested OPEN shadow root (composite MFEs
|
||||
* nest custom elements, each with its own shadow root).
|
||||
* deepEvaluate(page, fn, arg) page.evaluate with deepFind bound: fn receives (deepFind, arg).
|
||||
* DEEP_FIND_ALL_SRC page-side `deepFindAll(root, sel)`: the COUNTING counterpart —
|
||||
* returns EVERY match across nested open shadow roots, deduped
|
||||
* via a seen-Set (a node reachable through two roots counts once).
|
||||
* deepEvaluate(page, fn, arg) page.evaluate with both bound: fn receives
|
||||
* (deepFind, arg, deepFindAll). The third parameter is APPENDED,
|
||||
* so every existing caller taking (deepFind, arg) is unaffected.
|
||||
* Composed in Node (no eval inside the page, so a host CSP
|
||||
* cannot break it).
|
||||
*/
|
||||
|
|
@ -61,7 +66,38 @@ export const DEEP_FIND_SRC = `(root, sel) => {
|
|||
return null
|
||||
}`
|
||||
|
||||
// ── deepFindAll — the COUNTING counterpart to deepFind ───────────────────────────────────────
|
||||
//
|
||||
// WHY THIS EXISTS (added 2026-09-22). deepFind returns the FIRST match and stops. Every
|
||||
// count-based assertion needs all of them — how many rows, anchors, hints, canvases — and because
|
||||
// this file offered no such helper, each ad-hoc probe hand-rolled its own shadow walker. Measured
|
||||
// across one session of verification work, those hand-rolled walkers produced FOUR false results:
|
||||
//
|
||||
// · a naive walker double-visited through nested roots and reported 48 cards for 24;
|
||||
// · a single-level querySelector found 0 anchors in a composite that had 155;
|
||||
// · a `.rd-row` count included a DETAIL PANEL sharing that class, so a correct filter read as broken;
|
||||
// · a `<script` prefix match counted the word `scriptUrl` in a comment and flagged a valid file.
|
||||
//
|
||||
// Two of those nearly became bug reports against working code. A missing helper does not stop
|
||||
// anyone writing the check — it just moves the check somewhere nobody reviews.
|
||||
//
|
||||
// SEEN-SET DEDUPE IS THE LOAD-BEARING PART. A node reachable through two roots must be counted
|
||||
// ONCE; without the Set the count silently doubles, which is the first failure above.
|
||||
export const DEEP_FIND_ALL_SRC = `(root, sel, _seen) => {
|
||||
const seen = _seen || new Set()
|
||||
const out = []
|
||||
if (!root || !root.querySelectorAll) return out
|
||||
for (const el of root.querySelectorAll(sel)) { if (!seen.has(el)) { seen.add(el); out.push(el) } }
|
||||
for (const el of root.querySelectorAll('*')) {
|
||||
if (el.shadowRoot) out.push(...deepFindAll(el.shadowRoot, sel, seen))
|
||||
}
|
||||
return out
|
||||
}`
|
||||
|
||||
export function deepEvaluate(page, fn, arg) {
|
||||
const composed = new Function('arg', `const deepFind = ${DEEP_FIND_SRC};\nreturn (${fn.toString()})(deepFind, arg)`)
|
||||
const composed = new Function('arg',
|
||||
`const deepFind = ${DEEP_FIND_SRC};\n` +
|
||||
`const deepFindAll = ${DEEP_FIND_ALL_SRC};\n` +
|
||||
`return (${fn.toString()})(deepFind, arg, deepFindAll)`)
|
||||
return page.evaluate(composed, arg)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue