Mobile pass: 8 fixes per MOBILE-FIX.md (single-row 55px header, 560px-scroll charts w/ bigger SVG fonts + swipe hint, content-height chips sheet w/ scrim, 44px touch targets, year table 13px/-2 cols/fade/sticky Age, vision manifest-race fix, honest share fallback, theme-color+type bumps); before/after audits + geo-check evidence
This commit is contained in:
1 parent
be57deb5a2
commit
666b6c5e29
37 files changed
+625
-21
No files matched your search
@@ -0,0 +1,116 @@
|
||||
"""Spec MOBILE-FIX.md step 6: programmatic geometry re-check at 390px (after fixes)."""
|
||||
import json
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
CHROME = '/home/ubuntu/.cache/ms-playwright/chromium-1243/chrome-linux64/chrome'
|
||||
BASE = 'https://retire.krisforbes.ca/'
|
||||
LS = 'retire-planner-v1'
|
||||
ANSWERS = {"province": "ON", "age": 45, "household": "single",
|
||||
"partner": {"age": 45, "income": 0, "rrsp": 0, "tfsa": 0, "nonreg": 0, "monthlyContribution": 0},
|
||||
"income": 85000, "rrsp": 250000, "tfsa": 60000, "nonreg": 40000, "monthlyContribution": 800,
|
||||
"retireAge": 65, "spendingAnnual": 60000, "cppStartAge": 65, "oasStartAge": 65,
|
||||
"pensionAnnual": 12000, "rentalAnnual": 0, "otherAnnual": 0,
|
||||
"realReturnPre": 3.5, "realReturnPost": 2.5, "inflation": 2.0, "lifeExp": 95,
|
||||
"interests": ["dock", "travel"]}
|
||||
|
||||
with sync_playwright() as pw:
|
||||
b = pw.chromium.launch(executable_path=CHROME, headless=True, args=['--no-sandbox','--disable-dev-shm-usage'])
|
||||
ctx = b.new_context(viewport={'width': 390, 'height': 844}, device_scale_factor=3.0,
|
||||
is_mobile=True, has_touch=True,
|
||||
user_agent='Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1')
|
||||
page = ctx.new_page()
|
||||
page.goto(BASE, wait_until='networkidle')
|
||||
|
||||
def set_state(screen, step):
|
||||
page.evaluate("""([ls,a,screen,step]) => localStorage.setItem(ls, JSON.stringify({a,step,screen}))""", [LS, ANSWERS, screen, step])
|
||||
|
||||
R = {}
|
||||
# --- quiz screen ---
|
||||
set_state('quiz', 3)
|
||||
page.reload(wait_until='networkidle'); page.wait_for_timeout(700)
|
||||
R['quiz'] = page.evaluate("""() => {
|
||||
const vis = e => { const b=e.getBoundingClientRect(); return b.width>0 && b.height>0; };
|
||||
const hdr = document.querySelector('.hdr').getBoundingClientRect();
|
||||
const touches = [...document.querySelectorAll('.ghost,.opt,.cta')].filter(vis)
|
||||
.map(e => ({id: e.id||e.className, h: Math.round(e.getBoundingClientRect().height)}));
|
||||
const minTouch = Math.min(...touches.map(t=>t.h));
|
||||
const panel = document.querySelector('.panel').getBoundingClientRect();
|
||||
const chips = [...document.querySelectorAll('#panel-chips .chip')];
|
||||
const chipVals = chips.map(c => {
|
||||
const v = c.querySelector('.v'), b = v.getBoundingClientRect();
|
||||
const r = document.createRange(); r.selectNodeContents(v);
|
||||
const rb = r.getBoundingClientRect();
|
||||
return {label: c.querySelector('.k').textContent, value: v.textContent,
|
||||
fullyInView: b.top>=0 && b.bottom<=window.innerHeight && rb.bottom<=window.innerHeight,
|
||||
h: Math.round(b.height)};
|
||||
});
|
||||
const sheetCollapsed = !document.querySelector('.panel').classList.contains('open');
|
||||
const insightShown = getComputedStyle(document.querySelector('.insight')).display!=='none';
|
||||
const next = document.querySelector('#btn-next').getBoundingClientRect();
|
||||
const theme = document.querySelector('meta[name=theme-color]');
|
||||
return {hdrH: Math.round(hdr.height), hdrRows: Math.round(hdr.height/40),
|
||||
touchTargets: touches, minTouch, panelTop: Math.round(panel.top), panelBottom: Math.round(panel.bottom),
|
||||
innerH: window.innerHeight, nChips: chips.length, chipVals, sheetCollapsed,
|
||||
insightShownByDefault: insightShown,
|
||||
nextClearsSheet: Math.round(next.bottom) <= Math.round(panel.top),
|
||||
themeColor: theme ? theme.content : null};
|
||||
}""")
|
||||
# sheet expand + scrim
|
||||
page.click('.panel-h'); page.wait_for_timeout(400)
|
||||
R['sheet_open'] = page.evaluate("""() => {
|
||||
const p = document.querySelector('.panel'), s = document.getElementById('scrim');
|
||||
return {open: p.classList.contains('open'),
|
||||
insightShown: getComputedStyle(document.querySelector('.insight')).display!=='none',
|
||||
scrimVisible: !!s && !s.hidden && getComputedStyle(s).display!=='none',
|
||||
scrimZ: +getComputedStyle(s).zIndex, panelZ: +getComputedStyle(p).zIndex,
|
||||
hdrZ: +getComputedStyle(document.querySelector('.hdr')).zIndex};
|
||||
}""")
|
||||
R['scrim_close'] = page.evaluate("""() => {
|
||||
document.getElementById('scrim').click();
|
||||
const p=document.querySelector('.panel'), s=document.getElementById('scrim');
|
||||
return {panelOpen: p.classList.contains('open'), scrimHidden: s.hidden};
|
||||
}""")
|
||||
# --- results screen ---
|
||||
set_state('results', 0)
|
||||
page.reload(wait_until='networkidle'); page.wait_for_timeout(1500)
|
||||
R['results'] = page.evaluate("""() => {
|
||||
const box = document.querySelector('#g1');
|
||||
const svg = box.querySelector('svg');
|
||||
const fs = svg ? +svg.querySelector('text').getAttribute('font-size') : null;
|
||||
const rendered = fs ? Math.round(fs * svg.getBoundingClientRect().width / 640 * 10)/10 : null;
|
||||
const tw = document.querySelector('.tbl-wrap');
|
||||
const th = document.querySelector('#yr-table th:nth-child(4)');
|
||||
const th9 = document.querySelector('#yr-table th:nth-child(9)');
|
||||
const cs = getComputedStyle(document.querySelector('table'));
|
||||
return {g1svgW: Math.round(svg.getBoundingClientRect().width),
|
||||
g1svgMinW: getComputedStyle(svg).minWidth,
|
||||
g1scrollable: box.scrollWidth > box.clientWidth,
|
||||
g1clientW: box.clientWidth, g1scrollW: box.scrollWidth,
|
||||
tickFontSizeAttr: fs, tickRenderedPx: rendered,
|
||||
legendPx: getComputedStyle(document.querySelector('.legend')).fontSize,
|
||||
tblFont: cs.fontSize,
|
||||
col4Hidden: getComputedStyle(th).display==='none',
|
||||
col9Hidden: getComputedStyle(th9).display==='none',
|
||||
tblScrollW: tw.scrollWidth, tblClientW: tw.clientWidth,
|
||||
fadePresent: getComputedStyle(tw,'::before').backgroundImage.includes('gradient'),
|
||||
visionIsImg: !!document.querySelector('#vision-card img'),
|
||||
visionGrad: document.querySelector('#vision-card').classList.contains('grad')};
|
||||
}""")
|
||||
# table scrolled right: Age column sticky?
|
||||
page.evaluate("() => { document.querySelector('details.yearly').open = true; }")
|
||||
page.wait_for_timeout(200)
|
||||
page.locator('#yr-table').scroll_into_view_if_needed(); page.wait_for_timeout(300)
|
||||
R['table_sticky'] = page.evaluate("""() => {
|
||||
const tw = document.querySelector('.tbl-wrap'); tw.scrollLeft = 300;
|
||||
const first = document.querySelector('#yr-table td:first-child').getBoundingClientRect();
|
||||
const box = tw.getBoundingClientRect();
|
||||
return {scrolled: tw.scrollLeft, ageColVisible: first.left >= box.left - 1 && first.right > box.left,
|
||||
ageColLeftInBox: Math.round(first.left - box.left)};
|
||||
}""")
|
||||
# chart swipe hint visible?
|
||||
R['swipe_hint'] = page.evaluate("""() => {
|
||||
const b=document.querySelector('#g1'); const c=getComputedStyle(b,'::after').content + '|' + getComputedStyle(b,'::before').content;
|
||||
return {hasSwipe: c.includes('swipe')};
|
||||
}""")
|
||||
print(json.dumps(R, indent=1))
|
||||
ctx.close(); b.close()
|
||||
Reference in new issue
Block a user