feat(browser): migrate to CloakBrowser for browser emulation and streamline dependency management

- Replace Playwright-based browser emulation with CloakBrowser as default
- Update config to support CloakBrowser options and humanization presets
- Refactor browser helper to use CloakBrowser context and remove cf_clearance dependency
- Update Dockerfile, entrypoint, and update scripts to install CloakBrowser runtime
- Ensure CloakBrowser kernel is pre-installed during local setup and dependency updates
- Add tests for CloakBrowser integration and legacy compatibility
This commit is contained in:
jxxghp
2026-05-16 20:51:38 +08:00
parent 9069dccb2a
commit 0ee9fec1d2
10 changed files with 293 additions and 140 deletions

View File

@@ -0,0 +1,95 @@
from __future__ import annotations
import unittest
from unittest.mock import patch
from app.helper.browser import PlaywrightHelper
class _FakePage:
def __init__(self) -> None:
self.headers = None
self.loaded_url = None
self.closed = False
def set_extra_http_headers(self, headers: dict[str, str]) -> None:
self.headers = headers
def goto(self, url: str) -> None:
self.loaded_url = url
def wait_for_load_state(self, _state: str, timeout: int) -> None:
self.timeout = timeout
def content(self) -> str:
return "<html>ok</html>"
def close(self) -> None:
self.closed = True
class _FakeContext:
def __init__(self, page: _FakePage) -> None:
self.page = page
self.closed = False
def new_page(self) -> _FakePage:
return self.page
def close(self) -> None:
self.closed = True
class BrowserHelperTests(unittest.TestCase):
def _assert_get_page_source_uses_cloakbrowser(self, emulation: str) -> None:
page = _FakePage()
context = _FakeContext(page)
with patch("app.helper.browser.settings.BROWSER_EMULATION", emulation), \
patch.object(
PlaywrightHelper,
"_PlaywrightHelper__launch_cloakbrowser_context",
return_value=context,
) as launch_context:
source = PlaywrightHelper().get_page_source(
url="https://example.com",
cookies="uid=1",
ua="UA",
timeout=3,
)
self.assertEqual(source, "<html>ok</html>")
launch_context.assert_called_once_with(
headless=False,
user_agent="UA",
proxies=None,
)
self.assertEqual(page.headers, {"cookie": "uid=1"})
self.assertEqual(page.loaded_url, "https://example.com")
self.assertTrue(page.closed)
self.assertTrue(context.closed)
def test_default_emulation_uses_cloakbrowser_context(self):
self._assert_get_page_source_uses_cloakbrowser("cloakbrowser")
def test_legacy_playwright_emulation_uses_cloakbrowser_context(self):
self._assert_get_page_source_uses_cloakbrowser("Playwright")
def test_legacy_browser_type_constructor_is_accepted(self):
page = _FakePage()
context = _FakeContext(page)
with patch.object(
PlaywrightHelper,
"_PlaywrightHelper__launch_cloakbrowser_context",
return_value=context,
):
source = PlaywrightHelper(browser_type="firefox").get_page_source(
url="https://example.com"
)
self.assertEqual(source, "<html>ok</html>")
if __name__ == "__main__":
unittest.main()

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import importlib.util
import tempfile
import unittest
import uuid
from pathlib import Path
@@ -58,6 +59,35 @@ class LocalSetupConfigDirTests(unittest.TestCase):
self.assertIsNone(result)
prompt_mock.assert_not_called()
def test_install_deps_installs_browser_runtime(self):
module = load_local_setup_module()
with tempfile.TemporaryDirectory() as temp_dir:
venv_dir = (Path(temp_dir) / "venv").resolve()
venv_python = venv_dir / "bin" / "python"
venv_pip = venv_dir / "bin" / "pip"
with patch.object(module, "ensure_supported_python"), \
patch.object(
module,
"configure_venv_pip_compat",
return_value=venv_pip,
), \
patch.object(module, "run") as run_mock, \
patch.object(module, "install_browser_runtime") as install_browser:
result = module.install_deps(
python_bin="python3",
venv_dir=venv_dir,
recreate=False,
)
self.assertEqual(result, venv_python)
run_mock.assert_any_call(["python3", "-m", "venv", str(venv_dir)])
run_mock.assert_any_call(
[str(venv_pip), "install", "-r", str(module.ROOT / "requirements.txt")]
)
install_browser.assert_called_once_with(venv_python)
if __name__ == "__main__":
unittest.main()