Source code for scitex_browser.debugging._test_monitor

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Timestamp: 2025-12-09
# File: /home/ywatanabe/proj/scitex-code/src/scitex/browser/debugging/_test_monitor.py

"""
Test monitoring with periodic screenshots using scitex.capture.

Provides automated visual monitoring during E2E tests:
- Periodic screenshots at configurable intervals
- Integration with scitex.capture for WSL/Windows support
- GIF generation from test sessions
- Pytest fixture integration

Usage in conftest.py:
    from scitex_browser.debugging import (
        create_test_monitor,
        TestMonitor,
    )

    @pytest.fixture
    def test_monitor():
        monitor = TestMonitor(interval=2.0, verbose=True)
        monitor.start()
        yield monitor
        monitor.stop()
        monitor.create_gif()  # Optional: create GIF from screenshots
"""

from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Optional

from scitex_browser._state import test_monitor_dir

if TYPE_CHECKING:
    from playwright.sync_api import Page


[docs] class TestMonitor: """ Monitor E2E tests with periodic screenshots using scitex.capture. Captures screenshots at regular intervals during test execution, allowing visual inspection of test progress and debugging. """
[docs] def __init__( self, output_dir: str | Path = None, interval: float = 2.0, quality: int = 70, verbose: bool = False, test_name: str = None, ): """ Initialize test monitor. Args: output_dir: Directory for screenshots (default: $SCITEX_DIR/browser/runtime/test_monitor) interval: Seconds between screenshots (default: 2.0) quality: JPEG quality 1-100 (default: 70) verbose: Print capture messages test_name: Optional test name for session identification """ self.output_dir = test_monitor_dir(output_dir) self.interval = interval self.quality = quality self.verbose = verbose self.test_name = test_name self.session_id = None self._worker = None self._capture_manager = None
[docs] def start(self, test_name: str = None) -> str: """ Start periodic screenshot capture. Args: test_name: Optional test name to include in session Returns: Session ID for this capture session """ try: from scitex_capture import CaptureManager except ImportError: if self.verbose: print("[TestMonitor] scitex.capture not available") return None # Generate session ID timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") name = test_name or self.test_name or "test" safe_name = ( name.replace("::", "_").replace("[", "_").replace("]", "").replace("/", "_") ) self.session_id = f"{timestamp}_{safe_name}" # Create session directory session_dir = self.output_dir / self.session_id session_dir.mkdir(parents=True, exist_ok=True) # Start capture self._capture_manager = CaptureManager() self._worker = self._capture_manager.start_capture( output_dir=str(session_dir), interval=self.interval, jpeg=True, quality=self.quality, verbose=self.verbose, ) if self.verbose: print(f"[TestMonitor] Started: {session_dir} (interval: {self.interval}s)") return self.session_id
[docs] def stop(self) -> dict: """ Stop screenshot capture. Returns: Status dict with session info """ if self._capture_manager: self._capture_manager.stop_capture() status = self.get_status() if self.verbose and self._worker: print(f"[TestMonitor] Stopped: {self._worker.screenshot_count} screenshots") return status
[docs] def get_status(self) -> dict: """Get current monitor status.""" if self._worker: return self._worker.get_status() return { "running": False, "session_id": self.session_id, "output_dir": str(self.output_dir), }
[docs] def take_snapshot(self, message: str = None) -> Optional[str]: """ Take an immediate snapshot (in addition to periodic captures). Args: message: Optional message to include in filename Returns: Path to saved screenshot """ try: from scitex_capture import snap return snap(message=message, output_dir=str(self.output_dir)) except ImportError: return None
[docs] def create_gif( self, duration: float = 0.5, output_path: str = None ) -> Optional[str]: """ Create GIF from captured screenshots. Args: duration: Duration per frame in seconds output_path: Output path for GIF (auto-generated if None) Returns: Path to created GIF """ if not self.session_id: return None try: from scitex_capture import create_gif_from_session session_dir = self.output_dir / self.session_id if output_path is None: output_path = str(session_dir / f"{self.session_id}.gif") return create_gif_from_session( session_id=self.session_id, output_path=output_path, duration=duration, ) except ImportError: if self.verbose: print("[TestMonitor] GIF creation requires scitex.capture") return None
[docs] def get_screenshots(self) -> list: """Get list of captured screenshot paths.""" if not self.session_id: return [] session_dir = self.output_dir / self.session_id if not session_dir.exists(): return [] return sorted(session_dir.glob("*.jpg")) + sorted(session_dir.glob("*.png"))
[docs] def create_test_monitor_fixture( output_dir: str | Path = None, interval: float = 2.0, auto_gif: bool = False, ): """ Create a pytest fixture for test monitoring. Usage in conftest.py: from scitex_browser.debugging import create_test_monitor_fixture test_monitor = create_test_monitor_fixture(interval=2.0, auto_gif=True) Args: output_dir: Directory for screenshots interval: Seconds between screenshots auto_gif: Create GIF automatically on test completion Returns: A pytest fixture function """ import pytest @pytest.fixture def test_monitor(request): """Pytest fixture for visual test monitoring.""" monitor = TestMonitor( output_dir=output_dir, interval=interval, verbose=True, test_name=request.node.nodeid, ) monitor.start() yield monitor monitor.stop() if auto_gif: gif_path = monitor.create_gif() if gif_path: print(f"[TestMonitor] GIF created: {gif_path}") return test_monitor
# Convenience function for quick monitoring
[docs] def monitor_test( test_func=None, interval: float = 2.0, auto_gif: bool = False, ): """ Decorator for monitoring tests with periodic screenshots. Usage: @monitor_test(interval=1.0, auto_gif=True) def test_my_feature(page): # test code... Args: test_func: Test function (for use without parentheses) interval: Seconds between screenshots auto_gif: Create GIF on completion """ def decorator(func): def wrapper(*args, **kwargs): monitor = TestMonitor( interval=interval, verbose=True, test_name=func.__name__, ) monitor.start() try: return func(*args, **kwargs) finally: monitor.stop() if auto_gif: monitor.create_gif() return wrapper if test_func is not None: return decorator(test_func) return decorator
# EOF