diff --git a/TODO.md b/TODO.md index b0ceb21..4c1254d 100644 --- a/TODO.md +++ b/TODO.md @@ -44,6 +44,16 @@ then continue tagging as milestones land. # Completed Steps +- 2026-10-08: The browser suites no longer read a screen before the page has + shown it ([#502](https://git.eeqj.de/sneak/AutistMask/issues/502)). Their wait + for a screen used to pass as soon as the element laid out, which every view + does until the page's stylesheet has applied, so an approval test could read + the prompt's fields before the page's script had filled them. `visible()` in + `tests/e2e/harness.js` and `waitVisible()` in `tests/e2e/firefox/driver.js` + now also wait for the page to finish loading and for neither the element nor + anything around it to carry the `hidden` class that `showView()` puts on every + view but the current one. No caller changed. + - 2026-10-08: Switching the network in Settings now tells open pages ([#500](https://git.eeqj.de/sneak/AutistMask/issues/500)). Once the switch is saved, Settings asks the background to send `chainChanged` with the new chain diff --git a/tests/e2e/firefox/driver.js b/tests/e2e/firefox/driver.js index 0592baa..7aabf7a 100644 --- a/tests/e2e/firefox/driver.js +++ b/tests/e2e/firefox/driver.js @@ -279,12 +279,17 @@ class Driver { // Shown means shown: in the popup a view is switched by toggling a // "hidden" class, and an element that is present but collapsed is not - // the thing a test means by visible. + // the thing a test means by visible. Until the page's stylesheet has + // applied that class hides nothing and every view lays out, so the page + // must also have finished loading, which it does only after its + // stylesheet, and neither the element nor anything enclosing it may + // carry the class (https://git.eeqj.de/sneak/AutistMask/issues/502). async waitVisible(selector, timeout = DEFAULT_WAIT_MS) { return this.waitFor( "selector " + selector + " to be visible", `const el = document.querySelector(arguments[0]); - if (!el) return false; + if (document.readyState !== "complete" || !el) return false; + if (el.closest(".hidden")) return false; const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0;`, [selector], diff --git a/tests/e2e/harness.js b/tests/e2e/harness.js index d2c0a41..4e4047b 100644 --- a/tests/e2e/harness.js +++ b/tests/e2e/harness.js @@ -333,8 +333,32 @@ async function launch(routeOpts) { const PASSWORD = "e2e-harness-password"; +// Waits until the page shows `selector`, not only until it lays out. Until the +// page's stylesheet has applied, the `hidden` class that showView() keeps on +// every view but the current one hides nothing and every view lays out, so a +// test could read a screen before the page's script had filled it +// (https://git.eeqj.de/sneak/AutistMask/issues/502). So the page must also +// have finished loading, which it does only after its stylesheet, and neither +// the element nor anything enclosing it may carry `hidden`. Polled on a timer: +// animation frames are not guaranteed to a window that is not in front. async function visible(page, selector, timeout = 15000) { - await page.waitForSelector(selector, { state: "visible", timeout }); + await page + .waitForFunction( + (sel) => { + const el = document.querySelector(sel); + if (document.readyState !== "complete" || !el) return false; + if (el.closest(".hidden")) return false; + const r = el.getBoundingClientRect(); + return r.width > 0 && r.height > 0; + }, + selector, + { polling: 50, timeout }, + ) + .catch((e) => { + throw new Error( + "the page did not show " + selector + ": " + e.message, + ); + }); } // An empty WebAssembly module: magic number and version header, no