"""
Simple browser tools tests with mocked components.
"""
import pytest
from unittest.mock import AsyncMock, patch
from typing import Any, cast
from src.percepta_mcp.tools.browser_tools import BrowserAutomation
from src.percepta_mcp.config import Settings, BrowserConfig
from tests.patches.async_mocks import PlaywrightMock
@pytest.fixture
def settings() -> Settings:
"""Create test settings."""
return Settings(
ai_providers=[],
browser=BrowserConfig(
browser_type="chromium",
headless=True,
timeout=30000
)
)
class TestBrowserAutomationSimple:
"""Test browser automation with pre-mocked components."""
@pytest.mark.asyncio
async def test_navigate_with_mocked_page(self, settings: Settings) -> None:
"""Test navigation with mocked page."""
browser_automation = BrowserAutomation(settings)
# Mock the page directly
mock_page = AsyncMock()
mock_response = AsyncMock()
mock_response.status = 200
mock_page.goto.return_value = mock_response
mock_page.title.return_value = "Test Page"
mock_page.url = "https://example.com/" # 设置正确的URL格式,包含尾部斜杠
# 避免触发 _ensure_browser 逻辑
browser_automation.page = mock_page
browser_automation.browser = AsyncMock()
result = await browser_automation.navigate("https://example.com")
assert result["success"] is True
assert result["url"] == "https://example.com/" # 修正期望值匹配实际URL
assert result["status"] == 200
assert result["title"] == "Test Page"
@pytest.mark.asyncio
async def test_navigate_error_handling(self, settings: Settings) -> None:
"""Test navigation error handling."""
browser_automation = BrowserAutomation(settings)
# Mock the page with error
mock_page = AsyncMock()
mock_page.goto.side_effect = Exception("Navigation failed")
# 模拟已初始化状态,避免调用 _ensure_browser
browser_automation.page = mock_page
browser_automation.browser = AsyncMock()
# 模拟 _ensure_browser 的行为
async def mock_ensure_browser():
pass
browser_automation._ensure_browser = mock_ensure_browser
result = await browser_automation.navigate("https://example.com")
assert result["success"] is False
assert "Navigation failed" in result["error"]
@pytest.mark.asyncio
async def test_click_element(self, settings: Settings) -> None:
"""Test element clicking."""
browser_automation = BrowserAutomation(settings)
# 创建正确的模拟对象
mock_page = AsyncMock()
mock_element = AsyncMock()
mock_page.wait_for_selector.return_value = mock_element
# 模拟已初始化状态
browser_automation.page = mock_page
browser_automation.browser = AsyncMock()
# 模拟 _ensure_browser 方法
async def mock_ensure_browser():
pass
browser_automation._ensure_browser = mock_ensure_browser
result = await browser_automation.click("#button")
assert result["success"] is True
mock_page.wait_for_selector.assert_called_once_with("#button", timeout=30000)
mock_page.click.assert_called_once_with("#button")
@pytest.mark.asyncio
async def test_fill_element(self, settings: Settings) -> None:
"""Test form filling."""
browser_automation = BrowserAutomation(settings)
mock_page = AsyncMock()
mock_element = AsyncMock()
mock_page.wait_for_selector.return_value = mock_element
# 模拟已初始化状态
browser_automation.page = mock_page
browser_automation.browser = AsyncMock()
# 模拟 _ensure_browser 方法
async def mock_ensure_browser():
pass
browser_automation._ensure_browser = mock_ensure_browser
result = await browser_automation.fill("#input", "test text")
assert result["success"] is True
mock_page.wait_for_selector.assert_called_once_with("#input", timeout=30000)
mock_page.fill.assert_called_once_with("#input", "test text")
@pytest.mark.asyncio
async def test_screenshot(self, settings: Settings) -> None:
"""Test screenshot functionality."""
browser_automation = BrowserAutomation(settings)
mock_page = AsyncMock()
fake_image_data = b"fake_png_data"
mock_page.screenshot.return_value = fake_image_data
mock_page.url = "https://example.com/"
mock_page.title.return_value = "Test Page"
# 模拟已初始化状态
browser_automation.page = mock_page
browser_automation.browser = AsyncMock()
# 模拟 _ensure_browser 方法
async def mock_ensure_browser():
pass
browser_automation._ensure_browser = mock_ensure_browser
result = await browser_automation.screenshot(full_page=True)
assert result["success"] is True
# 正确检查返回字段(browser_tools.py 返回 "image" 而非 "screenshot")
assert "image" in result
assert result["mime_type"] == "image/png"
mock_page.screenshot.assert_called_once_with(
full_page=True,
type="png"
)
@pytest.mark.asyncio
async def test_extract_text(self, settings: Settings) -> None:
"""Test text extraction."""
browser_automation = BrowserAutomation(settings)
mock_page = AsyncMock()
mock_element = AsyncMock()
mock_element.text_content.return_value = "Extracted text"
mock_page.wait_for_selector.return_value = mock_element
mock_page.url = "https://example.com/"
# 模拟已初始化状态
browser_automation.page = mock_page
browser_automation.browser = AsyncMock()
# 模拟 _ensure_browser 方法
async def mock_ensure_browser():
pass
browser_automation._ensure_browser = mock_ensure_browser
result = await browser_automation.extract_text("#content")
assert result["success"] is True
assert result["text"] == "Extracted text"
mock_page.wait_for_selector.assert_called_once_with("#content")
@pytest.mark.asyncio
async def test_page_not_initialized(self, settings: Settings) -> None:
"""Test error when page is not initialized."""
browser_automation = BrowserAutomation(settings)
# 模拟 _ensure_browser 方法,但设置 page 为 None
async def mock_ensure_browser():
browser_automation.browser = AsyncMock()
browser_automation.page = None
browser_automation._ensure_browser = mock_ensure_browser
result = await browser_automation.navigate("https://example.com")
assert result["success"] is False
assert "Page not initialized" in result["error"]
@pytest.mark.asyncio
async def test_close_cleanup(self, settings: Settings) -> None:
"""Test cleanup on close."""
browser_automation = BrowserAutomation(settings)
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_page = AsyncMock()
mock_playwright = AsyncMock()
browser_automation.browser = mock_browser
browser_automation.context = mock_context
browser_automation.page = mock_page
browser_automation.playwright = mock_playwright
await browser_automation.close()
mock_page.close.assert_called_once()
mock_context.close.assert_called_once()
mock_browser.close.assert_called_once()
mock_playwright.stop.assert_called_once()