diff --git a/frontend/test/browser/take.mjs b/frontend/test/browser/take.mjs index 5f39d7d..a595ffb 100644 --- a/frontend/test/browser/take.mjs +++ b/frontend/test/browser/take.mjs @@ -37,6 +37,14 @@ const CHROME = process.env.CHROME ?? // nothing drawn, and every check below is a count of pixels that are not it. const BG = [0x12, 0x14, 0x1c]; +// :mouth-dark, from domain/palette, packed the way PROBE packs a tone. It is the +// mouth INTERIOR's colour and nothing else on the stage carries it, so its +// presence is the whole of "the mouth is open" without needing to know where the +// mouth is. A whole-canvas tone COUNT cannot answer that any more: step 7 put +// eyes, brows, an iris, a pupil and teeth on the same stage, so every frame has +// several tones whatever the mouth is doing. +const MOUTH_DARK = (0x24 << 16) | (0x16 << 8) | 0x1a; + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); let failures = 0; @@ -122,7 +130,22 @@ async function connect() { const r = await this.send('Page.captureScreenshot', { format: 'png' }); writeFileSync(join(OUT, `${name}.png`), Buffer.from(r.data, 'base64')); }, - close() { ws.close(); chrome.kill(); rmSync(profile, { recursive: true, force: true }); }, + // Teardown must not be able to fail the run. `chrome.kill()` returns as soon + // as the signal is sent, so Chrome is often still writing its profile when + // rmSync walks it, and macOS then throws ENOTEMPTY on a directory that + // reappeared mid-delete. That throw used to propagate out of main's `finally` + // and past the summary line, so the suite exited 2 and printed no verdict + // whether it had passed or failed — which is how a stale assertion sat here + // unnoticed. A leaked temp profile is worth less than a truthful exit code. + close() { + ws.close(); + chrome.kill(); + try { + rmSync(profile, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }); + } catch (e) { + console.log(` note could not remove the temp profile — ${e.code ?? e.message}`); + } + }, }; } @@ -154,7 +177,7 @@ const PROBE = `(() => { let h = 2166136261; for (let i = 0; i < d.length; i += 4) { h ^= d[i] + d[i+1] * 31 + d[i+2] * 131; h = Math.imul(h, 16777619); } return { - w: c.width, h: c.height, drawn, tones: [...tones].length, + w: c.width, h: c.height, drawn, tones: [...tones].length, toneSet: [...tones], cx: drawn ? cx / drawn : null, cy: drawn ? cy / drawn : null, hash: h >>> 0, frame: document.querySelector('.readout span')?.textContent ?? '', @@ -212,21 +235,24 @@ async function main() { // --- it is a mouth: two tones, one inside the other --- // // The three-layer structure is what makes a flat shape read as an opening - // rather than a blob, so the interior being a SECOND tone is the check that - // this is a mouth and not one polygon. + // rather than a blob, so the interior being its OWN tone is the check that + // this is a mouth and not one polygon. Asked of :mouth-dark specifically + // rather than of the tone count: the count is now dominated by the eyes and + // brows, which are on the stage whatever the mouth is doing. await page.eval(SEEK(10)); // beat 1: wide open await sleep(120); const open = await page.eval(PROBE); await page.shot('take-open'); - check(open.tones >= 2, 'an open mouth draws an outline and an interior', - `${open.tones} tones`); + check(open.toneSet.includes(MOUTH_DARK), + 'an open mouth draws an outline and an interior', + `${open.tones} tones, interior ${open.toneSet.includes(MOUTH_DARK) ? 'present' : 'MISSING'}`); await page.eval(SEEK(28)); // beat 3: shut await sleep(120); const shut = await page.eval(PROBE); await page.shot('take-shut'); - check(shut.tones === 1, 'a shut mouth draws the outline alone', - `${shut.tones} tones`); + check(!shut.toneSet.includes(MOUTH_DARK), 'a shut mouth draws the outline alone', + `${shut.tones} tones, interior ${shut.toneSet.includes(MOUTH_DARK) ? 'STILL DRAWN' : 'gone'}`); check(open.hash !== shut.hash, 'the open and the shut mouth are different pictures'); check(open.drawn > shut.drawn, 'the open mouth covers more of the stage', `${open.drawn} vs ${shut.drawn} px`);