#!/usr/bin/env python3 """ Automated Screenshot Capture Engine Captures 21 high-resolution screenshots (1440x900) of all pages, modal dialogues, interactive states, and progress workflows across the Dataset Enrichment & Retraining app. """ import json import os import sys import time from pathlib import Path from playwright.sync_api import sync_playwright BASE_DIR = Path(__file__).resolve().parent.parent BASE_URL = os.environ.get("BASE_URL", "http://localhost:5174") OUTPUT_DIR = os.environ.get("SCREENSHOTS_DIR", str(BASE_DIR / "screenshots")) os.makedirs(OUTPUT_DIR, exist_ok=True) def wait_page(page, ms=500): page.wait_for_load_state("domcontentloaded") try: page.evaluate("document.fonts.ready") except Exception: pass page.wait_for_timeout(ms) def run(): with sync_playwright() as p: browser = p.chromium.launch( headless=True, args=[ "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--window-size=1440,900" ] ) context = browser.new_context( viewport={"width": 1440, "height": 900}, device_scale_factor=1, color_scheme="dark" ) # 1. Projects Page print("[1/21] Capturing 01_projects_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects") page.wait_for_selector(".card-grid, .page-head") wait_page(page) page.screenshot(path=os.path.join(OUTPUT_DIR, "01_projects_page.png")) page.close() # 2. Project Create Modal print("[2/21] Capturing 02_project_create_modal.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects") page.wait_for_selector("button:has-text('New project')") page.click("button:has-text('New project')") page.wait_for_selector("form.form-panel, input#np-name") wait_page(page) page.screenshot(path=os.path.join(OUTPUT_DIR, "02_project_create_modal.png")) page.close() # 3. Library Video Archive print("[3/21] Capturing 03_library_video_archive.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5") page.wait_for_selector("table.video-table, nav.date-list") wait_page(page) page.screenshot(path=os.path.join(OUTPUT_DIR, "03_library_video_archive.png")) page.close() # 4. Trim Page print("[4/21] Capturing 04_trim_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/trim/2026-08-17%2F2026-08-17_09-57-27-503912.mp4") page.wait_for_selector(".trim-player, input#trim-start") wait_page(page, 700) page.screenshot(path=os.path.join(OUTPUT_DIR, "04_trim_page.png")) page.close() # 5. Batches Page print("[5/21] Capturing 05_batches_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/batches") page.wait_for_selector("table.video-table tbody tr") wait_page(page) page.screenshot(path=os.path.join(OUTPUT_DIR, "05_batches_page.png")) page.close() # 6. Batches SAM3 Auto-Annotate Modal print("[6/21] Capturing 06_batches_sam3_auto_annotate_modal.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/batches") page.wait_for_selector("table.video-table tbody tr button:has-text('Auto-annotate')") first_btn = page.locator("table.video-table tbody tr button:has-text('Auto-annotate')").first first_btn.click() page.wait_for_selector("div:has-text('Select Engine to Auto-annotate')") wait_page(page, 300) sam3_card = page.locator("div:has-text('SAM3 Zero-Shot')").first sam3_card.click() page.wait_for_selector("button:has-text('Start Auto-Annotation'), input[type='range']") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "06_batches_sam3_auto_annotate_modal.png")) page.close() # 7. Batches Mass Auto-Annotate Modal print("[7/21] Capturing 07_batches_mass_auto_annotate_modal.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/batches") page.wait_for_selector("button:has-text('Auto-Annotate All Batches')") page.click("button:has-text('Auto-Annotate All Batches')") page.wait_for_selector("h3:has-text('Mass auto-annotate'), button:has-text('Run Preview')") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "07_batches_mass_auto_annotate_modal.png")) page.close() # 8. Review & Annotation Canvas print("[8/21] Capturing 08_review_annotation_canvas.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/review?batch=66") page.wait_for_selector(".canvas-wrap img, .filmstrip") wait_page(page, 800) page.screenshot(path=os.path.join(OUTPUT_DIR, "08_review_annotation_canvas.png")) page.close() # 9. Review Filmstrip Quick Reclass Bar print("[9/21] Capturing 09_review_filmstrip_quick_reclass.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/review?batch=66") page.wait_for_selector(".canvas-wrap img, .filmstrip") wait_page(page, 500) shapes = page.locator("button.shape-pick, ul.shape-list li button") if shapes.count() > 0: shapes.first.click() else: page.click("button:has-text('Select')") page.wait_for_timeout(200) select_all_btn = page.locator("button:has-text('Select all')") if select_all_btn.count() > 0: select_all_btn.click() wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "09_review_filmstrip_quick_reclass.png")) page.close() # 10. Review Triage Crop Grid print("[10/21] Capturing 10_review_triage_crop_grid.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/data-prep?batches=66") page.wait_for_selector("h2:has-text('Check the boundary')") page.wait_for_selector("svg circle, button img[src*='crops']") wait_page(page, 400) page.evaluate("document.querySelector('main.roboflow-main').scrollTop = 1350") wait_page(page, 600) page.screenshot(path=os.path.join(OUTPUT_DIR, "10_review_triage_crop_grid.png")) page.close() # 11. Review Triage Scatter Plot print("[11/21] Capturing 11_review_triage_scatter_plot.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/data-prep?batches=66") page.wait_for_selector("h2:has-text('Check the boundary')") page.wait_for_selector("svg circle, button img[src*='crops']") wait_page(page, 400) page.evaluate("document.querySelector('main.roboflow-main').scrollTop = 680") wait_page(page, 600) page.screenshot(path=os.path.join(OUTPUT_DIR, "11_review_triage_scatter_plot.png")) page.close() # 12. Review Exemplar Pool Panel print("[12/21] Capturing 12_review_exemplar_pool_panel.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/review?batch=66") page.wait_for_selector(".canvas-wrap svg, .filmstrip") wait_page(page, 600) canvas = page.locator(".canvas-wrap svg") box = canvas.bounding_box() if box: cx = box["x"] + box["width"] * 0.35 cy = box["y"] + box["height"] * 0.35 page.mouse.move(cx, cy) page.mouse.down() page.mouse.move(cx + 120, cy + 90, steps=5) page.mouse.up() page.wait_for_selector(".exemplar-panel", timeout=5000) wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "12_review_exemplar_pool_panel.png")) page.close() # 13. Data Prep Quality Outliers print("[13/21] Capturing 13_data_prep_quality_outliers.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/data-prep?batches=66") page.wait_for_selector("h1:has-text('Data Prep'), h2:has-text('Outlier filter')") page.evaluate("document.querySelector('main.roboflow-main').scrollTop = 0") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "13_data_prep_quality_outliers.png")) page.close() # 14. Data Prep Augmentation Panel print("[14/21] Capturing 14_data_prep_augmentation_panel.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/data-prep?batches=66") page.wait_for_selector("h2:has-text('Augmentation')") fine_tune_btn = page.locator("button:has-text('Fine-tune individual settings')") if fine_tune_btn.count() > 0: fine_tune_btn.click() page.evaluate("document.querySelector('main.roboflow-main').scrollTop = 420") wait_page(page, 400) page.screenshot(path=os.path.join(OUTPUT_DIR, "14_data_prep_augmentation_panel.png")) page.close() # 15. Data Prep Merge Target Modal print("[15/21] Capturing 15_data_prep_merge_target_modal.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/data-prep?batches=66") page.wait_for_selector("button:has-text('Confirm merge')") page.evaluate("document.querySelector('main.roboflow-main').scrollTop = 2600") wait_page(page, 300) page.click("button:has-text('Confirm merge')") page.wait_for_selector("div:has-text('Merge Target'), input[name='merge-target']") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "15_data_prep_merge_target_modal.png")) page.close() # 16. Datasets Page print("[16/21] Capturing 16_datasets_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/datasets") page.wait_for_selector(".panel, h1:has-text('Datasets')") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "16_datasets_page.png")) page.close() # 17. Models & Training Page print("[17/21] Capturing 17_models_training_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/models") page.wait_for_selector("h2:has-text('Train Model'), .video-table.metrics") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "17_models_training_page.png")) page.close() # 18. Counting Bench Page print("[18/21] Capturing 18_counting_bench_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/counting-bench") page.wait_for_selector("table.video-table, .stat-value") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "18_counting_bench_page.png")) page.close() # 19. Live Count Page print("[19/21] Capturing 19_live_count_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/projects/5/live-count") page.wait_for_selector("h1:has-text('Live counting test'), input[type='range']") wait_page(page, 500) page.screenshot(path=os.path.join(OUTPUT_DIR, "19_live_count_page.png")) page.close() # 20. SAM3 Playground print("[20/21] Capturing 20_sam3_playground_page.png...") page = context.new_page() page.goto(f"{BASE_URL}/#/sam3-playground") page.wait_for_selector("h1:has-text('SAM3 Global Playground'), textarea, input[type='file']") sample_img = BASE_DIR / "data" / "projects" / "sack" / "batches" / "66" / "frames" / "000001.jpg" if sample_img.exists(): file_input = page.locator("input[type='file']").first file_input.set_input_files(str(sample_img)) wait_page(page, 600) page.screenshot(path=os.path.join(OUTPUT_DIR, "20_sam3_playground_page.png")) page.close() # 21. Workflow Progress States print("[21/21] Capturing 21_workflow_progress_states.png...") page = context.new_page() page.route("**/api/jobs*", lambda route: route.fulfill( status=200, content_type="application/json", body=json.dumps({ "jobs": [{ "id": 1600, "project_id": 5, "type": "train", "status": "running", "progress": 32, "total": 50, "log": [ "[04:47:44] Fine-tuning model.pt for 50 epoch(s) (batch=64, imgsz=640, device=0)", "[04:47:44] Augmentation: aggressive — degrees=10, fliplr=0.5, flipud=0.1, mosaic=1", "[04:47:44] Image cache: disk (RAM cache would need ~17.1 GB, 28.1 GB free)", "[04:48:12] Epoch 30/50: loss=0.0312, mAP50=0.912, precision=0.884, recall=0.862", "[04:48:25] Epoch 31/50: loss=0.0298, mAP50=0.924, precision=0.891, recall=0.871", "[04:48:38] Epoch 32/50: loss=0.0285, mAP50=0.931, precision=0.898, recall=0.877" ] }] }) )) page.goto(f"{BASE_URL}/#/projects/5/models") page.wait_for_selector('.panel:has-text("Training Job #1600 (running)")') wait_page(page, 400) page.screenshot(path=os.path.join(OUTPUT_DIR, "21_workflow_progress_states.png")) page.close() browser.close() print("ALL 21 SCREENSHOTS CAPTURED SUCCESSFULLY!") if __name__ == "__main__": run()