From 4a80600e22c9cb480fc0e72dd1dbf807c43ab350 Mon Sep 17 00:00:00 2001 From: dave Date: Fri, 24 Apr 2026 14:20:35 +0000 Subject: [PATCH] huskies: merge 614_bug_gateway_web_ui_has_no_vertical_scrollbars --- frontend/src/App.css | 1 - frontend/tests/e2e/gateway-scroll.spec.ts | 75 +++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 frontend/tests/e2e/gateway-scroll.spec.ts diff --git a/frontend/src/App.css b/frontend/src/App.css index 1e31af96..3b7e89f4 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -194,7 +194,6 @@ body, #root { height: 100%; margin: 0; - overflow: hidden; } /* Agent activity indicator pulse */ diff --git a/frontend/tests/e2e/gateway-scroll.spec.ts b/frontend/tests/e2e/gateway-scroll.spec.ts new file mode 100644 index 00000000..13e32f64 --- /dev/null +++ b/frontend/tests/e2e/gateway-scroll.spec.ts @@ -0,0 +1,75 @@ +import { expect, test } from "@playwright/test"; + +/// Regression test: gateway UI must have vertical scrolling when content +/// overflows the viewport. Verifies the `overflow: hidden` fix on +/// `html / body / #root` — without that fix the page is locked at y=0. +test.describe("Gateway UI scrolling", () => { + test("page scrolls when content exceeds viewport height", async ({ + page, + }) => { + // Use a small viewport to guarantee overflow even with modest content. + await page.setViewportSize({ width: 1280, height: 400 }); + + // --- mock API endpoints --- + + // Identify this server as a gateway. + await page.route("/gateway/mode", async (route) => { + await route.fulfill({ json: { mode: "gateway" } }); + }); + + // Return enough agents to push the page past 400 px. + const agents = Array.from({ length: 15 }, (_, i) => ({ + id: `agent-${i}`, + label: `Build Agent ${i}`, + address: `10.0.0.${i}:5000`, + registered_at: Date.now() / 1000 - 60, + last_seen: Date.now() / 1000 - 10, + })); + await page.route("/gateway/agents", async (route) => { + await route.fulfill({ json: agents }); + }); + + await page.route("/api/gateway", async (route) => { + await route.fulfill({ json: { active: "", projects: [] } }); + }); + + await page.route("/api/gateway/pipeline", async (route) => { + await route.fulfill({ json: { active: "", projects: {} } }); + }); + + // Non-gateway APIs called by App.tsx on startup — respond quickly so the + // loading gate (`isCheckingProject`) clears and the gateway panel renders. + await page.route("/api/project", async (route) => { + await route.fulfill({ json: null }); + }); + await page.route("/api/projects", async (route) => { + await route.fulfill({ json: [] }); + }); + await page.route("/oauth/status", async (route) => { + await route.fulfill({ json: { authenticated: false } }); + }); + await page.route("/api/home", async (route) => { + await route.fulfill({ json: "/home/test" }); + }); + + await page.goto("/"); + + // Wait until the gateway panel is visible. + await page.waitForSelector('[data-testid="add-agent-button"]'); + + // The scrolling element should be taller than the visible viewport. + const isOverflowing = await page.evaluate(() => { + const el = + document.scrollingElement ?? document.documentElement; + return el.scrollHeight > el.clientHeight; + }); + expect(isOverflowing).toBe(true); + + // Scrolling must actually move the viewport. + await page.evaluate(() => window.scrollBy(0, 300)); + const scrollY = await page.evaluate( + () => document.scrollingElement?.scrollTop ?? window.scrollY, + ); + expect(scrollY).toBeGreaterThan(0); + }); +});