from playwright.sync_api import sync_playwright BASE = "http://localhost:4321" ROUTES = ["/", "/diensten", "/diensten/gevelreiniging", "/diensten/voegwerk", "/diensten/dakreiniging-coating", "/diensten/schoorsteen-voegwerk", "/diensten/algemeen-reinigen", "/over-ons", "/contact"] def scroll_through(page): h = page.evaluate("document.body.scrollHeight") for y in range(0, h, 500): page.evaluate(f"window.scrollTo(0,{y})"); page.wait_for_timeout(40) page.evaluate("window.scrollTo(0,0)"); page.wait_for_timeout(250) with sync_playwright() as p: b = p.chromium.launch(headless=True) print("=== DESKTOP 1440: axe / images / headings ===") ctx = b.new_context(viewport={"width": 1440, "height": 900}) pg = ctx.new_page() for r in ROUTES: pg.goto(BASE + r, wait_until="networkidle", timeout=60000); pg.wait_for_timeout(400) scroll_through(pg) pg.add_script_tag(url="https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js") pg.wait_for_timeout(300) viol = pg.evaluate("""async()=>{const r=await axe.run(document,{runOnly:['wcag2a','wcag2aa','wcag21aa']});return r.violations.map(v=>v.impact+':'+v.id+'x'+v.nodes.length);}""") imgs = pg.evaluate("""()=>{const a=[...document.images];return {total:a.length,noalt:a.filter(i=>i.getAttribute('alt')===null).length,eager:a.filter(i=>i.loading!=='lazy').map(i=>i.currentSrc.split('/').pop())};}""") heads = pg.eval_on_selector_all("h1,h2,h3", "e=>e.map(x=>x.tagName)") print(f"{r:32} axe:{'clean' if not viol else viol} imgs:{imgs['total']} noalt:{imgs['noalt']} eager:{imgs['eager']} h1x{heads.count('H1')} hcount:{len(heads)}") ctx.close() print("\n=== OVERFLOW 1440/768/390 ===") for r in ROUTES: flags = [] for w in (1440, 768, 390): c = b.new_context(viewport={"width": w, "height": 900}); pp = c.new_page() pp.goto(BASE + r, wait_until="networkidle", timeout=60000) sw = pp.evaluate("document.documentElement.scrollWidth"); cw = pp.evaluate("document.documentElement.clientWidth") flags.append(f"{w}:{'OVERFLOW' if sw > cw + 1 else 'ok'}"); c.close() print(f"{r:32} {', '.join(flags)}") print("\n=== MOBIEL 390: tapdoelen <44px (zichtbare controls) ===") m = b.new_context(viewport={"width": 390, "height": 844}); mp = m.new_page() for r in ["/", "/contact", "/diensten/gevelreiniging", "/over-ons"]: mp.goto(BASE + r, wait_until="networkidle", timeout=60000); mp.wait_for_timeout(500) small = mp.evaluate("""()=>{const els=[...document.querySelectorAll('a,button,input,select,textarea')];const o=[]; for(const e of els){const r=e.getBoundingClientRect();if(r.width===0||r.height===0)continue;const s=getComputedStyle(e);if(s.visibility==='hidden'||s.display==='none')continue; if(r.height<44||r.width<44){const inline=(e.tagName==='A'&&s.display.includes('inline')&&!e.className);o.push((e.tagName.toLowerCase())+'.'+(String(e.className).split(' ')[0]||'?')+' '+Math.round(r.width)+'x'+Math.round(r.height)+(inline?' (inline)':''));}} return o;}""") print(f"{r:30} {len(small)} controls <44px: {small[:10]}") b.close()