"""
Simplified browser tools tests that avoid real browser startup.
"""
import pytest
import base64
from unittest.mock import AsyncMock, MagicMock, 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():
"""Create test settings."""
return Settings(
ai_providers=[],
browser=BrowserConfig(
browser_type="chromium",
headless=True,
timeout=30000
)
)
class TestBrowserAutomationUnit:
"""Unit tests for browser automation with full mocking."""
def setup_browser_mocks(self, browser_automation):
"""Set up all required browser mocks."""
# 创建和设置所有必要的模拟对象
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_playwright = PlaywrightMock()
# 设置所有需要的属性
browser_automation.browser = mock_browser
browser_automation.context = mock_context
browser_automation.playwright = mock_playwright
@pytest.mark.asyncio
async def test_init(self, settings):
"""Test initialization."""
browser_automation = BrowserAutomation(settings)
assert browser_automation.settings == settings
assert browser_automation.browser is None
assert browser_automation.context is None
assert browser_automation.page is None
assert browser_automation.playwright is None
@pytest.mark.asyncio
async def test_navigate_success(self, settings):
"""Test successful navigation."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
# Create mock page with all required methods
mock_page = AsyncMock()
mock_response = AsyncMock()
mock_response.status = 200
mock_page.goto = AsyncMock(return_value=mock_response)
mock_page.title = AsyncMock(return_value="Test Page")
mock_page.url = "https://example.com"
# 设置页面模拟对象
browser_automation.page = mock_page
result = await browser_automation.navigate("https://example.com")
assert result["success"] is True
assert result["url"] == "https://example.com"
assert result["title"] == "Test Page"
assert result["status"] == 200
mock_page.goto.assert_called_once_with("https://example.com", wait_until="networkidle", timeout=30000)
@pytest.mark.asyncio
async def test_navigate_error(self, settings):
"""Test navigation error handling."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
mock_page = AsyncMock()
mock_page.goto.side_effect = Exception("Navigation failed")
browser_automation.page = mock_page
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_success(self, settings):
"""Test successful element click."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
mock_page = AsyncMock()
mock_element = AsyncMock()
mock_page.wait_for_selector = AsyncMock(return_value=mock_element)
mock_page.click = AsyncMock()
browser_automation.page = mock_page
result = await browser_automation.click("#button")
assert result["success"] is True
assert result["selector"] == "#button"
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_click_error(self, settings):
"""Test click error handling."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
mock_page = AsyncMock()
mock_page.wait_for_selector.side_effect = Exception("Element not found")
browser_automation.page = mock_page
result = await browser_automation.click("#missing")
assert result["success"] is False
assert "Element not found" in result["error"]
assert result["selector"] == "#missing"
@pytest.mark.asyncio
async def test_fill_success(self, settings):
"""Test successful form filling."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
mock_page = AsyncMock()
mock_element = AsyncMock()
mock_page.wait_for_selector = AsyncMock(return_value=mock_element)
mock_page.fill = AsyncMock()
browser_automation.page = mock_page
result = await browser_automation.fill("#input", "test text")
assert result["success"] is True
assert result["selector"] == "#input"
assert result["text_length"] == 9
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_success(self, settings):
"""Test successful screenshot."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
mock_page = AsyncMock()
fake_image_data = b"fake_png_data"
mock_page.screenshot = AsyncMock(return_value=fake_image_data)
mock_page.url = "https://example.com"
mock_page.title = AsyncMock(return_value="Test Page")
browser_automation.page = mock_page
result = await browser_automation.screenshot(full_page=True)
assert result["success"] is True
assert result["image"] == base64.b64encode(fake_image_data).decode('utf-8')
assert result["mime_type"] == "image/png"
assert result["url"] == "https://example.com"
assert result["title"] == "Test Page"
mock_page.screenshot.assert_called_once_with(full_page=True, type="png")
@pytest.mark.asyncio
async def test_extract_text_with_selector(self, settings):
"""Test text extraction with selector."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
mock_page = AsyncMock()
mock_element = AsyncMock()
mock_element.text_content = AsyncMock(return_value="Extracted text")
mock_page.wait_for_selector = AsyncMock(return_value=mock_element)
mock_page.url = "https://example.com"
browser_automation.page = mock_page
result = await browser_automation.extract_text("#content")
assert result["success"] is True
assert result["text"] == "Extracted text"
assert result["selector"] == "#content"
assert result["url"] == "https://example.com"
mock_page.wait_for_selector.assert_called_once_with("#content")
mock_element.text_content.assert_called_once()
@pytest.mark.asyncio
async def test_extract_text_full_page(self, settings):
"""Test full page text extraction."""
browser_automation = BrowserAutomation(settings)
# 设置所有必要的模拟对象
self.setup_browser_mocks(browser_automation)
mock_page = AsyncMock()
mock_page.text_content = AsyncMock(return_value="Full page text")
mock_page.url = "https://example.com"
browser_automation.page = mock_page
result = await browser_automation.extract_text()
assert result["success"] is True
assert result["text"] == "Full page text"
assert result["selector"] is None
mock_page.text_content.assert_called_once_with('body')
@pytest.mark.asyncio
async def test_page_not_initialized(self, settings):
"""Test error when page is not initialized."""
browser_automation = BrowserAutomation(settings)
# 设置浏览器但不设置页面
self.setup_browser_mocks(browser_automation)
browser_automation.page = None
result = await browser_automation.click("#button")
assert result["success"] is False
assert "Page not initialized" in result["error"]
@pytest.mark.asyncio
async def test_close_cleanup(self, settings):
"""Test cleanup on close."""
browser_automation = BrowserAutomation(settings)
mock_page = AsyncMock()
mock_context = AsyncMock()
mock_browser = AsyncMock()
mock_playwright = AsyncMock()
browser_automation.page = mock_page
browser_automation.context = mock_context
browser_automation.browser = mock_browser
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()
assert browser_automation.page is None
assert browser_automation.context is None
assert browser_automation.browser is None
assert browser_automation.playwright is None