""" End-to-end tests for web client with headless browser. """ import os import socketserver import threading from http.server import HTTPServer, SimpleHTTPRequestHandler from pathlib import Path import pytest CLIENT_DIR = Path(__file__).parent.parent.parent / "client" class ThreadedHTTPServer(socketserver.ThreadingMixIn, HTTPServer): """Threaded HTTP server for handling concurrent requests.""" daemon_threads = True @pytest.fixture(scope="module") def http_server(): """Start a local HTTP server for the client.""" os.chdir(CLIENT_DIR) handler = SimpleHTTPRequestHandler server = ThreadedHTTPServer(("127.0.0.1", 0), handler) port = server.server_address[1] thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() yield f"http://127.0.0.1:{port}" server.shutdown() class TestWebClient: """Tests for the web client using Playwright.""" @pytest.fixture(autouse=True) def setup_browser(self): """Set up browser for tests.""" pytest.importorskip("playwright") from playwright.sync_api import sync_playwright self.playwright = sync_playwright().start() self.browser = self.playwright.chromium.launch(headless=True) self.context = self.browser.new_context() self.page = self.context.new_page() yield self.page.close() self.context.close() self.browser.close() self.playwright.stop() def test_client_loads(self, http_server): """Web client loads without errors.""" response = self.page.goto(http_server) assert response.status == 200, f"Page load failed with status {response.status}" self.page.wait_for_load_state("domcontentloaded") content = self.page.content() assert " 0, "Canvas not found" def test_status_shows_connecting(self, http_server): """Status shows connecting initially.""" self.page.goto(http_server) self.page.wait_for_load_state("domcontentloaded") status = self.page.locator("#status") assert status.count() > 0, "Status element not found" def test_canvas_has_dimensions(self, http_server): """Canvas has correct dimensions after load.""" self.page.goto(http_server) self.page.wait_for_load_state("domcontentloaded") canvas = self.page.locator("#terminal") assert canvas.count() > 0, "Canvas not found" def test_no_console_errors_on_load(self, http_server): """No JavaScript errors on page load (websocket errors are expected without server).""" js_errors = [] def handle_console(msg): if msg.type == "error": text = msg.text if "WebSocket" not in text: js_errors.append(text) self.page.on("console", handle_console) self.page.goto(http_server) self.page.wait_for_load_state("domcontentloaded") assert len(js_errors) == 0, f"JavaScript errors: {js_errors}" class TestWebClientProtocol: """Tests for WebSocket protocol handling in client.""" @pytest.fixture(autouse=True) def setup_browser(self): """Set up browser for tests.""" pytest.importorskip("playwright") from playwright.sync_api import sync_playwright self.playwright = sync_playwright().start() self.browser = self.playwright.chromium.launch(headless=True) self.context = self.browser.new_context() self.page = self.context.new_page() yield self.page.close() self.context.close() self.browser.close() self.playwright.stop() def test_websocket_reconnection(self, http_server): """Client attempts reconnection on disconnect.""" self.page.goto(http_server) self.page.wait_for_load_state("domcontentloaded") status = self.page.locator("#status") assert status.count() > 0, "Status element not found"