Skip to content

Commit 20e473c

Browse files
committed
refactor: add overloads for execute_script to improve type safety
1 parent 3b15a3c commit 20e473c

File tree

1 file changed

+66
-12
lines changed

1 file changed

+66
-12
lines changed

pydoll/browser/tab.py

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
TypeAlias,
1313
Union,
1414
cast,
15+
overload,
1516
)
1617

1718
import aiofiles
@@ -32,6 +33,7 @@
3233
IFrameNotFound,
3334
InvalidFileExtension,
3435
InvalidIFrame,
36+
InvalidScriptWithElement,
3537
NetworkEventsNotEnabled,
3638
NoDialogPresent,
3739
NotAnIFrame,
@@ -43,9 +45,13 @@
4345
from pydoll.protocol.network.types import Cookie, CookieParam, NetworkLog
4446
from pydoll.protocol.page.events import PageEvent
4547
from pydoll.protocol.page.responses import CaptureScreenshotResponse, PrintToPDFResponse
46-
from pydoll.protocol.runtime.responses import EvaluateResponse
48+
from pydoll.protocol.runtime.responses import CallFunctionOnResponse, EvaluateResponse
4749
from pydoll.protocol.storage.responses import GetCookiesResponse
48-
from pydoll.utils import decode_base64_to_bytes
50+
from pydoll.utils import (
51+
decode_base64_to_bytes,
52+
has_return_outside_function,
53+
is_script_already_function,
54+
)
4955

5056
if TYPE_CHECKING:
5157
from pydoll.browser.chromium.base import Browser
@@ -554,7 +560,15 @@ async def handle_dialog(self, accept: bool, prompt_text: Optional[str] = None):
554560
PageCommands.handle_javascript_dialog(accept=accept, prompt_text=prompt_text)
555561
)
556562

557-
async def execute_script(self, script: str, element: Optional[WebElement] = None):
563+
@overload
564+
async def execute_script(self, script: str) -> EvaluateResponse: ...
565+
566+
@overload
567+
async def execute_script(self, script: str, element: WebElement) -> CallFunctionOnResponse: ...
568+
569+
async def execute_script(
570+
self, script: str, element: Optional[WebElement] = None
571+
) -> Union[EvaluateResponse, CallFunctionOnResponse]:
558572
"""
559573
Execute JavaScript in page context.
560574
@@ -565,17 +579,17 @@ async def execute_script(self, script: str, element: Optional[WebElement] = None
565579
Examples:
566580
await page.execute_script('argument.click()', element)
567581
await page.execute_script('argument.value = "Hello"', element)
582+
583+
Raises:
584+
InvalidScriptWithElement: If script contains 'argument' but no element is provided.
568585
"""
586+
if 'argument' in script and element is None:
587+
raise InvalidScriptWithElement('Script contains "argument" but no element was provided')
588+
569589
if element:
570-
script = script.replace('argument', 'this')
571-
script = f'function(){{ {script} }}'
572-
object_id = element._object_id
573-
command = RuntimeCommands.call_function_on(
574-
object_id=object_id, function_declaration=script, return_by_value=True
575-
)
576-
else:
577-
command = RuntimeCommands.evaluate(expression=script)
578-
return await self._execute_command(command)
590+
return await self._execute_script_with_element(script, element)
591+
592+
return await self._execute_script_without_element(script)
579593

580594
@asynccontextmanager
581595
async def expect_file_chooser(
@@ -693,6 +707,46 @@ async def callback_wrapper(event):
693707
event_name, function_to_register, temporary
694708
)
695709

710+
async def _execute_script_with_element(self, script: str, element: WebElement):
711+
"""
712+
Execute script with element context.
713+
714+
Args:
715+
script: JavaScript code to execute.
716+
element: Element context (use 'argument' in script to reference).
717+
718+
Returns:
719+
The result of the script execution.
720+
"""
721+
if 'argument' not in script:
722+
raise InvalidScriptWithElement('Script does not contain "argument"')
723+
724+
script = script.replace('argument', 'this')
725+
726+
if not is_script_already_function(script):
727+
script = f'function(){{ {script} }}'
728+
729+
command = RuntimeCommands.call_function_on(
730+
object_id=element._object_id, function_declaration=script, return_by_value=True
731+
)
732+
return await self._execute_command(command)
733+
734+
async def _execute_script_without_element(self, script: str):
735+
"""
736+
Execute script without element context.
737+
738+
Args:
739+
script: JavaScript code to execute.
740+
741+
Returns:
742+
The result of the script execution.
743+
"""
744+
if has_return_outside_function(script):
745+
script = f'(function(){{ {script} }})()'
746+
747+
command = RuntimeCommands.evaluate(expression=script)
748+
return await self._execute_command(command)
749+
696750
async def _refresh_if_url_not_changed(self, url: str) -> bool:
697751
"""Refresh page if URL hasn't changed."""
698752
current_url = await self.current_url

0 commit comments

Comments
 (0)