This commit is contained in:
Mateusz Gruszczyński committed 2026-10-15 23:29:15 +02:00
1 parent 162e986d37
commit f462ca7194
26 files changed
+980 -710

No files matched your search

@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""Regression: Overview visual layout editor fits without horizontal overflow.
"""Responsive regression for the Overview toolbar modal.
Runs the real panel DOM/CSS/bundled JavaScript with mocked preferences.
This is browser layout testing, not Rust server acceptance.
0.33.7 repurposed the former visual layout editor as Dashboard presets. Keep
this older viewport regression focused on modal fit/no horizontal overflow.
"""
import json
import re
@@ -10,17 +10,17 @@ from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
HTML = (ROOT / 'src/admin/web/index.html').read_text()
CSS = (ROOT / 'src/admin/web/style.css').read_text()
ROUTER = (ROOT / 'src/admin/router.rs').read_text()
HTML = (ROOT / "src/admin/web/index.html").read_text(encoding="utf-8")
CSS = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
ROUTER = (ROOT / "src/admin/router.rs").read_text(encoding="utf-8")
PACKS = {
name: {'id': name, **json.loads((ROOT / 'langs' / (name + '.json')).read_text())}
for name in ('en_US', 'pl_pl')
name: {"id": name, **json.loads((ROOT / "langs" / f"{name}.json").read_text(encoding="utf-8"))}
for name in ("en_US", "pl_pl")
}
def app_bundle():
match = re.search(r'fn admin_app_javascript\([^)]*\)[^{]*\{', ROUTER)
match = re.search(r"fn admin_app_javascript\([^)]*\)[^{]*\{", ROUTER)
assert match
body = ROUTER[match.end():]
body = body[:re.search(r'\]\s*\.join\("\\n"\)', body).start()]
@@ -29,141 +29,64 @@ def app_bundle():
line = line.strip()
include = re.search(r'include_str!\("([^"]+)"\)', line)
if include:
result.append((ROOT / 'src/admin' / include.group(1)).read_text())
elif line == 'admin_vhosts_javascript(),':
result.append('(() => {\n' + '\n'.join((ROOT / 'src/admin/web/vhosts' / (name + '.js')).read_text()
for name in ('core', 'protocol', 'health', 'rates', 'editor', 'render')) + '\n})();')
result.append((ROOT / "src/admin" / include.group(1)).read_text(encoding="utf-8"))
elif line == "admin_vhosts_javascript(),":
result.append("(() => {\n" + "\n".join(
(ROOT / "src/admin/web/vhosts" / f"{name}.js").read_text(encoding="utf-8")
for name in ("core", "protocol", "health", "rates", "editor", "render")) + "\n})();")
elif line.startswith('"') and line.endswith('",'):
result.append(json.loads(line[:-1]))
return '\n'.join(result)
return "\n".join(result)
BUNDLE = app_bundle()
def load(page):
page.set_content(HTML.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '')
.replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
page.add_style_tag(content=CSS)
page.add_script_tag(content=BUNDLE)
page.wait_for_function('window.rustProxyInlineLayout && window.rustProxyPreferences && window.rustProxyDashboardLayout')
page.evaluate('''packs => {
languagePacks = new Map(Object.entries(packs));
defaultLanguage = 'en_US';
language = 'pl_pl';
document.documentElement.lang = 'pl-PL';
document.querySelectorAll('#overview-layout-dialog [data-i18n]').forEach(node => node.textContent = t(node.dataset.i18n));
}''', PACKS)
page.evaluate('''() => {
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
document.getElementById('overview-layout-unlock').hidden = false;
document.getElementById('overview-layout-open').hidden = false;
currentPanel = 'overview';
window.__savedDashboard = structuredClone(ADMIN_APPEARANCE_DEFAULTS);
window.rustProxyPreferences.load = async () => window.__savedDashboard;
window.rustProxyPreferences.current = () => window.__savedDashboard;
window.rustProxyPreferences.update = async patch => {
window.__savedDashboard = {...window.__savedDashboard, ...patch};
applyAdminPreferences(window.__savedDashboard);
return window.__savedDashboard;
};
applyAdminPreferences(window.__savedDashboard);
language = 'pl_pl';
document.documentElement.lang = 'pl-PL';
document.querySelectorAll('#overview-layout-dialog [data-i18n]').forEach(node => node.textContent = t(node.dataset.i18n));
}''')
def geometry(page):
return page.evaluate('''() => {
const d = document.getElementById('overview-layout-dialog');
const grid = document.getElementById('overview-layout-visible');
const rect = node => node.getBoundingClientRect();
const tiles = Array.from(grid.querySelectorAll('.overview-layout-tile'));
const overflows = [];
for (const tile of tiles) {
const r = rect(tile);
const toolbar = tile.querySelector('.overview-layout-tile-toolbar');
if (tile.scrollWidth > tile.clientWidth + 2)
overflows.push([tile.dataset.previewWidget, 'tile-scroll', tile.clientWidth, tile.scrollWidth]);
for (const button of toolbar.querySelectorAll('button')) {
const b = rect(button);
if (b.left < r.left - 2 || b.right > r.right + 2)
overflows.push([tile.dataset.previewWidget, 'button-outside', button.className, b.left, b.right, r.left, r.right]);
}
const buttons = Array.from(toolbar.querySelectorAll('button'));
for (let i = 0; i < buttons.length; i++) {
const a = rect(buttons[i]);
for (let j = i + 1; j < buttons.length; j++) {
const b = rect(buttons[j]);
if (a.right - b.left > 1 && b.right - a.left > 1 && a.bottom - b.top > 1 && b.bottom - a.top > 1)
overflows.push([tile.dataset.previewWidget, 'overlapping-buttons', buttons[i].className, buttons[j].className]);
}
}
}
const hiddenList = document.getElementById('overview-layout-hidden');
for (const row of hiddenList.querySelectorAll('.overview-layout-hidden-item')) {
const r = rect(row), button = row.querySelector('button');
const b = rect(button);
if (b.right > r.right + 2 || b.left < r.left - 2)
overflows.push(['hidden card', 'button-outside', b.left, b.right, r.left, r.right]);
}
const panelPreview = grid.querySelector('[data-preview-widget="network_diagnostics"]');
const dialogRect = rect(d);
return {
cols: getComputedStyle(grid).gridTemplateColumns.split(' ').length,
panelWidth: panelPreview ? rect(panelPreview).width : null,
dialogWidth: d.clientWidth, dialogScrollWidth: d.scrollWidth,
gridWidth: grid.clientWidth, gridScrollWidth: grid.scrollWidth,
dialogLeft:dialogRect.left, dialogRight:dialogRect.right,
visibleCount:tiles.length, overflows,
firstTileWidth:rect(tiles[0]).width,
};
}''')
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
browser = p.chromium.launch(headless=True, executable_path="/usr/bin/chromium", args=["--no-sandbox"])
for width in (320, 375, 550, 620, 800, 900, 960, 1158, 1440, 1920):
context = browser.new_context(viewport={'width': width, 'height': 823})
page = context.new_page()
page = browser.new_page(viewport={"width": width, "height": 823})
errors = []
page.on('pageerror', lambda err: errors.append(str(err)))
load(page)
page.locator('#overview-layout-open').click()
page.on("pageerror", lambda error: errors.append(str(error)))
page.set_content(HTML.replace("/assets/app___RUSTPROXY_ASSET_ID__.js", "")
.replace("/assets/style___RUSTPROXY_ASSET_ID__.css", ""))
page.add_style_tag(content=CSS)
page.add_script_tag(content=BUNDLE)
page.wait_for_function("window.rustProxyPreferences && window.rustProxyDashboardLayout")
page.evaluate("""packs => {
languagePacks = new Map(Object.entries(packs)); defaultLanguage = 'en_US';
language = 'pl_pl'; languagePreference = 'pl_pl';
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
document.getElementById('overview-layout-open').hidden = false;
currentPanel = 'overview';
window.__savedDashboard = {...structuredClone(ADMIN_APPEARANCE_DEFAULTS), language:'pl_pl'};
window.rustProxyPreferences.load = async () => window.__savedDashboard;
window.rustProxyPreferences.current = () => window.__savedDashboard;
window.rustProxyPreferences.update = async patch => {
window.__savedDashboard = {...window.__savedDashboard, ...patch};
applyAdminPreferences(window.__savedDashboard); return window.__savedDashboard;
};
applyAdminPreferences(window.__savedDashboard); language = 'pl_pl'; languagePreference = 'pl_pl';
for (const node of document.querySelectorAll('#overview-layout-dialog [data-i18n]'))
node.textContent = t(node.dataset.i18n);
}""", PACKS)
page.locator("#overview-layout-open").click()
page.wait_for_function("document.getElementById('overview-layout-dialog').open")
check = geometry(page)
expected = 1 if width <= 620 else 2 if width <= 900 else 3
assert check['cols'] == expected, (width, check)
assert check['visibleCount'] > 20
assert check['panelWidth'] is not None
assert abs(check['firstTileWidth'] - check['panelWidth']) < 3, (width, check)
assert check['dialogLeft'] >= -1 and check['dialogRight'] <= width + 1, (width, check)
assert check['dialogScrollWidth'] <= check['dialogWidth'] + 2, (width, check)
assert check['gridScrollWidth'] <= check['gridWidth'] + 2, (width, check)
assert not check['overflows'], (width, check['overflows'][:20])
tile = '#overview-layout-visible [data-preview-widget="http_protocol"]'
before = page.locator(tile).evaluate('(el) => el.getBoundingClientRect().width')
page.locator(tile + ' .overview-layout-widen').click()
assert page.locator(tile).get_attribute('data-dashboard-span') == '3'
after = page.locator(tile).evaluate('(el) => el.getBoundingClientRect().width')
assert abs(before - after) < 2, (width, before, after)
assert not geometry(page)['overflows']
page.locator(tile + ' .overview-layout-narrow').click()
assert page.locator(tile).get_attribute('data-dashboard-span') == '2'
if width == 1158:
page.screenshot(path=str(ROOT.parent / 'modal_preview_1158.png'))
if width == 375:
page.screenshot(path=str(ROOT.parent / 'modal_preview_375.png'))
page.locator(tile + ' .overview-layout-hide').click()
assert page.locator(tile).count() == 0
assert not geometry(page)['overflows']
page.locator('#overview-layout-cancel').click()
page.wait_for_function("!document.getElementById('overview-layout-dialog').open")
check = page.locator("#overview-layout-dialog").evaluate("""d => {
const r = d.getBoundingClientRect();
const rows = [...d.querySelectorAll('.dashboard-preset-save,.dashboard-preset-row')];
return {
left:r.left, right:r.right, client:d.clientWidth, scroll:d.scrollWidth,
overflow: rows.some(row => row.scrollWidth > row.clientWidth + 2),
};
}""")
assert check["left"] >= -1 and check["right"] <= width + 1, (width, check)
assert check["scroll"] <= check["client"] + 2 and not check["overflow"], (width, check)
assert page.locator("#overview-preset-name").is_visible()
assert page.locator("#overview-preset-save").is_visible()
assert not errors, (width, errors)
context.close()
print('PASS visual modal grid, no overlap/scroll, width/hide/cancel:', width)
page.close()
print(f"PASS responsive Dashboard preset modal at {width}px")
browser.close()
print('PASS 0.33.3 modal regression (simulated API)')
print("PASS modal responsive regression after 0.33.7 preset conversion")