mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
test: poll smoke_preloader's reduced-motion phase from Playwright, with navigation diagnostics
The beta.9 candidate failed the same phase twice in CI with "Resulting promise was garbage collected" (runs 34346813552, 34347910231) while it passes locally; a timer guard did not help, which points at a destroyed context rather than a starved animation-frame chain. The wait now runs as page.waitForFunction with raf polling, and the smoke logs frame navigations and page crashes as `diagnostic …` lines so the next failure names its cause. Verdict unchanged (animationName of the boot house, or 'missed'). Pre-release gate repair per PROCESS §11.4; locally OK ×2. Issue: #506 User-Visible: no Release: v1.73.0-beta.9
This commit is contained in:
+17
-21
@@ -118,35 +118,31 @@ check('kioskNoVeil', kiosk.kioskNoVeil);
|
||||
check('kioskPlanNeverHidden', kiosk.kioskPlanNeverHidden);
|
||||
|
||||
// ---------- phase 3: prefers-reduced-motion → the house does not pulse ----------
|
||||
// The wait lives on the Playwright side: an in-page promise that only a
|
||||
// requestAnimationFrame chain keeps alive was reported as "garbage collected"
|
||||
// on a CI runner (#506 beta.9 candidate) while the same phase passed locally.
|
||||
// Polling from outside cannot be collected, and a destroyed context or a
|
||||
// navigation surfaces as its own error instead of a vanished promise.
|
||||
page.on('framenavigated', (frame) => console.log('diagnostic navigation:', frame.url()));
|
||||
page.on('crash', () => console.log('diagnostic page crash'));
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
const reduced = await page.evaluate(async () => {
|
||||
await page.evaluate(() => {
|
||||
customElements.get('houseplan-card')?._warmBootReset?.(); // DEV-B703: this scenario simulates a COLD first open — forget the page's warm re-mount memo
|
||||
const c = document.createElement('houseplan-card');
|
||||
c.setConfig({ type: 'custom:houseplan-card' });
|
||||
c.hass = window.__card.hass;
|
||||
c.style.cssText = 'position:fixed;left:0;top:0;width:800px;z-index:99';
|
||||
document.body.appendChild(c);
|
||||
const sr = () => c.shadowRoot || c.renderRoot;
|
||||
const t0 = performance.now();
|
||||
let anim = 'missed';
|
||||
await new Promise((done) => {
|
||||
// A timer keeps this promise reachable even if the renderer withholds
|
||||
// animation frames for a while (a CI runner under load once let the
|
||||
// rAF-only chain be garbage-collected mid-wait); the verdict stays the
|
||||
// same: the house is found, or `anim` remains 'missed'.
|
||||
const settle = () => { clearTimeout(guard); done(); };
|
||||
const guard = setTimeout(settle, 700);
|
||||
const tick = () => {
|
||||
const house = sr().querySelector('.bootveil .boothouse');
|
||||
if (house) { anim = getComputedStyle(house).animationName; settle(); return; }
|
||||
if (performance.now() - t0 < 500) requestAnimationFrame(tick);
|
||||
else settle();
|
||||
};
|
||||
requestAnimationFrame(tick);
|
||||
});
|
||||
c.remove();
|
||||
return { anim };
|
||||
window.__preloaderReduced = { card: c, started: performance.now() };
|
||||
});
|
||||
const reduced = await page.waitForFunction(() => {
|
||||
const probe = window.__preloaderReduced;
|
||||
const root = probe.card.shadowRoot || probe.card.renderRoot;
|
||||
const house = root?.querySelector('.bootveil .boothouse');
|
||||
if (house) return { anim: getComputedStyle(house).animationName };
|
||||
return performance.now() - probe.started > 500 ? { anim: 'missed' } : null;
|
||||
}, null, { polling: 'raf', timeout: 5000 }).then((handle) => handle.jsonValue());
|
||||
await page.evaluate(() => { window.__preloaderReduced.card.remove(); delete window.__preloaderReduced; });
|
||||
check('reducedMotionStaticHouse', reduced.anim, 'none');
|
||||
|
||||
await finish(browser, { ...res, ...kiosk, ...reduced });
|
||||
|
||||
Reference in New Issue
Block a user