From 8e52528f8b46eb0bed467256122088c2e258eb12 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Thu, 8 Oct 2026 12:49:43 +0200 Subject: [PATCH] test: wait until the page shows a screen, not only until it lays out (closes #502) The browser suites' wait for a screen passed as soon as the element laid out. Until the page's stylesheet has applied, every view lays out, so an approval test could read the prompt before the page's script had filled it. visible() in the Chrome harness and waitVisible() in the Firefox driver 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() keeps on every view but the current one. Both also require the element's computed visibility to be visible, as the browser library's own check in Chrome did. No caller changed. Model: opus-5-5 --- TODO.md | 10 ++++++++++ tests/e2e/firefox/driver.js | 10 ++++++++-- tests/e2e/harness.js | 27 ++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) 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..8378011 100644 --- a/tests/e2e/firefox/driver.js +++ b/tests/e2e/firefox/driver.js @@ -279,12 +279,18 @@ 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; + if (getComputedStyle(el).visibility !== "visible") 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..cf0e058 100644 --- a/tests/e2e/harness.js +++ b/tests/e2e/harness.js @@ -333,8 +333,33 @@ 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; + if (getComputedStyle(el).visibility !== "visible") 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