| import os |
| import hashlib |
| from PIL import Image, ImageChops |
| import io |
| from browser.screenshot import take |
|
|
| _baselines: dict[str, str] = {} |
|
|
|
|
| async def check(url: str) -> dict: |
| """Compare current screenshot with baseline. Returns diff report.""" |
| current_path = await take(url, label="regression") |
|
|
| if current_path.startswith("screenshot_error"): |
| return {"check": "visual_regression", "overall": "error", "summary": current_path} |
|
|
| if url not in _baselines: |
| _baselines[url] = current_path |
| return { |
| "check": "visual_regression", |
| "overall": "ok", |
| "baseline_set": True, |
| "summary": "β
Baseline screenshot saved β will compare next run", |
| } |
|
|
| baseline_path = _baselines[url] |
|
|
| if not os.path.exists(baseline_path): |
| _baselines[url] = current_path |
| return { |
| "check": "visual_regression", |
| "overall": "ok", |
| "summary": "β
Baseline reset (previous file missing)", |
| } |
|
|
| try: |
| img_base = Image.open(baseline_path).convert("RGB") |
| img_current = Image.open(current_path).convert("RGB") |
|
|
| |
| if img_base.size != img_current.size: |
| img_current = img_current.resize(img_base.size, Image.LANCZOS) |
|
|
| diff = ImageChops.difference(img_base, img_current) |
| diff_pixels = sum(sum(row) for row in diff.getdata()) |
| total_pixels= img_base.width * img_base.height * 3 * 255 |
| diff_pct = round((diff_pixels / total_pixels) * 100, 2) |
|
|
| if diff_pct > 20: |
| status = "error" |
| summary = f"π¨ Visual change detected: {diff_pct}% of page changed!" |
| elif diff_pct > 5: |
| status = "warning" |
| summary = f"β οΈ Minor visual change: {diff_pct}% of page changed" |
| else: |
| status = "ok" |
| summary = f"β
No significant visual change ({diff_pct}%)" |
|
|
| |
| _baselines[url] = current_path |
|
|
| return { |
| "check": "visual_regression", |
| "overall": status, |
| "diff_percent": diff_pct, |
| "baseline_path": baseline_path, |
| "current_path": current_path, |
| "summary": summary, |
| } |
|
|
| except Exception as e: |
| return { |
| "check": "visual_regression", |
| "overall": "error", |
| "summary": f"β Visual comparison failed: {e}", |
| } |
|
|