test(sw): stabilize Firefox hover, pixel and timing checks

- hover-stability-firefox: instant scroll and state waits instead of a fixed 60ms delay (smooth scroll in preview).
- integrated-visual, viewer-regression, theme-inheritance: deterministic Chromium raster flags, animations held at
  time 0, layout settle wait, integer instant scroll of inner panels; tolerance stays 0.
- text-weight: wait for the Viewer More panel instead of a fixed 200ms and fix the failure report.
This commit is contained in:
shkim4 committed 2026-10-21 06:08:31 +09:00
1 parent 1ffff67991
commit af7f98d66b
5 files changed
+49 -14

No files matched your search

@@ -60,18 +60,28 @@ const geo = s => JSON.stringify([s.box[2], s.box[3], s.icon && [rd(s.icon[0] - s
async function cycleAll(sel, q, tag) {
const n = await ev(`[...document.querySelectorAll(${JSON.stringify(sel)})].filter(e => e.getClientRects().length).length`)
const results = []
// 2026-10-20: state-based waits replace the fixed 60–100ms sleeps (Firefox sometimes read a tile before :hover or its
// transition applied, so a tile looked unhovered or a read hit the wrong index). Each read waits until the item's
// :hover / :active state is the expected one and its transitions ended; a wait that times out is recorded and fails.
// The preview sets html { scroll-behavior: smooth } (src/hm-theme.css): scroll instantly, or the item is measured mid-scroll
// and the pointer lands on another tile (the former "저장" hover / index misses).
// After a release outside the item Firefox keeps :active on it until the next press, so that last wait checks :hover only.
const settle = (i, hover, active = false) => ev(`(async () => { const el = [...document.querySelectorAll(${JSON.stringify(sel)})].filter(e => e.getClientRects().length)[${i}]
const frame = () => new Promise(r => requestAnimationFrame(() => r()))
for (let t = 0; t < 120; t++) { await frame(); if (el.matches(':hover') === ${hover} && (${active === null} || el.matches(':active') === ${active}) && el.getAnimations({ subtree: true }).every(a => a.playState !== 'running')) { await frame(); return true } }
return false })()`)
for (let i = 0; i < n; i++) {
await ev(`[...document.querySelectorAll(${JSON.stringify(sel)})].filter(e => e.getClientRects().length)[${i}].scrollIntoView({ block: 'center', inline: 'center' }), true`); await sleep(60)
await move(1, 1); await sleep(80)
await ev(`[...document.querySelectorAll(${JSON.stringify(sel)})].filter(e => e.getClientRects().length)[${i}].scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }), true`)
await move(1, 1); const waits = [await settle(i, false)]
const d0 = await snap(sel, i, q)
const [x, y] = [d0.box[0] + d0.box[2] / 2, d0.box[1] + d0.box[3] / 2]
await move(x, y); await sleep(100); const h1 = await snap(sel, i, q)
await move(1, 1); await sleep(100); const d1 = await snap(sel, i, q)
await move(x, y); await sleep(100); const h2 = await snap(sel, i, q)
await pointer([{ type: 'pointerDown', button: 0 }]); await sleep(80); const a = await snap(sel, i, q)
await pointer([{ type: 'pointerMove', x: 1, y: 1, duration: 0 }, { type: 'pointerUp', button: 0 }]); await sleep(100)
const same = [h1, d1, h2, a].every(s => geo(s) === geo(d0))
results.push({ name: d0.name, same, hovered: d0.active || h1.color !== d0.color, d0, h1, a })
await move(x, y); waits.push(await settle(i, true)); const h1 = await snap(sel, i, q)
await move(1, 1); waits.push(await settle(i, false)); const d1 = await snap(sel, i, q)
await move(x, y); waits.push(await settle(i, true)); const h2 = await snap(sel, i, q)
await pointer([{ type: 'pointerDown', button: 0 }]); waits.push(await settle(i, true, true)); const a = await snap(sel, i, q)
await pointer([{ type: 'pointerMove', x: 1, y: 1, duration: 0 }, { type: 'pointerUp', button: 0 }]); waits.push(await settle(i, false, null))
const same = [h1, d1, h2, a].every(s => geo(s) === geo(d0)) && waits.every(Boolean)
results.push({ name: d0.name, same, hovered: d0.active || h1.color !== d0.color, d0, h1, a, waits })
check(`Firefox ${tag} "${d0.name}": icon · text · box unchanged (Default/Hover/Default/Hover/Active)`, same, { d0: geo(d0), h1: geo(h1), a: geo(a) })
}
return results