Skip to content

Commit c5fa956

Browse files
committed
refactor: refactor JavaScript execution and introduce runtime commands
1 parent 537dabe commit c5fa956

File tree

5 files changed

+81
-56
lines changed

5 files changed

+81
-56
lines changed

pydoll/browser/page.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
from pydoll.commands.fetch import FetchCommands
88
from pydoll.commands.network import NetworkCommands
99
from pydoll.commands.page import PageCommands
10+
from pydoll.commands.runtime import RuntimeCommands
1011
from pydoll.commands.storage import StorageCommands
1112
from pydoll.connection import ConnectionHandler
13+
from pydoll.element import WebElement
1214
from pydoll.events.page import PageEvents
1315
from pydoll.mixins.find_elements import FindElementsMixin
1416
from pydoll.utils import decode_image_to_bytes
@@ -328,20 +330,25 @@ async def callback_wrapper(event):
328330
event_name, function_to_register, temporary
329331
)
330332

331-
async def execute_js_script(self, script: str) -> dict:
333+
async def execute_script(self, script: str, element: WebElement = None):
332334
"""
333335
Executes a JavaScript script in the page.
334336
335337
Args:
336338
script (str): The JavaScript script to execute.
337-
338-
Returns:
339-
dict: The result of the JavaScript script execution.
340339
"""
341-
command = {
342-
'method': 'Runtime.evaluate',
343-
'params': {'expression': script, 'returnByValue': True},
344-
}
340+
if element:
341+
script = script.replace('argument', 'this')
342+
script = f'function(){{ {script} }}'
343+
object_id = element._node['objectId']
344+
command = RuntimeCommands.call_function_on(
345+
object_id, script, return_by_value=True
346+
)
347+
else:
348+
command = {
349+
'method': 'Runtime.evaluate',
350+
'params': {'expression': script, 'returnByValue': True},
351+
}
345352
return await self._execute_command(command)
346353

347354
async def _wait_page_load(self, timeout: int = 300):

pydoll/commands/dom.py

Lines changed: 6 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import copy
22
from typing import Literal
33

4+
from pydoll.commands.runtime import RuntimeCommands
45
from pydoll.constants import By
56

67

@@ -26,14 +27,9 @@ class DomCommands:
2627
'method': 'DOM.querySelectorAll',
2728
'params': {},
2829
}
29-
EVALUATE_TEMPLATE = {'method': 'Runtime.evaluate', 'params': {}}
3030
BOX_MODEL_TEMPLATE = {'method': 'DOM.getBoxModel', 'params': {}}
3131
RESOLVE_NODE_TEMPLATE = {'method': 'DOM.resolveNode', 'params': {}}
3232
REQUEST_NODE_TEMPLATE = {'method': 'DOM.requestNode', 'params': {}}
33-
CALL_FUNCTION_ON_TEMPLATE = {
34-
'method': 'Runtime.callFunctionOn',
35-
'params': {},
36-
}
3733
GET_OUTER_HTML = {
3834
'method': 'DOM.getOuterHTML',
3935
'params': {},
@@ -42,17 +38,6 @@ class DomCommands:
4238
'method': 'DOM.scrollIntoViewIfNeeded',
4339
'params': {},
4440
}
45-
GET_PROPERTIES = {
46-
'method': 'Runtime.getProperties',
47-
'params': {},
48-
}
49-
50-
@classmethod
51-
def get_properties(cls, object_id: str) -> dict:
52-
"""Generates the command to get the properties of a specific object."""
53-
command = cls._create_command(cls.GET_PROPERTIES, object_id=object_id)
54-
command['params']['ownProperties'] = True
55-
return command
5641

5742
@classmethod
5843
def scroll_into_view(
@@ -65,20 +50,6 @@ def scroll_into_view(
6550
object_id=object_id,
6651
)
6752

68-
@classmethod
69-
def call_function_on(
70-
cls,
71-
object_id: str,
72-
function_declaration: str,
73-
return_by_value: bool = False,
74-
) -> dict:
75-
"""Generates the command to call a function on a specific object."""
76-
command = cls._create_command(cls.CALL_FUNCTION_ON_TEMPLATE)
77-
command['params']['objectId'] = object_id
78-
command['params']['functionDeclaration'] = function_declaration
79-
command['params']['returnByValue'] = return_by_value
80-
return command
81-
8253
@classmethod
8354
def get_outer_html(cls, node_id: int) -> dict:
8455
"""Generates the command to get the outer HTML"""
@@ -123,7 +94,7 @@ def enable_dom_events(cls) -> dict:
12394
@classmethod
12495
def get_current_url(cls) -> dict:
12596
"""Generates the command to get the current URL of the page."""
126-
return cls.evaluate_js('window.location.href')
97+
return RuntimeCommands.evaluate_script('window.location.href')
12798

12899
@classmethod
129100
def find_element(
@@ -184,16 +155,6 @@ def resolve_node(cls, node_id: int) -> dict:
184155
"""Generates the command to resolve a specific DOM node."""
185156
return cls._create_command(cls.RESOLVE_NODE_TEMPLATE, node_id=node_id)
186157

187-
@classmethod
188-
def evaluate_js(cls, expression: str) -> dict:
189-
"""Generates the command to evaluate JavaScript code."""
190-
command = copy.deepcopy(cls.EVALUATE_TEMPLATE)
191-
command['params'] = {
192-
'expression': expression,
193-
'returnByValue': False,
194-
}
195-
return command
196-
197158
@classmethod
198159
def _create_command(
199160
cls,
@@ -230,7 +191,7 @@ def _find_element_by_xpath(cls, xpath: str, object_id: str) -> dict:
230191
escaped_value = xpath.replace('"', '\\"')
231192
if object_id:
232193
command = cls._create_command(
233-
cls.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
194+
RuntimeCommands.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
234195
)
235196
command['params']['functionDeclaration'] = (
236197
'function() {'
@@ -241,7 +202,7 @@ def _find_element_by_xpath(cls, xpath: str, object_id: str) -> dict:
241202
'}'
242203
)
243204
else:
244-
command = cls._create_command(cls.EVALUATE_TEMPLATE)
205+
command = cls._create_command(RuntimeCommands.EVALUATE_TEMPLATE)
245206
command['params']['expression'] = (
246207
'var element = document.evaluate('
247208
f'"{escaped_value}", document, null, '
@@ -268,7 +229,7 @@ def _find_elements_by_xpath(cls, xpath: str, object_id: str) -> dict:
268229
escaped_value = xpath.replace('"', '\\"')
269230
if object_id:
270231
command = cls._create_command(
271-
cls.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
232+
RuntimeCommands.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
272233
)
273234
command['params']['functionDeclaration'] = (
274235
'function() {'
@@ -284,7 +245,7 @@ def _find_elements_by_xpath(cls, xpath: str, object_id: str) -> dict:
284245
'}'
285246
)
286247
else:
287-
command = cls._create_command(cls.EVALUATE_TEMPLATE)
248+
command = cls._create_command(RuntimeCommands.EVALUATE_TEMPLATE)
288249
command['params']['expression'] = (
289250
'var elements = document.evaluate('
290251
f'"{escaped_value}", document, null, '

pydoll/commands/runtime.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import copy
2+
3+
4+
class RuntimeCommands:
5+
EVALUATE_TEMPLATE = {'method': 'Runtime.evaluate', 'params': {}}
6+
CALL_FUNCTION_ON_TEMPLATE = {
7+
'method': 'Runtime.callFunctionOn',
8+
'params': {},
9+
}
10+
GET_PROPERTIES = {
11+
'method': 'Runtime.getProperties',
12+
'params': {},
13+
}
14+
15+
@classmethod
16+
def get_properties(cls, object_id: str) -> dict:
17+
"""Generates the command to get the properties of a specific object."""
18+
command = copy.deepcopy(cls.GET_PROPERTIES)
19+
command['params']['objectId'] = object_id
20+
command['params']['ownProperties'] = True
21+
return command
22+
23+
@classmethod
24+
def call_function_on(
25+
cls,
26+
object_id: str,
27+
function_declaration: str,
28+
return_by_value: bool = False,
29+
) -> dict:
30+
"""Generates the command to call a function on a specific object."""
31+
command = copy.deepcopy(cls.CALL_FUNCTION_ON_TEMPLATE)
32+
command['params']['objectId'] = object_id
33+
command['params']['functionDeclaration'] = function_declaration
34+
command['params']['returnByValue'] = return_by_value
35+
return command
36+
37+
@classmethod
38+
def evaluate_script(cls, expression: str) -> dict:
39+
"""Generates the command to evaluate JavaScript code."""
40+
command = copy.deepcopy(cls.EVALUATE_TEMPLATE)
41+
command['params'] = {
42+
'expression': expression,
43+
'returnByValue': True,
44+
}
45+
return command

pydoll/element.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pydoll import exceptions
66
from pydoll.commands.dom import DomCommands
77
from pydoll.commands.input import InputCommands
8+
from pydoll.commands.runtime import RuntimeCommands
89
from pydoll.connection import ConnectionHandler
910
from pydoll.constants import By, Scripts
1011
from pydoll.mixins.find_elements import FindElementsMixin
@@ -146,7 +147,7 @@ async def _execute_script(
146147
script (str): The JavaScript script to execute.
147148
"""
148149
return await self._execute_command(
149-
DomCommands.call_function_on(
150+
RuntimeCommands.call_function_on(
150151
self._node['objectId'], script, return_by_value
151152
)
152153
)
@@ -204,7 +205,6 @@ async def scroll_into_view(self):
204205
await self._execute_command(command)
205206

206207
async def click(self):
207-
208208
if self._is_option_tag():
209209
return await self.click_option_tag()
210210

@@ -259,7 +259,7 @@ async def realistic_click(self, x_offset: int = 0, y_offset: int = 0):
259259

260260
async def click_option_tag(self):
261261
script = Scripts.CLICK_OPTION_TAG.replace('{self.value}', self.value)
262-
await self._execute_command(DomCommands.evaluate_js(script))
262+
await self._execute_command(RuntimeCommands.evaluate_script(script))
263263

264264
async def send_keys(self, text: str):
265265
"""

pydoll/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,15 @@ class BrowserNotRunning(Exception):
3232

3333
class ElementNotFound(Exception):
3434
pass
35+
36+
37+
class ClickIntercepted(Exception):
38+
pass
39+
40+
41+
class ElementNotVisible(Exception):
42+
pass
43+
44+
45+
class ElementNotInteractable(Exception):
46+
pass

0 commit comments

Comments
 (0)