Skip to content

Commit 1337fb1

Browse files
authored
Merge pull request #169 from autoscrape-labs/fix/set-input-files
Fix context manager to file upload
2 parents d0edfc8 + a0b8e40 commit 1337fb1

File tree

5 files changed

+195
-9
lines changed

5 files changed

+195
-9
lines changed

pydoll/browser/chromium/base.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,13 @@ async def get_opened_tabs(self) -> list[Tab]:
231231
"""
232232
targets = await self.get_targets()
233233
valid_tab_targets = [
234-
target for target in targets if target['type'] == 'page'
235-
and 'extension' not in target['url']
234+
target
235+
for target in targets
236+
if target['type'] == 'page' and 'extension' not in target['url']
236237
]
237238
return [
238-
Tab(self, self._connection_port, target['targetId']) for target
239-
in reversed(valid_tab_targets)
239+
Tab(self, self._connection_port, target['targetId'])
240+
for target in reversed(valid_tab_targets)
240241
]
241242

242243
async def set_download_path(self, path: str, browser_context_id: Optional[str] = None):

pydoll/browser/tab.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
WaitElementTimeout,
4242
)
4343
from pydoll.protocol.base import Response
44+
from pydoll.protocol.dom.types import EventFileChooserOpened
4445
from pydoll.protocol.network.responses import GetResponseBodyResponse
4546
from pydoll.protocol.network.types import Cookie, CookieParam, NetworkLog
4647
from pydoll.protocol.page.events import PageEvent
@@ -678,10 +679,11 @@ async def expect_file_chooser(
678679
files: File path(s) for upload.
679680
"""
680681

681-
async def event_handler(event):
682+
async def event_handler(event: EventFileChooserOpened):
683+
file_list = [str(file) for file in files] if isinstance(files, list) else [str(files)]
682684
await self._execute_command(
683-
DomCommands.upload_files(
684-
files=files,
685+
DomCommands.set_file_input_files(
686+
files=file_list,
685687
backend_node_id=event['params']['backendNodeId'],
686688
)
687689
)
@@ -695,7 +697,11 @@ async def event_handler(event):
695697
if self.intercept_file_chooser_dialog_enabled is False:
696698
await self.enable_intercept_file_chooser_dialog()
697699

698-
await self.on(PageEvent.FILE_CHOOSER_OPENED, event_handler, temporary=True)
700+
await self.on(
701+
PageEvent.FILE_CHOOSER_OPENED,
702+
cast(Callable[[dict], Any], event_handler),
703+
temporary=True,
704+
)
699705

700706
yield
701707

pydoll/protocol/dom/types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,14 @@ class BoxModel(TypedDict):
7979
width: int
8080
height: int
8181
shapeOutside: NotRequired[ShapeOutsideInfo]
82+
83+
84+
class EventFileChooserOpenedParams(TypedDict):
85+
frameId: str
86+
mode: str
87+
backendNodeId: int
88+
89+
90+
class EventFileChooserOpened(TypedDict):
91+
method: str
92+
params: EventFileChooserOpenedParams

pydoll/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ class TextExtractor(HTMLParser):
1919
Extracts visible text content from an HTML string, excluding the contents of
2020
tags specified in _skip_tags.
2121
"""
22+
2223
def __init__(self):
2324
super().__init__()
2425
self._parts = []
2526
self._skip = False
26-
self._skip_tags = {"script", "style", "template"}
27+
self._skip_tags = {'script', 'style', 'template'}
2728

2829
def handle_starttag(self, tag, attrs):
2930
"""

tests/test_browser/test_browser_tab.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,173 @@ async def test_expect_file_chooser_with_path_objects(self, tab):
805805
mock_enable_intercept.assert_called_once()
806806
mock_disable_intercept.assert_called_once()
807807

808+
@pytest.mark.asyncio
809+
async def test_expect_file_chooser_event_handler_single_file(self, tab):
810+
"""Test the real event_handler function with single file."""
811+
from pydoll.protocol.dom.types import EventFileChooserOpened
812+
from pydoll.protocol.page.events import PageEvent
813+
814+
# Mock execute_command to capture the call
815+
tab._execute_command = AsyncMock()
816+
817+
# Create mock event data
818+
mock_event: EventFileChooserOpened = {
819+
'method': 'Page.fileChooserOpened',
820+
'params': {
821+
'frameId': 'test-frame-id',
822+
'mode': 'selectSingle',
823+
'backendNodeId': 12345
824+
}
825+
}
826+
827+
# Capture the real event handler from expect_file_chooser
828+
captured_handler = None
829+
830+
async def mock_on(event_name, handler, temporary=False):
831+
nonlocal captured_handler
832+
if event_name == PageEvent.FILE_CHOOSER_OPENED:
833+
captured_handler = handler
834+
return 123
835+
836+
# Mock the required methods
837+
with patch.object(tab, 'enable_page_events', AsyncMock()):
838+
with patch.object(tab, 'enable_intercept_file_chooser_dialog', AsyncMock()):
839+
with patch.object(tab, 'disable_intercept_file_chooser_dialog', AsyncMock()):
840+
with patch.object(tab, 'disable_page_events', AsyncMock()):
841+
with patch.object(tab, 'on', mock_on):
842+
async with tab.expect_file_chooser('test.txt'):
843+
# Execute the captured real handler
844+
assert captured_handler is not None
845+
await captured_handler(mock_event)
846+
847+
# Verify the command was called correctly
848+
tab._execute_command.assert_called_once()
849+
call_args = tab._execute_command.call_args[0][0]
850+
assert call_args['method'] == 'DOM.setFileInputFiles'
851+
assert call_args['params']['files'] == ['test.txt']
852+
assert call_args['params']['backendNodeId'] == 12345
853+
854+
@pytest.mark.asyncio
855+
async def test_expect_file_chooser_event_handler_multiple_files(self, tab):
856+
"""Test the real event_handler function with multiple files."""
857+
from pydoll.protocol.dom.types import EventFileChooserOpened
858+
from pydoll.protocol.page.events import PageEvent
859+
860+
# Mock execute_command to capture the call
861+
tab._execute_command = AsyncMock()
862+
863+
# Create mock event data
864+
mock_event: EventFileChooserOpened = {
865+
'method': 'Page.fileChooserOpened',
866+
'params': {
867+
'frameId': 'test-frame-id',
868+
'mode': 'selectMultiple',
869+
'backendNodeId': 67890
870+
}
871+
}
872+
873+
# Capture the real event handler from expect_file_chooser
874+
captured_handler = None
875+
876+
async def mock_on(event_name, handler, temporary=False):
877+
nonlocal captured_handler
878+
if event_name == PageEvent.FILE_CHOOSER_OPENED:
879+
captured_handler = handler
880+
return 123
881+
882+
# Mock the required methods
883+
with patch.object(tab, 'enable_page_events', AsyncMock()):
884+
with patch.object(tab, 'enable_intercept_file_chooser_dialog', AsyncMock()):
885+
with patch.object(tab, 'disable_intercept_file_chooser_dialog', AsyncMock()):
886+
with patch.object(tab, 'disable_page_events', AsyncMock()):
887+
with patch.object(tab, 'on', mock_on):
888+
async with tab.expect_file_chooser(['file1.txt', 'file2.pdf', 'file3.jpg']):
889+
# Execute the captured real handler
890+
assert captured_handler is not None
891+
await captured_handler(mock_event)
892+
893+
# Verify the command was called correctly
894+
tab._execute_command.assert_called_once()
895+
call_args = tab._execute_command.call_args[0][0]
896+
assert call_args['method'] == 'DOM.setFileInputFiles'
897+
assert call_args['params']['files'] == ['file1.txt', 'file2.pdf', 'file3.jpg']
898+
assert call_args['params']['backendNodeId'] == 67890
899+
900+
async def _test_event_handler_with_files(self, tab, files, expected_files, backend_node_id):
901+
"""Helper method to test event handler with different file types."""
902+
from pydoll.protocol.dom.types import EventFileChooserOpened
903+
from pydoll.protocol.page.events import PageEvent
904+
905+
# Mock execute_command to capture the call
906+
tab._execute_command = AsyncMock()
907+
908+
# Create mock event data
909+
mock_event: EventFileChooserOpened = {
910+
'method': 'Page.fileChooserOpened',
911+
'params': {
912+
'frameId': 'test-frame-id',
913+
'mode': 'selectMultiple',
914+
'backendNodeId': backend_node_id
915+
}
916+
}
917+
918+
# Capture the real event handler from expect_file_chooser
919+
captured_handler = None
920+
921+
async def mock_on(event_name, handler, temporary=False):
922+
nonlocal captured_handler
923+
if event_name == PageEvent.FILE_CHOOSER_OPENED:
924+
captured_handler = handler
925+
return 123
926+
927+
# Mock the required methods
928+
with patch.object(tab, 'enable_page_events', AsyncMock()):
929+
with patch.object(tab, 'enable_intercept_file_chooser_dialog', AsyncMock()):
930+
with patch.object(tab, 'disable_intercept_file_chooser_dialog', AsyncMock()):
931+
with patch.object(tab, 'disable_page_events', AsyncMock()):
932+
with patch.object(tab, 'on', mock_on):
933+
async with tab.expect_file_chooser(files):
934+
# Execute the captured real handler
935+
assert captured_handler is not None
936+
await captured_handler(mock_event)
937+
938+
# Verify the command was called correctly
939+
tab._execute_command.assert_called_once()
940+
call_args = tab._execute_command.call_args[0][0]
941+
assert call_args['method'] == 'DOM.setFileInputFiles'
942+
assert call_args['params']['files'] == expected_files
943+
assert call_args['params']['backendNodeId'] == backend_node_id
944+
945+
@pytest.mark.asyncio
946+
async def test_expect_file_chooser_event_handler_path_objects(self, tab):
947+
"""Test the real event_handler function with Path objects."""
948+
from pathlib import Path
949+
950+
files = [Path('documents/file1.txt'), Path('images/file2.jpg')]
951+
expected_files = ['documents/file1.txt', 'images/file2.jpg']
952+
953+
await self._test_event_handler_with_files(tab, files, expected_files, 54321)
954+
955+
@pytest.mark.asyncio
956+
async def test_expect_file_chooser_event_handler_single_path_object(self, tab):
957+
"""Test the real event_handler function with single Path object."""
958+
from pathlib import Path
959+
960+
files = Path('documents/important.pdf')
961+
expected_files = ['documents/important.pdf']
962+
963+
await self._test_event_handler_with_files(tab, files, expected_files, 98765)
964+
965+
@pytest.mark.asyncio
966+
async def test_expect_file_chooser_event_handler_empty_list(self, tab):
967+
"""Test the real event_handler function with empty file list."""
968+
files = []
969+
expected_files = []
970+
971+
await self._test_event_handler_with_files(tab, files, expected_files, 11111)
972+
973+
974+
808975

809976
class TestTabCloudflareBypass:
810977
"""Test Tab Cloudflare bypass functionality."""

0 commit comments

Comments
 (0)