[test](trx-frontend-http): assert header geometry in the browser smoke test #33
@@ -9,6 +9,9 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { chromium } from "playwright-core";
|
||||
|
||||
// page.evaluate callbacks run in the browser, not in this Node process.
|
||||
/* global document */
|
||||
|
||||
const frontendDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
||||
const webDir = path.resolve(frontendDir, "../assets/web");
|
||||
const generatedDir = path.join(webDir, "generated");
|
||||
@@ -195,6 +198,55 @@ try {
|
||||
await page.locator("#tab-main").waitFor({ state: "visible" });
|
||||
assert.equal(new URL(page.url()).pathname, "/");
|
||||
assert.deepEqual(runtimeErrors, []);
|
||||
|
||||
// --- Layout regressions -------------------------------------------------
|
||||
// Every fault below shipped at some point while the rest of this file
|
||||
// passed, because nothing here looked at geometry: a header whose height
|
||||
// tracked the viewport, controls that stretched, a tab strip that ran under
|
||||
// the controls, and a dropdown that opened underneath the spectrum.
|
||||
for (const width of [1440, 1280, 1100, 900]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.waitForTimeout(250);
|
||||
const header = await page.evaluate(() => {
|
||||
const bar = document.querySelector(".tab-bar");
|
||||
const nav = document.querySelector(".tab-bar-nav");
|
||||
const actions = document.querySelector(".top-bar-actions");
|
||||
const controls = [...actions.children]
|
||||
.filter((el) => !el.hidden && el.getBoundingClientRect().height > 0)
|
||||
.map((el) => Math.round(el.getBoundingClientRect().height));
|
||||
return {
|
||||
barHeight: Math.round(bar.getBoundingClientRect().height),
|
||||
// The tabs, not the strip: with the strip allowed to overflow its box
|
||||
// shrinks while its content paints across the controls, so the
|
||||
// container's own rect never registers the collision.
|
||||
overlap: Math.round(Math.max(...[...nav.querySelectorAll(".tab")]
|
||||
.filter((tab) => tab.offsetParent !== null)
|
||||
.map((tab) => tab.getBoundingClientRect().right))
|
||||
- actions.getBoundingClientRect().left),
|
||||
heights: [...new Set(controls)],
|
||||
pageScrollsSideways: document.documentElement.scrollWidth > document.documentElement.clientWidth + 1,
|
||||
};
|
||||
});
|
||||
assert.ok(header.barHeight <= 96, `header is ${header.barHeight}px at ${width}px; it should stay one row`);
|
||||
assert.ok(header.overlap <= 0, `tab strip overlaps the controls by ${header.overlap}px at ${width}px`);
|
||||
assert.ok(header.heights.length <= 2, `controls have heights ${header.heights.join(", ")} at ${width}px`);
|
||||
assert.equal(header.pageScrollsSideways, false, `page scrolls sideways at ${width}px`);
|
||||
}
|
||||
|
||||
// A dropdown must paint over the page, not inside the header: fixed
|
||||
// positioning escapes clipping but not the header's stacking context.
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.waitForTimeout(250);
|
||||
const menu = await page.evaluate(() => {
|
||||
document.getElementById("mobile-more-btn").click();
|
||||
const element = document.getElementById("mobile-more-menu");
|
||||
const rect = element.getBoundingClientRect();
|
||||
const hit = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
||||
return { width: Math.round(rect.width), height: Math.round(rect.height), onTop: element.contains(hit) };
|
||||
});
|
||||
assert.ok(menu.height > 40 && menu.width > 80, `menu rendered ${menu.width}x${menu.height}`);
|
||||
assert.ok(menu.onTop, "menu is painted underneath the page");
|
||||
|
||||
} finally {
|
||||
await browser.close();
|
||||
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
||||
|
||||
Reference in New Issue
Block a user