198 lines
11 KiB
Python
Executable File
198 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""0.33.1 factory/default + unsaved layout regression against the full bundled JS.
|
|
|
|
The authenticated app bundle runs against simulated status/preferences; this is
|
|
not a Rust server integration test.
|
|
"""
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
PRESET = json.loads((ROOT / 'src/admin/web/dashboard-defaults.json').read_text())
|
|
REGISTRY = json.loads((ROOT / 'src/admin/web/dashboard-widgets.json').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()]
|
|
result = []
|
|
for line in body.splitlines():
|
|
line = line.strip()
|
|
include = re.search(r'include_str!\("([^"]+)"\)', line)
|
|
if include:
|
|
result.append((ROOT / 'src/admin' / include.group(1)).read_text())
|
|
elif line == 'admin_vhosts_javascript(),':
|
|
result.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('",'):
|
|
result.append(json.loads(line[:-1]))
|
|
return '\n'.join(result)
|
|
|
|
|
|
assert len(PRESET['dashboard_widgets']) == len(REGISTRY)
|
|
assert set(PRESET['dashboard_widgets']) == {w['id'] for w in REGISTRY}
|
|
assert len(set(PRESET['dashboard_hidden_widgets'])) == len(PRESET['dashboard_hidden_widgets'])
|
|
assert set(PRESET['dashboard_hidden_widgets']).issubset(set(PRESET['dashboard_widgets']))
|
|
assert PRESET['dashboard_density'] == 'compact' and PRESET['overview_samples'] == 600
|
|
assert sum(w['id'].startswith('www_') and w['id'] in PRESET['dashboard_hidden_widgets'] for w in REGISTRY) == 12
|
|
|
|
HTML = (ROOT / 'src/admin/web/index.html').read_text()
|
|
CSS = (ROOT / 'src/admin/web/style.css').read_text()
|
|
BUNDLE = app_bundle()
|
|
assert BUNDLE.index('const ADMIN_DASHBOARD_DEFAULTS =') < BUNDLE.index('window.rustProxyOverviewSamples =')
|
|
|
|
|
|
def load(page):
|
|
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=BUNDLE)
|
|
page.wait_for_function('window.rustProxyInlineLayout && window.rustProxyPreferences && window.rustProxyBackendDashboard')
|
|
|
|
|
|
def inspect(page, ids):
|
|
return page.evaluate('''ids => Object.fromEntries(ids.map(id => {
|
|
const node = document.querySelector(`[data-dashboard-widget="${id}"]`);
|
|
return [id, {hidden:node.hidden, display:getComputedStyle(node).display}];
|
|
}))''', ids)
|
|
|
|
|
|
def assert_visible(page, visible, hidden):
|
|
result = inspect(page, visible + hidden)
|
|
for widget in visible:
|
|
assert result[widget]['hidden'] is False and result[widget]['display'] != 'none', (widget, result)
|
|
for widget in hidden:
|
|
assert result[widget]['hidden'] is True and result[widget]['display'] == 'none', (widget, result)
|
|
|
|
|
|
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]:
|
|
context = browser.new_context(viewport={'width': width, 'height': 950})
|
|
page = context.new_page()
|
|
errors = []
|
|
page.on('pageerror', lambda e: errors.append(str(e)))
|
|
load(page)
|
|
result = page.evaluate('''() => {
|
|
const prefs = ADMIN_APPEARANCE_DEFAULTS;
|
|
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
|
|
document.getElementById('workspace').hidden = false;
|
|
document.getElementById('panel-overview').hidden = false;
|
|
document.getElementById('overview-layout-unlock').hidden = false;
|
|
currentPanel = 'overview';
|
|
window.__savedDashboard = JSON.parse(JSON.stringify(prefs));
|
|
window.rustProxyPreferences.load = async () => window.__savedDashboard;
|
|
window.rustProxyPreferences.current = () => window.__savedDashboard;
|
|
window.rustProxyPreferences.update = async patch => {
|
|
window.__savedDashboard = {...window.__savedDashboard, ...patch};
|
|
applyAdminPreferences(window.__savedDashboard);
|
|
return window.__savedDashboard;
|
|
};
|
|
applyAdminPreferences(window.__savedDashboard);
|
|
return {
|
|
order: ADMIN_DASHBOARD_WIDGETS,
|
|
hidden: ADMIN_DEFAULT_HIDDEN_WIDGETS,
|
|
density: document.documentElement.dataset.dashboardDensity,
|
|
samples:window.rustProxyOverviewSamples.getLimit(),
|
|
serviceCount:document.querySelectorAll('#dashboard-overview-grid [data-dashboard-widget]').length,
|
|
};
|
|
}''')
|
|
assert result['order'] == PRESET['dashboard_widgets'], (width, result['order'])
|
|
assert result['hidden'] == PRESET['dashboard_hidden_widgets'], (width, result['hidden'])
|
|
assert result['samples'] == 600 and result['density'] == 'compact' and result['serviceCount'] == 78, result
|
|
assert_visible(page, ['backend_traffic', 'extra_response_latency', 'wide_transform_activity'],
|
|
['backend_health', 'stream_sessions', 'www_status', 'bandwidth'])
|
|
|
|
# Reproduce the previously flaky operation: opt in hidden backend +
|
|
# Stream cards and opt out a visible backend card, WITHOUT saving yet.
|
|
page.locator('#overview-layout-unlock').click()
|
|
page.wait_for_function('window.rustProxyInlineLayout.isEditing()')
|
|
outcome = page.evaluate('''() => {
|
|
const add = id => {
|
|
const label = dashboardWidgetLabel(id);
|
|
const item = [...document.querySelectorAll('#overview-inline-hidden-list .overview-inline-hidden-item')]
|
|
.find(node => node.firstElementChild.textContent === label);
|
|
if (!item) throw new Error('no hidden card to restore: ' + id);
|
|
item.querySelector('button').click();
|
|
};
|
|
add('backend_health');
|
|
add('stream_sessions');
|
|
const card = document.querySelector('[data-dashboard-widget="backend_traffic"]');
|
|
card.querySelector('.overview-inline-hide').click();
|
|
return window.rustProxyInlineLayout.currentDraft();
|
|
}''')
|
|
assert 'backend_traffic' in outcome['hidden'] and 'backend_health' not in outcome['hidden']
|
|
assert 'stream_sessions' not in outcome['hidden']
|
|
assert_visible(page, ['backend_health', 'stream_sessions'], ['backend_traffic'])
|
|
for _ in range(5):
|
|
page.evaluate('''() => {
|
|
const status = {revision:4,backend_pools:[],streams:{active:[],desired:[],metrics:{}}};
|
|
window.rustProxyBackendDashboard.render(status);
|
|
window.rustProxyStreamDashboard.render(status);
|
|
}''')
|
|
assert_visible(page, ['backend_health', 'stream_sessions'], ['backend_traffic'])
|
|
# Unrelated settings update must not repaint the saved layout over the draft.
|
|
page.evaluate('''() => applyAdminPreferences({...window.__savedDashboard, ui_scale:105})''')
|
|
assert_visible(page, ['backend_health', 'stream_sessions'], ['backend_traffic'])
|
|
assert page.evaluate('window.rustProxyInlineLayout.isEditing()')
|
|
page.locator('#overview-inline-cancel').click()
|
|
assert not page.evaluate('window.rustProxyInlineLayout.isEditing()')
|
|
assert_visible(page, ['backend_traffic'], ['backend_health', 'stream_sessions'])
|
|
|
|
# Save under live traffic, ensure the in-flight preference application
|
|
# uses the current draft before the editor exits.
|
|
page.locator('#overview-layout-unlock').click()
|
|
page.wait_for_function('window.rustProxyInlineLayout.isEditing()')
|
|
page.evaluate('''() => {
|
|
const label = dashboardWidgetLabel('backend_health');
|
|
const item = [...document.querySelectorAll('#overview-inline-hidden-list .overview-inline-hidden-item')]
|
|
.find(node => node.firstElementChild.textContent === label);
|
|
item.querySelector('button').click();
|
|
document.querySelector('[data-dashboard-widget="backend_traffic"] .overview-inline-hide').click();
|
|
}''')
|
|
page.locator('#overview-inline-save').click()
|
|
page.wait_for_function('!window.rustProxyInlineLayout.isEditing()')
|
|
assert_visible(page, ['backend_health'], ['backend_traffic'])
|
|
saved = page.evaluate('''() => window.__savedDashboard.dashboard_hidden_widgets''')
|
|
assert 'backend_traffic' in saved and 'backend_health' not in saved
|
|
page.evaluate('''() => {
|
|
const status={backend_pools:[],streams:{active:[],desired:[],metrics:{}}};
|
|
window.rustProxyBackendDashboard.render(status);
|
|
window.rustProxyStreamDashboard.render(status);
|
|
}''')
|
|
assert_visible(page, ['backend_health'], ['backend_traffic'])
|
|
# Restore defaults in the modal after a custom layout was saved.
|
|
page.locator('#overview-layout-open').evaluate('(node) => node.hidden = false')
|
|
page.locator('#overview-layout-open').click()
|
|
page.wait_for_function("document.getElementById('overview-layout-dialog').open")
|
|
page.locator('#overview-layout-default').click()
|
|
page.locator('#overview-layout-save').click()
|
|
page.wait_for_function("!document.getElementById('overview-layout-dialog').open")
|
|
assert_visible(page, ['backend_traffic'], ['backend_health', 'stream_sessions', 'www_status'])
|
|
assert page.evaluate('window.__savedDashboard.dashboard_widgets') == PRESET['dashboard_widgets']
|
|
assert page.evaluate('window.__savedDashboard.dashboard_hidden_widgets') == PRESET['dashboard_hidden_widgets']
|
|
assert page.evaluate('document.documentElement.scrollWidth - window.innerWidth') <= 2
|
|
assert not errors, (width, errors)
|
|
context.close()
|
|
print('PASS inline layout defaults, add/hide, live ticks, other preferences, cancel/save', width)
|
|
# An already-persisted sample choice must override the factory 600.
|
|
context = browser.new_context()
|
|
page = context.new_page()
|
|
# This sandbox blocks all network origins; stub browser storage before
|
|
# loading the bundle rather than navigating to a real host.
|
|
page.evaluate("""() => Object.defineProperty(window, 'localStorage', {
|
|
configurable: true,
|
|
value: {getItem: key => key === 'rustproxy.overview.liveChartSamples' ? '120' : null,
|
|
setItem: () => undefined},
|
|
})""")
|
|
load(page)
|
|
assert page.evaluate('window.rustProxyOverviewSamples.getLimit()') == 120
|
|
context.close()
|
|
browser.close()
|
|
print('PASS RustProxy 0.33.1 simulated browser layout regression')
|