161 lines
7.2 KiB
Python
Executable File
161 lines
7.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""L99 DEEP VISUAL TEST — 3 pages with screenshots, video, UX overlap detection"""
|
|
import json, time, os, urllib.request, urllib.parse
|
|
|
|
SITE = "https://weval-consulting.com"
|
|
GROQ_KEY = os.popen("grep GROQ_KEY /etc/weval/secrets.env | cut -d= -f2").read().strip()
|
|
SNAP_DIR = "/opt/weval-l99/screenshots/deep-visual-12avr"
|
|
VID_DIR = "/opt/weval-l99/videos"
|
|
LOG = "/opt/weval-l99/logs/l99-deep-visual.json"
|
|
os.makedirs(SNAP_DIR, exist_ok=True)
|
|
os.makedirs(VID_DIR, exist_ok=True)
|
|
|
|
PAGES = [
|
|
("/agents-archi.html", "agents-archi", 15000),
|
|
("/enterprise-model.html", "enterprise-model", 10000),
|
|
("/wevia-meeting-rooms.html", "meeting-rooms", 8000),
|
|
]
|
|
|
|
results = []
|
|
def log_r(name, status, detail=""):
|
|
results.append({"name": name, "status": status, "detail": detail[:200], "ts": time.strftime("%H:%M:%S")})
|
|
icon = {"P": "\u2705", "F": "\u274c", "W": "\u26a0"}.get(status, "?")
|
|
print(f" {icon} {name}: {detail[:100]}", flush=True)
|
|
|
|
try:
|
|
from playwright.sync_api import sync_playwright
|
|
with sync_playwright() as pw:
|
|
br = pw.chromium.launch(headless=True, args=["--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"])
|
|
|
|
# Login
|
|
print("== LOGIN ==", flush=True)
|
|
cx = br.new_context(
|
|
viewport={"width": 1920, "height": 1080},
|
|
ignore_https_errors=True,
|
|
record_video_dir=VID_DIR,
|
|
record_video_size={"width": 1920, "height": 1080}
|
|
)
|
|
pg = cx.new_page()
|
|
pg.goto(f"{SITE}/login", timeout=15000)
|
|
pg.wait_for_timeout(1000)
|
|
pg.fill("input[name='user']", "yacine")
|
|
pg.fill("input[name='pass']", "Weval@2026")
|
|
pg.click("button[type='submit']")
|
|
pg.wait_for_timeout(3000)
|
|
log_r("login", "P" if "/login" not in pg.url else "F", pg.url[:60])
|
|
|
|
for path, name, wait_ms in PAGES:
|
|
print(f"\n== {name.upper()} ==", flush=True)
|
|
try:
|
|
pg.goto(f"{SITE}{path}", timeout=20000, wait_until="domcontentloaded")
|
|
pg.wait_for_timeout(wait_ms)
|
|
|
|
body = pg.evaluate("document.body.innerText.length") or 0
|
|
title = pg.title() or ""
|
|
log_r(f"{name}-load", "P" if body > 100 else "F", f"{body}ch title={title[:40]}")
|
|
|
|
# Full page screenshot
|
|
pg.screenshot(path=f"{SNAP_DIR}/{name}-full.png", full_page=True)
|
|
log_r(f"{name}-screenshot", "P", "full page captured")
|
|
|
|
# Viewport screenshot
|
|
pg.screenshot(path=f"{SNAP_DIR}/{name}-viewport.png")
|
|
log_r(f"{name}-viewport", "P", "viewport captured")
|
|
|
|
# UX overlap detection via JS
|
|
overlaps = pg.evaluate("""() => {
|
|
const els = document.querySelectorAll('div, span, a, button, h1, h2, h3, p, td');
|
|
const rects = [];
|
|
let overlaps = 0;
|
|
let details = [];
|
|
for (const el of els) {
|
|
const r = el.getBoundingClientRect();
|
|
if (r.width < 5 || r.height < 5 || r.top > 5000) continue;
|
|
const text = (el.innerText || '').trim().substring(0, 20);
|
|
if (!text) continue;
|
|
for (const prev of rects) {
|
|
if (r.left < prev.right && r.right > prev.left &&
|
|
r.top < prev.bottom && r.bottom > prev.top &&
|
|
Math.abs(r.left - prev.left) > 5) {
|
|
overlaps++;
|
|
if (details.length < 5) {
|
|
details.push(text + ' overlaps ' + prev.text);
|
|
}
|
|
}
|
|
}
|
|
rects.push({left: r.left, right: r.right, top: r.top, bottom: r.bottom, text: text});
|
|
}
|
|
return {count: overlaps, details: details};
|
|
}""")
|
|
overlap_count = overlaps.get("count", 0) if overlaps else 0
|
|
overlap_details = overlaps.get("details", []) if overlaps else []
|
|
if overlap_count > 10:
|
|
log_r(f"{name}-ux-overlap", "W", f"{overlap_count} overlaps: {'; '.join(overlap_details[:3])}")
|
|
else:
|
|
log_r(f"{name}-ux-overlap", "P", f"{overlap_count} overlaps (OK)")
|
|
|
|
# Check for JS errors
|
|
js_errors = pg.evaluate("""() => {
|
|
return window.__playwright_errors || [];
|
|
}""")
|
|
if js_errors and len(js_errors) > 0:
|
|
log_r(f"{name}-js-errors", "W", f"{len(js_errors)} JS errors")
|
|
else:
|
|
log_r(f"{name}-js-errors", "P", "0 JS errors")
|
|
|
|
# Check canvas/WebGL for agents-archi
|
|
if "archi" in name:
|
|
has_canvas = pg.evaluate("!!document.querySelector('canvas')")
|
|
log_r(f"{name}-3d-canvas", "P" if has_canvas else "F", f"Canvas: {has_canvas}")
|
|
|
|
# Check agent count
|
|
agent_count = pg.evaluate("""() => {
|
|
const cards = document.querySelectorAll('.ag-card, .agent-card, .room-card, [class*=agent], [class*=room]');
|
|
return cards.length;
|
|
}""")
|
|
log_r(f"{name}-agent-count", "P" if agent_count > 0 else "W", f"{agent_count} visible elements")
|
|
|
|
# Scroll test
|
|
pg.evaluate("window.scrollTo(0, document.body.scrollHeight)")
|
|
pg.wait_for_timeout(1000)
|
|
pg.screenshot(path=f"{SNAP_DIR}/{name}-bottom.png")
|
|
log_r(f"{name}-scroll", "P", "scrolled to bottom")
|
|
|
|
except Exception as e:
|
|
log_r(f"{name}-error", "F", str(e)[:100])
|
|
|
|
# Close + save video
|
|
pg.close()
|
|
cx.close()
|
|
br.close()
|
|
|
|
except Exception as e:
|
|
log_r("playwright", "F", str(e)[:100])
|
|
|
|
# Results
|
|
p = len([r for r in results if r["status"] == "P"])
|
|
f = len([r for r in results if r["status"] == "F"])
|
|
w = len([r for r in results if r["status"] == "W"])
|
|
t = len(results)
|
|
score = p * 100 // t if t else 0
|
|
|
|
print(f"\n{'='*60}", flush=True)
|
|
print(f"DEEP VISUAL: {p}/{t} ({score}%) | FAIL={f} WARN={w}", flush=True)
|
|
|
|
json.dump({"date": time.strftime("%Y-%m-%d %H:%M"), "pass": p, "fail": f, "warn": w, "total": t, "score": score, "results": results}, open(LOG, "w"), indent=2)
|
|
|
|
# Groq analysis of overlaps
|
|
if w > 0 and GROQ_KEY:
|
|
warns = "; ".join([f"{r['name']}: {r['detail']}" for r in results if r["status"] == "W"])
|
|
body_data = json.dumps({"model": "llama-3.3-70b-versatile", "messages": [{"role": "user", "content": f"UX visual test warns:\n{warns}\nDiagnose and suggest CSS fixes in 3 lines:"}], "max_tokens": 200}).encode()
|
|
req = urllib.request.Request("https://api.groq.com/openai/v1/chat/completions", body_data, {"Authorization": f"Bearer {GROQ_KEY}", "Content-Type": "application/json"})
|
|
try:
|
|
resp = json.loads(urllib.request.urlopen(req, timeout=15).read())
|
|
diag = resp["choices"][0]["message"]["content"]
|
|
print(f"\n\U0001f9e0 Groq UX: {diag}", flush=True)
|
|
except:
|
|
pass
|
|
|
|
print(f"\nScreenshots: {SNAP_DIR}/", flush=True)
|
|
print(f"Videos: {VID_DIR}/", flush=True)
|