forked from icd/rentgen
refactor(test): rewrite test_verify.sh to Python with guard clauses
Converted bash test script to Python for better maintainability: - Guard clause pattern replaces nested if statements - Early returns for cleaner control flow - Type hints for better documentation - Proper error handling and cleanup - More readable and testable code structure Features: - Starts Xvfb and web-ext - Waits for extension installation - Checks for JavaScript errors - Verifies debugger connectivity - Clean process termination Usage: scripts/test_verify.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d6c0353e24
commit
789194ee64
249
scripts/test_verify.py
Executable file
249
scripts/test_verify.py
Executable file
@ -0,0 +1,249 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
test_verify.py - Verifies extension and exits (doesn't wait forever)
|
||||||
|
|
||||||
|
This script is a Python version of test_verify.sh that:
|
||||||
|
- Starts Firefox with the extension
|
||||||
|
- Verifies extension loaded without errors
|
||||||
|
- EXITS after verification (instead of waiting forever)
|
||||||
|
|
||||||
|
Used for automated tests in CI/Docker.
|
||||||
|
Uses guard clauses pattern for cleaner flow control.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def print_header(text: str) -> None:
|
||||||
|
"""Print formatted header."""
|
||||||
|
print(f"\n{text}")
|
||||||
|
|
||||||
|
|
||||||
|
def print_success(text: str) -> None:
|
||||||
|
"""Print success message."""
|
||||||
|
print(f"✓ {text}")
|
||||||
|
|
||||||
|
|
||||||
|
def print_error(text: str) -> None:
|
||||||
|
"""Print error message."""
|
||||||
|
print(f"✗ {text}")
|
||||||
|
|
||||||
|
|
||||||
|
def start_xvfb() -> int:
|
||||||
|
"""Start Xvfb virtual X server. Returns PID."""
|
||||||
|
print_header("Starting Xvfb on display :99...")
|
||||||
|
|
||||||
|
xvfb = subprocess.Popen(
|
||||||
|
["Xvfb", ":99", "-screen", "0", "1024x768x24"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
|
||||||
|
os.environ["DISPLAY"] = ":99"
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
print_success(f"Xvfb started with PID: {xvfb.pid}")
|
||||||
|
return xvfb.pid
|
||||||
|
|
||||||
|
|
||||||
|
def start_webext() -> tuple[int, Path]:
|
||||||
|
"""Start web-ext and log output to file. Returns (PID, log_path)."""
|
||||||
|
print_header("Starting web-ext run with verbose logging...")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
log_path = Path("/tmp/web-ext.log")
|
||||||
|
log_file = open(log_path, "w")
|
||||||
|
|
||||||
|
webext = subprocess.Popen(
|
||||||
|
["npx", "web-ext", "run", "--verbose"],
|
||||||
|
stdout=log_file,
|
||||||
|
stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
|
|
||||||
|
return webext.pid, log_path
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_extension_install(log_path: Path, timeout_seconds: int = 30) -> bool:
|
||||||
|
"""Wait for extension installation confirmation in logs."""
|
||||||
|
print_header("Waiting for extension to install...")
|
||||||
|
|
||||||
|
for _ in range(timeout_seconds):
|
||||||
|
if not log_path.exists():
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
content = log_path.read_text()
|
||||||
|
if "Installed /app as a temporary add-on" in content:
|
||||||
|
print("=" * 40)
|
||||||
|
print_success("Extension installed!")
|
||||||
|
print_success("Firefox is running in headless mode")
|
||||||
|
print_success("Extension: rentgen@internet-czas-dzialac.pl")
|
||||||
|
return True
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_javascript_errors(log_path: Path) -> list[str]:
|
||||||
|
"""Check for JavaScript errors in extension code. Returns list of errors."""
|
||||||
|
print_header("Checking for JavaScript errors in extension code...")
|
||||||
|
|
||||||
|
content = log_path.read_text()
|
||||||
|
|
||||||
|
# Filter out unrelated Firefox errors
|
||||||
|
error_patterns = [
|
||||||
|
"JavaScript error.*background.js",
|
||||||
|
"SyntaxError.*background",
|
||||||
|
"ReferenceError.*background"
|
||||||
|
]
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
for line in content.split("\n"):
|
||||||
|
# Skip BackupService and RSLoader errors (Firefox internal)
|
||||||
|
if "BackupService" in line or "RSLoader" in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for pattern in error_patterns:
|
||||||
|
import re
|
||||||
|
if re.search(pattern, line, re.IGNORECASE):
|
||||||
|
errors.append(line)
|
||||||
|
break
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def check_debugger_port(log_path: Path) -> str | None:
|
||||||
|
"""Extract debugger port from logs. Returns port number or None."""
|
||||||
|
content = log_path.read_text()
|
||||||
|
|
||||||
|
import re
|
||||||
|
match = re.search(r"start-debugger-server (\d+)", content)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def test_debugger_connectivity(port: str) -> bool:
|
||||||
|
"""Test if remote debugging protocol is accessible."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["timeout", "2", "bash", "-c", f"echo > /dev/tcp/127.0.0.1/{port}"],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=3
|
||||||
|
)
|
||||||
|
return result.returncode == 0
|
||||||
|
except (subprocess.TimeoutExpired, subprocess.SubprocessError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup(xvfb_pid: int, webext_pid: int) -> None:
|
||||||
|
"""Cleanup processes."""
|
||||||
|
try:
|
||||||
|
os.kill(webext_pid, signal.SIGTERM)
|
||||||
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.kill(xvfb_pid, signal.SIGTERM)
|
||||||
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""Main test verification logic using guard clauses."""
|
||||||
|
|
||||||
|
# Start Xvfb
|
||||||
|
xvfb_pid = start_xvfb()
|
||||||
|
|
||||||
|
# Start web-ext
|
||||||
|
webext_pid, log_path = start_webext()
|
||||||
|
|
||||||
|
# Guard: Check if extension installed
|
||||||
|
if not wait_for_extension_install(log_path):
|
||||||
|
print_error("Extension failed to install within 30s")
|
||||||
|
cleanup(xvfb_pid, webext_pid)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Give extension time to initialize
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
# Guard: Check for JavaScript errors
|
||||||
|
errors = check_javascript_errors(log_path)
|
||||||
|
if errors:
|
||||||
|
print()
|
||||||
|
print("=" * 40)
|
||||||
|
print("✗✗✗ CRITICAL ERROR ✗✗✗")
|
||||||
|
print("=" * 40)
|
||||||
|
print("Found JavaScript errors in background.js!")
|
||||||
|
print("Extension installed but CODE DID NOT EXECUTE!")
|
||||||
|
print()
|
||||||
|
print("Errors:")
|
||||||
|
for error in errors[:10]:
|
||||||
|
print(f" {error}")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
cleanup(xvfb_pid, webext_pid)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print_success("NO JavaScript errors in background.js")
|
||||||
|
|
||||||
|
# Functional test: Verify extension code execution
|
||||||
|
print_header("Functional test: Verifying extension code execution...")
|
||||||
|
|
||||||
|
port = check_debugger_port(log_path)
|
||||||
|
|
||||||
|
# Guard: Check if debugger port found
|
||||||
|
if not port:
|
||||||
|
print("⚠ Could not find debugger port (but extension installed OK)")
|
||||||
|
else:
|
||||||
|
print_success(f"Firefox debugger port: {port}")
|
||||||
|
|
||||||
|
# Guard: Check debugger connectivity
|
||||||
|
if not test_debugger_connectivity(port):
|
||||||
|
print("⚠ Remote debugging not accessible (but extension installed OK)")
|
||||||
|
else:
|
||||||
|
print_success("Remote debugging protocol accessible")
|
||||||
|
print_success("Extension code VERIFIED executing")
|
||||||
|
print()
|
||||||
|
print("NOTE: Verified by:")
|
||||||
|
print(" - Extension installed without errors")
|
||||||
|
print(" - Background page loaded (debugger accessible)")
|
||||||
|
print(" - No JavaScript errors detected")
|
||||||
|
|
||||||
|
# Show process info
|
||||||
|
print()
|
||||||
|
print_success("Process info:")
|
||||||
|
subprocess.run(["ps", "aux"], stdout=subprocess.PIPE, text=True, check=False)
|
||||||
|
result = subprocess.run(
|
||||||
|
["bash", "-c", "ps aux | grep -E '(firefox|Xvfb)' | grep -v grep | head -3"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
print("=" * 40)
|
||||||
|
print("Extension is VERIFIED working!")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
cleanup(xvfb_pid, webext_pid)
|
||||||
|
|
||||||
|
print("Test completed successfully.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nInterrupted by user")
|
||||||
|
sys.exit(130)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Unexpected error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
Loading…
x
Reference in New Issue
Block a user