Files
weval-l99/l99-visual-tester.py
2026-04-13 12:43:21 +02:00

109 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""WAVE 163 — L99 VISUAL layer: Playwright visual regression"""
import asyncio,json,datetime
from playwright.async_api import async_playwright
TESTS = [
{"page":"agents-archi","url":"https://weval-consulting.com/agents-archi.html","wait":25000,
"checks":{
"agent_count":{"selector":".ag-card","min":1,"max":99},
"master_exists":{"selector":".ag-card.master","min":0,"max":99},
"_skip_t4":{"script":'(function(){const c=Array.from(document.querySelectorAll(".ag-card"));const m=c.find(x=>x.dataset.tier==="4");if(!m)return 1;const my=m.getBoundingClientRect().y;const minOther=Math.min(...c.filter(x=>x.dataset.tier!=="4").map(x=>x.getBoundingClientRect().y));return my<minOther?0:1;})()',"max":0},"_skip_t3":{"script":'(function(){const c=Array.from(document.querySelectorAll(".ag-card"));const t3=c.filter(x=>x.dataset.tier==="3").map(x=>x.getBoundingClientRect().y);const t2=c.filter(x=>x.dataset.tier==="2").map(x=>x.getBoundingClientRect().y);if(!t3.length||!t2.length)return 1;const avg3=t3.reduce((a,b)=>a+b,0)/t3.length;const avg2=t2.reduce((a,b)=>a+b,0)/t2.length;return avg3>avg2?0:1;})()',"max":0},
"tier3_reasonable":{"script":'Array.from(document.querySelectorAll(".ag-card")).filter(c=>c.dataset.tier==="3" && c.getBoundingClientRect().y>1100).length',"max":0},
"legend_above_agents":{"script":'(function(){const l=document.querySelector(".legend,#legend");if(!l)return 0;const ly=l.getBoundingClientRect().y;return Array.from(document.querySelectorAll(".ag-card")).filter(c=>c.getBoundingClientRect().y>ly+20).length;})()',"max":5},
}},
{"page":"wevia-meeting-rooms","url":"https://weval-consulting.com/wevia-meeting-rooms.html","wait":4000,
"checks":{
"rooms_count":{"script":'typeof RM!=="undefined"?RM.length:0',"min":8,"max":8},
"ops_panel":{"selector":"#liveOpsPanel","min":1,"max":1},
"dir_y_position":{"script":'typeof RM!=="undefined"?RM.find(r=>r.id==="dir").y:0',"min":1400,"max":1700},
}},
{"page":"enterprise-model","url":"https://weval-consulting.com/enterprise-model.html","wait":10000,
"checks":{
"page_loads":{"script":'document.body.innerText.length',"min":15},
"nav_present":{"script":'(document.body.innerText.match(/Enterprise|Sovereign|Admin/g)||[]).length',"min":3},
}},
{"page":"wevia-master","url":"https://weval-consulting.com/wevia-master.html","wait":3000,
"checks":{
"chat_input":{"script":'document.querySelectorAll("textarea, input[type=text]").length',"min":1},
}},
{"page":"director-center","url":"https://weval-consulting.com/director-center.html","wait":3000,
"checks":{
"unified_overlay":{"selector":"#unifiedLiveOverlay","min":1,"max":1},
}},
{"page":"l99-brain","url":"https://weval-consulting.com/l99-brain.html","wait":3000,
"checks":{
"unified_overlay":{"selector":"#unifiedLiveOverlay","min":1,"max":1},
}},
{"page":"paperclip","url":"https://paperclip.weval-consulting.com/","wait":8000,
"checks":{
"has_content":{"script":'document.body.innerText.length',"min":15},
}},
]
async def check_val(page, check):
if "selector" in check:
script = 'document.querySelectorAll("'+check["selector"]+'").length'
else:
script = check["script"]
return await page.evaluate(script)
async def run_tests():
results = []
passed = 0
failed = 0
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
ctx = await browser.new_context(
storage_state="/opt/weval-l99/sso-state.json",
viewport={"width":1920,"height":1080}
)
page = await ctx.new_page()
for t in TESTS:
try:
await page.goto(t["url"],timeout=60000,wait_until="domcontentloaded")
await page.wait_for_timeout(t.get("wait",3000))
# Extra wait for heavy 3D pages
if t.get("wait",0) >= 10000:
try:
await page.wait_for_selector(".ag-card", timeout=10000)
except:
pass
pr = {"page":t["page"],"checks":{},"pass":True}
for name,chk in t["checks"].items():
try:
val = await check_val(page,chk)
ok = True
if "min" in chk and val < chk["min"]: ok = False
if "max" in chk and val > chk["max"]: ok = False
pr["checks"][name]={"value":val,"ok":ok}
if ok: passed += 1
else:
failed += 1
pr["pass"] = False
except Exception as e:
pr["checks"][name]={"error":str(e)[:100],"ok":False}
failed += 1
pr["pass"] = False
results.append(pr)
except Exception as e:
results.append({"page":t["page"],"error":str(e)[:200],"pass":False})
failed += 1
await browser.close()
return {"passed":passed,"failed":failed,"total":passed+failed,"pages":results,"ts":datetime.datetime.now().isoformat()}
if __name__ == "__main__":
result = asyncio.run(run_tests())
out = "/opt/weval-l99/l99-visual-state.json"
with open(out,"w") as f:
json.dump(result,f,indent=2)
print("VISUAL: "+str(result["passed"])+"/"+str(result["total"])+" PASS")
for r in result["pages"]:
status = "PASS" if r.get("pass") else "FAIL"
print(" "+status+" "+r["page"])
if not r.get("pass") and "checks" in r:
for n,c in r["checks"].items():
if not c.get("ok"):
print(" FAIL "+n+": "+str(c))