#!/usr/bin/env python3 """0.33.5 CPU plot autoscaling regression with production HTML/CSS/JS. Uses Chromium with simulated authenticated DOM and status samples, not a live RustProxy server, SSE stream or real CPU telemetry. """ from pathlib import Path import re from playwright.sync_api import sync_playwright ROOT = Path(__file__).resolve().parents[2] HTML = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8') CSS = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8') WIDE_JS = (ROOT / 'src/admin/web/app/overview-wide-metrics.js').read_text(encoding='utf-8') def inspect(page): return page.evaluate('''() => { const get = id => document.querySelector(`#dashboard-overview-grid [data-dashboard-widget="${id}"]`); const cpu = get('wide_cpu'), cpuSvg = cpu.querySelector('svg'); const protocolSvg = get('wide_protocol_rates').querySelector('svg'); const backendSvg = get('backend_traffic').querySelector('svg'); const legend = cpu.querySelector('.overview-wide-legend').getBoundingClientRect(); const bounds = cpu.getBoundingClientRect(); const matrix = cpuSvg.getScreenCTM(); return { cpuHeight: cpuSvg.getBoundingClientRect().height, protocolHeight: protocolSvg.getBoundingClientRect().height, backendHeight: backendSvg.getBoundingClientRect().height, svgWidth: cpuSvg.getBoundingClientRect().width, drawnWidth: Math.hypot(matrix.a, matrix.b) * cpuSvg.viewBox.baseVal.width, ratio: cpuSvg.getAttribute('preserveAspectRatio'), cpuSeries: cpuSvg.querySelectorAll('.overview-wide-series').length, lineEffect: getComputedStyle(cpuSvg.querySelector('.overview-wide-series')).vectorEffect, gridEffect: getComputedStyle(cpuSvg.querySelector('.chart-grid')).vectorEffect, legendInCard: legend.top >= bounds.top && legend.bottom <= bounds.bottom + 1, overflow: document.documentElement.scrollWidth - innerWidth, }; }''') def render_cpu(page, revision, system, process): return page.evaluate('''({revision,system,process}) => { window.rustProxyWideOverview.render({revision,live_system:{ cpu_percent:system, process_cpu_percent:process }}); const read = name => { const prefix = `overview-wide-wide_cpu-${name}`; return { path: document.getElementById(`${prefix}-line`).getAttribute('d'), label: document.getElementById(`${prefix}-value`).textContent, }; }; return {system:read('system'),process:read('process')}; }''', {'revision': revision, 'system': system, 'process': process}) def ys(path): return [int(v) for v in re.findall(r'[ML]\d+\s+(\d+)', path)] with sync_playwright() as p: browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox']) for viewport in (320, 375, 768, 1440, 1920): page = browser.new_page(viewport={'width': viewport, 'height': 950}) 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.evaluate('''() => { document.documentElement.classList.remove('app-loading', 'app-startup-failed'); document.getElementById('workspace').hidden = false; document.getElementById('panel-overview').hidden = false; const grid = document.getElementById('dashboard-overview-grid'); grid.classList.add('is-aligned'); for (const card of grid.querySelectorAll(':scope > [data-dashboard-widget]')) { card.hidden = !['wide_cpu', 'wide_protocol_rates', 'backend_traffic'].includes(card.dataset.dashboardWidget); } window.currentPanel = 'overview'; window.$ = id => document.getElementById(id); window.fmt = (value, digits = 0) => Number(value).toFixed(digits); window.fmtBytes = value => `${value} B`; window.rustProxyOverviewSamples = {getLimit: () => 60, onChange: () => {}}; }''') page.add_script_tag(content=WIDE_JS) for density in ('compact', 'normal', 'ultra'): page.evaluate('(density) => document.documentElement.dataset.dashboardDensity = density', density) result = inspect(page) assert result['protocolHeight'] >= 124, (viewport, density, result) assert abs(result['cpuHeight'] - result['protocolHeight']) <= 1, ( 'CPU must use standard chart height', viewport, density, result) assert result['backendHeight'] > 0, (viewport, density, result) assert result['ratio'] == 'none' and result['cpuSeries'] == 2, result assert result['lineEffect'] == result['gridEffect'] == 'non-scaling-stroke', result assert result['svgWidth'] > 100 and abs(result['svgWidth'] - result['drawnWidth']) < 2, result assert result['legendInCard'] and result['overflow'] <= 2, (viewport, density, result) # Measured 0.5-0.8% must be visible, not flattened by a 100% ceiling. first = render_cpu(page, 1, 0.5, 0.2) second = render_cpu(page, 1, 0.8, 0.6) assert first['system']['label'] == '0.5%' and second['process']['label'] == '0.6%' assert ys(second['system']['path']) == [77, 41], (viewport, second) assert ys(second['process']['path']) == [113, 65], (viewport, second) # The peak follows observed history and also works above 100% (multi-core). third = render_cpu(page, 1, 3.4, 2.2) assert ys(third['system']['path'])[-1] == 17, (viewport, third) assert ys(third['process']['path'])[-1] == 59, (viewport, third) high = render_cpu(page, 1, 250, 123) assert high['system']['label'] == '250.0%' assert ys(high['system']['path'])[-1] == 17, (viewport, high) assert 17 <= ys(high['process']['path'])[-1] < 137, (viewport, high) # Revision changes reset the history. Zero stays at the plot baseline. idle = render_cpu(page, 2, 0, 0) assert ys(idle['system']['path']) == [137] and ys(idle['process']['path']) == [137] missing = render_cpu(page, 3, None, None) assert missing['system']['path'] == '' and missing['process']['label'] == '-' assert not errors, (viewport, errors) print(f'PASS CPU auto Y-scale and standard plot height at {viewport}px (all densities)') page.close() browser.close()