Improve backtest lazy loading and test automation

This commit is contained in:
Bu5hm4nn
2026-04-07 12:18:50 +02:00
parent ccc10923d9
commit b2bc4db41a
18 changed files with 504 additions and 300 deletions

View File

@@ -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."""

View File

@@ -11,8 +11,11 @@ from __future__ import annotations
from pathlib import Path
import pytest
from playwright.sync_api import expect
pytestmark = [pytest.mark.playwright, pytest.mark.e2e]
ARTIFACTS = Path("tests/artifacts")
ARTIFACTS.mkdir(parents=True, exist_ok=True)