"""Integration tests for ZhihuCrawler stealth functionality. Tests that stealth fingerprint masking is properly integrated into ZhihuCrawler's browser initialization. """ import pytest from unittest.mock import MagicMock, patch, call class TestZhihuCrawlerStealthIntegration: """Tests for stealth integration in ZhihuCrawler.""" def test_crawler_has_stealth_config_param(self): """Test that ZhihuCrawler accepts stealth_config parameter.""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler from kb_workflow.crawler.stealth import StealthConfig config = StealthConfig(stealth_enabled=False) crawler = ZhihuCrawler(stealth_config=config) assert crawler._stealth_config == config def test_crawler_default_stealth_enabled(self): """Test that stealth is enabled by default (None = use defaults).""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler crawler = ZhihuCrawler() # None means use default stealth config (enabled) assert crawler._stealth_config is None def test_crawler_can_disable_stealth(self): """Test that stealth can be explicitly disabled.""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler from kb_workflow.crawler.stealth import StealthConfig disabled_config = StealthConfig(stealth_enabled=False) crawler = ZhihuCrawler(stealth_config=disabled_config) assert crawler._stealth_config.stealth_enabled is False @patch('kb_workflow.crawler.zhihu_crawler.sync_playwright') @patch('kb_workflow.crawler.zhihu_crawler.apply_stealth_sync') def test_start_browser_applies_stealth( self, mock_apply_stealth, mock_sync_playwright, ): """Test that _start_browser_with_proxy calls apply_stealth_sync.""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler # Setup mocks mock_playwright = MagicMock() mock_browser = MagicMock() mock_context = MagicMock() mock_sync_playwright.return_value.start.return_value = mock_playwright mock_playwright.chromium.launch.return_value = mock_browser mock_browser.new_context.return_value = mock_context crawler = ZhihuCrawler() crawler._start_browser_with_proxy(cookie="test_cookie") # Verify apply_stealth_sync was called mock_apply_stealth.assert_called_once() call_args = mock_apply_stealth.call_args assert call_args[0][0] == mock_context # First arg is context @patch('kb_workflow.crawler.zhihu_crawler.sync_playwright') @patch('kb_workflow.crawler.zhihu_crawler.apply_stealth_sync') def test_start_browser_launch_args_include_disable_blink_features( self, mock_apply_stealth, mock_sync_playwright, ): """Test that launch includes --disable-blink-features=AutomationControlled.""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler # Setup mocks mock_playwright = MagicMock() mock_browser = MagicMock() mock_context = MagicMock() mock_sync_playwright.return_value.start.return_value = mock_playwright mock_playwright.chromium.launch.return_value = mock_browser mock_browser.new_context.return_value = mock_context crawler = ZhihuCrawler() crawler._start_browser_with_proxy(cookie="test_cookie") # Verify launch was called with correct args launch_call = mock_playwright.chromium.launch launch_call.assert_called_once() call_kwargs = launch_call.call_args[1] assert 'args' in call_kwargs assert '--disable-blink-features=AutomationControlled' in call_kwargs['args'] @patch('kb_workflow.crawler.zhihu_crawler.sync_playwright') @patch('kb_workflow.crawler.zhihu_crawler.apply_stealth_sync') def test_start_browser_stealth_disabled_no_blink_features_arg( self, mock_apply_stealth, mock_sync_playwright, ): """Test that --disable-blink-features is not added when stealth disabled.""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler from kb_workflow.crawler.stealth import StealthConfig # Setup mocks mock_playwright = MagicMock() mock_browser = MagicMock() mock_context = MagicMock() mock_sync_playwright.return_value.start.return_value = mock_playwright mock_playwright.chromium.launch.return_value = mock_browser mock_browser.new_context.return_value = mock_context disabled_config = StealthConfig(stealth_enabled=False) crawler = ZhihuCrawler(stealth_config=disabled_config) crawler._start_browser_with_proxy(cookie="test_cookie") # Verify launch was called without args or with empty/None args launch_call = mock_playwright.chromium.launch call_kwargs = launch_call.call_args[1] # When stealth disabled, args should be None or not contain the flag if 'args' in call_kwargs and call_kwargs['args']: assert '--disable-blink-features=AutomationControlled' not in call_kwargs['args'] # apply_stealth_sync should still be called but with disabled config mock_apply_stealth.assert_called_once() class TestZhihuCrawlerBackwardCompatibility: """Tests for backward compatibility after stealth integration.""" def test_crawler_works_without_stealth_config(self): """Test that ZhihuCrawler works without explicit stealth_config.""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler # Should not raise any errors crawler = ZhihuCrawler( headless=True, timeout=30 ) assert crawler is not None def test_crawler_accepts_all_existing_params(self): """Test that all existing parameters still work.""" from kb_workflow.crawler.zhihu_crawler import ZhihuCrawler from kb_workflow.crawler.config import CrawlerConfig config = CrawlerConfig() crawler = ZhihuCrawler( headless=False, timeout=60, config=config ) assert crawler.headless is False assert crawler.timeout == 60 assert crawler.config == config # Wave 0 completion marker WAVE_0_COMPLETE = True