#!/usr/bin/env python3 """Real-browser regression for Overview chart content when card width changes. Runs the production HTML/CSS/assembled admin JS against a simulated DOM and fake network-interface samples. This does not exercise Rust HTTP/SSE. """ import json import re from pathlib import Path from panel_html import render_admin_index from playwright.sync_api import sync_playwright ROOT = Path(__file__).resolve().parents[2] HTML = render_admin_index(ROOT) CSS = (ROOT / 'src/admin/web/style.css').read_text() ROUTER = (ROOT / 'src/admin/router.rs').read_text() def app_bundle(): 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()] scripts = [] for line in body.splitlines(): line = line.strip() include = re.search(r'include_str!\("([^"]+)"\)', line) if include: scripts.append((ROOT / 'src/admin' / include.group(1)).read_text()) elif line == 'admin_vhosts_javascript(),': scripts.append('(() => {\n' + '\n'.join( (ROOT / 'src/admin/web/vhosts' / (name + '.js')).read_text() for name in ('core', 'protocol', 'health', 'rates', 'editor', 'render')) + '\n})();') elif line.startswith('"') and line.endswith('",'): scripts.append(json.loads(line[:-1])) return '\n'.join(scripts) def inspect(page): return page.evaluate('''() => { const selectors = { 'websocket_protocol':'[data-dashboard-widget="websocket_protocol"] svg', 'wide_transform_activity':'[data-dashboard-widget="wide_transform_activity"] svg', 'www_requests':'[data-dashboard-widget="www_requests"] svg', 'traffic_chart':'[data-dashboard-widget="traffic_chart"] svg', 'system_chart':'[data-dashboard-widget="system_chart"] svg', 'cache_diagnostics':'[data-dashboard-widget="cache_diagnostics"] .mini-live-chart svg', 'network_diagnostics':'[data-dashboard-widget="network_diagnostics"] .mini-live-chart svg', 'network_interface':'[data-dashboard-widget="network_diagnostics"] .network-interface-row svg', }; return Object.fromEntries(Object.entries(selectors).map(([name, selector]) => { const svg = document.querySelector(selector); if (!svg) throw new Error('Missing SVG: ' + name); const box = svg.getBoundingClientRect(); const matrix = svg.getScreenCTM(); const viewBox = svg.viewBox.baseVal; const contentWidth = Math.hypot(matrix.a, matrix.b) * viewBox.width; const series = svg.querySelector('.overview-wide-series'); return [name, {width:box.width, contentWidth, preservation:svg.getAttribute('preserveAspectRatio'), lineStroke: series ? getComputedStyle(series).vectorEffect : null}]; })); }''') with sync_playwright() as browser_api: browser = browser_api.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox']) for screen_width in (320, 375, 800, 1440, 1920): page = browser.new_page(viewport={'width': screen_width, 'height': 920}) errors = [] 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=app_bundle()) page.evaluate('''() => { document.documentElement.classList.remove('app-loading', 'app-startup-failed'); document.getElementById('workspace').hidden = false; document.getElementById('panel-overview').hidden = false; currentPanel = 'overview'; renderNetworkInterfaceCharts([{name:'eth0',rx_bytes_per_second:12500,tx_bytes_per_second:5200}], true); }''') ids = ['websocket_protocol', 'wide_transform_activity', 'www_requests', 'traffic_chart', 'system_chart', 'cache_diagnostics', 'network_diagnostics'] results = [] for span in (3, 12): page.evaluate('''({ids,span}) => { const hidden = ADMIN_DASHBOARD_WIDGETS.filter(id => !ids.includes(id)); const sizes = Object.fromEntries(ids.map(id => [id, span])); applyDashboardLayout([...ids, ...hidden], hidden, sizes); }''', {'ids': ids, 'span': span}) measured = inspect(page) results.append(measured) for chart, value in measured.items(): assert value['width'] > 50, (screen_width, span, chart, value) assert abs(value['width'] - value['contentWidth']) <= 2, ( 'chart not filling its own SVG viewport', screen_width, span, chart, value) assert value['preservation'] == 'none', (screen_width, chart, value) if value['lineStroke'] is not None: assert value['lineStroke'] == 'non-scaling-stroke', (chart, value) if screen_width >= 1440: for name in results[0]: assert results[1][name]['width'] > results[0][name]['width'] * 1.4, ( 'chart does not grow when widening card', screen_width, name, results) assert page.evaluate('document.documentElement.scrollWidth - innerWidth') <= 2, screen_width assert not errors, (screen_width, errors) print('PASS SVG chart uses full width at spans 3 and 12:', screen_width) page.close() browser.close() print('PASS resizable Overview chart content regression')