#!/usr/bin/env python3 """0.33.6 plot size/scale checks in simulated Chromium, not a running RustProxy.""" 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') SCALES = (ROOT / 'src/admin/web/app/chart-scales.js').read_text(encoding='utf-8') RATES = (ROOT / 'src/admin/web/app/rate-sampling.js').read_text(encoding='utf-8') WIDE = (ROOT / 'src/admin/web/app/overview-wide-metrics.js').read_text(encoding='utf-8') WIDE_IDS = ('wide_protocol_rates', 'wide_cpu', 'wide_memory', 'wide_cache_activity', 'wide_connections', 'wide_backend_health', 'wide_latency', 'wide_transform_activity', 'wide_compression_activity', 'www_requests', 'www_latency', 'www_failures', 'www_traffic', 'www_status_trend') with sync_playwright() as api: browser = api.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox']) for width in (320, 375, 768, 1440, 1920): page = browser.new_page(viewport={'width': 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.evaluate('''() => { document.documentElement.classList.remove('app-loading','app-startup-failed'); document.getElementById('workspace').hidden = false; document.getElementById('panel-overview').hidden = false; document.getElementById('dashboard-overview-grid').classList.add('is-aligned'); for (const card of document.querySelectorAll('#dashboard-overview-grid>[data-dashboard-widget]')) card.hidden = !['wide_protocol_rates','wide_cpu','wide_memory','wide_cache_activity', 'wide_connections','wide_backend_health','wide_latency', 'wide_transform_activity','wide_compression_activity', 'www_requests','www_latency','www_failures','www_traffic','www_status_trend'].includes(card.dataset.dashboardWidget); window.currentPanel='overview'; window.$ = id => document.getElementById(id); window.fmt = (n, digits=0) => Number(n).toFixed(digits); window.fmtBytes = n => `${n} B`; window.rustProxyOverviewSamples = {getLimit:()=>60,onChange:()=>{}}; window.rustProxyRefresh = {current:()=>({overview_seconds:3})}; window.__clock = 1000; Object.defineProperty(performance,'now',{configurable:true,value:()=>window.__clock}); }''') for script in (RATES, SCALES, WIDE): page.add_script_tag(content=script) for density in ('normal','compact','ultra'): measured = page.evaluate('''density => { document.documentElement.dataset.dashboardDensity=density; const results={}; for (const card of document.querySelectorAll('#dashboard-overview-grid > .overview-wide-chart')) { const svg=card.querySelector('svg.overview-wide-svg'); if (!svg) continue; const r=svg.getBoundingClientRect(); results[card.dataset.dashboardWidget]={height:r.height, width:r.width, grid:svg.querySelector('.chart-grid').getAttribute('d'), contentWidth:Math.hypot(svg.getScreenCTM().a,svg.getScreenCTM().b)*600}; } document.getElementById('panel-transform').hidden=false; document.getElementById('panel-cache').hidden=false; for(const id of ['transform-live-chart','cache-live-chart'])document.getElementById(id).open=true; results.transformDetails=document.getElementById('transform-live-activity-chart').getBoundingClientRect().height; results.cacheDetails=document.getElementById('cache-live-activity-chart').getBoundingClientRect().height; return results; }''', density) assert abs(measured['transformDetails'] - 180) < 1, (width,density,measured) assert abs(measured['cacheDetails'] - 180) < 1, (width,density,measured) for name in WIDE_IDS: data=measured[name] assert abs(data['height']-125)<1, (width,density,name,data) assert data['width']>50 and abs(data['width']-data['contentWidth'])<2, (width,density,name,data) assert data['grid']=='M0 47H600 M0 77H600 M0 107H600', (width,density,name,data) result = page.evaluate('''() => { const c=window.rustProxyChartScale; const first=c.peak('probe:rate','rate',[[.2,.7]]); const count=c.peak('probe:count','count',[[0,1]]); const latency=c.peak('probe:ms','ms',[[.1,1.5]]); const memory=c.peak('probe:bytes','bytes',[[1000]]); const high=c.peak('probe:rate','rate',[[101]]); const held=c.peak('probe:rate','rate',[[.3]]); c.reset('probe:'); const reset=c.peak('probe:rate','rate',[[.3]]); const percent=c.peak('probe:cpu','percent',[[.1,500]]); const blank=c.peak('probe:blank','rate',[[null,NaN,Infinity]]); const before={revision:10,live_system:{cpu_percent:0.7,process_cpu_percent:0.2}, metrics:{compression_gzip:50,compression_br:100,compression_zstd:10,compression_deflate:5}, transform_metrics:{applied:2,cache_hits:10,skipped:1,errors:0}}; window.rustProxyWideOverview.render(before); window.__clock=4000; before.metrics.compression_gzip+=3;before.metrics.compression_br+=6; before.metrics.compression_zstd+=0;before.metrics.compression_deflate+=0; before.transform_metrics.applied+=6;before.transform_metrics.cache_hits+=3; window.rustProxyWideOverview.render(before); const get = (id,key) => document.getElementById(`overview-wide-${id}-${key}-line`).getAttribute('d'); const compressionLow=get('wide_compression_activity','gzip'); const transformLow=get('wide_transform_activity','applied'); const cpu=get('wide_cpu','system'); const lowLabel=document.getElementById('overview-wide-wide_compression_activity-gzip-value').textContent; window.__clock=7000; before.metrics.compression_gzip+=603; // now 201/s => ceiling expands to 500/s before.transform_metrics.applied+=603; window.rustProxyWideOverview.render(before); const highLabel=document.getElementById('overview-wide-wide_compression_activity-gzip-value').textContent; const compressionHigh=get('wide_compression_activity','gzip'); window.__clock=10000; before.metrics.compression_gzip+=3; before.transform_metrics.applied+=3; window.rustProxyWideOverview.render(before); const afterSpike=get('wide_compression_activity','gzip'); return {first,count,latency,memory,high,held,reset,percent,blank, lowLabel,highLabel,compressionLow,transformLow,cpu,compressionHigh,afterSpike}; }''') assert result['first']==100 and result['count']==10 and result['latency']==100, (width,result) assert result['memory']>=64*1024*1024, (width,result) assert (result['high'],result['held'],result['reset'],result['percent'],result['blank']) == (200,200,100,100,100), (width,result) assert result['lowLabel']=='1.0/s' and result['highLabel']=='201.0/s', (width,result) def yvalues(line): return [int(n) for n in re.findall(r'[ML]\d+ (\d+)',line)] assert yvalues(result['compressionLow'])[-1]>=135, (width,result) assert yvalues(result['transformLow'])[-1]>=130, (width,result) assert yvalues(result['cpu'])==[136,136], (width,result) assert yvalues(result['compressionHigh'])[-1]<100, (width,result) assert yvalues(result['afterSpike'])[-1]>=135, (width,result) assert not errors, (width,errors) print('PASS all 14 overview-wide plot sizes/grids + full Cache/Transform charts + stable scales:', width) page.close() browser.close()