Improve backtest lazy loading and test automation
This commit is contained in:
@@ -1,125 +1 @@
|
||||
"""Pytest configuration for Playwright tests.
|
||||
|
||||
This conftest creates module-scoped fixtures that start the FastAPI server
|
||||
before running Playwright tests and stop it after all tests complete.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import uvicorn
|
||||
|
||||
# Suppress NiceGUI banner noise
|
||||
logging.getLogger("nicegui").setLevel(logging.WARNING)
|
||||
|
||||
|
||||
def find_free_port() -> int:
|
||||
"""Find a free port on localhost."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(("", 0))
|
||||
s.listen(1)
|
||||
port = s.getsockname()[1]
|
||||
return port
|
||||
|
||||
|
||||
class ServerManager:
|
||||
"""Manages a NiceGUI/FastAPI server for testing."""
|
||||
|
||||
_instance: "ServerManager | None" = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __init__(self, port: int) -> None:
|
||||
self.port = port
|
||||
self.url = f"http://localhost:{port}"
|
||||
self._thread: threading.Thread | None = None
|
||||
self._server: uvicorn.Server | None = None
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the server in a background thread."""
|
||||
self._thread = threading.Thread(target=self._run_server, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
# Wait for server to be ready
|
||||
if not self._wait_for_connection(timeout=30):
|
||||
raise RuntimeError("Server did not start within 30 seconds")
|
||||
|
||||
def _run_server(self) -> None:
|
||||
"""Run the FastAPI server with uvicorn."""
|
||||
# Ensure project root is on sys.path
|
||||
project_root = str(Path(__file__).parent.parent)
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
os.environ["APP_ENV"] = "test"
|
||||
os.environ["NICEGUI_STORAGE_SECRET"] = "test-secret-key"
|
||||
|
||||
# Import after environment is set
|
||||
from app.main import app
|
||||
|
||||
# Configure uvicorn
|
||||
config = uvicorn.Config(
|
||||
app,
|
||||
host="127.0.0.1",
|
||||
port=self.port,
|
||||
log_level="warning",
|
||||
access_log=False,
|
||||
)
|
||||
self._server = uvicorn.Server(config)
|
||||
self._server.run()
|
||||
|
||||
def _wait_for_connection(self, timeout: float = 10.0) -> bool:
|
||||
"""Wait for server to accept connections."""
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", self.port), timeout=1):
|
||||
return True
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
return False
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the server."""
|
||||
if self._server:
|
||||
self._server.should_exit = True
|
||||
if self._thread and self._thread.is_alive():
|
||||
self._thread.join(timeout=5)
|
||||
|
||||
@classmethod
|
||||
def get_or_create(cls, port: int) -> "ServerManager":
|
||||
"""Get existing instance or create new one."""
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = cls(port)
|
||||
return cls._instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def server_url() -> Generator[str, None, None]:
|
||||
"""Start the server once per module and yield its URL."""
|
||||
port = find_free_port()
|
||||
server = ServerManager(port)
|
||||
|
||||
# Start server
|
||||
server.start()
|
||||
|
||||
yield server.url
|
||||
|
||||
# Cleanup
|
||||
server.stop()
|
||||
ServerManager._instance = None
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def base_url(server_url: str) -> str:
|
||||
"""Alias for server_url for naming consistency with Playwright conventions."""
|
||||
return server_url
|
||||
"""Root tests/conftest.py provides the shared Playwright server fixtures."""
|
||||
|
||||
Reference in New Issue
Block a user