#!/usr/bin/env python3 """Optional real-CSS/JS Chromium regression for the horizontal Overview card ribbon. Mocks authenticated preferences; no server or physical iOS device involved. Run with: python tools/checks/dashboard_appearance_ribbon_0326_browser.py """ from pathlib import Path from playwright.sync_api import sync_playwright import shutil import json root=Path(__file__).resolve().parents[2] html=(root/'src/admin/web/index.html').read_text() css=(root/'src/admin/web/style.css').read_text() prefs=(root/'src/admin/web/app/preferences.js').read_text() registry=json.loads((root/'src/admin/web/dashboard-widgets.json').read_text()) translations=json.loads((root/'langs/pl_pl.json').read_text())['translations'] heading=html.index('data-i18n="dashboardLayout"') section_start=html.rfind('
',0,heading) section_end=html.index('
',heading)+len('') assert section_start >= 0 source=html[section_start:section_end] start=prefs.index('function renderDashboardWidgetSettings() {') end=prefs.index('function refreshChartStylePalette(',start) code=prefs[start:end] stub=''' const $ = id => document.getElementById(id); const el = (tag, txt, classes) => { let node=document.createElement(tag); if (txt!==undefined) node.textContent=String(txt); if (classes) node.className=classes; return node; }; const labels = __TRANSLATIONS__; const registry = __REGISTRY__; const ADMIN_DASHBOARD_BY_ID = new Map(registry.map(def=>[def.id,def])); const order = registry.map(def=>def.id); let adminPreferences = {dashboard_widgets:order, dashboard_hidden_widgets:registry.filter(def=>def.default_hidden).map(def=>def.id)}; const ADMIN_APPEARANCE_DEFAULTS = adminPreferences; function t(key){ return labels[key] || key; } function dashboardWidgetLabel(id) { return t(ADMIN_DASHBOARD_BY_ID.get(id)?.label_key || id); } function normalizeDashboardWidgets(value){return Array.isArray(value)?value:order;} function normalizeHiddenDashboardWidgets(value){return Array.isArray(value)?value:[];} function applyDashboardLayout(order,hidden){ window.__state={order,hidden}; } async function saveAppearancePreference(next){ Object.assign(adminPreferences,next);return {}; } '''.replace('__TRANSLATIONS__',json.dumps(translations,ensure_ascii=False)).replace('__REGISTRY__',json.dumps(registry)) with sync_playwright() as p: browser=p.chromium.launch(executable_path=shutil.which('chromium') or shutil.which('chromium-browser'),args=['--no-sandbox'],headless=True) for width,height in [(1500,950),(393,850)]: page=browser.new_page(viewport={'width':width,'height':height},device_scale_factor=1) page.set_content('
'+source+'
',wait_until='domcontentloaded') page.add_script_tag(content=stub+code+'\nrenderDashboardWidgetSettings();') page.wait_for_timeout(90) metrics=page.evaluate('''() => { const list=document.getElementById('dashboard-widget-list'), section=document.querySelector('.appearance-section'); const parent=list.closest('.dashboard-widget-settings'); return { total: list.children.length, listHeight:list.getBoundingClientRect().height, listWidth:list.getBoundingClientRect().width, contentWidth:list.scrollWidth, sectionHeight:section.getBoundingClientRect().height, overflowX:getComputedStyle(list).overflowX, firstWidth:list.firstElementChild.getBoundingClientRect().width, density: document.querySelector('.dashboard-density-setting').getBoundingClientRect().width, widget:parent.getBoundingClientRect().width, viewportOverflow:document.documentElement.scrollWidth>window.innerWidth, nextDisabled:document.getElementById('dashboard-widget-scroll-next').disabled}; }''') print(width,'COMPACT',metrics) assert metrics['total']==len(registry) assert metrics['listHeight']<66,(width,metrics) assert metrics['contentWidth']>metrics['listWidth'],(width,metrics) assert metrics['sectionHeight'] < (320 if width>700 else 450), (width,metrics) assert not metrics['viewportOverflow'] page.locator('#dashboard-widget-scroll-next').click() page.wait_for_timeout(450) pos=page.evaluate('document.getElementById("dashboard-widget-list").scrollLeft') print(width,'scroll after next:',pos) assert pos>10 page.locator('.dashboard-widget-setting').nth(7).locator('button.dashboard-widget-toggle').click(force=True) page.wait_for_timeout(60) state=page.evaluate('window.__state') assert state and len(state['order'])==len(registry) retained=page.evaluate('document.getElementById("dashboard-widget-list").scrollLeft') assert retained>=pos-15,(pos,retained) page.locator('#dashboard-widget-expand').click() page.wait_for_timeout(60) expanded=page.evaluate('''() => { const list = document.getElementById("dashboard-widget-list"); return { height:list.getBoundingClientRect().height, mode:getComputedStyle(list).display, count:list.children.length, expanded:document.getElementById('dashboard-widget-expand').getAttribute('aria-expanded'), prevHidden:document.getElementById('dashboard-widget-scroll-prev').hidden } }''') print(width,'EXPANDED',expanded) assert expanded['mode']=='grid' assert expanded['expanded']=='true' and expanded['prevHidden'] assert expanded['height']>metrics['listHeight']*3 page.locator('#dashboard-widget-expand').click() assert page.locator('#dashboard-widget-expand').get_attribute('aria-expanded')=='false' outfile=str(root.parent / f'dashboard_appearance_0326_preview_{width}.png') page.locator('.appearance-section').screenshot(path=outfile) print('screenshot',outfile) page.close() browser.close()