seleniumbase 4.24.10__py3-none-any.whl → 4.33.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. sbase/__init__.py +1 -0
  2. sbase/steps.py +7 -0
  3. seleniumbase/__init__.py +16 -7
  4. seleniumbase/__version__.py +1 -1
  5. seleniumbase/behave/behave_sb.py +97 -32
  6. seleniumbase/common/decorators.py +16 -7
  7. seleniumbase/config/proxy_list.py +3 -3
  8. seleniumbase/config/settings.py +4 -0
  9. seleniumbase/console_scripts/logo_helper.py +47 -8
  10. seleniumbase/console_scripts/run.py +345 -335
  11. seleniumbase/console_scripts/sb_behave_gui.py +5 -12
  12. seleniumbase/console_scripts/sb_caseplans.py +6 -13
  13. seleniumbase/console_scripts/sb_commander.py +5 -12
  14. seleniumbase/console_scripts/sb_install.py +62 -54
  15. seleniumbase/console_scripts/sb_mkchart.py +13 -20
  16. seleniumbase/console_scripts/sb_mkdir.py +11 -17
  17. seleniumbase/console_scripts/sb_mkfile.py +69 -43
  18. seleniumbase/console_scripts/sb_mkpres.py +13 -20
  19. seleniumbase/console_scripts/sb_mkrec.py +88 -21
  20. seleniumbase/console_scripts/sb_objectify.py +30 -30
  21. seleniumbase/console_scripts/sb_print.py +5 -12
  22. seleniumbase/console_scripts/sb_recorder.py +16 -11
  23. seleniumbase/core/browser_launcher.py +1658 -221
  24. seleniumbase/core/detect_b_ver.py +7 -8
  25. seleniumbase/core/log_helper.py +42 -27
  26. seleniumbase/core/mysql.py +1 -4
  27. seleniumbase/core/proxy_helper.py +35 -30
  28. seleniumbase/core/recorder_helper.py +24 -5
  29. seleniumbase/core/sb_cdp.py +1951 -0
  30. seleniumbase/core/sb_driver.py +162 -8
  31. seleniumbase/core/settings_parser.py +6 -0
  32. seleniumbase/core/style_sheet.py +10 -0
  33. seleniumbase/extensions/recorder.zip +0 -0
  34. seleniumbase/fixtures/base_case.py +1234 -632
  35. seleniumbase/fixtures/constants.py +10 -1
  36. seleniumbase/fixtures/js_utils.py +171 -144
  37. seleniumbase/fixtures/page_actions.py +177 -13
  38. seleniumbase/fixtures/page_utils.py +25 -53
  39. seleniumbase/fixtures/shared_utils.py +97 -11
  40. seleniumbase/js_code/active_css_js.py +1 -1
  41. seleniumbase/js_code/recorder_js.py +1 -1
  42. seleniumbase/plugins/base_plugin.py +2 -3
  43. seleniumbase/plugins/driver_manager.py +340 -65
  44. seleniumbase/plugins/pytest_plugin.py +276 -47
  45. seleniumbase/plugins/sb_manager.py +412 -99
  46. seleniumbase/plugins/selenium_plugin.py +122 -17
  47. seleniumbase/translate/translator.py +0 -7
  48. seleniumbase/undetected/__init__.py +59 -52
  49. seleniumbase/undetected/cdp.py +0 -1
  50. seleniumbase/undetected/cdp_driver/__init__.py +1 -0
  51. seleniumbase/undetected/cdp_driver/_contradict.py +110 -0
  52. seleniumbase/undetected/cdp_driver/browser.py +829 -0
  53. seleniumbase/undetected/cdp_driver/cdp_util.py +458 -0
  54. seleniumbase/undetected/cdp_driver/config.py +334 -0
  55. seleniumbase/undetected/cdp_driver/connection.py +639 -0
  56. seleniumbase/undetected/cdp_driver/element.py +1168 -0
  57. seleniumbase/undetected/cdp_driver/tab.py +1323 -0
  58. seleniumbase/undetected/dprocess.py +4 -7
  59. seleniumbase/undetected/options.py +6 -8
  60. seleniumbase/undetected/patcher.py +11 -13
  61. seleniumbase/undetected/reactor.py +0 -1
  62. seleniumbase/undetected/webelement.py +16 -3
  63. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/LICENSE +1 -1
  64. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/METADATA +299 -252
  65. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/RECORD +68 -70
  66. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/WHEEL +1 -1
  67. sbase/ReadMe.txt +0 -2
  68. seleniumbase/ReadMe.md +0 -25
  69. seleniumbase/common/ReadMe.md +0 -71
  70. seleniumbase/console_scripts/ReadMe.md +0 -731
  71. seleniumbase/drivers/ReadMe.md +0 -27
  72. seleniumbase/extensions/ReadMe.md +0 -12
  73. seleniumbase/masterqa/ReadMe.md +0 -61
  74. seleniumbase/resources/ReadMe.md +0 -31
  75. seleniumbase/resources/favicon.ico +0 -0
  76. seleniumbase/utilities/selenium_grid/ReadMe.md +0 -84
  77. seleniumbase/utilities/selenium_ide/ReadMe.md +0 -111
  78. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/entry_points.txt +0 -0
  79. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,458 @@
1
+ """CDP-Driver is based on NoDriver"""
2
+ from __future__ import annotations
3
+ import asyncio
4
+ import fasteners
5
+ import logging
6
+ import os
7
+ import time
8
+ import types
9
+ import typing
10
+ from contextlib import suppress
11
+ from seleniumbase import config as sb_config
12
+ from seleniumbase.config import settings
13
+ from seleniumbase.fixtures import constants
14
+ from seleniumbase.fixtures import shared_utils
15
+ from typing import Optional, List, Union, Callable
16
+ from .element import Element
17
+ from .browser import Browser
18
+ from .browser import PathLike
19
+ from .config import Config
20
+ from .tab import Tab
21
+ import mycdp as cdp
22
+
23
+ logger = logging.getLogger(__name__)
24
+ IS_LINUX = shared_utils.is_linux()
25
+ T = typing.TypeVar("T")
26
+
27
+
28
+ def __activate_standard_virtual_display():
29
+ from sbvirtualdisplay import Display
30
+ width = settings.HEADLESS_START_WIDTH
31
+ height = settings.HEADLESS_START_HEIGHT
32
+ with suppress(Exception):
33
+ _xvfb_display = Display(
34
+ visible=0, size=(width, height)
35
+ )
36
+ _xvfb_display.start()
37
+ sb_config._virtual_display = _xvfb_display
38
+ sb_config.headless_active = True
39
+
40
+
41
+ def __activate_virtual_display_as_needed(
42
+ headless, headed, xvfb, xvfb_metrics
43
+ ):
44
+ """This is only needed on Linux."""
45
+ if IS_LINUX and (not headed or xvfb):
46
+ from sbvirtualdisplay import Display
47
+ pip_find_lock = fasteners.InterProcessLock(
48
+ constants.PipInstall.FINDLOCK
49
+ )
50
+ with pip_find_lock: # Prevent issues with multiple processes
51
+ if not headless:
52
+ import Xlib.display
53
+ try:
54
+ _xvfb_width = None
55
+ _xvfb_height = None
56
+ if xvfb_metrics:
57
+ with suppress(Exception):
58
+ metrics_string = xvfb_metrics
59
+ metrics_string = metrics_string.replace(" ", "")
60
+ metrics_list = metrics_string.split(",")[0:2]
61
+ _xvfb_width = int(metrics_list[0])
62
+ _xvfb_height = int(metrics_list[1])
63
+ # The minimum width,height is: 1024,768
64
+ if _xvfb_width < 1024:
65
+ _xvfb_width = 1024
66
+ sb_config._xvfb_width = _xvfb_width
67
+ if _xvfb_height < 768:
68
+ _xvfb_height = 768
69
+ sb_config._xvfb_height = _xvfb_height
70
+ xvfb = True
71
+ if not _xvfb_width:
72
+ _xvfb_width = 1366
73
+ if not _xvfb_height:
74
+ _xvfb_height = 768
75
+ _xvfb_display = Display(
76
+ visible=True,
77
+ size=(_xvfb_width, _xvfb_height),
78
+ backend="xvfb",
79
+ use_xauth=True,
80
+ )
81
+ _xvfb_display.start()
82
+ if "DISPLAY" not in os.environ.keys():
83
+ print(
84
+ "\nX11 display failed! Will use regular xvfb!"
85
+ )
86
+ __activate_standard_virtual_display()
87
+ else:
88
+ sb_config._virtual_display = _xvfb_display
89
+ sb_config.headless_active = True
90
+ except Exception as e:
91
+ if hasattr(e, "msg"):
92
+ print("\n" + str(e.msg))
93
+ else:
94
+ print(e)
95
+ print("\nX11 display failed! Will use regular xvfb!")
96
+ __activate_standard_virtual_display()
97
+ return
98
+ pyautogui_is_installed = False
99
+ try:
100
+ import pyautogui
101
+ with suppress(Exception):
102
+ use_pyautogui_ver = constants.PyAutoGUI.VER
103
+ if pyautogui.__version__ != use_pyautogui_ver:
104
+ del pyautogui # To get newer ver
105
+ shared_utils.pip_install(
106
+ "pyautogui", version=use_pyautogui_ver
107
+ )
108
+ import pyautogui
109
+ pyautogui_is_installed = True
110
+ except Exception:
111
+ message = (
112
+ "PyAutoGUI is required for UC Mode on Linux! "
113
+ "Installing now..."
114
+ )
115
+ print("\n" + message)
116
+ shared_utils.pip_install(
117
+ "pyautogui", version=constants.PyAutoGUI.VER
118
+ )
119
+ import pyautogui
120
+ pyautogui_is_installed = True
121
+ if (
122
+ pyautogui_is_installed
123
+ and hasattr(pyautogui, "_pyautogui_x11")
124
+ ):
125
+ try:
126
+ pyautogui._pyautogui_x11._display = (
127
+ Xlib.display.Display(os.environ['DISPLAY'])
128
+ )
129
+ sb_config._pyautogui_x11_display = (
130
+ pyautogui._pyautogui_x11._display
131
+ )
132
+ except Exception as e:
133
+ if hasattr(e, "msg"):
134
+ print("\n" + str(e.msg))
135
+ else:
136
+ print(e)
137
+ else:
138
+ __activate_standard_virtual_display()
139
+
140
+
141
+ async def start(
142
+ config: Optional[Config] = None,
143
+ *,
144
+ user_data_dir: Optional[PathLike] = None,
145
+ headless: Optional[bool] = False,
146
+ incognito: Optional[bool] = False,
147
+ guest: Optional[bool] = False,
148
+ browser_executable_path: Optional[PathLike] = None,
149
+ browser_args: Optional[List[str]] = None,
150
+ xvfb_metrics: Optional[List[str]] = None, # "Width,Height" for Linux
151
+ sandbox: Optional[bool] = True,
152
+ lang: Optional[str] = None,
153
+ host: Optional[str] = None,
154
+ port: Optional[int] = None,
155
+ xvfb: Optional[int] = None, # Use a special virtual display on Linux
156
+ headed: Optional[bool] = None, # Override default Xvfb mode on Linux
157
+ expert: Optional[bool] = None, # Open up closed Shadow-root elements
158
+ **kwargs: Optional[dict],
159
+ ) -> Browser:
160
+ """
161
+ Helper function to launch a browser. It accepts several keyword parameters.
162
+ Conveniently, you can just call it bare (no parameters) to quickly launch
163
+ an instance with best practice defaults.
164
+ Note: Due to a Chrome-130 bug, use start_async or start_sync instead.
165
+ (Calling this method directly could lead to an unresponsive browser)
166
+ Note: New args are expected: Use kwargs only!
167
+ Note: This should be called ``await start()``
168
+ :param user_data_dir:
169
+ :type user_data_dir: PathLike
170
+ :param headless:
171
+ :type headless: bool
172
+ :param browser_executable_path:
173
+ :type browser_executable_path: PathLike
174
+ :param browser_args:
175
+ ["--some-chromeparam=somevalue", "some-other-param=someval"]
176
+ :type browser_args: List[str]
177
+ :param sandbox: Default True, but when set to False it adds --no-sandbox
178
+ to the params, also when using linux under a root user,
179
+ it adds False automatically (else Chrome won't start).
180
+ :type sandbox: bool
181
+ :param lang: language string
182
+ :type lang: str
183
+ :param port: If you connect to an existing debuggable session,
184
+ you can specify the port here.
185
+ If both host and port are provided,
186
+ then a local Chrome browser will not be started!
187
+ :type port: int
188
+ :param host: If you connect to an existing debuggable session,
189
+ you can specify the host here.
190
+ If both host and port are provided,
191
+ then a local Chrome browser will not be started!
192
+ :type host: str
193
+ :param expert: When set to True, "expert" mode is enabled.
194
+ This means adding: --disable-web-security --disable-site-isolation-trials,
195
+ as well as some scripts and patching useful for debugging.
196
+ (For example, ensuring shadow-root is always in "open" mode.)
197
+ :type expert: bool
198
+ """
199
+ if IS_LINUX and not headless and not headed and not xvfb:
200
+ xvfb = True # The default setting on Linux
201
+ __activate_virtual_display_as_needed(headless, headed, xvfb, xvfb_metrics)
202
+ if not config:
203
+ config = Config(
204
+ user_data_dir,
205
+ headless,
206
+ incognito,
207
+ guest,
208
+ browser_executable_path,
209
+ browser_args,
210
+ sandbox,
211
+ lang,
212
+ host=host,
213
+ port=port,
214
+ expert=expert,
215
+ **kwargs,
216
+ )
217
+ try:
218
+ return await Browser.create(config)
219
+ except Exception:
220
+ time.sleep(0.15)
221
+ return await Browser.create(config)
222
+
223
+
224
+ async def start_async(*args, **kwargs) -> Browser:
225
+ headless = False
226
+ binary_location = None
227
+ if "browser_executable_path" in kwargs:
228
+ binary_location = kwargs["browser_executable_path"]
229
+ if shared_utils.is_chrome_130_or_newer(binary_location):
230
+ if "headless" in kwargs:
231
+ headless = kwargs["headless"]
232
+ decoy_args = kwargs
233
+ decoy_args["headless"] = True
234
+ driver = await start(**decoy_args)
235
+ kwargs["headless"] = headless
236
+ kwargs["user_data_dir"] = driver.config.user_data_dir
237
+ time.sleep(0.2)
238
+ driver.stop() # Due to Chrome-130, must stop & start
239
+ time.sleep(0.1)
240
+ return await start(*args, **kwargs)
241
+
242
+
243
+ def start_sync(*args, **kwargs) -> Browser:
244
+ loop = asyncio.get_event_loop()
245
+ headless = False
246
+ binary_location = None
247
+ if "browser_executable_path" in kwargs:
248
+ binary_location = kwargs["browser_executable_path"]
249
+ if shared_utils.is_chrome_130_or_newer(binary_location):
250
+ if "headless" in kwargs:
251
+ headless = kwargs["headless"]
252
+ decoy_args = kwargs
253
+ decoy_args["headless"] = True
254
+ driver = loop.run_until_complete(start(**decoy_args))
255
+ kwargs["headless"] = headless
256
+ kwargs["user_data_dir"] = driver.config.user_data_dir
257
+ time.sleep(0.2)
258
+ driver.stop() # Due to Chrome-130, must stop & start
259
+ time.sleep(0.1)
260
+ return loop.run_until_complete(start(*args, **kwargs))
261
+
262
+
263
+ async def create_from_driver(driver) -> Browser:
264
+ """Create a Browser instance from a running driver instance."""
265
+ from .config import Config
266
+
267
+ conf = Config()
268
+ host, port = driver.options.debugger_address.split(":")
269
+ conf.host, conf.port = host, int(port)
270
+ # Create Browser instance
271
+ browser = await start(conf)
272
+ browser._process_pid = driver.browser_pid
273
+ # Stop chromedriver binary
274
+ driver.service.stop()
275
+ driver.browser_pid = -1
276
+ driver.user_data_dir = None
277
+ return browser
278
+
279
+
280
+ def free_port() -> int:
281
+ """Determines a free port using sockets."""
282
+ import socket
283
+
284
+ free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
285
+ free_socket.bind(("127.0.0.1", 0))
286
+ free_socket.listen(5)
287
+ port: int = free_socket.getsockname()[1]
288
+ free_socket.close()
289
+ return port
290
+
291
+
292
+ def filter_recurse_all(
293
+ doc: T, predicate: Callable[[cdp.dom.Node, Element], bool]
294
+ ) -> List[T]:
295
+ """
296
+ Test each child using predicate(child),
297
+ and return all children for which predicate(child) == True
298
+ :param doc: The cdp.dom.Node object or :py:class:`cdp_driver.Element`
299
+ :param predicate: A function which takes a node as first parameter
300
+ and returns a boolean, where True means include.
301
+ """
302
+ if not hasattr(doc, "children"):
303
+ raise TypeError("Object should have a .children attribute!")
304
+ out = []
305
+ if doc and doc.children:
306
+ for child in doc.children:
307
+ if predicate(child):
308
+ out.append(child)
309
+ if child.shadow_roots is not None:
310
+ out.extend(
311
+ filter_recurse_all(child.shadow_roots[0], predicate)
312
+ )
313
+ out.extend(filter_recurse_all(child, predicate))
314
+ return out
315
+
316
+
317
+ def filter_recurse(
318
+ doc: T, predicate: Callable[[cdp.dom.Node, Element], bool]
319
+ ) -> T:
320
+ """
321
+ Test each child using predicate(child),
322
+ and return the first child of which predicate(child) == True
323
+ :param doc: the cdp.dom.Node object or :py:class:`cdp_driver.Element`
324
+ :param predicate: a function which takes a node as first parameter
325
+ and returns a boolean, where True means include.
326
+ """
327
+ if not hasattr(doc, "children"):
328
+ raise TypeError("Object should have a .children attribute!")
329
+ if doc and doc.children:
330
+ for child in doc.children:
331
+ if predicate(child):
332
+ return child
333
+ if child.shadow_roots:
334
+ shadow_root_result = filter_recurse(
335
+ child.shadow_roots[0], predicate
336
+ )
337
+ if shadow_root_result:
338
+ return shadow_root_result
339
+ result = filter_recurse(child, predicate)
340
+ if result:
341
+ return result
342
+
343
+
344
+ def circle(
345
+ x, y=None, radius=10, num=10, dir=0
346
+ ) -> typing.Generator[typing.Tuple[float, float], None, None]:
347
+ """
348
+ A generator will calculate coordinates around a circle.
349
+ :param x: start x position
350
+ :type x: int
351
+ :param y: start y position
352
+ :type y: int
353
+ :param radius: size of the circle
354
+ :type radius: int
355
+ :param num: the amount of points calculated
356
+ (higher => slower, more cpu, but more detailed)
357
+ :type num: int
358
+ """
359
+ import math
360
+
361
+ r = radius
362
+ w = num
363
+ if not y:
364
+ y = x
365
+ a = int(x - r * 2)
366
+ b = int(y - r * 2)
367
+ m = (2 * math.pi) / w
368
+ if dir == 0:
369
+ # Regular direction
370
+ ran = 0, w + 1, 1
371
+ else:
372
+ # Opposite direction
373
+ ran = w + 1, 0, -1
374
+ for i in range(*ran):
375
+ x = a + r * math.sin(m * i)
376
+ y = b + r * math.cos(m * i)
377
+ yield x, y
378
+
379
+
380
+ def remove_from_tree(tree: cdp.dom.Node, node: cdp.dom.Node) -> cdp.dom.Node:
381
+ if not hasattr(tree, "children"):
382
+ raise TypeError("Object should have a .children attribute!")
383
+ if tree and tree.children:
384
+ for child in tree.children:
385
+ if child.backend_node_id == node.backend_node_id:
386
+ tree.children.remove(child)
387
+ remove_from_tree(child, node)
388
+ return tree
389
+
390
+
391
+ async def html_from_tree(
392
+ tree: Union[cdp.dom.Node, Element], target: Tab
393
+ ):
394
+ if not hasattr(tree, "children"):
395
+ raise TypeError("Object should have a .children attribute!")
396
+ out = ""
397
+ if tree and tree.children:
398
+ for child in tree.children:
399
+ if isinstance(child, Element):
400
+ out += await child.get_html()
401
+ else:
402
+ out += await target.send(
403
+ cdp.dom.get_outer_html(
404
+ backend_node_id=child.backend_node_id
405
+ )
406
+ )
407
+ out += await html_from_tree(child, target)
408
+ return out
409
+
410
+
411
+ def compare_target_info(
412
+ info1: cdp.target.TargetInfo, info2: cdp.target.TargetInfo
413
+ ) -> List[typing.Tuple[str, typing.Any, typing.Any]]:
414
+ """
415
+ When logging mode is set to debug, browser object will log when target info
416
+ is changed. To provide more meaningful log messages,
417
+ this function is called to check what has actually changed
418
+ between the 2 (by simple dict comparison).
419
+ It returns a list of tuples
420
+ [ ... ( key_which_has_changed, old_value, new_value) ]
421
+ :param info1:
422
+ :param info2:
423
+ """
424
+ d1 = info1.__dict__
425
+ d2 = info2.__dict__
426
+ return [(k, v, d2[k]) for (k, v) in d1.items() if d2[k] != v]
427
+
428
+
429
+ def loop():
430
+ loop = asyncio.new_event_loop()
431
+ asyncio.set_event_loop(loop)
432
+ return loop
433
+
434
+
435
+ def cdp_get_module(domain: Union[str, types.ModuleType]):
436
+ """
437
+ Get cdp module by given string.
438
+ :param domain:
439
+ """
440
+ import importlib
441
+
442
+ if isinstance(domain, types.ModuleType):
443
+ domain_mod = domain
444
+ else:
445
+ try:
446
+ if domain in ("input",):
447
+ domain = "input_"
448
+ domain_mod = getattr(cdp, domain)
449
+ if not domain_mod:
450
+ raise AttributeError
451
+ except AttributeError:
452
+ try:
453
+ domain_mod = importlib.import_module(domain)
454
+ except ModuleNotFoundError:
455
+ raise ModuleNotFoundError(
456
+ "Could not find cdp module from input '%s'" % domain
457
+ )
458
+ return domain_mod