Skip to content

Commit cee24f2

Browse files
authored
Merge pull request #22 from thalissonvs/feat/check-click
Feat: check if the click was completed correctly
2 parents ab953af + 7166fb4 commit cee24f2

File tree

6 files changed

+270
-68
lines changed

6 files changed

+270
-68
lines changed

pydoll/browser/page.py

Lines changed: 21 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,31 @@ 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.
336+
If an element is provided, the script will be executed in the context
337+
of that element. To provide the element context, use the 'argument'
338+
keyword in the script.
339+
340+
Examples:
341+
```python
342+
await page.execute_script('argument.click()', element)
343+
await page.execute_script('argument.value = "Hello, World!"', element)
344+
```
334345
335346
Args:
336347
script (str): The JavaScript script to execute.
337-
338-
Returns:
339-
dict: The result of the JavaScript script execution.
340348
"""
341-
command = {
342-
'method': 'Runtime.evaluate',
343-
'params': {'expression': script, 'returnByValue': True},
344-
}
349+
if element:
350+
script = script.replace('argument', 'this')
351+
script = f'function(){{ {script} }}'
352+
object_id = element._node['objectId']
353+
command = RuntimeCommands.call_function_on(
354+
object_id, script, return_by_value=True
355+
)
356+
else:
357+
command = RuntimeCommands.evaluate_script(script)
345358
return await self._execute_command(command)
346359

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

pydoll/commands/dom.py

Lines changed: 6 additions & 31 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(
@@ -109,7 +94,7 @@ def enable_dom_events(cls) -> dict:
10994
@classmethod
11095
def get_current_url(cls) -> dict:
11196
"""Generates the command to get the current URL of the page."""
112-
return cls.evaluate_js('window.location.href')
97+
return RuntimeCommands.evaluate_script('window.location.href')
11398

11499
@classmethod
115100
def find_element(
@@ -170,16 +155,6 @@ def resolve_node(cls, node_id: int) -> dict:
170155
"""Generates the command to resolve a specific DOM node."""
171156
return cls._create_command(cls.RESOLVE_NODE_TEMPLATE, node_id=node_id)
172157

173-
@classmethod
174-
def evaluate_js(cls, expression: str) -> dict:
175-
"""Generates the command to evaluate JavaScript code."""
176-
command = copy.deepcopy(cls.EVALUATE_TEMPLATE)
177-
command['params'] = {
178-
'expression': expression,
179-
'returnByValue': False,
180-
}
181-
return command
182-
183158
@classmethod
184159
def _create_command(
185160
cls,
@@ -216,7 +191,7 @@ def _find_element_by_xpath(cls, xpath: str, object_id: str) -> dict:
216191
escaped_value = xpath.replace('"', '\\"')
217192
if object_id:
218193
command = cls._create_command(
219-
cls.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
194+
RuntimeCommands.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
220195
)
221196
command['params']['functionDeclaration'] = (
222197
'function() {'
@@ -227,7 +202,7 @@ def _find_element_by_xpath(cls, xpath: str, object_id: str) -> dict:
227202
'}'
228203
)
229204
else:
230-
command = cls._create_command(cls.EVALUATE_TEMPLATE)
205+
command = cls._create_command(RuntimeCommands.EVALUATE_TEMPLATE)
231206
command['params']['expression'] = (
232207
'var element = document.evaluate('
233208
f'"{escaped_value}", document, null, '
@@ -254,7 +229,7 @@ def _find_elements_by_xpath(cls, xpath: str, object_id: str) -> dict:
254229
escaped_value = xpath.replace('"', '\\"')
255230
if object_id:
256231
command = cls._create_command(
257-
cls.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
232+
RuntimeCommands.CALL_FUNCTION_ON_TEMPLATE, object_id=object_id
258233
)
259234
command['params']['functionDeclaration'] = (
260235
'function() {'
@@ -270,7 +245,7 @@ def _find_elements_by_xpath(cls, xpath: str, object_id: str) -> dict:
270245
'}'
271246
)
272247
else:
273-
command = cls._create_command(cls.EVALUATE_TEMPLATE)
248+
command = cls._create_command(RuntimeCommands.EVALUATE_TEMPLATE)
274249
command['params']['expression'] = (
275250
'var elements = document.evaluate('
276251
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/constants.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,54 @@ class By(str, Enum):
77
CLASS_NAME = 'class_name'
88
ID = 'id'
99
TAG_NAME = 'tag_name'
10+
11+
12+
class Scripts:
13+
ELEMENT_VISIBLE = """
14+
function() {
15+
const rect = this.getBoundingClientRect();
16+
return (
17+
rect.width > 0 && rect.height > 0
18+
&& getComputedStyle(this).visibility !== 'hidden'
19+
&& getComputedStyle(this).display !== 'none'
20+
)
21+
}
22+
"""
23+
24+
ELEMENT_ON_TOP = """
25+
function() {
26+
const rect = this.getBoundingClientRect();
27+
const elementFromPoint = document.elementFromPoint(
28+
rect.x + rect.width / 2,
29+
rect.y + rect.height / 2
30+
);
31+
return elementFromPoint === this;
32+
}
33+
"""
34+
35+
CLICK = """
36+
function(){
37+
clicked = false;
38+
this.addEventListener('click', function(){
39+
clicked = true;
40+
});
41+
this.click();
42+
return clicked;
43+
}
44+
"""
45+
46+
CLICK_OPTION_TAG = """
47+
document.querySelector('option[value="{self.value}"]').selected = true;
48+
var selectParentXpath = (
49+
'//option[@value="{self.value}"]//ancestor::select'
50+
);
51+
var select = document.evaluate(
52+
selectParentXpath,
53+
document,
54+
null,
55+
XPathResult.FIRST_ORDERED_NODE_TYPE,
56+
null
57+
).singleNodeValue;
58+
var event = new Event('change', { bubbles: true });
59+
select.dispatchEvent(event);
60+
"""

0 commit comments

Comments
 (0)