pydoll-python 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,108 @@
1
+ class BrowserCommands:
2
+ """
3
+ BrowserCommands class provides a set of commands to interact with the
4
+ browser's main functionality based on CDP. These commands allow for
5
+ managing browser windows, such as closing windows, retrieving window IDs,
6
+ and adjusting window bounds (size and state).
7
+
8
+ The following operations can be performed:
9
+ - Close the browser.
10
+ - Get the ID of the current window.
11
+ - Set the size and position of a specific window.
12
+ - Maximize or minimize a specific window.
13
+
14
+ Each method generates a command that can be sent to the browser
15
+ as part of the communication with the browser's underlying API.
16
+ """
17
+
18
+ CLOSE = {'method': 'Browser.close'}
19
+ GET_WINDOW_ID = {'method': 'Browser.WindowID'}
20
+ SET_WINDOW_BOUNDS_TEMPLATE = {
21
+ 'method': 'Browser.setWindowBounds',
22
+ 'params': {},
23
+ }
24
+ SET_DOWNLOAD_BEHAVIOR = {
25
+ 'method': 'Browser.setDownloadBehavior',
26
+ 'params': {},
27
+ }
28
+
29
+ @classmethod
30
+ def set_download_path(cls, path: str) -> dict:
31
+ """
32
+ Generates the command to set the download path for the browser.
33
+
34
+ Args:
35
+ path (str): The path to set for downloads.
36
+
37
+ Returns:
38
+ dict: The command to be sent to the browser.
39
+ """
40
+ command = cls.SET_DOWNLOAD_BEHAVIOR.copy()
41
+ command['params']['behavior'] = 'allow'
42
+ command['params']['downloadPath'] = path
43
+ return command
44
+
45
+ @classmethod
46
+ def close(cls) -> dict:
47
+ """
48
+ Generates the command to close the browser.
49
+
50
+ Returns:
51
+ dict: The command to be sent to the browser.
52
+ """
53
+ return cls.CLOSE
54
+
55
+ @classmethod
56
+ def get_window_id(cls) -> dict:
57
+ """
58
+ Generates the command to get the ID of the current window.
59
+
60
+ Returns:
61
+ dict: The command to be sent to the browser.
62
+ """
63
+ return cls.GET_WINDOW_ID
64
+
65
+ @classmethod
66
+ def set_window_bounds(cls, window_id: int, bounds: dict) -> dict:
67
+ """
68
+ Generates the command to set the bounds of a window.
69
+
70
+ Args:
71
+ window_id (int): The ID of the window to set the bounds for.
72
+ bounds (dict): The bounds to set for the window,
73
+ which should include width, height,
74
+ and optionally x and y coordinates.
75
+
76
+ Returns:
77
+ dict: The command to be sent to the browser.
78
+ """
79
+ command = cls.SET_WINDOW_BOUNDS_TEMPLATE.copy()
80
+ command['params']['windowId'] = window_id
81
+ command['params']['bounds'] = bounds
82
+ return command
83
+
84
+ @classmethod
85
+ def set_window_maximized(cls, window_id: int) -> dict:
86
+ """
87
+ Generates the command to maximize a window.
88
+
89
+ Args:
90
+ window_id (int): The ID of the window to maximize.
91
+
92
+ Returns:
93
+ dict: The command to be sent to the browser.
94
+ """
95
+ return cls.set_window_bounds(window_id, {'windowState': 'maximized'})
96
+
97
+ @classmethod
98
+ def set_window_minimized(cls, window_id: int) -> dict:
99
+ """
100
+ Generates the command to minimize a window.
101
+
102
+ Args:
103
+ window_id (int): The ID of the window to minimize.
104
+
105
+ Returns:
106
+ dict: The command to be sent to the browser.
107
+ """
108
+ return cls.set_window_bounds(window_id, {'windowState': 'minimized'})
pydoll/commands/dom.py ADDED
@@ -0,0 +1,212 @@
1
+ import copy
2
+ from typing import Literal
3
+
4
+ from pydoll.commands.runtime import RuntimeCommands
5
+ from pydoll.constants import By, Scripts
6
+
7
+
8
+ class DomCommands:
9
+ """
10
+ A class to define commands for interacting with the Document
11
+ Object Model (DOM) using the Chrome DevTools Protocol (CDP).
12
+ The commands allow for various operations on DOM nodes,
13
+ such as enabling the DOM domain, retrieving the
14
+ DOM document, describing nodes, and querying elements.
15
+
16
+ Attributes:
17
+ SelectorType (Literal): A type definition for supported selector types.
18
+ """
19
+
20
+ SelectorType = Literal[
21
+ By.CSS_SELECTOR, By.XPATH, By.CLASS_NAME, By.ID, By.TAG_NAME
22
+ ]
23
+
24
+ ENABLE = {'method': 'DOM.enable'}
25
+ DOM_DOCUMENT = {'method': 'DOM.getDocument'}
26
+ DESCRIBE_NODE_TEMPLATE = {'method': 'DOM.describeNode', 'params': {}}
27
+ FIND_ELEMENT_TEMPLATE = {'method': 'DOM.querySelector', 'params': {}}
28
+ FIND_ALL_ELEMENTS_TEMPLATE = {
29
+ 'method': 'DOM.querySelectorAll',
30
+ 'params': {},
31
+ }
32
+ BOX_MODEL_TEMPLATE = {'method': 'DOM.getBoxModel', 'params': {}}
33
+ RESOLVE_NODE_TEMPLATE = {'method': 'DOM.resolveNode', 'params': {}}
34
+ REQUEST_NODE_TEMPLATE = {'method': 'DOM.requestNode', 'params': {}}
35
+ GET_OUTER_HTML = {
36
+ 'method': 'DOM.getOuterHTML',
37
+ 'params': {},
38
+ }
39
+ SCROLL_INTO_VIEW_IF_NEEDED = {
40
+ 'method': 'DOM.scrollIntoViewIfNeeded',
41
+ 'params': {},
42
+ }
43
+
44
+ @classmethod
45
+ def scroll_into_view(cls, object_id: str) -> dict:
46
+ """Generates the command to scroll a specific DOM node into view."""
47
+ command = copy.deepcopy(cls.SCROLL_INTO_VIEW_IF_NEEDED)
48
+ command['params']['objectId'] = object_id
49
+ return command
50
+
51
+ @classmethod
52
+ def get_outer_html(cls, object_id: int) -> dict:
53
+ """Generates the command to get the outer HTML"""
54
+ command = copy.deepcopy(cls.GET_OUTER_HTML)
55
+ command['params']['objectId'] = object_id
56
+ return command
57
+
58
+ @classmethod
59
+ def dom_document(cls) -> dict:
60
+ """
61
+ Generates the command to get the root DOM node of the current page.
62
+ """
63
+ return cls.DOM_DOCUMENT
64
+
65
+ @classmethod
66
+ def request_node(cls, object_id: str) -> dict:
67
+ """Generates the command to request a specific DOM node by its object
68
+ ID."""
69
+ command = copy.deepcopy(cls.REQUEST_NODE_TEMPLATE)
70
+ command['params']['objectId'] = object_id
71
+ return command
72
+
73
+ @classmethod
74
+ def describe_node(cls, object_id: str) -> dict:
75
+ """Generates the command to describe a specific DOM node."""
76
+ command = copy.deepcopy(cls.DESCRIBE_NODE_TEMPLATE)
77
+ command['params']['objectId'] = object_id
78
+ return command
79
+
80
+ @classmethod
81
+ def box_model(cls, object_id: str) -> dict:
82
+ """
83
+ Generates the command to get the box model of a specific DOM node.
84
+ """
85
+ command = copy.deepcopy(cls.BOX_MODEL_TEMPLATE)
86
+ command['params']['objectId'] = object_id
87
+ return command
88
+
89
+ @classmethod
90
+ def enable_dom_events(cls) -> dict:
91
+ """Generates the command to enable the DOM domain."""
92
+ return cls.ENABLE
93
+
94
+ @classmethod
95
+ def get_current_url(cls) -> dict:
96
+ """Generates the command to get the current URL of the page."""
97
+ return RuntimeCommands.evaluate_script('window.location.href')
98
+
99
+ @classmethod
100
+ def find_element(
101
+ cls,
102
+ by: SelectorType,
103
+ value: str,
104
+ object_id: str = '',
105
+ ) -> dict:
106
+ """Generates a command to find a DOM element based on the specified
107
+ criteria."""
108
+ escaped_value = value.replace('"', '\\"')
109
+ match by:
110
+ case By.CLASS_NAME:
111
+ selector = f'.{escaped_value}'
112
+ case By.ID:
113
+ selector = f'#{escaped_value}'
114
+ case _:
115
+ selector = escaped_value
116
+ if object_id and not by == By.XPATH:
117
+ script = Scripts.RELATIVE_QUERY_SELECTOR.replace(
118
+ '{selector}', selector
119
+ )
120
+ command = RuntimeCommands.call_function_on(
121
+ object_id,
122
+ script,
123
+ return_by_value=False,
124
+ )
125
+ elif by == By.XPATH:
126
+ command = cls._find_element_by_xpath(value, object_id)
127
+ else:
128
+ command = RuntimeCommands.evaluate_script(
129
+ Scripts.QUERY_SELECTOR.replace('{selector}', selector)
130
+ )
131
+ return command
132
+
133
+ @classmethod
134
+ def find_elements(
135
+ cls,
136
+ by: SelectorType,
137
+ value: str,
138
+ object_id: str = '',
139
+ ) -> dict:
140
+ """Generates a command to find multiple DOM elements based on the
141
+ specified criteria."""
142
+ escaped_value = value.replace('"', '\\"')
143
+ match by:
144
+ case By.CLASS_NAME:
145
+ selector = f'.{escaped_value}'
146
+ case By.ID:
147
+ selector = f'#{escaped_value}'
148
+ case _:
149
+ selector = escaped_value
150
+ if object_id and not by == By.XPATH:
151
+ script = Scripts.RELATIVE_QUERY_SELECTOR_ALL.replace(
152
+ '{selector}', escaped_value
153
+ )
154
+ command = RuntimeCommands.call_function_on(
155
+ object_id,
156
+ script,
157
+ return_by_value=False,
158
+ )
159
+ elif by == By.XPATH:
160
+ command = cls._find_elements_by_xpath(value, object_id)
161
+ else:
162
+ command = RuntimeCommands.evaluate_script(
163
+ Scripts.QUERY_SELECTOR_ALL.replace('{selector}', selector)
164
+ )
165
+ return command
166
+
167
+ @classmethod
168
+ def _find_element_by_xpath(cls, xpath: str, object_id: str) -> dict:
169
+ """Creates a command to find a DOM element by XPath."""
170
+ escaped_value = xpath.replace('"', '\\"')
171
+ if object_id:
172
+ escaped_value = cls._ensure_relative_xpath(escaped_value)
173
+ script = Scripts.FIND_RELATIVE_XPATH_ELEMENT.replace(
174
+ '{escaped_value}', escaped_value
175
+ )
176
+ command = RuntimeCommands.call_function_on(
177
+ object_id,
178
+ script,
179
+ return_by_value=False,
180
+ )
181
+ else:
182
+ script = Scripts.FIND_XPATH_ELEMENT.replace(
183
+ '{escaped_value}', escaped_value
184
+ )
185
+ command = RuntimeCommands.evaluate_script(script)
186
+ return command
187
+
188
+ @classmethod
189
+ def _find_elements_by_xpath(cls, xpath: str, object_id: str) -> dict:
190
+ """Creates a command to find multiple DOM elements by XPath."""
191
+ escaped_value = xpath.replace('"', '\\"')
192
+ if object_id:
193
+ escaped_value = cls._ensure_relative_xpath(escaped_value)
194
+ script = Scripts.FIND_RELATIVE_XPATH_ELEMENTS.replace(
195
+ '{escaped_value}', escaped_value
196
+ )
197
+ command = RuntimeCommands.call_function_on(
198
+ object_id,
199
+ script,
200
+ return_by_value=False,
201
+ )
202
+ else:
203
+ script = Scripts.FIND_XPATH_ELEMENTS.replace(
204
+ '{escaped_value}', escaped_value
205
+ )
206
+ command = RuntimeCommands.evaluate_script(script)
207
+ return command
208
+
209
+ @staticmethod
210
+ def _ensure_relative_xpath(xpath: str) -> str:
211
+ """Ensures that the XPath expression is relative."""
212
+ return f'.{xpath}' if not xpath.startswith('.') else xpath
@@ -0,0 +1,308 @@
1
+ class FetchCommands:
2
+ """
3
+ A collection of command templates for handling fetch events in the browser.
4
+
5
+ This class provides a structured way to create and manage commands related
6
+ to fetch operations intercepted by the Fetch API. Each command corresponds
7
+ to specific actions that can be performed on fetch requests, such as
8
+ continuing a fetch request, fulfilling a fetch response, or handling
9
+ authentication challenges.
10
+
11
+ Attributes:
12
+ CONTINUE_REQUEST (dict): Template for continuing an intercepted fetch
13
+ request.
14
+ CONTINUE_REQUEST_WITH_AUTH (dict): Template for continuing a fetch
15
+ request that requires authentication.
16
+ DISABLE (dict): Template for disabling fetch interception.
17
+ ENABLE (dict): Template for enabling fetch interception.
18
+ FAIL_REQUEST (dict): Template for simulating a failure in a fetch
19
+ request.
20
+ FULFILL_REQUEST (dict): Template for fulfilling a fetch request with
21
+ custom responses.
22
+ GET_RESPONSE_BODY (dict): Template for retrieving the response body of
23
+ a fetch request.
24
+ CONTINUE_RESPONSE (dict): Template for continuing a fetch response for
25
+ an intercepted request.
26
+ """
27
+
28
+ CONTINUE_REQUEST = {'method': 'Fetch.continueRequest', 'params': {}}
29
+ CONTINUE_REQUEST_WITH_AUTH = {
30
+ 'method': 'Fetch.continueWithAuth',
31
+ 'params': {},
32
+ }
33
+ DISABLE = {'method': 'Fetch.disable', 'params': {}}
34
+ ENABLE = {'method': 'Fetch.enable', 'params': {}}
35
+ FAIL_REQUEST = {'method': 'Fetch.failRequest', 'params': {}}
36
+ FULFILL_REQUEST = {'method': 'Fetch.fulfillRequest', 'params': {}}
37
+ GET_RESPONSE_BODY = {'method': 'Fetch.getResponseBody', 'params': {}}
38
+ CONTINUE_RESPONSE = {'method': 'Fetch.continueResponse', 'params': {}}
39
+
40
+ @classmethod
41
+ def continue_request( # noqa: PLR0913, PLR0917
42
+ cls,
43
+ request_id: str,
44
+ url: str = '',
45
+ method: str = '',
46
+ post_data: str = '',
47
+ headers: dict = {},
48
+ intercept_response: bool = False,
49
+ ):
50
+ """
51
+ Creates a command to continue a paused fetch request.
52
+
53
+ This command allows the browser to resume a fetch operation that has
54
+ been intercepted. You can modify the fetch request URL, method,
55
+ headers, and body before continuing.
56
+
57
+ Args:
58
+ request_id (str): The ID of the fetch request to continue.
59
+ url (str, optional): The new URL for the fetch request. Defaults to
60
+ ''.
61
+ method (str, optional): The HTTP method to use (e.g., 'GET',
62
+ 'POST'). Defaults to ''.
63
+ postData (str, optional): The body data to send with the fetch
64
+ request. Defaults to ''.
65
+ headers (dict, optional): A dictionary of HTTP headers to include
66
+ in the fetch request. Defaults to {}.
67
+ interceptResponse (bool, optional): Indicates if the response
68
+ should be intercepted. Defaults to False.
69
+
70
+ Returns:
71
+ dict: A command template for continuing the fetch request.
72
+ """
73
+ continue_request_template = cls.CONTINUE_REQUEST.copy()
74
+ continue_request_template['params']['requestId'] = request_id
75
+ if url:
76
+ continue_request_template['params']['url'] = url
77
+ if method:
78
+ continue_request_template['params']['method'] = method
79
+ if post_data:
80
+ continue_request_template['params']['postData'] = post_data
81
+ if headers:
82
+ continue_request_template['params']['headers'] = headers
83
+ if intercept_response:
84
+ continue_request_template['params']['interceptResponse'] = (
85
+ intercept_response
86
+ )
87
+ return continue_request_template
88
+
89
+ @classmethod
90
+ def continue_request_with_auth(
91
+ cls, request_id: str, proxy_username: str, proxy_password: str
92
+ ):
93
+ """
94
+ Creates a command to continue a paused fetch request with
95
+ authentication.
96
+
97
+ This command is used when the fetch operation requires authentication.
98
+ It provides the necessary credentials to continue the request.
99
+
100
+ Args:
101
+ request_id (str): The ID of the fetch request to continue.
102
+ proxy_username (str): The username for proxy authentication.
103
+ proxy_password (str): The password for proxy authentication.
104
+
105
+ Returns:
106
+ dict: A command template for continuing the fetch request with
107
+ authentication.
108
+ """
109
+ continue_request_with_auth_template = (
110
+ cls.CONTINUE_REQUEST_WITH_AUTH.copy()
111
+ )
112
+ continue_request_with_auth_template['params']['requestId'] = request_id
113
+ continue_request_with_auth_template['params'][
114
+ 'authChallengeResponse'
115
+ ] = {
116
+ 'response': 'ProvideCredentials',
117
+ 'username': proxy_username,
118
+ 'password': proxy_password,
119
+ }
120
+ return continue_request_with_auth_template
121
+
122
+ @classmethod
123
+ def disable_fetch_events(cls):
124
+ """
125
+ Creates a command to disable fetch interception.
126
+
127
+ This command stops the browser from intercepting fetch requests.
128
+
129
+ Returns:
130
+ dict: A command template for disabling fetch interception.
131
+ """
132
+ return cls.DISABLE
133
+
134
+ @classmethod
135
+ def enable_fetch_events(
136
+ cls, handle_auth_requests: bool, resource_type: str
137
+ ):
138
+ """
139
+ Creates a command to enable fetch interception.
140
+
141
+ This command allows the browser to start intercepting fetch requests.
142
+ You can specify whether to handle authentication challenges and the
143
+ types of resources to intercept.
144
+
145
+ Args:
146
+ handle_auth_requests (bool): Indicates if authentication requests
147
+ should be handled.
148
+ resource_type (str): The type of resource to intercept (e.g.,
149
+ 'Document', 'Image').
150
+
151
+ Returns:
152
+ dict: A command template for enabling fetch interception.
153
+ """
154
+ enable_fetch_events_template = cls.ENABLE.copy()
155
+ enable_fetch_events_template['params']['patterns'] = [
156
+ {'urlPattern': '*'}
157
+ ]
158
+ if resource_type:
159
+ enable_fetch_events_template['params']['patterns'][0][
160
+ 'resourceType'
161
+ ] = resource_type
162
+
163
+ enable_fetch_events_template['params']['handleAuthRequests'] = (
164
+ handle_auth_requests
165
+ )
166
+ return enable_fetch_events_template
167
+
168
+ @classmethod
169
+ def fail_request(cls, request_id: str, error_reason: str):
170
+ """
171
+ Creates a command to simulate a failure in a fetch request.
172
+
173
+ This command allows you to simulate a failure for a specific fetch
174
+ operation, providing a reason for the failure.
175
+
176
+ Args:
177
+ request_id (str): The ID of the fetch request to fail.
178
+ errorReason (str): A description of the failure reason.
179
+
180
+ Returns:
181
+ dict: A command template for failing the fetch request.
182
+ """
183
+ fail_request_template = cls.FAIL_REQUEST.copy()
184
+ fail_request_template['params']['requestId'] = request_id
185
+ fail_request_template['params']['errorReason'] = error_reason
186
+ return fail_request_template
187
+
188
+ @classmethod
189
+ def fulfill_request( # noqa: PLR0913, PLR0917
190
+ cls,
191
+ request_id: str,
192
+ response_code: int,
193
+ response_headers: dict = {},
194
+ binary_response_headers: str = '',
195
+ body: str = '',
196
+ response_phrase: str = '',
197
+ ):
198
+ """
199
+ Creates a command to fulfill a fetch request with a custom response.
200
+
201
+ This command allows you to provide a custom response for a fetch
202
+ operation, including the HTTP status code, headers, and body content.
203
+
204
+ Args:
205
+ request_id (str): The ID of the fetch request to fulfill.
206
+ responseCode (int): The HTTP status code to return.
207
+ responseHeaders (dict, optional): A dictionary of response headers.
208
+ Defaults to {}.
209
+ binaryResponseHeaders (str, optional): Binary response headers.
210
+ Defaults to ''.
211
+ body (str, optional): The body content of the response. Defaults to
212
+ ''.
213
+ responsePhrase (str, optional): The response phrase (e.g., 'OK',
214
+ 'Not Found'). Defaults to ''.
215
+
216
+ Returns:
217
+ dict: A command template for fulfilling the fetch request.
218
+ """
219
+ fulfill_request_template = cls.FULFILL_REQUEST.copy()
220
+ fulfill_request_template['params']['requestId'] = request_id
221
+ if response_code:
222
+ fulfill_request_template['params']['responseCode'] = response_code
223
+ if response_headers:
224
+ fulfill_request_template['params']['responseHeaders'] = (
225
+ response_headers
226
+ )
227
+ if binary_response_headers:
228
+ fulfill_request_template['params']['binaryResponseHeaders'] = (
229
+ binary_response_headers
230
+ )
231
+ if body:
232
+ fulfill_request_template['params']['body'] = body
233
+ if response_phrase:
234
+ fulfill_request_template['params']['responsePhrase'] = (
235
+ response_phrase
236
+ )
237
+ return fulfill_request_template
238
+
239
+ @classmethod
240
+ def get_response_body(cls, request_id: str):
241
+ """
242
+ Creates a command to retrieve the response body of a fetch request.
243
+
244
+ This command allows you to access the body of a completed fetch
245
+ operation, which can be useful for analyzing the response data.
246
+
247
+ Args:
248
+ request_id (str): The ID of the fetch request to retrieve the body
249
+ from.
250
+
251
+ Returns:
252
+ dict: A command template for getting the response body.
253
+ """
254
+ get_response_body_template = cls.GET_RESPONSE_BODY.copy()
255
+ get_response_body_template['params']['requestId'] = request_id
256
+ return get_response_body_template
257
+
258
+ @classmethod
259
+ def continue_response(
260
+ cls,
261
+ request_id: str,
262
+ response_code: int = '',
263
+ response_headers: dict = {},
264
+ binary_response_headers: str = '',
265
+ response_phrase: str = '',
266
+ ):
267
+ """
268
+ Creates a command to continue a fetch response for an intercepted
269
+ request.
270
+
271
+ This command allows the browser to continue the response flow for a
272
+ specific fetch request, including customizing the HTTP status code,
273
+ headers, and response phrase.
274
+
275
+ Args:
276
+ requestId (str): The ID of the fetch request to continue the
277
+ response for.
278
+ responseCode (int, optional): The HTTP status code to send.
279
+ Defaults to ''.
280
+ responseHeaders (dict, optional): A dictionary of response headers.
281
+ Defaults to {}.
282
+ binaryResponseHeaders (str, optional): Binary response headers.
283
+ Defaults to ''.
284
+ responsePhrase (str, optional): The response phrase (e.g., 'OK').
285
+ Defaults to ''.
286
+
287
+ Returns:
288
+ dict: A command template for continuing the fetch response.
289
+ """
290
+ continue_response_template = cls.CONTINUE_RESPONSE.copy()
291
+ continue_response_template['params']['requestId'] = request_id
292
+ if response_code:
293
+ continue_response_template['params']['responseCode'] = (
294
+ response_code
295
+ )
296
+ if response_headers:
297
+ continue_response_template['params']['responseHeaders'] = (
298
+ response_headers
299
+ )
300
+ if binary_response_headers:
301
+ continue_response_template['params']['binaryResponseHeaders'] = (
302
+ binary_response_headers
303
+ )
304
+ if response_phrase:
305
+ continue_response_template['params']['responsePhrase'] = (
306
+ response_phrase
307
+ )
308
+ return continue_response_template