314 lines
14 KiB
Python
Executable File
314 lines
14 KiB
Python
Executable File
#!/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()
|