seleniumbase 4.24.10__py3-none-any.whl → 4.33.15__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.
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