Files
RustProxy/tools/checks/overview_gutters_0324_browser.py
T
2026-10-14 16:07:58 +02:00

83 lines
4.5 KiB
Python
Executable File

#!/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 = ("<!DOCTYPE html><html data-theme='dark' data-dashboard-density='normal'>"
"<head><meta charset='utf-8'><style>" + css + "</style></head>"
"<body><section id='panel-overview' style='width:1680px'>"
"<div id='dashboard-overview-grid' class='stats-grid dashboard-overview-grid'>"
+ "".join(map(str, widgets)) + "</div></section></body></html>")
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 = old_gap
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")