CoolFace
Apppublic

thenuke02/cs2-analyzer

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
visual_audit.py447 linesDownload Raw Back to scripts
1"""2Full visual audit of OpenSight — uploads demo, screenshots every tab and sub-tab.3 4Usage:5    py -3.11 scripts/visual_audit.py                          # Upload demo + screenshot all6    py -3.11 scripts/visual_audit.py --job-id <ID>            # Skip upload, reuse job7    py -3.11 scripts/visual_audit.py --job-id <ID> --tab overview  # Single tab only8    py -3.11 scripts/visual_audit.py --job-id <ID> --batch 3  # Single batch only9"""10 11import argparse12import json13import os14import sys15import time16from pathlib import Path17 18from playwright.sync_api import sync_playwright19 20BASE_URL = "http://localhost:7860"21GOLDEN_RESULT = Path(__file__).parent.parent / "tests" / "fixtures" / "golden_result.json"22SCREENSHOT_DIR = Path(__file__).parent.parent / "screenshots"23 24# All main tabs (data-tab attribute values)25MAIN_TABS = [26    "overview",27    "match-details",28    "timeline",29    "matrix",30    "headtohead",31    "heatmap",32    "statsgraph",33    "economy",34    "replay",35    "coaching",36    "gameplan",37    "tactical",38    "stratsteal",39    "selfreview",40    "veto",41    "yourmatch",42    "trends",43    "profile",44]45 46# All Match Details sub-tabs (data-subtab attribute values)47MATCH_DETAIL_SUBTABS = [48    "general",49    "timeline",50    "aim",51    "utility",52    "activity",53    "trades",54    "spray-transfers",55    "opening-duels",56    "entry-breakdown",57    "mistakes",58    "lurk-stats",59    "positioning",60    "clutches",61    "synergy",62    "side-stats",63    "economy-detail",64    "events",65    "round-impact",66    "round-type",67    "by-half",68    "weapons",69    "map-control",70    "utility-timeline",71    "kill-sequence",72    "score-progression",73    "perf-heatmap",74    "team-radar",75]76 77 78def screenshot(page, name, full_page=True):79    """Take and save a screenshot."""80    path = SCREENSHOT_DIR / f"{name}.png"81    page.screenshot(path=str(path), full_page=full_page)82    print(f"  [OK] {name}.png")83 84 85def click_tab(page, tab_id):86    """Click a main tab via JS (avoids visibility issues with dropdown groups)."""87    page.evaluate(88        f"""(() => {{89        const btn = document.querySelector('.tab-btn[data-tab="{tab_id}"]');90        if (!btn) return;91        const group = btn.closest('.tab-group');92        if (group && !group.classList.contains('open')) {{93            const groupBtn = group.querySelector('.tab-group-btn');94            if (groupBtn) groupBtn.click();95        }}96        setTimeout(() => btn.click(), 100);97    }})()"""98    )99    page.wait_for_timeout(1500)100 101 102def click_subtab(page, subtab_id):103    """Click a Match Details sub-tab via JS."""104    page.evaluate(105        f"""(() => {{106        const btn = document.querySelector('.sub-tab[data-subtab="{subtab_id}"]');107        if (btn) btn.click();108    }})()"""109    )110    page.wait_for_timeout(1000)111 112 113def inject_golden_result(page):114    """Load the analyze page and inject golden_result.json via renderResults()."""115    print("\n=== INJECTING GOLDEN RESULT ===")116    page.goto(f"{BASE_URL}/analyze", wait_until="networkidle")117    page.wait_for_timeout(1000)118 119    # Read golden result fixture120    with open(GOLDEN_RESULT) as f:121        data = json.load(f)122 123    # Inject via JS — call renderResults directly124    page.evaluate(125        """(data) => {126        window.currentAnalysis = data;127        window.__opensightResults = data;128        if (typeof renderResults === 'function') {129            renderResults(data);130        }131    }""",132        data,133    )134    page.wait_for_timeout(3000)  # Let all lazy content settle135 136    has_results = page.evaluate(137        """138        document.querySelector('#results') &&139        document.querySelector('#results').children.length > 0140    """141    )142    if has_results:143        print("  Results injected and rendered")144    else:145        print("  [WARN] Results container may be empty — check screenshot")146 147 148def load_results(page, job_id):149    """Load analysis results using a known job_id."""150    print(f"\n=== LOADING RESULTS (job_id: {job_id}) ===")151    page.goto(f"{BASE_URL}/analyze?job={job_id}", wait_until="networkidle")152 153    start = time.time()154    while time.time() - start < 30:155        has_results = page.evaluate(156            """157            document.querySelector('#results') &&158            document.querySelector('#results').children.length > 0159        """160        )161        if has_results:162            break163        page.wait_for_timeout(1000)164 165    page.wait_for_timeout(2000)166    print("  Results loaded")167 168 169def batch1_core(page):170    """Batch 1: Core tabs — what every user sees first."""171    print("\n=== BATCH 1: CORE TABS ===")172 173    # Landing page174    p2 = page.context.new_page()175    p2.set_viewport_size({"width": 1440, "height": 900})176    p2.goto(f"{BASE_URL}/", wait_until="networkidle")177    p2.wait_for_timeout(1000)178    screenshot(p2, "b1_01_landing")179    p2.close()180 181    # Empty analyze page182    p3 = page.context.new_page()183    p3.set_viewport_size({"width": 1440, "height": 900})184    p3.goto(f"{BASE_URL}/analyze", wait_until="networkidle")185    p3.wait_for_timeout(1000)186    screenshot(p3, "b1_02_upload_empty")187    p3.close()188 189 190def batch1_results(page):191    """Batch 1 continued: Overview and Match Details general."""192    click_tab(page, "overview")193    page.evaluate("window.scrollTo(0, 0)")194    page.wait_for_timeout(500)195    screenshot(page, "b1_03_overview")196 197    click_tab(page, "match-details")198    click_subtab(page, "general")199    page.evaluate("window.scrollTo(0, 0)")200    page.wait_for_timeout(500)201    screenshot(page, "b1_04_match_details_general")202 203 204def batch2_match_data(page):205    """Batch 2: Match data tabs."""206    print("\n=== BATCH 2: MATCH DATA TABS ===")207 208    for tab_id in ["timeline", "matrix", "headtohead", "economy", "statsgraph"]:209        click_tab(page, tab_id)210        page.evaluate("window.scrollTo(0, 0)")211        page.wait_for_timeout(1000)212        screenshot(page, f"b2_{tab_id}")213 214 215def batch3_match_detail_subtabs(page):216    """Batch 3: All Match Details sub-tabs."""217    print("\n=== BATCH 3: ALL MATCH DETAILS SUB-TABS ===")218 219    click_tab(page, "match-details")220    page.wait_for_timeout(500)221 222    for i, subtab in enumerate(MATCH_DETAIL_SUBTABS):223        click_subtab(page, subtab)224        page.evaluate("window.scrollTo(0, 0)")225        page.wait_for_timeout(300)226        screenshot(page, f"b3_{i + 1:02d}_{subtab}")227 228 229def batch4_analysis_tools(page):230    """Batch 4: Analysis & AI tools tabs."""231    print("\n=== BATCH 4: ANALYSIS & AI TOOLS ===")232 233    # Heatmap — default234    click_tab(page, "heatmap")235    page.evaluate("window.scrollTo(0, 0)")236    page.wait_for_timeout(1500)237    screenshot(page, "b4_heatmap_default")238 239    # Heatmap — dots240    page.evaluate(241        """(() => {242        const btn = document.querySelector('[data-mode="dots"]');243        if (btn) btn.click();244    })()"""245    )246    page.wait_for_timeout(1000)247    screenshot(page, "b4_heatmap_dots")248 249    # Heatmap — zones250    page.evaluate(251        """(() => {252        const btn = document.querySelector('[data-mode="zones"]');253        if (btn) btn.click();254    })()"""255    )256    page.wait_for_timeout(1000)257    screenshot(page, "b4_heatmap_zones")258 259    # 2D Replay260    click_tab(page, "replay")261    page.evaluate("window.scrollTo(0, 0)")262    page.wait_for_timeout(2000)263    screenshot(page, "b4_replay")264 265    # AI tabs266    for tab_id in [267        "coaching",268        "gameplan",269        "tactical",270        "stratsteal",271        "selfreview",272        "veto",273    ]:274        click_tab(page, tab_id)275        page.evaluate("window.scrollTo(0, 0)")276        page.wait_for_timeout(500)277        screenshot(page, f"b4_{tab_id}")278 279 280def batch5_personal(page):281    """Batch 5: Personal tabs + standalone pages."""282    print("\n=== BATCH 5: PERSONAL TABS ===")283 284    for tab_id in ["yourmatch", "trends", "profile"]:285        click_tab(page, tab_id)286        page.evaluate("window.scrollTo(0, 0)")287        page.wait_for_timeout(500)288        screenshot(page, f"b5_{tab_id}")289 290    # Profile page standalone291    p2 = page.context.new_page()292    p2.set_viewport_size({"width": 1440, "height": 900})293    p2.goto(f"{BASE_URL}/profile", wait_until="networkidle")294    p2.wait_for_timeout(1000)295    screenshot(p2, "b5_profile_page")296    p2.close()297 298    # Compare page standalone299    p3 = page.context.new_page()300    p3.set_viewport_size({"width": 1440, "height": 900})301    p3.goto(f"{BASE_URL}/compare", wait_until="networkidle")302    p3.wait_for_timeout(1000)303    screenshot(p3, "b5_compare_page")304    p3.close()305 306 307def batch6_mobile(page):308    """Batch 6: Mobile (375px) views."""309    print("\n=== BATCH 6: MOBILE VIEWS ===")310 311    # Landing mobile312    p = page.context.new_page()313    p.set_viewport_size({"width": 375, "height": 812})314    p.goto(f"{BASE_URL}/", wait_until="networkidle")315    p.wait_for_timeout(1000)316    screenshot(p, "b6_mobile_landing")317    p.close()318 319    # Set main page to mobile320    page.set_viewport_size({"width": 375, "height": 812})321    page.wait_for_timeout(500)322 323    # Core tabs mobile324    for tab_id in [325        "overview",326        "match-details",327        "timeline",328        "matrix",329        "heatmap",330        "economy",331        "statsgraph",332    ]:333        click_tab(page, tab_id)334        page.evaluate("window.scrollTo(0, 0)")335        page.wait_for_timeout(800)336        screenshot(page, f"b6_mobile_{tab_id}")337 338    # Key sub-tabs mobile339    click_tab(page, "match-details")340    for subtab in [341        "general",342        "aim",343        "utility",344        "trades",345        "economy-detail",346        "side-stats",347    ]:348        click_subtab(page, subtab)349        page.evaluate("window.scrollTo(0, 0)")350        page.wait_for_timeout(500)351        screenshot(page, f"b6_mobile_subtab_{subtab}")352 353    # Restore desktop354    page.set_viewport_size({"width": 1440, "height": 900})355    page.wait_for_timeout(500)356 357 358def main():359    parser = argparse.ArgumentParser(description="Visual audit of OpenSight")360    parser.add_argument("--job-id", help="Reuse existing job ID (skip upload)")361    parser.add_argument("--tab", help="Screenshot only this tab")362    parser.add_argument("--batch", type=int, help="Run only this batch (1-6)")363    # Support positional job_id for backwards compat364    parser.add_argument("job_id_pos", nargs="?", help=argparse.SUPPRESS)365    args = parser.parse_args()366 367    job_id_arg = args.job_id or args.job_id_pos368 369    SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)370 371    # Clean old screenshots only if doing a full run372    if not args.tab and not args.batch:373        for f in SCREENSHOT_DIR.glob("*.png"):374            f.unlink()375 376    print("=" * 60)377    print("OpenSight Visual Audit")378    print(f"Server: {BASE_URL}")379    print(f"Fixture: {GOLDEN_RESULT}")380    print("=" * 60)381 382    with sync_playwright() as p:383        browser = p.chromium.launch(headless=True)384        context = browser.new_context(viewport={"width": 1440, "height": 900})385        page = context.new_page()386 387        # Load results — inject golden fixture or load from job_id388        job_id = job_id_arg389        if job_id:390            load_results(page, job_id)391        else:392            inject_golden_result(page)393            job_id = "golden-fixture"394 395        # Single tab mode396        if args.tab:397            if args.tab in MATCH_DETAIL_SUBTABS:398                click_tab(page, "match-details")399                click_subtab(page, args.tab)400            else:401                click_tab(page, args.tab)402            page.evaluate("window.scrollTo(0, 0)")403            page.wait_for_timeout(500)404            screenshot(page, f"single_{args.tab}")405            browser.close()406            print(f"\nDone. JOB_ID={job_id}")407            return408 409        # Batch mode or full run410        batches = {411            1: lambda: (batch1_core(page), batch1_results(page)),412            2: lambda: batch2_match_data(page),413            3: lambda: batch3_match_detail_subtabs(page),414            4: lambda: batch4_analysis_tools(page),415            5: lambda: batch5_personal(page),416            6: lambda: batch6_mobile(page),417        }418 419        if args.batch:420            if args.batch in batches:421                batches[args.batch]()422            else:423                print(f"[ERROR] Invalid batch {args.batch}. Valid: 1-6")424        else:425            batch1_core(page)426            batch1_results(page)427            batch2_match_data(page)428            batch3_match_detail_subtabs(page)429            batch4_analysis_tools(page)430            batch5_personal(page)431            batch6_mobile(page)432 433        browser.close()434 435    screenshots = list(SCREENSHOT_DIR.glob("*.png"))436    print(f"\n{'=' * 60}")437    print("AUDIT COMPLETE")438    print(f"{'=' * 60}")439    print(f"Screenshots: {len(screenshots)}")440    print(f"Saved to: {SCREENSHOT_DIR}")441    print(f"JOB_ID: {job_id}")442    print(f"\nReuse with: py -3.11 scripts/visual_audit.py --job-id {job_id}")443 444 445if __name__ == "__main__":446    main()447