File indexing completed on 2026-09-28 09:37:30
0001
0002 """Check that a natively rendered page carries the same nav as a proxied one.
0003
0004 Usage: check_nav_parity.py [native_url] [proxied_url] [out_dir]
0005 Defaults: https://epic-devcloud.org/prod/mcp/ against /prod/about/.
0006 Signs in with SWF_REMOTE_CLAUDE_USER / SWF_REMOTE_CLAUDE_PASSWORD, loads both
0007 pages, and compares the nav geometry: the nav box, its computed background
0008 and padding, every top-level item's text and rectangle, and the first
0009 dropdown's opened item rectangles. Saves a screenshot of each nav strip, one
0010 with the dropdown open. Exit status 1 on any difference above half a pixel.
0011 Run with the playwright wrapper: /home/admin/tools/playwright/python.
0012 """
0013 import os
0014 import sys
0015
0016 from playwright.sync_api import sync_playwright
0017
0018 native = sys.argv[1] if len(sys.argv) > 1 else 'https://epic-devcloud.org/prod/mcp/'
0019 proxied = sys.argv[2] if len(sys.argv) > 2 else 'https://epic-devcloud.org/prod/about/'
0020 out_dir = sys.argv[3] if len(sys.argv) > 3 else '.'
0021 user, password = os.environ['SWF_REMOTE_CLAUDE_USER'], os.environ['SWF_REMOTE_CLAUDE_PASSWORD']
0022 TOL = 0.5
0023
0024 MEASURE = '''() => {
0025 const nav = document.querySelector('nav');
0026 const r = e => { const b = e.getBoundingClientRect(); return [b.x, b.y, b.width, b.height].map(v => Math.round(v * 10) / 10); };
0027 const cs = getComputedStyle(nav);
0028 const items = [];
0029 for (const el of nav.children) {
0030 if (!(el.offsetWidth || el.offsetHeight) && !el.classList.contains('nav-mode')) continue;
0031 const kids = el.classList.contains('nav-mode') ? [...el.children] : [el];
0032 for (const k of kids) {
0033 const label = (k.querySelector('.dropbtn') || k).innerText.trim().replace(/\\s+/g, ' ') || k.className;
0034 items.push([label, r(k)]);
0035 }
0036 }
0037 return {nav: r(nav), background: cs.backgroundColor, padding: cs.padding, gap: cs.gap, items};
0038 }'''
0039
0040 OPEN_FIRST = '''() => {
0041 const dd = document.querySelector('nav .nav-production .dropdown') || document.querySelector('nav .dropdown');
0042 const c = dd.querySelector('.dropdown-content'); c.style.display = 'block';
0043 const r = e => { const b = e.getBoundingClientRect(); return [b.x, b.y, b.width, b.height].map(v => Math.round(v * 10) / 10); };
0044 return [...c.querySelectorAll('a')].map(a => [a.innerText.trim(), r(a), getComputedStyle(a).padding]);
0045 }'''
0046
0047
0048 def diff(label, a, b):
0049 """Print and count differences between two measurements."""
0050 bad = 0
0051 if isinstance(a, list) and a and isinstance(a[0], (int, float)):
0052 d = [abs(x - y) for x, y in zip(a, b)]
0053 if len(a) != len(b) or max(d) > TOL:
0054 print(f' DIFF {label}: {a} vs {b}'); bad += 1
0055 elif isinstance(a, list):
0056 if len(a) != len(b):
0057 print(f' DIFF {label}: {len(a)} vs {len(b)} entries'); bad += 1
0058 for i, (x, y) in enumerate(zip(a, b)):
0059 bad += diff(f'{label}[{i}]', x, y)
0060 elif isinstance(a, dict):
0061 for k in a:
0062 bad += diff(f'{label}.{k}', a[k], b.get(k))
0063 elif a != b:
0064 print(f' DIFF {label}: {a!r} vs {b!r}'); bad += 1
0065 return bad
0066
0067
0068 with sync_playwright() as p:
0069 browser = p.chromium.launch()
0070 ctx = browser.new_context(viewport={'width': 1600, 'height': 900})
0071 page = ctx.new_page()
0072 page.goto('https://epic-devcloud.org/prod/accounts/login/', wait_until='networkidle')
0073 page.fill('input[name=username]', user)
0074 page.fill('input[name=password]', password)
0075 page.click('form:has(input[name=password]) button[type=submit]')
0076 page.wait_for_load_state('networkidle')
0077 signed_in = page.locator('nav .nav-auth').inner_text()
0078 if user not in signed_in:
0079 sys.exit(f'sign-in failed: nav auth block reads {signed_in!r}')
0080 results = {}
0081 for name, url in (('native', native), ('proxied', proxied)):
0082 page.goto(url, wait_until='networkidle')
0083 if page.url != url:
0084 sys.exit(f'{name}: {url} landed on {page.url}')
0085 if name == 'proxied' and 'Built at' not in page.inner_text('main'):
0086 sys.exit(f'{name}: {url} shows no monitor build line, so it is not a proxied page')
0087 m = page.evaluate(MEASURE)
0088 m['dropdown'] = page.evaluate(OPEN_FIRST)
0089 x, y, w, h = m['nav']
0090 page.screenshot(path=f'{out_dir}/nav-{name}.png', clip={'x': x, 'y': y, 'width': w, 'height': h + 260})
0091 results[name] = m
0092 print(f'{name}: {url}: nav {m["nav"]}, background {m["background"]}, padding {m["padding"]}, '
0093 f'{len(m["items"])} items, first dropdown {len(m["dropdown"])} entries')
0094 browser.close()
0095
0096 bad = diff('nav', results['native'], results['proxied'])
0097 print('nav parity: identical' if not bad else f'nav parity: {bad} difference(s)')
0098 sys.exit(1 if bad else 0)