Skip to main content
Glama
nickweedon

Skeleton MCP Server

by nickweedon
test_process_manager.py9.93 kB
""" Tests for playwright process manager """ import asyncio from unittest.mock import AsyncMock, Mock, patch import pytest from playwright_proxy_mcp.playwright.process_manager import PlaywrightProcessManager @pytest.fixture def process_manager(): """Create a process manager instance.""" return PlaywrightProcessManager() @pytest.fixture def mock_config(): """Create a test config.""" return { "browser": "chromium", "headless": True, "caps": "vision,pdf", "timeout_action": 5000, "timeout_navigation": 60000, } class TestPlaywrightProcessManager: """Tests for PlaywrightProcessManager.""" def test_init(self, process_manager): """Test process manager initialization.""" assert process_manager.process is None assert not process_manager._shutdown_event.is_set() @pytest.mark.asyncio async def test_start_npx_not_found(self, process_manager, mock_config): """Test that start raises error when npx is not found.""" with patch("shutil.which", return_value=None): with pytest.raises(RuntimeError, match="npx not found"): await process_manager.start(mock_config) @pytest.mark.asyncio async def test_start_success(self, process_manager, mock_config): """Test successful process start.""" mock_process = Mock() mock_process.pid = 12345 mock_process.returncode = None mock_process.stderr = None with patch("shutil.which", return_value="/usr/bin/npx"): with patch( "asyncio.create_subprocess_exec", return_value=mock_process ) as mock_create: result = await process_manager.start(mock_config) assert result == mock_process assert process_manager.process == mock_process # Verify npx was called with @playwright/mcp call_args = mock_create.call_args[0] assert call_args[0] == "npx" assert call_args[1] == "@playwright/mcp" @pytest.mark.asyncio async def test_start_process_fails(self, process_manager, mock_config): """Test handling of process start failure.""" mock_process = Mock() mock_process.returncode = 1 mock_stderr = Mock() mock_stderr.read = AsyncMock(return_value=b"Error message") mock_process.stderr = mock_stderr with patch("shutil.which", return_value="/usr/bin/npx"): with patch("asyncio.create_subprocess_exec", return_value=mock_process): with pytest.raises(RuntimeError, match="failed to start"): await process_manager.start(mock_config) @pytest.mark.asyncio async def test_stop_no_process(self, process_manager): """Test stopping when no process is running.""" await process_manager.stop() assert process_manager.process is None @pytest.mark.asyncio async def test_stop_graceful(self, process_manager): """Test graceful process stop.""" mock_process = Mock() mock_process.terminate = Mock() mock_process.wait = AsyncMock() process_manager.process = mock_process await process_manager.stop() mock_process.terminate.assert_called_once() mock_process.wait.assert_called_once() assert process_manager.process is None @pytest.mark.asyncio async def test_stop_force_kill(self, process_manager): """Test force kill when graceful stop times out.""" mock_process = Mock() mock_process.terminate = Mock() mock_process.wait = AsyncMock(side_effect=asyncio.TimeoutError) mock_process.kill = Mock() process_manager.process = mock_process await process_manager.stop() mock_process.terminate.assert_called_once() mock_process.kill.assert_called_once() assert process_manager.process is None @pytest.mark.asyncio async def test_restart(self, process_manager, mock_config): """Test process restart.""" mock_process = Mock() mock_process.pid = 12345 mock_process.returncode = None mock_process.stderr = None with patch.object(process_manager, "stop") as mock_stop: with patch.object( process_manager, "start", return_value=mock_process ) as mock_start: result = await process_manager.restart(mock_config) mock_stop.assert_called_once() mock_start.assert_called_once_with(mock_config) assert result == mock_process def test_is_healthy_no_process(self, process_manager): """Test health check when no process.""" assert not process_manager.is_healthy() def test_is_healthy_running(self, process_manager): """Test health check when process is running.""" mock_process = Mock() mock_process.returncode = None process_manager.process = mock_process assert process_manager.is_healthy() def test_is_healthy_stopped(self, process_manager): """Test health check when process has stopped.""" mock_process = Mock() mock_process.returncode = 0 process_manager.process = mock_process assert not process_manager.is_healthy() @pytest.mark.asyncio async def test_build_command_basic(self, process_manager): """Test building basic command.""" config = {"browser": "chromium", "headless": True} command = await process_manager._build_command(config) assert command[0] == "npx" assert command[1] == "@playwright/mcp" assert "--browser" in command assert "chromium" in command assert "--headless" in command @pytest.mark.asyncio async def test_build_command_all_options(self, process_manager): """Test building command with all options.""" config = { "browser": "firefox", "headless": False, "device": "iPhone 12", "viewport_size": "1920x1080", "isolated": True, "user_data_dir": "/path/to/data", "storage_state": "/path/to/state.json", "allowed_origins": "example.com", "blocked_origins": "ads.com", "proxy_server": "proxy.com:8080", "caps": "vision,pdf", "save_session": True, "save_trace": True, "save_video": "on-failure", "output_dir": "/output", "timeout_action": 10000, "timeout_navigation": 30000, "image_responses": "base64", } command = await process_manager._build_command(config) assert "--browser" in command assert "firefox" in command assert "--device" in command assert "iPhone 12" in command assert "--viewport-size" in command assert "1920x1080" in command assert "--isolated" in command assert "--user-data-dir" in command assert "/path/to/data" in command assert "--storage-state" in command assert "--allowed-origins" in command assert "--blocked-origins" in command assert "--proxy-server" in command assert "--caps" in command assert "vision,pdf" in command assert "--save-session" in command assert "--save-trace" in command assert "--save-video" in command assert "on-failure" in command assert "--output-dir" in command assert "--timeout-action" in command assert "10000" in command assert "--timeout-navigation" in command assert "30000" in command assert "--image-responses" in command assert "base64" in command @pytest.mark.asyncio async def test_build_command_headless_false(self, process_manager): """Test that headless flag is not added when headless is False.""" config = {"browser": "chromium", "headless": False} command = await process_manager._build_command(config) assert "--headless" not in command @pytest.mark.asyncio async def test_get_stderr_output_no_process(self, process_manager): """Test getting stderr when no process.""" output = await process_manager.get_stderr_output() assert output == "" @pytest.mark.asyncio async def test_get_stderr_output_no_stderr(self, process_manager): """Test getting stderr when process has no stderr.""" mock_process = Mock() mock_process.stderr = None process_manager.process = mock_process output = await process_manager.get_stderr_output() assert output == "" @pytest.mark.asyncio async def test_get_stderr_output_with_data(self, process_manager): """Test getting stderr output.""" mock_stderr = Mock() async def mock_read(size): # Return empty on second call to break the loop if not hasattr(mock_read, "called"): mock_read.called = True return b"Error output" return b"" mock_stderr.read = mock_read mock_process = Mock() mock_process.stderr = mock_stderr process_manager.process = mock_process output = await process_manager.get_stderr_output() assert "Error output" in output or output == "" @pytest.mark.asyncio async def test_get_stderr_output_handles_errors(self, process_manager): """Test that get_stderr_output handles errors gracefully.""" mock_stderr = Mock() mock_stderr.read = Mock(side_effect=Exception("Read error")) mock_process = Mock() mock_process.stderr = mock_stderr process_manager.process = mock_process output = await process_manager.get_stderr_output() assert output == ""

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nickweedon/playwritght-proxy-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server