Files
RustProxy/scripts/tests/web_server_0330_ui_test.py
T
2026-10-15 14:42:56 +02:00

153 lines
8.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Simulated Web Server telemetry against the production admin JS/CSS bundle.
Browser-only test: does not assert live RustProxy, PHP-FPM, or network behavior.
Run: python3 scripts/tests/web_server_0330_ui_test.py
"""
from __future__ import annotations
import json
from pathlib import Path
import re
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
router = (ROOT / 'src/admin/router.rs').read_text()
match = re.search(r'fn admin_app_javascript\([^)]*\)[^{]*\{', router)
assert match, 'admin_app_javascript definition missing'
portion = router[match.end():]
portion = portion[:re.search(r'\]\s*\.join\("\\n"\)', portion).start()]
fragments = []
for line in portion.splitlines():
line = line.strip()
if 'include_str!("' in line:
path = re.search(r'include_str!\("([^"]+)"\)', line).group(1)
fragments.append((ROOT / 'src/admin' / path).read_text())
elif line == 'admin_vhosts_javascript(),':
fragments.append('(() => {\n' + '\n'.join((ROOT / 'src/admin/web/vhosts' / f'{part}.js').read_text() for part in ['core', 'protocol', 'health', 'rates', 'editor', 'render']) + '\n})();')
elif line.startswith('"') and line.endswith('",'):
fragments.append(json.loads(line[:-1]))
BUNDLE = '\n'.join(fragments)
assert BUNDLE.index('window.rustProxyOverviewSamples =') < BUNDLE.index('window.rustProxyOverviewSamples.onChange(')
WEB = {
'requests': 100, 'status_2xx': 90, 'status_3xx': 2, 'status_4xx': 5, 'status_5xx': 3,
'response_headers_us': 2_500_000, 'response_bytes': 10_000, 'active_headers': 2,
'static_requests': 70, 'php_requests': 25, 'other_requests': 5,
'methods': [80, 10, 5, 2, 3], 'selected_status': [75, 5, 2, 0, 5, 1, 1, 1],
'failures': [0] * 9, 'compression': [5, 3, 2, 1],
}
PHP = {
'requests': 25, 'status_2xx': 22, 'status_3xx': 0, 'status_4xx': 1, 'status_5xx': 2,
'response_headers_us': 1_000_000, 'response_bytes': 2_000,
'failures': [1, 2, 3, 4, 5, 6, 7, 8, 9],
}
def observation(factor: int):
web = {key: ([factor * v for v in value] if isinstance(value, list) else factor * value)
for key, value in WEB.items()}
php = {key: ([factor * v for v in value] if isinstance(value, list) else factor * value)
for key, value in PHP.items()}
web['active_headers'] = 2
return {'revision': 10, 'web_server_metrics': {
'site-A': {'web': web, 'php': {'metrics': php, 'capacity_limit': 32, 'capacity_used': 18}}
}}
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
for width in (320, 375, 800, 1440):
page = browser.new_page(viewport={'width': width, 'height': 900})
errors = []
page.on('pageerror', lambda err: errors.append(str(err)))
page.set_content((ROOT / 'src/admin/web/index.html').read_text())
page.add_style_tag(content=(ROOT / 'src/admin/web/style.css').read_text())
page.add_script_tag(content=BUNDLE)
page.evaluate("""() => {
document.querySelector('#workspace').hidden = false;
document.querySelector('#panel-overview').hidden = false;
document.querySelectorAll('[data-dashboard-widget^="www_"]').forEach(n => n.hidden = false);
currentPanel = 'overview';
window.__clock = 1000;
Object.defineProperty(performance, 'now', { configurable: true, value: () => window.__clock });
}""")
first, second = observation(1), observation(2)
state = page.evaluate("""([one, two]) => {
const api = window.rustProxyWebOverview;
api.render(one);
window.__clock = 4000;
api.render(two);
return {
rps: document.getElementById('www_requests-all-value').textContent,
php: document.getElementById('www_requests-php-value').textContent,
latency: document.getElementById('www_latency-web-value').textContent,
capacity: document.getElementById('www_capacity-value').textContent,
types: document.getElementById('www_kind-value').textContent,
status: document.getElementById('www_status-value').textContent,
bytes: document.getElementById('www_traffic-value').textContent,
top: document.getElementById('www_top_services-value').textContent,
layoutWidth: document.documentElement.scrollWidth,
screenWidth: window.innerWidth,
children: document.querySelectorAll('[data-dashboard-widget^="www_"]').length,
sampleCount: document.getElementById('www_requests-all-line').getAttribute('d'),
limit: window.rustProxyOverviewSamples.getLimit(),
};
}""", [first, second])
assert state['children'] == 12, state
assert state['rps'] != '-' and state['php'] != '-', state
assert 'ms' in state['latency'] and '18' in state['capacity'], state
assert state['types'].endswith('%') and state['status'].endswith('%'), state
assert '/s' in state['top'], state
assert state['layoutWidth'] <= state['screenWidth'] + 2, (width, state)
assert not errors, (width, errors)
# Exercise the always-visible per-service metrics with the same status,
# without any real HTTP/PHP request or extra polling.
details = page.evaluate("""(status) => {
envelope = { revision: 10, config: {
web_services: [{ id: 'site-A', name: 'Example', document_root: '/var/www/site-A',
index: ['index.html'], php: { enabled: true } }],
vhosts: [], php_fpm_profiles: []
}};
currentPanel = 'web-server';
window.rustProxyWebServer.render();
const detail = document.querySelector('#web-service-list .www-service-panel');
if (!detail) return { missing: true };
window.rustProxyWebServer.renderLive(status, false);
return { text: detail.textContent, rows: detail.querySelectorAll('[data-web-live-field]').length,
alwaysVisible: detail.tagName === 'SECTION' && !detail.querySelector('summary') };
}""", second)
assert not details.get('missing') and details['rows'] == 7 and details['alwaysVisible'], (width, details)
assert '18 / 32' in details['text'], (width, details)
# The first reading after an overdue browser interval must establish a
# fresh baseline without adding a fake 0/s dip to the service chart.
continuity = page.evaluate("""([first, second, paused, resumed]) => {
const api = window.rustProxyWebServer;
const chart = document.querySelector('#web-service-list [data-web-live-id="site-A"] polyline');
const rate = document.querySelector('#web-service-list [data-web-live-field="rate"]');
window.__clock = 5000; api.renderLive(first);
window.__clock = 8000; api.renderLive(second);
const before = chart.getAttribute('points');
window.__clock = 100000; api.renderLive(paused);
const during = chart.getAttribute('points'), unavailable = rate.textContent;
window.__clock = 103000; api.renderLive(resumed);
return { before, during, unavailable, after: chart.getAttribute('points'), resumedRate: rate.textContent };
}""", [observation(2), observation(3), observation(10), observation(12)])
assert continuity['before'] == continuity['during'], (width, continuity)
assert continuity['unavailable'] == '-', (width, continuity)
assert continuity['resumedRate'] != '-', (width, continuity)
assert continuity['after'] != continuity['before'], (width, continuity)
assert not errors, (width, errors)
old = page.evaluate("""() => {
currentPanel = 'overview';
window.rustProxyWebOverview.render({ revision: 11, web_server_metrics: {} });
return {
rate: document.getElementById('www_requests-value').textContent,
status: document.getElementById('www_status-value').textContent,
top: document.getElementById('www_top_services-bars').childElementCount,
};
}""")
assert old == {'rate': '-', 'status': '-', 'top': 0}, (width, old)
assert not errors, (width, errors)
print(f'PASS simulated Web Server cards and always-visible service metrics at {width}px: {state["rps"]}, {state["php"]}, layout {state["layoutWidth"]}px')
page.close()
browser.close()
print('PASS production JS bundle + all twelve visible WWW widgets (simulated data only)')