@@ -539,6 +539,130 @@ async def test_create_browser_context(mock_browser):
539539 )
540540
541541
542+ @pytest .mark .asyncio
543+ async def test_create_browser_context_with_private_proxy_sanitizes_and_stores_auth (mock_browser ):
544+ mock_browser ._execute_command = AsyncMock ()
545+ mock_browser ._execute_command .return_value = {'result' : {'browserContextId' : 'ctx1' }}
546+
547+ context_id = await mock_browser .create_browser_context (
548+ proxy_server = 'http://user:[email protected] :8080' ,
549+ proxy_bypass_list = 'localhost' ,
550+ )
551+
552+ assert context_id == 'ctx1'
553+ # Should send sanitized proxy (without credentials) to CDP
554+ mock_browser ._execute_command .assert_called_with (
555+ TargetCommands .create_browser_context (
556+ proxy_server = 'http://proxy.example.com:8080' , proxy_bypass_list = 'localhost'
557+ )
558+ )
559+ # Credentials must be stored per-context for later Tab setup
560+ assert mock_browser ._context_proxy_auth ['ctx1' ] == ('user' , 'pass' )
561+
562+
563+ @pytest .mark .asyncio
564+ async def test_create_browser_context_with_private_proxy_no_scheme_sanitizes_and_stores_auth (
565+ mock_browser ,
566+ ):
567+ mock_browser ._execute_command = AsyncMock ()
568+ mock_browser ._execute_command .return_value = {'result' : {'browserContextId' : 'ctx2' }}
569+
570+ # Without scheme -> should default to http://
571+ context_id = await mock_browser .create_browser_context (
572+ proxy_server = 'user:[email protected] :9000' 573+ )
574+
575+ assert context_id == 'ctx2'
576+ mock_browser ._execute_command .assert_called_with (
577+ TargetCommands .create_browser_context (proxy_server = 'http://host.local:9000' , proxy_bypass_list = None )
578+ )
579+ assert mock_browser ._context_proxy_auth ['ctx2' ] == ('user' , 'pwd' )
580+
581+
582+ @pytest .mark .parametrize (
583+ 'input_proxy, expected_sanitized, expected_creds' ,
584+ [
585+ ('username:password@host:8080' , 'http://host:8080' , ('username' , 'password' )),
586+ ('http://username:password@host:8080' , 'http://host:8080' , ('username' , 'password' )),
587+ ('socks5://user:[email protected] :1080' , 'socks5://10.0.0.1:1080' , ('user' , 'pass' )), 588+ ('host:3128' , 'http://host:3128' , None ),
589+ ],
590+ )
591+ def test__sanitize_proxy_and_extract_auth_variants (input_proxy , expected_sanitized , expected_creds ):
592+ sanitized , creds = Browser ._sanitize_proxy_and_extract_auth (input_proxy )
593+ assert sanitized == expected_sanitized
594+ assert creds == expected_creds
595+
596+
597+ @pytest .mark .asyncio
598+ @patch ('pydoll.browser.chromium.base.Tab' )
599+ async def test_new_tab_sets_up_context_proxy_auth_handlers (MockTab , mock_browser ):
600+ # Arrange context credentials
601+ context_id = 'ctx-auth'
602+ mock_browser ._context_proxy_auth [context_id ] = ('u1' , 'p1' )
603+
604+ # Mock CDP create_target response
605+ mock_browser ._connection_handler .execute_command .return_value = {
606+ 'result' : {'targetId' : 'new_page_ctx' }
607+ }
608+
609+ # Fake Tab with async methods
610+ fake_tab = MagicMock ()
611+ fake_tab .enable_fetch_events = AsyncMock ()
612+ fake_tab .on = AsyncMock ()
613+ MockTab .return_value = fake_tab
614+
615+ # Act
616+ tab = await mock_browser .new_tab (browser_context_id = context_id )
617+
618+ # Assert: enable fetch events with auth handling
619+ fake_tab .enable_fetch_events .assert_awaited_once ()
620+ enable_call = fake_tab .enable_fetch_events .await_args
621+ assert enable_call .kwargs .get ('handle_auth' ) is True
622+
623+ # Assert: event handlers registered with temporary=True
624+ from pydoll .protocol .fetch .events import FetchEvent as FE
625+ # First: request paused
626+ assert any (
627+ (c .args [0 ] == FE .REQUEST_PAUSED and c .kwargs .get ('temporary' ) is True )
628+ for c in fake_tab .on .await_args_list
629+ )
630+ # Second: auth required
631+ auth_calls = [c for c in fake_tab .on .await_args_list if c .args [0 ] == FE .AUTH_REQUIRED ]
632+ assert len (auth_calls ) == 1
633+ cb = auth_calls [0 ].args [1 ]
634+ from functools import partial as _partial
635+ assert isinstance (cb , _partial )
636+ assert cb .keywords .get ('proxy_username' ) == 'u1'
637+ assert cb .keywords .get ('proxy_password' ) == 'p1'
638+ assert cb .keywords .get ('tab' ) is fake_tab
639+
640+ # Returned tab is the fake
641+ assert tab is fake_tab
642+
643+
644+ @pytest .mark .asyncio
645+ @patch ('pydoll.browser.chromium.base.Tab' )
646+ async def test_new_tab_without_context_proxy_auth_does_not_setup_handlers (MockTab , mock_browser ):
647+ # No credentials stored for this context
648+ context_id = 'ctx-no-auth'
649+ mock_browser ._context_proxy_auth .pop (context_id , None )
650+
651+ mock_browser ._connection_handler .execute_command .return_value = {
652+ 'result' : {'targetId' : 'new_page2' }
653+ }
654+
655+ fake_tab = MagicMock ()
656+ fake_tab .enable_fetch_events = AsyncMock ()
657+ fake_tab .on = AsyncMock ()
658+ MockTab .return_value = fake_tab
659+
660+ await mock_browser .new_tab (browser_context_id = context_id )
661+
662+ fake_tab .enable_fetch_events .assert_not_called ()
663+ fake_tab .on .assert_not_called ()
664+
665+
542666@pytest .mark .asyncio
543667async def test_delete_browser_context (mock_browser ):
544668 mock_browser ._execute_command = AsyncMock ()
0 commit comments