|
|
@@ -1,4 +1,6 @@
|
|
|
-from unittest.mock import MagicMock, patch
|
|
|
+import asyncio
|
|
|
+import pytest
|
|
|
+from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
from app.use_cases.camoufox.wrapper import (
|
|
|
CamoufoxWrapper,
|
|
|
_detect_headless_mode,
|
|
|
@@ -6,6 +8,10 @@ from app.use_cases.camoufox.wrapper import (
|
|
|
)
|
|
|
|
|
|
|
|
|
+def _run_async(coro):
|
|
|
+ return asyncio.run(coro)
|
|
|
+
|
|
|
+
|
|
|
class TestDetectHeadlessMode:
|
|
|
@patch("sys.platform", "darwin")
|
|
|
def test_macos_returns_true(self):
|
|
|
@@ -94,3 +100,134 @@ class TestCamoufoxWrapper:
|
|
|
assert result["server"] == "http://host:8080"
|
|
|
assert result["username"] == "user"
|
|
|
assert "password" not in result
|
|
|
+
|
|
|
+
|
|
|
+class TestCamoufoxWrapperScreenshot:
|
|
|
+ def test_get_screenshot_quality(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ assert wrapper._get_screenshot_quality() == 60
|
|
|
+
|
|
|
+ def test_apply_screenshot_delay_zero(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
|
|
+ _run_async(wrapper._apply_screenshot_delay({"screenshotDelay": 0}))
|
|
|
+ mock_sleep.assert_not_awaited()
|
|
|
+
|
|
|
+ def test_apply_screenshot_delay_positive(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
|
|
+ _run_async(wrapper._apply_screenshot_delay({"screenshotDelay": 500}))
|
|
|
+ mock_sleep.assert_awaited_once_with(0.5)
|
|
|
+
|
|
|
+ def test_click_element_no_selector(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = MagicMock()
|
|
|
+ _run_async(wrapper._click_element(page, {}))
|
|
|
+ page.wait_for_selector.assert_not_called()
|
|
|
+ page.click.assert_not_called()
|
|
|
+
|
|
|
+ def test_click_element_success(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ _run_async(wrapper._click_element(page, {"clickSelector": "button#btn", "selectorTimeout": 2000}))
|
|
|
+ page.wait_for_selector.assert_awaited_once_with("button#btn", timeout=2000)
|
|
|
+ page.click.assert_awaited_once_with("button#btn")
|
|
|
+
|
|
|
+ def test_click_element_failure_logs_warning(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ page.wait_for_selector.side_effect = Exception("timeout")
|
|
|
+ with patch("app.use_cases.camoufox.wrapper.log.logger.warning") as mock_warn:
|
|
|
+ _run_async(wrapper._click_element(page, {"clickSelector": "button#btn"}))
|
|
|
+ mock_warn.assert_called_once()
|
|
|
+
|
|
|
+ def test_take_screenshot_full_page(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ page.screenshot.return_value = b"fullpage"
|
|
|
+ result = _run_async(wrapper._take_screenshot(page, {}))
|
|
|
+ page.screenshot.assert_awaited_once_with(full_page=True, type="jpeg", quality=60)
|
|
|
+ assert result == b"fullpage"
|
|
|
+
|
|
|
+ def test_take_screenshot_by_selector(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ element = AsyncMock()
|
|
|
+ element.screenshot.return_value = b"element"
|
|
|
+ page.query_selector.return_value = element
|
|
|
+ result = _run_async(
|
|
|
+ wrapper._take_screenshot(page, {"screenshotSelector": "#product", "selectorTimeout": 2000})
|
|
|
+ )
|
|
|
+ page.wait_for_selector.assert_awaited_once_with("#product", timeout=2000)
|
|
|
+ page.query_selector.assert_awaited_once_with("#product")
|
|
|
+ element.screenshot.assert_awaited_once_with(type="jpeg", quality=60)
|
|
|
+ assert result == b"element"
|
|
|
+
|
|
|
+ def test_take_screenshot_selector_fallback_to_full_page(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ page.query_selector.return_value = None
|
|
|
+ page.screenshot.return_value = b"fallback"
|
|
|
+ with patch("app.use_cases.camoufox.wrapper.log.logger.warning") as mock_warn:
|
|
|
+ result = _run_async(
|
|
|
+ wrapper._take_screenshot(page, {"screenshotSelector": "#missing"})
|
|
|
+ )
|
|
|
+ mock_warn.assert_called_once()
|
|
|
+ page.screenshot.assert_awaited_once_with(full_page=True, type="jpeg", quality=60)
|
|
|
+ assert result == b"fallback"
|
|
|
+
|
|
|
+ def test_take_screenshot_with_delay(self):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ page.screenshot.return_value = b"delayed"
|
|
|
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
|
|
+ result = _run_async(wrapper._take_screenshot(page, {"screenshotDelay": 300}))
|
|
|
+ mock_sleep.assert_awaited_once_with(0.3)
|
|
|
+ assert result == b"delayed"
|
|
|
+
|
|
|
+
|
|
|
+class TestCamoufoxWrapperVisitScreenshotFlow:
|
|
|
+ @patch("app.use_cases.camoufox.wrapper._resolve_headless_mode", return_value=True)
|
|
|
+ @patch("app.use_cases.camoufox.wrapper.AsyncCamoufox")
|
|
|
+ @patch("app.use_cases.camoufox.wrapper.base64.b64encode", return_value=b"base64data")
|
|
|
+ def test_visit_triggers_screenshot_with_screenshot_selector(
|
|
|
+ self, mock_b64encode, mock_browser_cls, mock_headless
|
|
|
+ ):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ element = AsyncMock()
|
|
|
+ element.screenshot.return_value = b"element-shot"
|
|
|
+ page.query_selector.return_value = element
|
|
|
+ page.content.return_value = "<html></html>"
|
|
|
+ browser = AsyncMock()
|
|
|
+ browser.new_page.return_value = page
|
|
|
+ mock_browser_cls.return_value.__aenter__ = AsyncMock(return_value=browser)
|
|
|
+ mock_browser_cls.return_value.__aexit__ = AsyncMock(return_value=False)
|
|
|
+
|
|
|
+ result = _run_async(
|
|
|
+ wrapper.visit("https://example.com", {"screenshotSelector": "#product"})
|
|
|
+ )
|
|
|
+
|
|
|
+ page.wait_for_selector.assert_awaited_once_with("#product", timeout=1000)
|
|
|
+ element.screenshot.assert_awaited_once()
|
|
|
+ assert result["screenshot"] == "base64data"
|
|
|
+
|
|
|
+ @patch("app.use_cases.camoufox.wrapper._resolve_headless_mode", return_value=True)
|
|
|
+ @patch("app.use_cases.camoufox.wrapper.AsyncCamoufox")
|
|
|
+ def test_visit_triggers_click_then_screenshot(
|
|
|
+ self, mock_browser_cls, mock_headless
|
|
|
+ ):
|
|
|
+ wrapper = CamoufoxWrapper()
|
|
|
+ page = AsyncMock()
|
|
|
+ page.content.return_value = "<html></html>"
|
|
|
+ page.screenshot.return_value = b"shot"
|
|
|
+ browser = AsyncMock()
|
|
|
+ browser.new_page.return_value = page
|
|
|
+ mock_browser_cls.return_value.__aenter__ = AsyncMock(return_value=browser)
|
|
|
+ mock_browser_cls.return_value.__aexit__ = AsyncMock(return_value=False)
|
|
|
+
|
|
|
+ _run_async(wrapper.visit("https://example.com", {"clickSelector": "button"}))
|
|
|
+
|
|
|
+ page.wait_for_selector.assert_any_await("button", timeout=1000)
|
|
|
+ page.click.assert_awaited_once_with("button")
|
|
|
+ page.screenshot.assert_awaited_once()
|