import os #!/usr/bin/env python3 """L99 UX AGENT v3.0 — FULL INFRASTRUCTURE E2E Tests: 3 servers, 24 docker, 8 domains, all APIs, blade, DBs, AI providers, BPMN processes, SOA services, crons, NonReg, architecture pipeline. Cron: 0 */6 on S204 """ import json,time,sys,os,subprocess as sp SP=sp R={"pass":0,"fail":0,"warn":0,"tests":[],"timestamp":"","version":"3.0"} def ok(n,d=""):R["pass"]+=1;R["tests"].append({"name":n,"status":"P","detail":d});print(f" P {n}"+f" ({d})" if d else f" P {n}") def fail(n,d=""):R["fail"]+=1;R["tests"].append({"name":n,"status":"F","detail":d});print(f" F {n} — {d}" if d else f" F {n}") def warn(n,d=""):R["warn"]+=1;R["tests"].append({"name":n,"status":"W","detail":d});print(f" W {n} — {d}" if d else f" W {n}") def sh(c,t=8): try:r=sp.run(c,shell=True,capture_output=True,text=True,timeout=t);return r.stdout.strip() except:return"" def curl_code(u,t=8):return sh(f'curl -sk -o /dev/null -w "%{{http_code}}" "{u}" --max-time {t}') def curl_json(u,t=10): try:r=sh(f'curl -sk "{u}" --max-time {t}');return json.loads(r) except:return None def sentinel(c): try:r=sh(f"curl -sk 'http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd={c}' --max-time 8");return r except:return"" SITE="https://weval-consulting.com" print("=== L99 UX AGENT v3.0 — FULL INFRA E2E ===\n") R["timestamp"]=time.strftime("%Y-%m-%d %H:%M:%S") # ═══════════════════════════════════════════ # S1: SERVERS # ═══════════════════════════════════════════ print("--- S1: Servers ---") # S204 disk=sh("df / --output=pcent | tail -1").strip().replace('%','') try: d=int(disk) if d<90:ok("S204_DISK",f"{d}%") elif d<95:warn("S204_DISK",f"{d}%") else:fail("S204_DISK",f"{d}% CRITICAL") except:fail("S204_DISK","Cannot read") nginx=sh("systemctl is-active nginx") if nginx=="active":ok("S204_NGINX") else:fail("S204_NGINX",nginx) php=sh("systemctl is-active php8.5-fpm") if php=="active":ok("S204_PHP") else:fail("S204_PHP",php) # S95 s95=sentinel("hostname") if s95 and 'snapshot' in s95:ok("S95_ALIVE",s95[:30]) else:fail("S95_ALIVE",s95[:30] if s95 else "No response") # S151 s151_code=curl_code("https://culturellemejean.charity/",5) if s151_code in ["200","301","302"]:ok("S151_ALIVE",s151_code) else:warn("S151_ALIVE",f"HTTP {s151_code}") # ═══════════════════════════════════════════ # S2: DOCKER (all 24) # ═══════════════════════════════════════════ print("\n--- S2: Docker Containers ---") docker_raw=sh("docker ps --format '{{.Names}}:{{.Status}}' --no-trunc",10) containers={} for line in docker_raw.split('\n'): if ':' not in line:continue name,status=line.split(':',1) containers[name.strip()]=status.strip() expected=['authentik-server','authentik-worker','authentik-db','authentik-redis', 'mattermost','n8n','uptime-kuma','qdrant','searxng','plausible','twenty', 'vaultwarden','prometheus','node-exporter','loki'] for c in expected: if c in containers: st=containers[c] if 'Up' in st:ok(f"DOCKER_{c}","healthy" if "healthy" in st else "up") else:fail(f"DOCKER_{c}",st[:30]) else:fail(f"DOCKER_{c}","NOT RUNNING") total_up=sum(1 for s in containers.values() if 'Up' in s) ok("DOCKER_TOTAL",f"{total_up}/{len(containers)} up") if total_up==len(containers) else warn("DOCKER_TOTAL",f"{total_up}/{len(containers)}") # ═══════════════════════════════════════════ # S3: BLADE GPU # ═══════════════════════════════════════════ print("\n--- S3: Blade GPU ---") blade=curl_json(f"{SITE}/api/blade-tasks/heartbeat.json") if blade: ts=blade.get('ts','') hostname=blade.get('hostname','') ok("BLADE_HEARTBEAT",f"{hostname} @ {ts[:19]}") # Check freshness try: from datetime import datetime bt=datetime.fromisoformat(ts.replace('Z','+00:00')) age=(datetime.now(bt.tzinfo)-bt).total_seconds()/60 if age<15:ok("BLADE_FRESH",f"{age:.0f}min ago") elif age<60:warn("BLADE_FRESH",f"{age:.0f}min ago") else:warn("BLADE_STALE",f"{age:.0f}min ago") except:warn("BLADE_TIME","Cannot parse timestamp") else:warn("BLADE_HEARTBEAT","No heartbeat file") # ═══════════════════════════════════════════ # S4: DATABASES # ═══════════════════════════════════════════ print("\n--- S4: Databases ---") import psycopg2 try: c=psycopg2.connect("host=127.0.0.1 dbname=postgres user=admin password=admin123") cur=c.cursor() cur.execute("SELECT datname FROM pg_database WHERE datistemplate=false") dbs=[r[0] for r in cur.fetchall()] ok("PG_CONNECT",f"{len(dbs)} databases") c.close() expected_dbs=['adx_system','wevia_db','twenty_db','mattermost_db','paperclip','deerflow'] for db in expected_dbs: if db in dbs:ok(f"DB_{db}") else:fail(f"DB_{db}","Missing") # Key table counts c2=psycopg2.connect("host=127.0.0.1 dbname=adx_system user=admin password=admin123") cur2=c2.cursor() cur2.execute("SELECT count(*) FROM kb_learnings") kb=cur2.fetchone()[0] if kb>100:ok("KB_ENTRIES",f"{kb}") else:warn("KB_ENTRIES",f"Only {kb}") cur2.execute("SELECT count(*) FROM ethica.medecins_validated") ethica=cur2.fetchone()[0] if ethica>10000:ok("ETHICA_HCP",f"{ethica:,}") else:warn("ETHICA_HCP",f"{ethica}") c2.close() except Exception as e: fail("PG_CONNECT",str(e)[:60]) # ═══════════════════════════════════════════ # S5: AI STACK # ═══════════════════════════════════════════ print("\n--- S5: AI Stack ---") # Ollama ollama=curl_json("http://127.0.0.1:11435/api/tags") if ollama and 'models' in ollama: models=ollama['models'] ok("OLLAMA_UP",f"{len(models)} models") for m in models: ok(f"OLLAMA_{m['name'].split(':')[0]}",m['details'].get('parameter_size','?')) else:fail("OLLAMA_UP","Cannot reach :11435") # Qdrant qd=curl_json("http://127.0.0.1:6333/collections") if qd and 'result' in qd: colls=qd['result'].get('collections',[]) ok("QDRANT_UP",f"{len(colls)} collections") total_vec=0 for c in colls: info=curl_json(f"http://127.0.0.1:6333/collections/{c['name']}") vecs=info.get('result',{}).get('points_count',0) if info else 0 total_vec+=vecs ok(f"QDRANT_{c['name']}",f"{vecs:,} vectors") ok("QDRANT_TOTAL",f"{total_vec:,} vectors") else:fail("QDRANT_UP","Cannot reach :6333") # WEVIA Master wm=curl_json(f"{SITE}/api/wevia-master-api.php?health") if wm and wm.get('ollama')=='UP': ok("WEVIA_MASTER",f"T1={wm.get('tier1_providers',0)} providers") else:fail("WEVIA_MASTER","DOWN") # AI request try: r=sp.run(['curl','-sk','-X','POST',f'{SITE}/api/weval-ia','-H','Content-Type: application/json', '-d','{"message":"test","widget":true}','--max-time','20'],capture_output=True,text=True,timeout=25) d=json.loads(r.stdout) if (d.get('provider') or d.get('result')) and len(d.get('result','') or d.get('response',''))>5: ok("AI_PIPELINE",f"{d.get('provider','chatbot')}: {len(d.get('result','') or d.get('response',''))} chars") else:fail("AI_PIPELINE","No provider or empty response") except Exception as e:fail("AI_PIPELINE",str(e)[:50]) # MiroFish Swarm Intelligence mf=curl_json("http://127.0.0.1:5001/health") ##if mf and mf.get("status")=="ok":ok("MIROFISH_API","UP") # REMOVED: MiroFish container deleted ##else:fail("MIROFISH_API","DOWN") # REMOVED: MiroFish container deleted mfr=curl_json("http://127.0.0.1:5001/api/report/list") ##if mfr and mfr.get("success"):ok("MIROFISH_REPORTS",str(mfr.get("count",0))+" reports") # REMOVED: MiroFish container deleted ##else:warn("MIROFISH_REPORTS","Cannot list") # REMOVED: MiroFish container deleted # # REMOVED (container deleted for disk): mfb=curl_json("https://weval-consulting.com/api/mirofish-bri ##if mfb and mfb.get("status")=="active":ok("MIROFISH_BRIDGE",mfb.get("service","?")) # REMOVED: MiroFish container deleted ##else:fail("MIROFISH_BRIDGE","DOWN") # REMOVED: MiroFish container deleted # # REMOVED (container deleted for disk): mfc=curl_json("https://weval-consulting.com/api/mirofish-bri ##if mfc and "mirofish" in mfc:ok("MIROFISH_CEO","score="+str(mfc.get("infrastructure",{}).get("score",0))) # REMOVED: MiroFish container deleted ##else:fail("MIROFISH_CEO","No CEO data") # REMOVED: MiroFish container deleted # ═══════════════════════════════════════════ # S95 MTA ports via sentinel s95_ss = sh("curl -sf 'http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=ss%20-tln' --max-time 5") for svc, port in [("pmta",25),("kumomta",587),("postfix",2525),("sentinel",5890),("adx",5821)]: if f":{port} " in s95_ss: ok(f"MTA_{svc}",str(port)) else: fail(f"MTA_{svc}",f":{port} not on S95") # SSL CERT EXPIRY import subprocess as _sp r=_sp.run(['openssl','x509','-in','/var/www/weval/ssl/fullchain.pem','-noout','-enddate'],capture_output=True,text=True,timeout=5) if r.stdout: import datetime exp_str=r.stdout.strip().split('=')[1] exp=datetime.datetime.strptime(exp_str,'%b %d %H:%M:%S %Y %Z') days=(exp-datetime.datetime.utcnow()).days if days>14: ok("SSL_EXPIRY",f"{days} days") elif days>0: warn("SSL_EXPIRY",f"{days} days!") else: fail("SSL_EXPIRY",f"EXPIRED {days}d ago!") # REGRESSION CHECK: Critical APIs must return 200 import subprocess as _sp2 for api in ['optimisation-engine.php','ads-api.php','wevia-manifest.php','wevia-action-engine.php?action=help','wevia-dashboard.php']: r=_sp2.run(['curl','-sk','-o','/dev/null','-w','%{http_code}',f'https://weval-consulting.com/api/{api}','--max-time','5'],capture_output=True,text=True,timeout=8) if r.stdout=='200': ok(f"REGRESSION_API:{api.split('?')[0][:20]}") else: fail(f"REGRESSION_API:{api.split('?')[0][:20]}",f"HTTP {r.stdout}") # REGRESSION CHECK: Critical pages for page,name in [('/',':home'),('/enterprise-model.html',':enterprise'),('/wevia.html',':wevia')]: r=_sp2.run(['curl','-sk','-o','/dev/null','-w','%{http_code}',f'https://weval-consulting.com{page}','--max-time','5'],capture_output=True,text=True,timeout=8) if r.stdout in ['200','302']: ok(f"REGRESSION_PAGE{name}") else: fail(f"REGRESSION_PAGE{name}",f"HTTP {r.stdout}") # S5b: ALL-SERVER PORT MONITORING print(chr(10)+"--- S5b: All Server Ports ---") import socket def ckp(h,p,t=3): try: s=socket.socket();s.settimeout(t);s.connect((h,p));s.close();return True except: return False for svc,port in [("nginx",80),("php-fpm",9000),("postgresql",5432),("deerflow",2024),("ollama",11435),("qdrant",6333),("authentik",9090),("n8n",5678),("mattermost",8065),("searxng",8888),("loki",3100)]: if ckp("127.0.0.1",port): ok(f"S204_{svc}",str(port)) else: fail(f"S204_{svc}",f":{port} DOWN") s95_ss=sh("curl -sf 'http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=ss%20-tln' --max-time 5") for svc,port in [("pmta",25),("kumomta",587),("kumomta-api",8010),("postfix",2525),("postgresql",5432),("sentinel",5890),("adx",5821),("arsenal",5822)]: if f":{port} " in s95_ss: ok(f"S95_{svc}",str(port)) else: fail(f"S95_{svc}",f":{port} DOWN") for svc,url in [("nginx","https://culturellemejean.charity/")]: code=curl_code(url,5) if code in ["200","301","302"]: ok(f"S151_{svc}",code) else: fail(f"S151_{svc}",f"HTTP {code}") bl=curl_json("https://weval-consulting.com/api/blade-tasks/heartbeat.json") if bl and bl.get("ts"): ok("BLADE_LIVE",bl.get("hostname","?")) else: warn("BLADE_HB","no data") # S6: AUTH DOMAINS (all 8) # ═══════════════════════════════════════════ print("\n--- S6: Auth Domains ---") domains=[ ("weval-consulting.com","/products/workspace.html"), ("wevads.weval-consulting.com","/auth/login.html"), ("analytics.weval-consulting.com","/"), ("crm.weval-consulting.com","/"), ("deerflow.weval-consulting.com","/"), ("mm.weval-consulting.com","/"), ("monitor.weval-consulting.com","/"), ("n8n.weval-consulting.com","/"), ] for domain,path in domains: code=curl_code(f"https://{domain}{path}",5) if code=="302": # Check redirect goes to outpost redir=sh(f'curl -sk -o /dev/null -w "%{{redirect_url}}" "https://{domain}{path}" --max-time 5 --max-redirs 0') if 'outpost' in redir:ok(f"AUTH_{domain.split('.')[0]}","302→outpost") else:fail(f"AUTH_{domain.split('.')[0]}",f"302 but not outpost: {redir[:40]}") elif code=="200":warn(f"AUTH_{domain.split('.')[0]}","200 (public or session)") else:fail(f"AUTH_{domain.split('.')[0]}",f"HTTP {code}") # ═══════════════════════════════════════════ # S7: CRONS HEALTH # ═══════════════════════════════════════════ print("\n--- S7: Crons ---") cron_count=sh("crontab -l 2>/dev/null | grep -cv '^#\\|^$'") try: cc=int(cron_count) if cc>=20:ok("CRONS_COUNT",f"{cc} active") else:warn("CRONS_COUNT",f"Only {cc}") except:fail("CRONS_COUNT","Cannot read") # Check key cron outputs exist and are fresh from datetime import datetime cron_checks=[ ("L99_MASTER","/var/www/html/api/l99-ux-results.json","timestamp"), ("NONREG","/var/www/html/api/nonreg-latest.json","ts"), ("ARCH_INDEX","/var/www/html/api/architecture-index.json","generated"), ("ARCH_TOPO","/var/www/html/api/architecture-topology.json","generated"), ] for name,path,ts_key in cron_checks: try: d=json.load(open(path)) ts=d.get(ts_key,'') if ts: ok(f"CRON_{name}",f"ts={ts[:16]}") else: warn(f"CRON_{name}","No timestamp") except:fail(f"CRON_{name}","File missing or invalid") # ═══════════════════════════════════════════ # S8: NONREG STATUS # ═══════════════════════════════════════════ print("\n--- S8: NonReg ---") nr=curl_json(f"{SITE}/api/nonreg-latest.json") if nr: total=nr.get('total',0);passed=nr.get('pass',0) if total>100 and passed==total:ok("NONREG_ALL",f"{passed}/{total}") elif total>100:warn("NONREG_PARTIAL",f"{passed}/{total}") else:fail("NONREG_COUNT",f"Only {total} tests") else:fail("NONREG_FILE","Cannot load") # ═══════════════════════════════════════════ # S9: ARCHITECTURE PIPELINE # ═══════════════════════════════════════════ print("\n--- S9: Architecture Pipeline ---") idx=curl_json(f"{SITE}/api/architecture-index.json") topo=curl_json(f"{SITE}/api/architecture-topology.json") if idx: # Score score=idx.get('recommendations',{}).get('score',0) if score>=80:ok("ARCH_SCORE",f"{score}/100") elif score>=50:warn("ARCH_SCORE",f"{score}/100") else:fail("ARCH_SCORE",f"{score}/100") # Sections count keys=len(idx) if keys>=20:ok("ARCH_SECTIONS",f"{keys}") else:warn("ARCH_SECTIONS",f"Only {keys}") # Recommendations reco_count=idx.get('recommendations',{}).get('total',0) ok("ARCH_RECO",f"{reco_count} items") # UX agent wired ux=idx.get('ux_agent',{}) if ux.get('pass',0)>0:ok("ARCH_UX_WIRED",f"{ux['pass']}/{ux.get('total',0)}") else:warn("ARCH_UX_WIRED","Not in index") else:fail("ARCH_INDEX","Cannot load") if topo: nodes=topo.get('stats',{}).get('nodes',0) edges=topo.get('stats',{}).get('edges',0) if nodes>=40:ok("TOPO_NODES",f"{nodes}") else:fail("TOPO_NODES",f"Only {nodes}") if edges>=20:ok("TOPO_EDGES",f"{edges}") else:fail("TOPO_EDGES",f"Only {edges}") else:fail("TOPO_FILE","Cannot load") # ═══════════════════════════════════════════ # S10: BPMN PROCESS VERIFICATION # ═══════════════════════════════════════════ print("\n--- S10: BPMN Processes ---") if topo and topo.get('bpmn_processes'): procs=topo['bpmn_processes'] ok("BPMN_COUNT",f"{len(procs)}") total_auto=0;total_steps=0 for p in procs: steps=p.get('steps',[]) auto=sum(1 for s in steps if s.get('status')=='automated') total_auto+=auto;total_steps+=len(steps) status=p.get('status','?') if len(steps)>=3:ok(f"BPMN_{p['id']}",f"{p['name']}: {auto}/{len(steps)} auto ({status})") else:fail(f"BPMN_{p['id']}",f"Only {len(steps)} steps") pct=round(total_auto/total_steps*100) if total_steps else 0 if pct>=60:ok("BPMN_AUTOMATION",f"{pct}% ({total_auto}/{total_steps})") else:warn("BPMN_AUTOMATION",f"{pct}%") else:fail("BPMN_DATA","Not in topology") # ═══════════════════════════════════════════ # S11: SOA SERVICES # ═══════════════════════════════════════════ print("\n--- S11: SOA Services ---") if topo and topo.get('soa_services'): svcs=topo['soa_services'] groups={} for s in svcs: g=s.get('group','other') if g not in groups:groups[g]=[] groups[g].append(s) ok("SOA_TOTAL",f"{len(svcs)} services") for g,items in sorted(groups.items()): active=sum(1 for s in items if s.get('status') in ['active','up','healthy']) if active==len(items):ok(f"SOA_{g}",f"{active}/{len(items)}") else:warn(f"SOA_{g}",f"{active}/{len(items)} active") else:fail("SOA_DATA","Not in topology") # ═══════════════════════════════════════════ # S12: BROWSER E2E (SSO + page + data) # ═══════════════════════════════════════════ print("\n--- S12: Browser E2E ---") 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'],executable_path='/usr/bin/google-chrome-stable') ctx=br.new_context(ignore_https_errors=True,viewport={"width":1440,"height":900}) pg=ctx.new_page() js_err=[] pg.on("console",lambda m:js_err.append(m.text) if m.type=="error" else None) pg.goto(f"{SITE}/architecture.html",wait_until='networkidle',timeout=30000) time.sleep(2) uid=pg.query_selector('input[name="uidField"]') if uid: uid.fill('yacine');time.sleep(0.3) pg.query_selector('button[type="submit"]').click();time.sleep(3) ppw=pg.query_selector('input[type="password"]') if ppw:ppw.fill('YacineWeval2026');time.sleep(0.3);pg.query_selector('button[type="submit"]').click();time.sleep(6) ok("E2E_LOGIN") pg.goto(f"{SITE}/architecture.html",wait_until='networkidle',timeout=30000) else: ok("E2E_SESSION","Already authenticated") time.sleep(5) # Ensure page fully loaded pg.wait_for_load_state('networkidle') time.sleep(3) # Data loaded try: check=pg.evaluate("()=>{if(typeof D==='undefined')return null;return{docker:D.docker?.length,score:D.recommendations?.score,nodes:typeof T!='undefined'&&T?T.stats?.nodes:0}}") except: check=None if check: ok("E2E_DATA",f"Docker={check.get('docker',0)} Score={check.get('score',0)} Nodes={check.get('nodes',0)}") else:warn("E2E_DATA","D not loaded (auth required)") # All 12 tabs render tabs_ok=0 for tid in ['overview','reco','cortex','pipes','apps','infra','ai','data','bpmn','soa','topo','log']: btn=pg.query_selector(f'button[data-id="{tid}"]') if btn: btn.click();time.sleep(0.4) pnl=pg.query_selector(f'#p-{tid}') if pnl and pg.evaluate("el=>el.offsetHeight>30&&el.innerHTML.length>50",pnl):tabs_ok+=1 if tabs_ok==12:ok("E2E_TABS",f"{tabs_ok}/12") else:warn("E2E_TABS",f"{tabs_ok}/12") # Gauges centered pg.query_selector('button[data-id="overview"]').click();time.sleep(1) rings=pg.query_selector_all('.ring') for i,ring in enumerate(rings[:2]): c=pg.evaluate("""el=>{const r=el.getBoundingClientRect();const v=el.querySelector('.rv');if(!v)return null;const vr=v.getBoundingClientRect();return{x:Math.round(Math.abs((vr.left+vr.width/2)-(r.left+r.width/2))),y:Math.round(Math.abs((vr.top+vr.height/2)-(r.top+r.height/2)))}}""",ring) label=['Health','Automation'][i] if c and c['x']<=2 and c['y']<=2:ok(f"E2E_CENTER_{label}",f"X={c['x']}px Y={c['y']}px") elif c:fail(f"E2E_CENTER_{label}",f"X={c['x']}px Y={c['y']}px") # Drill-down cards=pg.query_selector_all('.cd.click') if cards: cards[0].click();time.sleep(0.8) if pg.query_selector('.modal-bg.open'): ok("E2E_DRILL");pg.query_selector('.modal-x').click() else:warn("E2E_DRILL") # Search→navigate gs=pg.query_selector('#gs') if gs: gs.fill('qdrant');time.sleep(1) sr=pg.query_selector_all('.sr-item') if sr: pg.evaluate("()=>document.querySelector('.sr-item')?.click()");time.sleep(0.5) tab=pg.evaluate("()=>document.querySelector('.tab.on')?.dataset?.id||'none'") ok("E2E_SEARCH",f"→{tab}") else:warn("E2E_SEARCH","No results") gs.fill('') # Trigger scan pg.query_selector('button[data-id="overview"]').click();time.sleep(0.5) trig=pg.query_selector('#wmb') if trig and pg.evaluate("el=>el.offsetHeight>0",trig): try: old_ts=pg.evaluate('()=>window.D?window.D.generated:null') except: old_ts='N/A' trig.scroll_into_view_if_needed();trig.click();time.sleep(5) try: new_ts=pg.evaluate('()=>window.D?window.D.generated:null') except: new_ts='N/A' if new_ts!=old_ts:ok("E2E_TRIGGER",f"{old_ts[:16]}→{new_ts[:16]}") else:warn("E2E_TRIGGER","Same timestamp") # JS errors real=[e for e in js_err if "404" not in e and "Failed to load" not in e] if not real:ok("E2E_JS_CLEAN",f"0 errors ({len(js_err)} network filtered)") else:warn("E2E_JS_ERRORS",f"{len(real)} errors") # Tokens tk=pg.evaluate("()=>{const s=getComputedStyle(document.documentElement);return{bg:s.getPropertyValue('--bg').trim(),card:s.getPropertyValue('--card').trim()}}") if tk.get('bg')=='#09090b':ok("E2E_TOKENS",f"bg={tk['bg']}") else:fail("E2E_TOKENS",f"bg={tk.get('bg')}") # ═══ S13: WEVIA MASTER PAGE ═══ print("\n--- S13: WEVIA Master Page ---") try: pg.goto(f"{SITE}/wevia-master.html",wait_until='networkidle',timeout=30000) time.sleep(4) if 'WEVIA' in pg.title(): ok("WM_PAGE",pg.title()[:30]) else: fail("WM_PAGE",pg.title()[:30]) wc=pg.evaluate("()=>{const c=document.querySelectorAll('.wcard');const n=Array.from(c).filter(x=>getComputedStyle(x).textAlign==='center').length;return{t:c.length,c:n}}") if wc and wc.get('t',0)>0 and wc.get('c',0)==wc.get('t',0): ok("WM_CARDS_CENTER",str(wc['c'])+"/"+str(wc['t'])) elif wc and wc.get('t',0)>0: fail("WM_CARDS_CENTER",str(wc.get('c',0))+"/"+str(wc.get('t',0))) sb=pg.evaluate("()=>{const b=document.getElementById('scrollBtn');return b?b.getAttribute('onclick'):'NONE'}") if sb and 'scrollChat' in sb: ok("WM_SCROLL_BTN",sb) else: fail("WM_SCROLL_BTN",str(sb)) ci=pg.query_selector("textarea") if ci:ok("WM_CHAT_INPUT") else:fail("WM_CHAT_INPUT") sl=pg.evaluate("()=>document.querySelectorAll('[class*=sidebar] [onclick]').length") if sl>=5:ok("WM_SIDEBAR",str(sl)+" items") else:warn("WM_SIDEBAR",str(sl)) except Exception as e: fail("WM_E2E",str(e)[:60]) # ═══ S14: PUBLIC PAGES ═══ print("\n--- S14: Public Pages ---") br.close() except Exception as e: warn("E2E_BROWSER",str(e)[:80]) # S14 continued (no browser needed) pub_pages=[("/","Home"),("/wevia.html","WEVIA"),("/wevia-widget.html","Widget"),("/enterprise-model.html","Enterprise")] for path,name in pub_pages: code=curl_code(f"{SITE}{path}",5) if code=="200":ok(f"PUB_{name}") else:fail(f"PUB_{name}",f"HTTP {code}") # ═══ SAVE ═══ total=R["pass"]+R["fail"]+R["warn"] pct=round(R["pass"]/total*100) if total else 0 print(f"\n{'='*60}") print(f"L99 UX AGENT v3.0: {R['pass']}/{total} pass ({pct}%) | {R['fail']} fail | {R['warn']} warn") print(f"{'='*60}") with open("/var/www/html/api/l99-ux-results.json","w") as f: # ═══ SESSION 6-AVR TESTS ═══ # Anti-regression: homepage no redirect # Old homepage test removed (replaced by SESSION 6-AVR version) # ═══ SESSION 6-AVR AGENT TESTS ═══ # Homepage no redirect try: js = open('/var/www/html/weval-faq-fix.js').read() has_redir = 'ak_redirect' in js and 'REMOVED' not in js r = sp.run(['curl','-sk','-o','/dev/null','-w','%{http_code}','https://weval-consulting.com/','--max-time','8'],capture_output=True,text=True,timeout=12) code = r.stdout.strip() if code == '200' and not has_redir: ok('HOMEPAGE_NO_REDIRECT','HTTP 200') elif has_redir: fail('HOMEPAGE_NO_REDIRECT','ak_redirect present!') else: warn('HOMEPAGE_NO_REDIRECT','HTTP '+code) except Exception as e: warn('HOMEPAGE_NO_REDIRECT',str(e)[:30]) # Chatbot POST health (file-based) try: r = sp.run(['php','-l','/var/www/html/api/weval-ia-fast.php'],capture_output=True,text=True,timeout=5) php_ok = 'No syntax errors' in r.stdout fast = open('/var/www/html/api/weval-ia-fast.php').read() dup = fast.count('function wevia_mirofish_insights') > 0 wire_path = '/var/www/weval/wevia-ia/cognitive-wire.php' wire_dup = open(wire_path).read().count('function wevia_mirofish_insights') > 0 if os.path.exists(wire_path) else False both_dup = dup and wire_dup if php_ok and not both_dup: ok('CHATBOT_POST_HEALTH','PHP OK no dup') elif both_dup: fail('CHATBOT_POST_HEALTH','DUPLICATE function!') else: fail('CHATBOT_POST_HEALTH','PHP error') except Exception as e: warn('CHATBOT_POST_HEALTH',str(e)[:30]) # Quality Agent try: d = json.loads(open('/var/www/html/api/wevia-quality-status.json').read()) rate = d.get('global_rate',0) if rate >= 95: ok('QUALITY_AGENT',str(rate)+'%') elif rate >= 80: warn('QUALITY_AGENT',str(rate)+'%') else: fail('QUALITY_AGENT',str(rate)+'%') except: warn('QUALITY_AGENT','No status') # Anti-Regression try: d = json.loads(open('/var/www/html/api/wevia-antiregression-status.json').read()) if d.get('healthy'): ok('ANTIREG_AGENT','HEALTHY') else: fail('ANTIREG_AGENT',str(d.get('issues_count',0))+' issues') except: warn('ANTIREG_AGENT','No status') # Auth Agent try: d = json.loads(open('/var/www/html/api/wevia-auth-status.json').read()) if d.get('healthy'): ok('AUTH_AGENT',str(d.get('flow_ok',0))+'/9') else: fail('AUTH_AGENT','unhealthy') except: warn('AUTH_AGENT','No status') # Visual Analysis try: d = json.loads(open('/var/www/html/api/l99-analysis.json').read()) total_a = d.get('stats',{}).get('total',0) ok_a = d.get('stats',{}).get('success',0) if total_a >= 10: ok('VISUAL_ANALYSIS',str(ok_a)+'/'+str(total_a)) elif total_a > 0: warn('VISUAL_ANALYSIS',str(total_a)+' analyzed') else: fail('VISUAL_ANALYSIS','0 analyses') except: warn('VISUAL_ANALYSIS','No data') # Action Engine (file check) try: ae = '/var/www/html/api/wevia-action-engine.php' r = sp.run(['php','-l',ae],capture_output=True,text=True,timeout=5) if 'No syntax errors' in r.stdout: ok('ACTION_ENGINE',str(os.path.getsize(ae)//1024)+'KB') else: fail('ACTION_ENGINE','PHP error') except Exception as e: fail('ACTION_ENGINE',str(e)[:30]) # Qdrant try: r = sp.run(['curl','-sf','--max-time','3','http://127.0.0.1:6333/collections'],capture_output=True,text=True,timeout=5) d = json.loads(r.stdout) cols = len(d.get('result',{}).get('collections',[])) if cols >= 3: ok('QDRANT_VECTORS',str(cols)+' collections') else: warn('QDRANT_VECTORS',str(cols)) except: warn('QDRANT_VECTORS','timeout') # Wiki KB try: ae = '/var/www/html/api/wevia-action-engine.php' if os.path.exists(ae) and os.path.getsize(ae) > 20000: ok('WIKI_ENTRIES','Engine OK') else: warn('WIKI_ENTRIES','Check') except: warn('WIKI_ENTRIES','Error') # ═══ END SESSION 6-AVR ═══ json.dump(R,f,indent=2) print("Saved")