Skip to content

Commit 57bbc00

Browse files
committed
test: add tests for proxy handling and context auth storage in browser
1 parent 93e501b commit 57bbc00

File tree

1 file changed

+124
-0
lines changed

1 file changed

+124
-0
lines changed

tests/test_browser/test_browser_base.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
543667
async def test_delete_browser_context(mock_browser):
544668
mock_browser._execute_command = AsyncMock()

0 commit comments

Comments
 (0)