#!/usr/bin/env python3 """Render real Vhost cards with mock config/status to verify badge placement/color. Headless Chromium fixture only: does not verify authenticated Rust runtime/ALPN. """ from pathlib import Path import shutil from playwright.sync_api import sync_playwright ROOT = Path(__file__).resolve().parents[2] WEB = ROOT / 'src/admin/web' source = (WEB / 'vhosts/render.js').read_text() start = source.index('const VHOST_PROTOCOL_FRESH_MS = ') end = source.index('function backendTrafficRate(', start) renderer = source[start:end] css = (WEB / 'style.css').read_text() html = (f'' f'' f'
' f'
' f'
' f'
') stubs = r''' const $ = id => document.getElementById(id); const t = key => key; const locale = () => 'en-US'; const el = (tag, value, cls) => { const node=document.createElement(tag); if (value !== undefined) node.textContent=value; if (cls) node.className=cls; return node; }; const text = (tag, key, cls) => el(tag, t(key), cls); const action = (name, fn) => { const btn=el('button', name, 'quiet'); btn.addEventListener('click', fn); return btn; }; const button = (name, fn, cls) => { const btn=action(name, fn); if (cls) btn.className=cls; return btn; }; const initialized=true; let editing=null, latestStatus=null; const vhostListPrefs={sort:'id',descending:false}; const envelope={config:{listeners:{endpoints:[]},vhosts:[ {id:'proxy-test',enabled:true,frontend:{hosts:['proxy.example'],listener_ids:[]}, source:{type:'reverse_proxy'},force_ssl:true,websockets:true, backends:[{id:'origin1',protocol:'auto'}, {id:'origin2',protocol:'http1'}]}, {id:'static-test',enabled:true,frontend:{hosts:['static.example'],listener_ids:[]}, source:{type:'web_server',web_service_id:'public'}, backends:[{id:'unused',protocol:'auto'}]}, ]}}; ''' with sync_playwright() as playwright: browser = playwright.chromium.launch(headless=True, executable_path=shutil.which('chromium'), args=['--no-sandbox']) for width in (393, 1024): page = browser.new_page(viewport={'width': width, 'height': 844}) page.set_content(html, wait_until='domcontentloaded') page.add_script_tag(content=stubs + renderer + '\nrender();') initial = page.evaluate('''() => { const card=document.querySelector('[data-vhost-card="proxy-test"]'); const badge=card.querySelector('.vhost-backend-protocol-badge'); const group=card.querySelector('.vhost-policy-pills'); const staticCard=document.querySelector('[data-vhost-card="static-test"]'); return {badges:group.querySelectorAll('.vhost-backend-protocol-badge').length, otherPills:group.querySelectorAll('.pill').length, parent:badge.parentElement===group, lastChild:card.lastElementChild===group, separateRows:card.querySelectorAll('.vhost-backend-protocols').length, staticBadges:staticCard.querySelectorAll('.vhost-backend-protocol-badge').length, initialText:badge.textContent, scroll:document.documentElement.scrollWidth,screen:innerWidth}; }''') assert initial['badges'] == 2 and initial['otherPills'] == 2, initial assert initial['parent'] and initial['lastChild'], initial assert initial['separateRows'] == 0 and initial['staticBadges'] == 0, initial assert initial['initialText'] == 'origin1 · AUTO → ?', initial assert initial['scroll'] <= initial['screen'] + 2, initial appearance = page.evaluate('''() => { const badge=document.querySelector('[data-upstream-protocol-backend="origin1"]'); const expected=token=>{const n=document.createElement('span');n.style.color=`var(${token})`; document.body.append(n);const c=getComputedStyle(n).color;n.remove();return c;}; const unknown=getComputedStyle(badge).color; renderUpstreamProtocolBadges({backend_pools:[{id:'proxy-test',members:[ {id:'origin1',upstream_protocol_observed:'h2',upstream_protocol_last_seen_ms:Date.now()-1000}, {id:'origin2',upstream_protocol_observed:'h1',upstream_protocol_last_seen_ms:Date.now()-1000} ]}]}); const h2={text:badge.textContent,observed:badge.dataset.observed, color:getComputedStyle(badge).color,expected:expected('--green')}; return {unknown, expectedUnknown:expected('--muted'), h2}; }''') assert appearance['unknown'] == appearance['expectedUnknown'], (width, appearance) assert appearance['h2']['color'] == appearance['h2']['expected'], (width, appearance) assert appearance['h2']['observed'] == 'true' and appearance['h2']['text'] == 'origin1 · AUTO → H2', appearance print(f'PASS: {width}px Vhost protocol badges share policy group and preserve green/neutral states') page.close() browser.close()