browser-toolkit 0.0.1a2__tar.gz → 0.0.1a3__tar.gz

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.
Files changed (30) hide show
  1. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/PKG-INFO +1 -1
  2. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/__init__.py +1 -0
  3. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/base_toolkit.py +77 -21
  4. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/create_browser/playwright.py +5 -6
  5. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/playwright.py +72 -36
  6. browser_toolkit-0.0.1a3/browser_toolkit/pydoll.py +542 -0
  7. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/selenium_toolkit/selenium_toolkit.py +2 -1
  8. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/types.py +11 -1
  9. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit.egg-info/PKG-INFO +1 -1
  10. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit.egg-info/SOURCES.txt +1 -0
  11. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/pyproject.toml +1 -1
  12. browser_toolkit-0.0.1a3/tests/test_network_requests.py +18 -0
  13. browser_toolkit-0.0.1a2/tests/test_network_requests.py +0 -20
  14. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/LICENSE +0 -0
  15. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/README.md +0 -0
  16. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/camoufox.py +0 -0
  17. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/create_browser/__init__.py +0 -0
  18. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/selenium.py +0 -0
  19. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/selenium_toolkit/__init__.py +0 -0
  20. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/selenium_toolkit/utils.py +0 -0
  21. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit/utils.py +0 -0
  22. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit.egg-info/dependency_links.txt +0 -0
  23. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit.egg-info/requires.txt +0 -0
  24. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/browser_toolkit.egg-info/top_level.txt +0 -0
  25. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/setup.cfg +0 -0
  26. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/tests/test_decorators.py +0 -0
  27. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/tests/test_open.py +0 -0
  28. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/tests/test_scroll_window.py +0 -0
  29. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/tests/test_session_data.py +0 -0
  30. {browser_toolkit-0.0.1a2 → browser_toolkit-0.0.1a3}/tests/test_wait.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: browser-toolkit
3
- Version: 0.0.1a2
3
+ Version: 0.0.1a3
4
4
  Summary: Toolkit that provides a single inteface to interact with different browser automations.
5
5
  Project-URL: Homepage, https://github.com/toriium/browser-toolkit
6
6
  Project-URL: Documentation, https://github.com/toriium/browser-toolkit/blob/master/README.md
@@ -1,4 +1,5 @@
1
1
  from .playwright import PlaywrightTollKit
2
2
  from .camoufox import CamoufoxTollKit
3
+ from .pydoll import PydollTollKit
3
4
  from .selenium_toolkit.selenium_toolkit import SeleniumToolKit
4
5
  from .base_toolkit import BaseBrowserToolkit, Cookie
@@ -4,20 +4,51 @@ import inspect
4
4
  from abc import ABC, abstractmethod
5
5
  from datetime import datetime
6
6
  from random import uniform
7
- from typing import Self
7
+ from typing import Self, Any
8
+
9
+ from browser_toolkit.types import Cookie, BoundingBox
10
+
11
+
8
12
 
9
- from browser_toolkit.types import Cookie
10
13
 
11
14
 
12
15
  class BaseWebElement(ABC):
13
16
  """Base class for web elements"""
14
17
 
15
18
  @abstractmethod
16
- async def click(self, delay: int = 0) -> None:
19
+ async def get_text(self) -> str:
20
+ """
21
+ Gets the text from the element
22
+
23
+ :return: str - text of the element
24
+ """
25
+
26
+ @abstractmethod
27
+ async def get_attribute(self, attribute: str) -> str | None:
28
+ """
29
+ Gets the attribute from the element
30
+
31
+ :param attribute: string - attribute name
32
+ :return: str - attribute of the element
33
+ """
34
+ pass
35
+
36
+ @abstractmethod
37
+ async def get_position(self) -> BoundingBox:
38
+ """
39
+ Gets the position of the element
40
+
41
+ :return: BoundingBox - position of the element
42
+ """
43
+ pass
44
+
45
+
46
+ @abstractmethod
47
+ async def click(self, hold_time: int = 0) -> None:
17
48
  """
18
49
  Clicks the element
19
50
 
20
- :param delay: int - time to keep the mouse button pressed in seconds (default: 0)
51
+ :param hold_time: int - time to keep the mouse button pressed in seconds (default: 0)
21
52
  :return:
22
53
  """
23
54
  pass
@@ -53,7 +84,7 @@ class BaseWebElement(ABC):
53
84
  pass
54
85
 
55
86
  @abstractmethod
56
- async def selector(self, selector: str) -> Self | None:
87
+ async def query(self, selector: str) -> Self | None:
57
88
  """
58
89
  Queries the web_element and returns the first element matching the selector.
59
90
  :param selector:
@@ -62,7 +93,7 @@ class BaseWebElement(ABC):
62
93
  pass
63
94
 
64
95
  @abstractmethod
65
- async def selector_all(self, selector: str) -> list[Self]:
96
+ async def query_all(self, selector: str) -> list[Self]:
66
97
  """
67
98
  Queries the web_element and returns all elements matching the selector.
68
99
  :param selector:
@@ -168,24 +199,20 @@ class BaseBrowserToolkit(ABC, AutoDecorate):
168
199
 
169
200
  # --------------------------- START selectors ---------------------------
170
201
  @abstractmethod
171
- async def selector(self, selector: str, web_element: BaseWebElement | None = None) -> BaseWebElement | None:
202
+ async def query(self, selector: str) -> BaseWebElement | None:
172
203
  """
173
204
  Queries the page and returns the first element matching the selector.
174
- If a web_element is provided, it queries within that element instead of the whole page.
175
205
  :param selector:
176
- :param web_element:
177
- :return:
206
+ :return: BaseWebElement | None
178
207
  """
179
208
  pass
180
209
 
181
210
  @abstractmethod
182
- async def selector_all(self, selector: str, web_element: BaseWebElement | None = None) -> list[BaseWebElement]:
211
+ async def query_all(self, selector: str) -> list[BaseWebElement]:
183
212
  """
184
213
  Queries the page and returns all elements matching the selector.
185
- If a web_element is provided, it queries within that element instead of the whole page.
186
- :param selector:
187
- :param web_element:
188
- :return:
214
+ :param selector: str
215
+ :return: list[BaseWebElement]
189
216
  """
190
217
  pass
191
218
 
@@ -205,12 +232,12 @@ class BaseBrowserToolkit(ABC, AutoDecorate):
205
232
  pass
206
233
 
207
234
  @abstractmethod
208
- async def click(self, selector: str, delay: int = 0) -> None:
235
+ async def click(self, selector: str, hold_time: int = 0) -> None:
209
236
  """
210
237
  Clicks the element matching the selector
211
238
 
212
239
  :param selector: string - CSS selector or XPath
213
- :param delay: int - time to keep the mouse button pressed in seconds (default: 0)
240
+ :param hold_time: int - time to keep the mouse button pressed in seconds (default: 0)
214
241
  :return:
215
242
  """
216
243
  pass
@@ -337,7 +364,7 @@ class BaseBrowserToolkit(ABC, AutoDecorate):
337
364
  """
338
365
 
339
366
  @abstractmethod
340
- async def get_attribute(self, selector: str, attribute: str) -> str:
367
+ async def get_attribute(self, selector: str, attribute: str) -> str | None:
341
368
  """
342
369
  Gets the attribute from the element matching the selector
343
370
 
@@ -381,7 +408,7 @@ class BaseBrowserToolkit(ABC, AutoDecorate):
381
408
 
382
409
  # --------------------------- START scripts ---------------------------
383
410
  @abstractmethod
384
- async def execute_script(self, script: str) -> any:
411
+ async def execute_script(self, script: str) -> Any:
385
412
  """
386
413
  Executes the JavaScript script in the context of the current page
387
414
 
@@ -391,7 +418,7 @@ class BaseBrowserToolkit(ABC, AutoDecorate):
391
418
  pass
392
419
 
393
420
  @abstractmethod
394
- async def execute_cdp_cmd(self, cmd: str, params: dict) -> any:
421
+ async def execute_cdp_cmd(self, cmd: str, params: dict) -> Any:
395
422
  """
396
423
  Executes the Chrome DevTools Protocol command in the context of the current page
397
424
 
@@ -578,7 +605,7 @@ class BaseBrowserToolkit(ABC, AutoDecorate):
578
605
  """
579
606
  pass
580
607
 
581
- async def get_local_storage_filter(self, name: str) -> str | None:
608
+ async def get_local_storage(self, name: str) -> str | None:
582
609
  """
583
610
  Gets a local storage item value by name
584
611
 
@@ -588,4 +615,33 @@ class BaseBrowserToolkit(ABC, AutoDecorate):
588
615
  local_storage = await self.get_all_local_storage()
589
616
  return local_storage.get(name, None)
590
617
 
618
+ async def set_local_storage(self, name: str, value: str) -> None:
619
+ """
620
+ Sets a local storage item
621
+
622
+ :param name:
623
+ :param value:
624
+ :return:
625
+ """
626
+ script = f'localStorage.setItem("{name}", "{value}");'
627
+ await self.execute_script(script)
628
+
629
+ async def delete_all_local_storage(self) -> None:
630
+ """
631
+ Deletes all local storage items
632
+ :return:
633
+ """
634
+ script = 'localStorage.clear();'
635
+ await self.execute_script(script)
636
+
637
+ async def delete_local_storage(self, name: str) -> None:
638
+ """
639
+ Deletes a local storage item by name
640
+
641
+ :param name:
642
+ :return:
643
+ """
644
+ script = f'localStorage.removeItem("{name}");'
645
+ await self.execute_script(script)
646
+
591
647
  # --------------------------- END session data ---------------------------
@@ -1,3 +1,4 @@
1
+
1
2
  from playwright.async_api import async_playwright, Browser, Page
2
3
  from browser_toolkit.playwright import PlaywrightTollKit
3
4
 
@@ -19,9 +20,7 @@ async def get_playwright_toolkit() -> PlaywrightTollKit:
19
20
 
20
21
 
21
22
  async def main():
22
- async with async_playwright() as p:
23
- browser = await p.chromium.launch()
24
- page = await browser.new_page()
25
- await page.goto("https://playwright.dev")
26
- print(await page.title())
27
- await browser.close()
23
+ browser_toolkit = await get_playwright_toolkit()
24
+ await browser_toolkit.goto("https://www.google.com")
25
+ title = await browser_toolkit.title
26
+ await browser_toolkit.close_browser()
@@ -1,10 +1,10 @@
1
- from typing import Self
1
+ from typing import Self, Any
2
2
 
3
3
  from playwright._impl._api_structures import SetCookieParam
4
- from playwright.async_api import Page, Browser, TimeoutError, ElementHandle, Cookie as PlaywrightCookie
4
+ from playwright.async_api import Page, Request, Browser, TimeoutError, ElementHandle, Cookie as PlaywrightCookie
5
5
 
6
6
  from browser_toolkit.base_toolkit import BaseBrowserToolkit, BaseWebElement
7
- from browser_toolkit.types import Cookie
7
+ from browser_toolkit.types import Cookie, BoundingBox
8
8
  from browser_toolkit.utils import raise_not_implemented
9
9
 
10
10
 
@@ -12,14 +12,48 @@ class PlaywrightWebElement(BaseWebElement):
12
12
  def __init__(self, web_element: ElementHandle):
13
13
  self.web_element = web_element
14
14
 
15
- async def click(self, delay: int = 0) -> None:
15
+ async def get_text(self) -> str:
16
+ """
17
+ Gets the text from the element
18
+
19
+ :return: str - text of the element
20
+ """
21
+ text = await self.web_element.text_content()
22
+ return text or ""
23
+
24
+ async def get_attribute(self, attribute: str) -> str | None:
25
+ """
26
+ Gets the attribute from the element
27
+
28
+ :param attribute: string - attribute name
29
+ :return: str - attribute of the element
30
+ """
31
+ return await self.web_element.get_attribute(name=attribute)
32
+
33
+ async def get_position(self) -> BoundingBox:
34
+ """
35
+ Gets the position of the element
36
+
37
+ :return: BoundingBox - position of the element
38
+ """
39
+ bounding_box = await self.web_element.bounding_box()
40
+ if not bounding_box:
41
+ raise ValueError("Could not get bounding box for element")
42
+ return BoundingBox(
43
+ x=bounding_box["x"],
44
+ y=bounding_box["y"],
45
+ width=bounding_box["width"],
46
+ height=bounding_box["height"],
47
+ )
48
+
49
+ async def click(self, hold_time: int = 0) -> None:
16
50
  """
17
51
  Clicks the element
18
52
 
19
- :param delay: int - time to keep the mouse button pressed in seconds (default: 0)
53
+ :param hold_time: int - time to keep the mouse button pressed in seconds (default: 0)
20
54
  :return:
21
55
  """
22
- await self.web_element.click(delay=delay)
56
+ await self.web_element.click(delay=hold_time*1000)
23
57
 
24
58
  async def click_js(self) -> None:
25
59
  """
@@ -50,7 +84,7 @@ class PlaywrightWebElement(BaseWebElement):
50
84
  """
51
85
  await self.web_element.fill("")
52
86
 
53
- async def selector(self, selector: str) -> Self | None:
87
+ async def query(self, selector: str) -> Self | None:
54
88
  """
55
89
  Queries the web_element and returns the first element matching the selector.
56
90
  :param selector:
@@ -61,7 +95,7 @@ class PlaywrightWebElement(BaseWebElement):
61
95
  return PlaywrightWebElement(web_element=element)
62
96
  return None
63
97
 
64
- async def selector_all(self, selector: str) -> list[Self]:
98
+ async def query_all(self, selector: str) -> list[Self]:
65
99
  """
66
100
  Queries the web_element and returns all elements matching the selector.
67
101
  :param selector:
@@ -96,26 +130,22 @@ class PlaywrightTollKit(BaseBrowserToolkit):
96
130
 
97
131
  # --------------------------- START selectors ---------------------------
98
132
 
99
- async def selector(self, selector: str, web_element: BaseWebElement | None = None) -> BaseWebElement | None:
133
+ async def query(self, selector: str) -> BaseWebElement | None:
100
134
  """
101
135
  Queries the page and returns the first element matching the selector.
102
- If a web_element is provided, it queries within that element instead of the whole page.
103
136
  :param selector:
104
- :param web_element:
105
- :return:
137
+ :return: BaseWebElement | None
106
138
  """
107
139
  element = await self.page.query_selector(selector=selector)
108
140
  if element:
109
141
  return PlaywrightWebElement(web_element=element)
110
142
  return None
111
143
 
112
- async def selector_all(self, selector: str, web_element: BaseWebElement | None = None) -> list[BaseWebElement]:
144
+ async def query_all(self, selector: str) -> list[BaseWebElement]:
113
145
  """
114
146
  Queries the page and returns all elements matching the selector.
115
- If a web_element is provided, it queries within that element instead of the whole page.
116
- :param selector:
117
- :param web_element:
118
- :return:
147
+ :param selector: str
148
+ :return: list[BaseWebElement]
119
149
  """
120
150
  elements = await self.page.query_selector_all(selector=selector)
121
151
  return [PlaywrightWebElement(web_element=element) for element in elements]
@@ -134,15 +164,15 @@ class PlaywrightTollKit(BaseBrowserToolkit):
134
164
  """
135
165
  await self.page.goto(url=url, timeout=timeout * 1000)
136
166
 
137
- async def click(self, selector: str, delay: int = 0) -> None:
167
+ async def click(self, selector: str, hold_time: int = 0) -> None:
138
168
  """
139
169
  Clicks the element matching the selector
140
170
 
141
171
  :param selector: string - CSS selector or XPath
142
- :param delay: int - time to keep the mouse button pressed in seconds (default: 0)
172
+ :param hold_time: int - time to keep the mouse button pressed in seconds (default: 0)
143
173
  :return:
144
174
  """
145
- await self.page.locator(selector=selector).click(delay=delay)
175
+ await self.page.locator(selector=selector).click(delay=hold_time*1000)
146
176
 
147
177
  async def click_js(self, selector: str) -> None:
148
178
  """
@@ -152,7 +182,7 @@ class PlaywrightTollKit(BaseBrowserToolkit):
152
182
  :return:
153
183
  """
154
184
  command = f"document.querySelector('{selector}').click()"
155
- await self.page.evaluate(expression=command)
185
+ await self.execute_script(script=command)
156
186
 
157
187
  async def type(self, text: str, selector: str, interval: float | int = 0, clear_before: bool = False) -> None:
158
188
  """
@@ -184,8 +214,8 @@ class PlaywrightTollKit(BaseBrowserToolkit):
184
214
  :param selector: string - CSS selector or XPath
185
215
  :return:
186
216
  """
187
- command = f"document.querySelector('{selector}').scrollIntoView()"
188
- await self.page.evaluate(expression=command)
217
+ script = f"document.querySelector('{selector}').scrollIntoView()"
218
+ await self.execute_script(script=script)
189
219
 
190
220
  async def scroll_to_top(self) -> None:
191
221
  """
@@ -193,7 +223,8 @@ class PlaywrightTollKit(BaseBrowserToolkit):
193
223
 
194
224
  :return:
195
225
  """
196
- await self.page.evaluate("window.scrollTo(0, 0);")
226
+ script = "window.scrollTo(0, 0)"
227
+ await self.execute_script(script=script)
197
228
 
198
229
  async def scroll_to_bottom(self) -> None:
199
230
  """
@@ -201,7 +232,8 @@ class PlaywrightTollKit(BaseBrowserToolkit):
201
232
 
202
233
  :return:
203
234
  """
204
- await self.page.evaluate("window.scrollTo(0, document.body.scrollHeight);")
235
+ script = "window.scrollTo(0, document.body.scrollHeight);"
236
+ await self.execute_script(script=script)
205
237
 
206
238
  async def reload(self) -> None:
207
239
  """
@@ -217,8 +249,8 @@ class PlaywrightTollKit(BaseBrowserToolkit):
217
249
 
218
250
  :return:
219
251
  """
220
- cmd = "window.location.reload()"
221
- await self.page.evaluate(expression=cmd)
252
+ script = "window.location.reload()"
253
+ await self.execute_script(script=script)
222
254
 
223
255
  # --------------------------- END Actions ---------------------------
224
256
 
@@ -257,9 +289,10 @@ class PlaywrightTollKit(BaseBrowserToolkit):
257
289
  :param selector: string - CSS selector or XPath
258
290
  :return: str - text of the element
259
291
  """
260
- return await self.page.locator(selector=selector).text_content()
292
+ text = await self.page.locator(selector=selector).text_content()
293
+ return text or ""
261
294
 
262
- async def get_attribute(self, selector: str, attribute: str) -> str:
295
+ async def get_attribute(self, selector: str, attribute: str) -> str | None:
263
296
  """
264
297
  Gets the attribute from the element matching the selector
265
298
 
@@ -281,12 +314,13 @@ class PlaywrightTollKit(BaseBrowserToolkit):
281
314
 
282
315
  # --------------------------- START network ---------------------------
283
316
 
284
- async def get_network_requests(self) -> list[dict]:
317
+ async def get_network_requests(self) -> list[Request]:
285
318
  """
286
319
  Get all network Requests
287
320
  :return:
288
321
  """
289
- raise_not_implemented()
322
+ # return await self.page.requests()
323
+ pass
290
324
 
291
325
  async def get_network_response_body(self, request_id: str) -> str:
292
326
  """
@@ -300,7 +334,7 @@ class PlaywrightTollKit(BaseBrowserToolkit):
300
334
  # --------------------------- END network ---------------------------
301
335
 
302
336
  # --------------------------- START scripts ---------------------------
303
- async def execute_script(self, script: str) -> any:
337
+ async def execute_script(self, script: str) -> Any:
304
338
  """
305
339
  Executes the JavaScript script in the context of the current page
306
340
 
@@ -309,7 +343,7 @@ class PlaywrightTollKit(BaseBrowserToolkit):
309
343
  """
310
344
  return await self.page.evaluate(expression=script)
311
345
 
312
- async def execute_cdp_cmd(self, cmd: str, params: dict) -> any:
346
+ async def execute_cdp_cmd(self, cmd: str, params: dict) -> Any:
313
347
  """
314
348
  Executes the Chrome DevTools Protocol command in the context of the current page
315
349
 
@@ -317,7 +351,8 @@ class PlaywrightTollKit(BaseBrowserToolkit):
317
351
  :param params: dict - parameters for the CDP command
318
352
  :return:
319
353
  """
320
- raise_not_implemented()
354
+ client = await self.page.context.new_cdp_session(self.page)
355
+ return await client.send(cmd, params=params)
321
356
 
322
357
  # --------------------------- END scripts ---------------------------
323
358
 
@@ -501,9 +536,10 @@ class PlaywrightTollKit(BaseBrowserToolkit):
501
536
  async def get_all_local_storage(self) -> dict:
502
537
  """
503
538
  Gets all local storage
504
- :return: dict
539
+ :return: list[Cookie]
505
540
  """
506
- local_storage = await self.page.evaluate("() => Object.fromEntries(Object.entries(localStorage))")
541
+ cmd = "() => Object.fromEntries(Object.entries(localStorage))"
542
+ local_storage = await self.execute_script(script=cmd)
507
543
  if not isinstance(local_storage, dict):
508
544
  local_storage = {}
509
545
  return local_storage
@@ -0,0 +1,542 @@
1
+ from typing import Self, Any
2
+
3
+ from pydoll.protocol.network.types import CookieParam, Cookie as PydollCookie
4
+ from pydoll.browser.tab import Tab
5
+ from pydoll.browser.chromium.chrome import Chrome
6
+ from pydoll.elements.web_element import WebElement
7
+
8
+ from browser_toolkit.base_toolkit import BaseBrowserToolkit, BaseWebElement
9
+ from browser_toolkit.types import Cookie, BoundingBox
10
+ from browser_toolkit.utils import raise_not_implemented
11
+
12
+
13
+ class PydollWebElement(BaseWebElement):
14
+ def __init__(self, web_element: WebElement):
15
+ self.web_element = web_element
16
+
17
+ async def get_text(self) -> str:
18
+ """
19
+ Gets the text from the element
20
+
21
+ :return: str - text of the element
22
+ """
23
+ text = await self.web_element.text
24
+ return text or ""
25
+
26
+ async def get_attribute(self, attribute: str) -> str | None:
27
+ """
28
+ Gets the attribute from the element
29
+
30
+ :param attribute: string - attribute name
31
+ :return: str - attribute of the element
32
+ """
33
+ return self.web_element.get_attribute(name=attribute)
34
+
35
+ async def get_position(self) -> BoundingBox:
36
+ """
37
+ Gets the position of the element
38
+
39
+ :return: BoundingBox - position of the element
40
+ """
41
+ bounding_box = await self.web_element.bounds
42
+ if not bounding_box:
43
+ raise ValueError("Could not get bounding box for element")
44
+ return BoundingBox(
45
+ x=bounding_box["x"],
46
+ y=bounding_box["y"],
47
+ width=bounding_box["width"],
48
+ height=bounding_box["height"],
49
+ )
50
+
51
+ async def click(self, hold_time: int = 0) -> None:
52
+ """
53
+ Clicks the element
54
+
55
+ :param hold_time: int - time to keep the mouse button pressed in seconds (default: 0)
56
+ :return:
57
+ """
58
+ await self.web_element.click(hold_time=hold_time, humanize=True)
59
+
60
+ async def click_js(self) -> None:
61
+ """
62
+ Clicks the element using JavaScript
63
+
64
+ :return:
65
+ """
66
+ await self.web_element.click_using_js()
67
+
68
+ async def type(self, text: str, interval: float | int = 0, clear_before: bool = False) -> None:
69
+ """
70
+ Fills the element with the text
71
+
72
+ :param text: string - text to fill
73
+ :param interval: float or int - time to wait in seconds between each character
74
+ :param clear_before: bool - whether to clear the field before filling
75
+ :return:
76
+ """
77
+ if clear_before:
78
+ await self.clear()
79
+ await self.web_element.type_text(text=text, humanize=True, interval=interval)
80
+
81
+ async def clear(self) -> None:
82
+ """
83
+ Clears the element
84
+
85
+ :return:
86
+ """
87
+ await self.web_element.clear()
88
+
89
+ async def query(self, selector: str) -> Self | None:
90
+ """
91
+ Queries the web_element and returns the first element matching the selector.
92
+ :param selector:
93
+ :return:
94
+ """
95
+ element = await self.web_element.query(expression=selector, timeout=0, find_all=False, raise_exc=False)
96
+ if element:
97
+ return PydollWebElement(web_element=element)
98
+ return None
99
+
100
+ async def query_all(self, selector: str) -> list[Self]:
101
+ """
102
+ Queries the web_element and returns all elements matching the selector.
103
+ :param selector:
104
+ :return:
105
+ """
106
+ elements = await self.web_element.query(expression=selector, timeout=0, find_all=True, raise_exc=False)
107
+ return [PydollWebElement(web_element=element) for element in elements]
108
+
109
+
110
+ class PydollTollKit(BaseBrowserToolkit):
111
+ def __init__(self, browser: Chrome, page: Tab, *args, **kwargs):
112
+ self.browser: Chrome = browser
113
+ self.page: Tab = page
114
+
115
+ # --------------------------- START session management ---------------------------
116
+
117
+ async def close_page(self) -> None:
118
+ """
119
+ Closes the browser tab
120
+ :return:
121
+ """
122
+ await self.page.close()
123
+
124
+ async def close_browser(self) -> None:
125
+ """
126
+ Closes the browser process and all its pages
127
+ :return:
128
+ """
129
+ await self.browser.stop()
130
+
131
+ # --------------------------- END session management ---------------------------
132
+
133
+ # --------------------------- START selectors ---------------------------
134
+
135
+ async def query(self, selector: str) -> BaseWebElement | None:
136
+ """
137
+ Queries the page and returns the first element matching the selector.
138
+ :param selector:
139
+ :return: BaseWebElement | None
140
+ """
141
+ element = await self.page.query(expression=selector, timeout=0, find_all=False, raise_exc=False)
142
+ if element:
143
+ return PydollWebElement(web_element=element)
144
+ return None
145
+
146
+ async def query_all(self, selector: str) -> list[BaseWebElement]:
147
+ """
148
+ Queries the page and returns all elements matching the selector.
149
+ :param selector: str
150
+ :return: list[BaseWebElement]
151
+ """
152
+ elements = await self.page.query(expression=selector, timeout=0, find_all=True, raise_exc=False)
153
+ return [PydollWebElement(web_element=element) for element in elements]
154
+
155
+ # --------------------------- END selectors ---------------------------
156
+
157
+ # --------------------------- START Actions ---------------------------
158
+
159
+ async def goto(self, url: str, timeout: int = 30) -> None:
160
+ """
161
+ Navigates to URL
162
+
163
+ :param url: url to navigate to
164
+ :param timeout: maximum time to wait for the page to load in seconds
165
+ :return:
166
+ """
167
+ await self.page.go_to(url=url, timeout=timeout)
168
+
169
+ async def click(self, selector: str, hold_time: int = 0) -> None:
170
+ """
171
+ Clicks the element matching the selector
172
+
173
+ :param selector: string - CSS selector or XPath
174
+ :param hold_time: int - time to keep the mouse button pressed in seconds (default: 0)
175
+ :return:
176
+ """
177
+ element = await self.page.query(expression=selector, timeout=0, find_all=False, raise_exc=True)
178
+ await element.click(hold_time=hold_time, humanize=True)
179
+
180
+ async def click_js(self, selector: str) -> None:
181
+ """
182
+ Clicks the element matching the selector using JavaScript
183
+
184
+ :param selector: string - CSS selector or XPath
185
+ :return:
186
+ """
187
+ element = await self.page.query(expression=selector, timeout=0, find_all=False, raise_exc=True)
188
+ await element.click_using_js()
189
+
190
+ async def type(self, text: str, selector: str, interval: float | int = 0, clear_before: bool = False) -> None:
191
+ """
192
+ Fills the element matching the selector with the text
193
+
194
+ :param text: string - text to fill
195
+ :param selector: string - CSS selector or XPath
196
+ :param interval: float or int - time to wait in seconds between each character
197
+ :param clear_before: bool - whether to clear the field before filling
198
+ :return:
199
+ """
200
+ if clear_before:
201
+ await self.clear(selector=selector)
202
+
203
+ element = await self.page.query(expression=selector, timeout=0, find_all=False, raise_exc=True)
204
+ await element.type_text(text=text, humanize=True, interval=interval)
205
+
206
+ async def clear(self, selector: str) -> None:
207
+ """
208
+ Clears the element matching the selector
209
+
210
+ :param selector: string - CSS selector or XPath
211
+ :return:
212
+ """
213
+ element = await self.page.query(expression=selector, timeout=0, find_all=False, raise_exc=True)
214
+ await element.clear()
215
+
216
+ async def scroll_to_element(self, selector: str) -> None:
217
+ """
218
+ Scrolls to the element matching the selector
219
+
220
+ :param selector: string - CSS selector or XPath
221
+ :return:
222
+ """
223
+ command = f"document.querySelector('{selector}').scrollIntoView()"
224
+ await self.execute_script(script=command)
225
+
226
+ async def scroll_to_top(self) -> None:
227
+ """
228
+ Scrolls to the top of the page
229
+
230
+ :return:
231
+ """
232
+ cmd = "window.scrollTo(0, 0)"
233
+ await self.execute_script(script=cmd)
234
+
235
+ async def scroll_to_bottom(self) -> None:
236
+ """
237
+ Scrolls to the bottom of the page
238
+
239
+ :return:
240
+ """
241
+ cmd = "window.scrollTo(0, document.body.scrollHeight);"
242
+ await self.execute_script(script=cmd)
243
+
244
+ async def reload(self) -> None:
245
+ """
246
+ Reloads the page
247
+
248
+ :return:
249
+ """
250
+ await self.page.refresh(ignore_cache=False)
251
+
252
+ async def hard_reload(self) -> None:
253
+ """
254
+ Hard reloads the page (ignoring cache)
255
+
256
+ :return:
257
+ """
258
+ await self.page.refresh(ignore_cache=True)
259
+
260
+
261
+ # --------------------------- END Actions ---------------------------
262
+
263
+ # --------------------------- START page data ---------------------------
264
+ @property
265
+ async def current_url(self) -> str:
266
+ """
267
+ Gets the current URL
268
+
269
+ :return: str - current URL
270
+ """
271
+ return await self.page.current_url
272
+
273
+ @property
274
+ async def title(self) -> str:
275
+ """
276
+ Gets the page title
277
+
278
+ :return: str - page title
279
+ """
280
+ return await self.page.title
281
+
282
+ @property
283
+ async def page_source(self) -> str:
284
+ """
285
+ Gets the page source
286
+
287
+ :return: str - page source
288
+ """
289
+ return await self.page.page_source
290
+
291
+ async def get_text(self, selector: str) -> str:
292
+ """
293
+ Gets the text from the element matching the selector
294
+
295
+ :param selector: string - CSS selector or XPath
296
+ :return: str - text of the element
297
+ """
298
+ element = await self.page.query(expression=selector, timeout=0, find_all=False, raise_exc=True)
299
+ return await element.text
300
+
301
+ async def get_attribute(self, selector: str, attribute: str) -> str | None:
302
+ """
303
+ Gets the attribute from the element matching the selector
304
+
305
+ :param selector: str - CSS selector or XPath
306
+ :param attribute: str - attribute name
307
+ :return: str - attribute of the element
308
+ """
309
+ element = await self.page.query(expression=selector, timeout=0, find_all=False, raise_exc=True)
310
+ return element.get_attribute(name=attribute)
311
+
312
+ async def save_screenshot(self, file_path: str) -> None:
313
+ """
314
+ Takes a screenshot of the current page and saves it to the exception directory if it is set
315
+ :param file_path:
316
+ :return:
317
+ """
318
+ await self.page.take_screenshot(path=file_path, quality=100)
319
+
320
+ # --------------------------- END page data ---------------------------
321
+
322
+ # --------------------------- START network ---------------------------
323
+
324
+ async def get_network_requests(self) -> list[dict]:
325
+ """
326
+ Get all network Requests
327
+ :return:
328
+ """
329
+ return await self.page.get_network_logs()
330
+ pass
331
+
332
+ async def get_network_response_body(self, request_id: str) -> str:
333
+ """
334
+ Gets the response body of the network request with the given request ID
335
+
336
+ :param request_id: string - network request ID
337
+ :return: str - response body of the network request
338
+ """
339
+ return await self.page.get_network_response_body(request_id=request_id)
340
+
341
+ # --------------------------- END network ---------------------------
342
+
343
+ # --------------------------- START scripts ---------------------------
344
+ async def execute_script(self, script: str) -> Any:
345
+ """
346
+ Executes the JavaScript script in the context of the current page
347
+
348
+ :param script: string - JavaScript code to execute
349
+ :return:
350
+ """
351
+ return await self.page.execute_script(script=script)
352
+
353
+ async def execute_cdp_cmd(self, cmd: str, params: dict) -> Any:
354
+ """
355
+ Executes the Chrome DevTools Protocol command in the context of the current page
356
+
357
+ :param cmd: string - CDP command to execute
358
+ :param params: dict - parameters for the CDP command
359
+ :return:
360
+ """
361
+ raise_not_implemented()
362
+
363
+ # --------------------------- END scripts ---------------------------
364
+
365
+ # --------------------------- START wait ---------------------------
366
+ async def element_is_present(self, selector: str, timeout: int) -> bool:
367
+ """
368
+ Checks if the element matching the selector is present
369
+
370
+ :param selector: string - CSS selector or XPath
371
+ :param timeout: int - seconds to wait for the element
372
+ :return: bool - whether the element is present
373
+ """
374
+ element = await self.page.query(expression=selector, timeout=timeout, find_all=True, raise_exc=False)
375
+ return bool(element)
376
+
377
+ async def element_is_visible(self, selector: str, timeout: int) -> bool:
378
+ """
379
+ Checks if the element matching the selector is visible
380
+
381
+ :param selector: string - CSS selector or XPath
382
+ :param timeout: int - seconds to wait for the element
383
+ :return: bool - whether the element is visible
384
+ """
385
+ element = await self.page.query(expression=selector, timeout=timeout, find_all=True, raise_exc=False)
386
+ return bool(element)
387
+
388
+ async def element_is_invisible(self, selector: str, timeout: int) -> bool:
389
+ """
390
+ Checks if the element matching the selector is invisible
391
+
392
+ :param selector: string - CSS selector or XPath
393
+ :param timeout: int - seconds to wait for the element
394
+ :return: bool - whether the element is invisible
395
+ """
396
+ element = await self.page.query(expression=selector, timeout=timeout, find_all=True, raise_exc=False)
397
+ return True if element is None else False
398
+
399
+ async def element_is_clickable(self, selector: str, timeout: int) -> bool:
400
+ """
401
+ Checks if the element matching the selector is clickable
402
+
403
+ :param selector: string - CSS selector or XPath
404
+ :param timeout: int - seconds to wait for the element
405
+ :return: bool - whether the element is clickable
406
+ """
407
+ element = await self.page.query(expression=selector, timeout=timeout, find_all=False, raise_exc=False)
408
+ if not element:
409
+ return False
410
+ return await element.is_visible()
411
+
412
+ async def text_is_present(self, text: str, selector: str, timeout: int) -> bool:
413
+ """
414
+ Checks if the text is present in the element matching the selector
415
+
416
+ :param text: string - text to check
417
+ :param selector: string - CSS selector or XPath
418
+ :param timeout: int - seconds to wait for the element
419
+ :return: bool - whether the text is present in the element
420
+ """
421
+ element = await self.query(selector=selector)
422
+ if not element:
423
+ return False
424
+ element_text = await element.get_text()
425
+ return text in element_text
426
+
427
+
428
+ async def alert_is_present(self, timeout: int, message: str) -> bool:
429
+ """
430
+ Checks if an alert is present
431
+
432
+ :param timeout: int - seconds to wait for the alert
433
+ :param message: str - alert message
434
+ :return: bool - whether an alert is present
435
+ """
436
+ raise_not_implemented()
437
+
438
+ async def page_is_loading(self, timeout: int) -> bool:
439
+ """
440
+ Checks if the page is ready
441
+
442
+ :param timeout: int - seconds to wait for the page to be ready
443
+ :return: bool - whether the page is ready
444
+ """
445
+ cmd = "return document.readyState"
446
+ if self.page.execute_script(cmd) != "complete":
447
+ return True
448
+ else:
449
+ return False
450
+
451
+ # --------------------------- END wait ---------------------------
452
+
453
+ # --------------------------- START session data ---------------------------
454
+ async def get_all_cookies(self) -> list[Cookie]:
455
+ """
456
+ Gets all cookies
457
+ :return: list[Cookie]
458
+ """
459
+ raw_cookies: list[PydollCookie] = await self.page.get_cookies()
460
+ transformed_cookies: list[Cookie] = []
461
+ for raw_cookie in raw_cookies:
462
+ transformed_cookie = Cookie(
463
+ name=raw_cookie.get("name"),
464
+ value=raw_cookie.get("value"),
465
+ url=raw_cookie.get("url"),
466
+ domain=raw_cookie.get("domain"),
467
+ path=raw_cookie.get("path"),
468
+ expires=raw_cookie.get("expires"),
469
+ httpOnly=raw_cookie.get("httpOnly"),
470
+ secure=raw_cookie.get("secure"),
471
+ sameSite=raw_cookie.get("sameSite"),
472
+ partitionKey=raw_cookie.get("partitionKey"),
473
+ )
474
+ transformed_cookies.append(transformed_cookie)
475
+
476
+ return transformed_cookies
477
+
478
+ async def add_cookie(self, cookie: Cookie) -> None:
479
+ """
480
+ Adds a cookie to the current session
481
+
482
+ :param cookie: Cookie - cookie to add
483
+ :return:
484
+ """
485
+ transformed_cookie: CookieParam = CookieParam(
486
+ name=cookie.name,
487
+ value=cookie.value,
488
+ url=cookie.url,
489
+ domain=cookie.domain,
490
+ path=cookie.path,
491
+ expires=cookie.expires,
492
+ httpOnly=cookie.httpOnly,
493
+ secure=cookie.secure,
494
+ sameSite=cookie.sameSite,
495
+ partitionKey=cookie.partitionKey,
496
+ )
497
+ await self.page.set_cookies(cookies=[transformed_cookie])
498
+
499
+ async def delete_all_cookies(self) -> None:
500
+ """
501
+ Deletes all cookies from the current session
502
+ :return:
503
+ """
504
+ await self.page.delete_all_cookies()
505
+
506
+ async def delete_cookie_by_name(self, name: str) -> None:
507
+ """
508
+ Deletes a cookie by name
509
+
510
+ :param name: string - name of the cookie to delete
511
+ :return:
512
+ """
513
+ raise_not_implemented()
514
+
515
+ async def delete_cookie_filter(
516
+ self, name: str | None = None, domain: str | None = None, path: str | None = None
517
+ ) -> None:
518
+ """
519
+ Deletes cookies by name, domain, and path
520
+
521
+ :param name:
522
+ :param domain:
523
+ :param path:
524
+ :return:
525
+ """
526
+ raise_not_implemented()
527
+
528
+ async def get_all_local_storage(self) -> dict:
529
+ """
530
+ Gets all local storage
531
+ :return: dict
532
+ """
533
+ cmd = "() => Object.fromEntries(Object.entries(localStorage))"
534
+ raw = await self.page.execute_script(script=cmd, return_by_value=True)
535
+ if not isinstance(raw, dict):
536
+ return {}
537
+ local_storage = raw["result"]["result"]["value"]
538
+ return local_storage
539
+
540
+
541
+
542
+ # --------------------------- END session data ---------------------------
@@ -225,7 +225,8 @@ class SeleniumToolKit:
225
225
  return False
226
226
 
227
227
  def page_is_loading(self) -> bool:
228
- if self.__driver.execute_script("return document.readyState") != "complete":
228
+ cmd = "return document.readyState"
229
+ if self.__driver.execute_script(cmd) != "complete":
229
230
  return True
230
231
  else:
231
232
  return False
@@ -2,9 +2,18 @@ from dataclasses import dataclass
2
2
  from enum import StrEnum
3
3
  from typing import Literal
4
4
 
5
- from pydantic import BaseModel
5
+ from pydantic import BaseModel, Field
6
6
 
7
7
 
8
+ class BoundingBox(BaseModel):
9
+ """
10
+ Represents the position and size of an element on the page.
11
+ """
12
+ x: float = Field(description="the x coordinate of the element in pixels")
13
+ y: float = Field(description="the y coordinate of the element in pixels")
14
+ width: float = Field(description="the width of the element in pixels")
15
+ height: float = Field(description="the height of the element in pixels")
16
+
8
17
  class Cookie(BaseModel):
9
18
  name: str
10
19
  value: str
@@ -41,6 +50,7 @@ class Redirect:
41
50
 
42
51
  @dataclass
43
52
  class Request:
53
+ method: str
44
54
  url: str
45
55
  request_id: str
46
56
  cookies: dict
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: browser-toolkit
3
- Version: 0.0.1a2
3
+ Version: 0.0.1a3
4
4
  Summary: Toolkit that provides a single inteface to interact with different browser automations.
5
5
  Project-URL: Homepage, https://github.com/toriium/browser-toolkit
6
6
  Project-URL: Documentation, https://github.com/toriium/browser-toolkit/blob/master/README.md
@@ -5,6 +5,7 @@ browser_toolkit/__init__.py
5
5
  browser_toolkit/base_toolkit.py
6
6
  browser_toolkit/camoufox.py
7
7
  browser_toolkit/playwright.py
8
+ browser_toolkit/pydoll.py
8
9
  browser_toolkit/selenium.py
9
10
  browser_toolkit/types.py
10
11
  browser_toolkit/utils.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "browser-toolkit"
3
- version = "0.0.1a2"
3
+ version = "0.0.1a3"
4
4
  description = "Toolkit that provides a single inteface to interact with different browser automations."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -0,0 +1,18 @@
1
+ import asyncio
2
+
3
+ from browser_toolkit.create_browser.playwright import get_playwright_toolkit
4
+
5
+
6
+ async def test_network_requests():
7
+ btk = await get_playwright_toolkit()
8
+
9
+ await btk.goto("https://scrapingtest.com/ecommerce/pagination")
10
+ await asyncio.sleep(5)
11
+
12
+ all_requests = await btk.get_network_requests()
13
+
14
+ assert all_requests
15
+
16
+
17
+ if __name__ == "__main__":
18
+ asyncio.run(test_network_requests())
@@ -1,20 +0,0 @@
1
- import asyncio
2
-
3
- from browser_toolkit.create_browser.playwright import get_playwright_toolkit
4
-
5
-
6
- async def test_element_is_present():
7
- btk = await get_playwright_toolkit()
8
-
9
- await btk.goto("https://statusinvest.com.br/")
10
- await asyncio.sleep(5)
11
-
12
- request_data = await btk.get_requests(request_url="https://statusinvest.com.br/account/userdata")
13
- value = await btk.get_response_body_from_request_id(request_id=request_data[0].request_id)
14
- await btk.close()
15
-
16
- assert value
17
-
18
-
19
- if __name__ == "__main__":
20
- test_element_is_present()