Files
Timmy-time-dashboard/scripts/deploy_verify.py
Alexander Whitestone 10eb29f57b
Some checks are pending
Tests / lint (pull_request) Waiting to run
Tests / test (pull_request) Blocked by required conditions
feat: add deployment visual verification script
Post-deployment UI verification using vision:
- Screenshot-based page rendering checks
- Error detection (404, 500, broken layouts)
- Expected content verification
- Batch URL checking for multi-endpoint deployments

Relates to #1485
2026-04-09 14:55:28 +00:00

147 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""
Deployment Visual Verification
==============================
Post-deployment step that uses vision to verify UI is rendered correctly.
Takes screenshots of deployed endpoints and checks for:
- Page rendering errors
- Missing assets
- Layout breaks
- Error messages visible
- Expected content present
Usage:
python scripts/deploy_verify.py check https://my-app.com
python scripts/deploy_verify.py check https://my-app.com --expect "Welcome"
python scripts/deploy_verify.py batch urls.txt
"""
import json
import sys
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Optional
@dataclass
class DeployCheck:
"""A single deployment verification check."""
url: str
status: str # passed, failed, warning
issues: list = field(default_factory=list)
screenshot_path: Optional[str] = None
expected_content: str = ""
timestamp: str = ""
def summary(self) -> str:
emoji = {"passed": "", "failed": "", "warning": "⚠️"}.get(self.status, "")
lines = [
f"{emoji} {self.url}",
f" Checked: {self.timestamp or 'pending'}",
]
if self.expected_content:
lines.append(f" Expected: '{self.expected_content}'")
if self.issues:
lines.append(" Issues:")
for i in self.issues:
lines.append(f" - {i}")
else:
lines.append(" No issues detected")
return "\n".join(lines)
class DeployVerifier:
"""Verifies deployed UI renders correctly using screenshots."""
def build_check_prompt(self, url: str, expected: str = "") -> dict:
"""Build verification prompt for a deployed URL."""
expect_clause = ""
if expected:
expect_clause = f"\n- Verify the text \"{expected}\" is visible on the page"
prompt = f"""Take a screenshot of {url} and verify the deployment is healthy.
Check for:
- Page loads without errors (no 404, 500, connection refused)
- No visible error messages or stack traces
- Layout is not broken (elements properly aligned, no overlapping)
- Images and assets load correctly (no broken image icons)
- Navigation elements are present and clickable{expect_clause}
- No "under construction" or placeholder content
- Responsive design elements render properly
Return as JSON:
```json
{{
"status": "passed|failed|warning",
"issues": ["list of issues found"],
"confidence": 0.9,
"page_title": "detected page title",
"visible_text_sample": "first 100 chars of visible text"
}}
```
"""
return {
"url": url,
"prompt": prompt,
"screenshot_needed": True,
"instruction": f"browser_navigate to {url}, take screenshot with browser_vision, analyze with prompt"
}
def verify_deployment(self, url: str, expected: str = "", screenshot_path: str = "") -> DeployCheck:
"""Create a deployment verification check."""
check = DeployCheck(
url=url,
status="pending",
expected_content=expected,
timestamp=datetime.now().isoformat(),
screenshot_path=screenshot_path or f"/tmp/deploy_verify_{url.replace('://', '_').replace('/', '_')}.png"
)
return check
def main():
if len(sys.argv) < 2:
print("Usage: deploy_verify.py <check|batch> [args...]")
return 1
verifier = DeployVerifier()
cmd = sys.argv[1]
if cmd == "check":
if len(sys.argv) < 3:
print("Usage: deploy_verify.py check <url> [--expect 'text']")
return 1
url = sys.argv[2]
expected = ""
if "--expect" in sys.argv:
idx = sys.argv.index("--expect")
if idx + 1 < len(sys.argv):
expected = sys.argv[idx + 1]
result = verifier.build_check_prompt(url, expected)
print(json.dumps(result, indent=2))
elif cmd == "batch":
if len(sys.argv) < 3:
print("Usage: deploy_verify.py batch <urls_file>")
return 1
urls_file = Path(sys.argv[2])
if not urls_file.exists():
print(f"File not found: {urls_file}")
return 1
urls = [line.strip() for line in urls_file.read_text().splitlines() if line.strip() and not line.startswith("#")]
for url in urls:
print(f"\n--- {url} ---")
result = verifier.build_check_prompt(url)
print(json.dumps(result, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())