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.
- sbase/__init__.py +1 -0
- sbase/steps.py +7 -0
- seleniumbase/__init__.py +16 -7
- seleniumbase/__version__.py +1 -1
- seleniumbase/behave/behave_sb.py +97 -32
- seleniumbase/common/decorators.py +16 -7
- seleniumbase/config/proxy_list.py +3 -3
- seleniumbase/config/settings.py +4 -0
- seleniumbase/console_scripts/logo_helper.py +47 -8
- seleniumbase/console_scripts/run.py +345 -335
- seleniumbase/console_scripts/sb_behave_gui.py +5 -12
- seleniumbase/console_scripts/sb_caseplans.py +6 -13
- seleniumbase/console_scripts/sb_commander.py +5 -12
- seleniumbase/console_scripts/sb_install.py +62 -54
- seleniumbase/console_scripts/sb_mkchart.py +13 -20
- seleniumbase/console_scripts/sb_mkdir.py +11 -17
- seleniumbase/console_scripts/sb_mkfile.py +69 -43
- seleniumbase/console_scripts/sb_mkpres.py +13 -20
- seleniumbase/console_scripts/sb_mkrec.py +88 -21
- seleniumbase/console_scripts/sb_objectify.py +30 -30
- seleniumbase/console_scripts/sb_print.py +5 -12
- seleniumbase/console_scripts/sb_recorder.py +16 -11
- seleniumbase/core/browser_launcher.py +1658 -221
- seleniumbase/core/detect_b_ver.py +7 -8
- seleniumbase/core/log_helper.py +42 -27
- seleniumbase/core/mysql.py +1 -4
- seleniumbase/core/proxy_helper.py +35 -30
- seleniumbase/core/recorder_helper.py +24 -5
- seleniumbase/core/sb_cdp.py +1951 -0
- seleniumbase/core/sb_driver.py +162 -8
- seleniumbase/core/settings_parser.py +6 -0
- seleniumbase/core/style_sheet.py +10 -0
- seleniumbase/extensions/recorder.zip +0 -0
- seleniumbase/fixtures/base_case.py +1234 -632
- seleniumbase/fixtures/constants.py +10 -1
- seleniumbase/fixtures/js_utils.py +171 -144
- seleniumbase/fixtures/page_actions.py +177 -13
- seleniumbase/fixtures/page_utils.py +25 -53
- seleniumbase/fixtures/shared_utils.py +97 -11
- seleniumbase/js_code/active_css_js.py +1 -1
- seleniumbase/js_code/recorder_js.py +1 -1
- seleniumbase/plugins/base_plugin.py +2 -3
- seleniumbase/plugins/driver_manager.py +340 -65
- seleniumbase/plugins/pytest_plugin.py +276 -47
- seleniumbase/plugins/sb_manager.py +412 -99
- seleniumbase/plugins/selenium_plugin.py +122 -17
- seleniumbase/translate/translator.py +0 -7
- seleniumbase/undetected/__init__.py +59 -52
- seleniumbase/undetected/cdp.py +0 -1
- seleniumbase/undetected/cdp_driver/__init__.py +1 -0
- seleniumbase/undetected/cdp_driver/_contradict.py +110 -0
- seleniumbase/undetected/cdp_driver/browser.py +829 -0
- seleniumbase/undetected/cdp_driver/cdp_util.py +458 -0
- seleniumbase/undetected/cdp_driver/config.py +334 -0
- seleniumbase/undetected/cdp_driver/connection.py +639 -0
- seleniumbase/undetected/cdp_driver/element.py +1168 -0
- seleniumbase/undetected/cdp_driver/tab.py +1323 -0
- seleniumbase/undetected/dprocess.py +4 -7
- seleniumbase/undetected/options.py +6 -8
- seleniumbase/undetected/patcher.py +11 -13
- seleniumbase/undetected/reactor.py +0 -1
- seleniumbase/undetected/webelement.py +16 -3
- {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/LICENSE +1 -1
- {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/METADATA +299 -252
- {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/RECORD +68 -70
- {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/WHEEL +1 -1
- sbase/ReadMe.txt +0 -2
- seleniumbase/ReadMe.md +0 -25
- seleniumbase/common/ReadMe.md +0 -71
- seleniumbase/console_scripts/ReadMe.md +0 -731
- seleniumbase/drivers/ReadMe.md +0 -27
- seleniumbase/extensions/ReadMe.md +0 -12
- seleniumbase/masterqa/ReadMe.md +0 -61
- seleniumbase/resources/ReadMe.md +0 -31
- seleniumbase/resources/favicon.ico +0 -0
- seleniumbase/utilities/selenium_grid/ReadMe.md +0 -84
- seleniumbase/utilities/selenium_ide/ReadMe.md +0 -111
- {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/entry_points.txt +0 -0
- {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
|