#!/usr/bin/env python3 """Browser regression: compact, matching Overview gutters in both directions. Fixture uses live CSS and the real grid grouping script, but mocked content. """ from pathlib import Path from bs4 import BeautifulSoup from playwright.sync_api import sync_playwright root = Path(__file__).resolve().parents[2] / "src/admin/web" soup = BeautifulSoup((root / "index.html").read_text(), "html.parser") widgets = soup.select("#dashboard-overview-grid > [data-dashboard-widget]") assert len(widgets) == 62, len(widgets) css = (root / "style.css").read_text() html = ("" "" "
" "
" + "".join(map(str, widgets)) + "
") snapshot = """() => { const grid = document.getElementById('dashboard-overview-grid'); const style = getComputedStyle(grid); const parentGap = parseFloat(getComputedStyle(document.body).getPropertyValue('--ui-gap')); const rows = []; for (const node of grid.children) { if (!node.dataset.dashboardWidget || node.hidden) continue; const rect = node.getBoundingClientRect(); const group = window.rustProxyMasonry.groupFor(node.dataset.dashboardWidget); let row = rows.find(r => Math.abs(r.top - rect.top) < 0.75); if (!row) { row = {top: rect.top, bottom: rect.bottom, group, cards: []}; rows.push(row); } row.bottom = Math.max(row.bottom, rect.bottom); row.cards.push({left: rect.left, right: rect.right, bottom: rect.bottom}); } return { rowGap: parseFloat(style.rowGap), columnGap: parseFloat(style.columnGap), parentGap, margin: style.marginBottom, rows: rows.sort((a, b) => a.top - b.top) }; }""" with sync_playwright() as playwright: browser = playwright.chromium.launch( headless=True, executable_path="/usr/bin/chromium", args=["--no-sandbox"]) page = browser.new_page(viewport={"width": 1800, "height": 1100}) page.set_content(html) page.add_script_tag(content=(root / "app/overview-masonry.js").read_text()) checks = 0 for width in (900, 1220, 1680): page.evaluate("w => document.getElementById('panel-overview').style.width = w + 'px'", width) for ui_density, old_gap in (("normal", 5), ("dense", 4), ("comfortable", 13)): page.evaluate("d => {document.body.dataset.density = d === 'normal' ? '' : d;}", ui_density) for card_density in ("normal", "compact", "ultra"): page.evaluate("d => document.documentElement.dataset.dashboardDensity = d", card_density) data = page.evaluate(snapshot) expected = max(3, old_gap - 1) assert abs(data["parentGap"] - old_gap) < 0.01, data assert abs(data["rowGap"] - expected) < 0.01, data assert abs(data["columnGap"] - expected) < 0.01, data assert data["margin"] in ("0px", "0"), data rows = data["rows"] assert len(rows) >= 4, (width, ui_density, card_density) for before, after in zip(rows[:10], rows[1:11]): distance = after["top"] - before["bottom"] assert abs(distance - expected) < 1.05, (width, ui_density, card_density, distance, expected) for row in rows[:10]: cards = sorted(row["cards"], key=lambda card: card["left"]) assert max(c["bottom"] for c in cards) - min(c["bottom"] for c in cards) < 1.1 for a, b in zip(cards, cards[1:]): assert abs(b["left"] - a["right"] - expected) < 1.1, ( width, ui_density, card_density, a, b, expected) first_large = next((i for i, row in enumerate(rows) if row["group"] != 0), None) assert first_large is not None and first_large > 0 transition = rows[first_large]["top"] - rows[first_large-1]["bottom"] assert abs(transition - expected) < 1.05, (width, ui_density, card_density, transition) checks += 1 print(f"PASS: {width}px/{ui_density}: uniform {expected}px gutters, all Dashboard densities") browser.close() print(f"PASS: {checks} Chromium layout checks including small-to-large transitions")