letsbe-mcp-browser/app/playwright_client.py

88 lines
2.3 KiB
Python
Raw Permalink Normal View History

"""Playwright browser management."""
import asyncio
from typing import Optional
from playwright.async_api import Browser, Playwright, async_playwright
from app.config import settings
class PlaywrightClient:
"""
Manages the Playwright browser instance.
Provides lifecycle management for a single Chromium browser
that serves all sessions.
"""
def __init__(self):
self._playwright: Optional[Playwright] = None
self._browser: Optional[Browser] = None
@property
def browser(self) -> Browser:
"""Get the browser instance."""
if self._browser is None:
raise RuntimeError("Browser not started. Call start() first.")
return self._browser
@property
def is_running(self) -> bool:
"""Check if browser is running."""
return self._browser is not None
async def start(self) -> Browser:
"""
Start the Playwright browser.
Returns:
The Browser instance
"""
if self._browser is not None:
return self._browser
self._playwright = await async_playwright().start()
# Launch Chromium with Docker-compatible settings
self._browser = await self._playwright.chromium.launch(
headless=settings.browser_headless,
args=[
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
"--single-process",
],
)
return self._browser
async def stop(self) -> None:
"""Stop the Playwright browser and cleanup."""
if self._browser:
try:
await self._browser.close()
except Exception:
pass
self._browser = None
if self._playwright:
try:
await self._playwright.stop()
except Exception:
pass
self._playwright = None
# Global singleton instance
_playwright_client: Optional[PlaywrightClient] = None
def get_playwright_client() -> PlaywrightClient:
"""Get the global PlaywrightClient instance."""
global _playwright_client
if _playwright_client is None:
_playwright_client = PlaywrightClient()
return _playwright_client