diff --git a/changelog.d/browser-checks-wait-for-what-they-read.md b/changelog.d/browser-checks-wait-for-what-they-read.md new file mode 100644 index 0000000000..6a18a55ee2 --- /dev/null +++ b/changelog.d/browser-checks-wait-for-what-they-read.md @@ -0,0 +1,5 @@ +### Maintenance +- Four browser checks no longer fail on a slow run: the terminal input check waits for the + connection before it asks for a liveness probe, the update line and plan-limit checks count a + request once it has been answered or by the page that made it, and the phone transcript check + reads the transcript's style in one step. diff --git a/scripts/mobile-viewport-regression.ts b/scripts/mobile-viewport-regression.ts index e22074aaa2..7e4f870451 100644 --- a/scripts/mobile-viewport-regression.ts +++ b/scripts/mobile-viewport-regression.ts @@ -34,7 +34,9 @@ export async function checkMobileViewport(browser: Browser, origin: string, pane await page.getByTitle("Chat transcript (⌘⇧J)", { exact: true }).click(); const composer = page.getByRole("textbox", { name: "Message", exact: true }); await composer.waitFor(); - assert.equal(await page.locator(".chat-view").evaluate((node) => getComputedStyle(node).overscrollBehaviorY), "contain", + // read in one step: the transcript's first node is replaced as the conversation loads, and a node + // found before that and read after it is out of the document, with no style at all + assert.equal(await page.evaluate(() => { const view = document.querySelector(".chat-view"); return view ? getComputedStyle(view).overscrollBehaviorY : "no .chat-view"; }), "contain", "a drag past the transcript's top stays in the transcript, or Android Chrome reloads the app"); assert.deepEqual(await page.evaluate(() => [document.documentElement, document.body].map((node) => getComputedStyle(node).overscrollBehaviorY)), ["contain", "contain"], "a drag on a surface with nothing to scroll reaches the page, which Android Chrome before 144 reloads unless html and body contain it"); diff --git a/scripts/terminal-input-regression.ts b/scripts/terminal-input-regression.ts index 8afa7a845c..386ae76bf1 100644 --- a/scripts/terminal-input-regression.ts +++ b/scripts/terminal-input-regression.ts @@ -75,6 +75,9 @@ export async function checkTerminalInput(browser: Browser, origin: string, pane: // A liveness probe refuses input without a frame to say so: on a quiet pane the 1 s poll reads // it, so the terminal shows it waits instead of looking ready while typing goes to the draft. const waiting = page.locator(".terminal-banner", { hasText: "Waiting for terminal input…" }); + // the banner is drawn only on a connected pane: before the socket opens it is absent too, and + // a wake then finds nothing to probe + await page.locator(".conn-live").waitFor(); await waiting.waitFor({ state: "hidden" }); holdPings = true; await page.evaluate(() => window.dispatchEvent(new Event("online"))); diff --git a/scripts/update-notice-regression.ts b/scripts/update-notice-regression.ts index ac44e9fc2c..79cdbd2e64 100644 --- a/scripts/update-notice-regression.ts +++ b/scripts/update-notice-regression.ts @@ -69,8 +69,12 @@ export async function checkUpdateNotice(browser: Browser, origin: string, shots? await fits("available"); await oneLine("available"); await shot("1-available"); + // the line says it started as the button is pressed, before the request it sends is counted here + const installAnswered = () => page.waitForResponse((response) => new URL(response.url()).pathname === "/api/updates/install"); + const firstInstall = installAnswered(); await page.getByRole("button", { name: "Update", exact: true }).click(); await page.getByText("Starting the update…", { exact: true }).waitFor(); + await firstInstall; assert.equal(installs, 1, "one tap asks for the install"); assert.equal(await page.getByRole("dialog", { name: "Settings" }).count(), 0, "Settings stays closed"); @@ -93,8 +97,10 @@ export async function checkUpdateNotice(browser: Browser, origin: string, shots? await page.getByRole("button", { name: "Details", exact: true }).click(); await page.getByRole("dialog", { name: "Settings" }).getByText("bun run build failed: fixture").waitFor(); await page.getByRole("button", { name: "Close settings", exact: true }).click(); + const secondInstall = installAnswered(); await page.getByRole("button", { name: "Try again", exact: true }).click(); await page.getByText("Starting the update…", { exact: true }).waitFor(); + await secondInstall; assert.equal(installs, 2, "Try again asks for the install again"); // the check an install starts with fails: nothing is available any more, and the line still says so status = { ...status, phase: "error", available: false, error: "git fetch failed: fixture" }; diff --git a/scripts/usage-regression.ts b/scripts/usage-regression.ts index d09acf8f62..b53bd26441 100644 --- a/scripts/usage-regression.ts +++ b/scripts/usage-regression.ts @@ -31,19 +31,24 @@ const report = (): UsageReport => ({ ], }); -async function staged(context: BrowserContext): Promise { +/** a header the check puts on every request of the page it reloads: what that page asked for, whenever it is counted */ +const RELOADED = "x-usage-qa-reloaded"; + +async function staged(context: BrowserContext): Promise<{ asked: string[]; afterReload: string[] }> { const asked: string[] = []; + const afterReload: string[] = []; await context.route("**/api/usage*", (route) => { asked.push(new URL(route.request().url()).search); + if (route.request().headers()[RELOADED]) afterReload.push(route.request().url()); return route.fulfill({ json: report() }); }); - return asked; + return { asked, afterReload }; } export async function checkUsageMeters(browser: Browser, origin: string): Promise { const context = await browser.newContext({ viewport: { width: 1280, height: 800 }, locale: "en-US" }); try { - const asked = await staged(context); + const { asked, afterReload } = await staged(context); const page = await context.newPage(); const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); @@ -151,10 +156,13 @@ export async function checkUsageMeters(browser: Browser, origin: string): Promis await openSettingsPage(page, "Subscription usage"); await toggle.click(); await strip.waitFor({ state: "detached" }); - const before = asked.length; + // a request Settings made before the switch can still be on its way here, so the count is of + // the reloaded page's own requests, not of what arrives after the reload + await context.setExtraHTTPHeaders({ [RELOADED]: "1" }); await page.reload(); await page.locator(".sidebar-footer").waitFor(); - assert.equal(asked.length, before, "a hidden strip asks for nothing"); + assert.deepEqual(afterReload, [], "a hidden strip asks for nothing"); + await context.setExtraHTTPHeaders({}); assert.deepEqual(errors, []); } finally { await context.close();