seleniumbase 4.34.7__py3-none-any.whl → 4.36.2__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.
- seleniumbase/__init__.py +2 -0
- seleniumbase/__version__.py +1 -1
- seleniumbase/config/proxy_list.py +2 -2
- seleniumbase/console_scripts/sb_mkdir.py +3 -0
- seleniumbase/core/browser_launcher.py +160 -38
- seleniumbase/core/detect_b_ver.py +12 -13
- seleniumbase/core/proxy_helper.py +7 -5
- seleniumbase/core/sb_cdp.py +335 -93
- seleniumbase/core/sb_driver.py +7 -0
- seleniumbase/extensions/ad_block.zip +0 -0
- seleniumbase/extensions/disable_csp.zip +0 -0
- seleniumbase/fixtures/base_case.py +104 -19
- seleniumbase/fixtures/js_utils.py +33 -9
- seleniumbase/plugins/base_plugin.py +12 -15
- seleniumbase/plugins/driver_manager.py +19 -2
- seleniumbase/plugins/pytest_plugin.py +3 -0
- seleniumbase/plugins/sb_manager.py +18 -1
- seleniumbase/plugins/selenium_plugin.py +1 -0
- seleniumbase/undetected/__init__.py +38 -17
- seleniumbase/undetected/cdp_driver/__init__.py +2 -0
- seleniumbase/undetected/cdp_driver/browser.py +16 -10
- seleniumbase/undetected/cdp_driver/cdp_util.py +143 -8
- seleniumbase/undetected/cdp_driver/config.py +10 -0
- seleniumbase/undetected/cdp_driver/connection.py +1 -1
- seleniumbase/undetected/cdp_driver/tab.py +41 -4
- seleniumbase/undetected/webelement.py +3 -2
- {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/METADATA +30 -28
- {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/RECORD +32 -32
- {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/WHEEL +1 -1
- {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/entry_points.txt +0 -0
- {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info/licenses}/LICENSE +0 -0
- {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/top_level.txt +0 -0
@@ -452,14 +452,24 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
|
|
452
452
|
self.service.start()
|
453
453
|
with suppress(Exception):
|
454
454
|
self.start_session()
|
455
|
+
time.sleep(0.0075)
|
455
456
|
with suppress(Exception):
|
456
|
-
|
457
|
-
self.
|
458
|
-
if self.
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
457
|
+
for window_handle in self.window_handles:
|
458
|
+
self.switch_to.window(window_handle)
|
459
|
+
if self.current_url.startswith(
|
460
|
+
"chrome-extension://"
|
461
|
+
):
|
462
|
+
# https://issues.chromium.org/issues/396611138
|
463
|
+
# (Uncomment below when resolved)
|
464
|
+
# self.close()
|
465
|
+
if self.service.is_connectable():
|
466
|
+
self.stop_client()
|
467
|
+
self.service.stop()
|
468
|
+
self.service.start()
|
469
|
+
self.start_session()
|
470
|
+
time.sleep(0.003)
|
471
|
+
with suppress(Exception):
|
472
|
+
self.switch_to.window(self.window_handles[-1])
|
463
473
|
self._is_connected = True
|
464
474
|
|
465
475
|
def disconnect(self):
|
@@ -469,6 +479,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
|
|
469
479
|
with suppress(Exception):
|
470
480
|
if self.service.is_connectable():
|
471
481
|
self.stop_client()
|
482
|
+
time.sleep(0.003)
|
472
483
|
self.service.stop()
|
473
484
|
self._is_connected = False
|
474
485
|
|
@@ -480,14 +491,24 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
|
|
480
491
|
self.service.start()
|
481
492
|
with suppress(Exception):
|
482
493
|
self.start_session()
|
494
|
+
time.sleep(0.0075)
|
483
495
|
with suppress(Exception):
|
484
|
-
|
485
|
-
self.
|
486
|
-
if self.
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
496
|
+
for window_handle in self.window_handles:
|
497
|
+
self.switch_to.window(window_handle)
|
498
|
+
if self.current_url.startswith(
|
499
|
+
"chrome-extension://"
|
500
|
+
):
|
501
|
+
# https://issues.chromium.org/issues/396611138
|
502
|
+
# (Uncomment below when resolved)
|
503
|
+
# self.close()
|
504
|
+
if self.service.is_connectable():
|
505
|
+
self.stop_client()
|
506
|
+
self.service.stop()
|
507
|
+
self.service.start()
|
508
|
+
self.start_session()
|
509
|
+
time.sleep(0.003)
|
510
|
+
with suppress(Exception):
|
511
|
+
self.switch_to.window(self.window_handles[-1])
|
491
512
|
self._is_connected = True
|
492
513
|
|
493
514
|
def start_session(self, capabilities=None):
|
@@ -576,14 +597,14 @@ def find_chrome_executable():
|
|
576
597
|
if IS_POSIX:
|
577
598
|
for item in os.environ.get("PATH").split(os.pathsep):
|
578
599
|
for subitem in (
|
579
|
-
"chromium",
|
580
600
|
"google-chrome",
|
581
|
-
"chromium-browser",
|
582
|
-
"chrome",
|
583
601
|
"google-chrome-stable",
|
584
602
|
"google-chrome-beta",
|
585
603
|
"google-chrome-dev",
|
586
604
|
"google-chrome-unstable",
|
605
|
+
"chrome",
|
606
|
+
"chromium",
|
607
|
+
"chromium-browser",
|
587
608
|
):
|
588
609
|
candidates.add(os.sep.join((item, subitem)))
|
589
610
|
if "darwin" in sys.platform:
|
@@ -15,6 +15,7 @@ import urllib.parse
|
|
15
15
|
import urllib.request
|
16
16
|
import warnings
|
17
17
|
from collections import defaultdict
|
18
|
+
from seleniumbase import config as sb_config
|
18
19
|
from typing import List, Set, Tuple, Union
|
19
20
|
import mycdp as cdp
|
20
21
|
from . import cdp_util as util
|
@@ -265,6 +266,8 @@ class Browser:
|
|
265
266
|
:param new_window: Open new window
|
266
267
|
:return: Page
|
267
268
|
"""
|
269
|
+
if url and ":" not in url:
|
270
|
+
url = "https://" + url
|
268
271
|
if new_tab or new_window:
|
269
272
|
# Create new target using the browser session.
|
270
273
|
target_id = await self.connection.send(
|
@@ -285,6 +288,9 @@ class Browser:
|
|
285
288
|
filter(lambda item: item.type_ == "page", self.targets)
|
286
289
|
)
|
287
290
|
# Use the tab to navigate to new url
|
291
|
+
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
|
292
|
+
await connection.send(cdp.page.navigate("about:blank"))
|
293
|
+
await connection.set_locale(sb_config._cdp_locale)
|
288
294
|
frame_id, loader_id, *_ = await connection.send(
|
289
295
|
cdp.page.navigate(url)
|
290
296
|
)
|
@@ -565,10 +571,11 @@ class Browser:
|
|
565
571
|
# asyncio.get_running_loop().create_task(
|
566
572
|
# self.connection.send(cdp.browser.close())
|
567
573
|
# )
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
574
|
+
if self.connection:
|
575
|
+
asyncio.get_event_loop().create_task(self.connection.aclose())
|
576
|
+
logger.debug(
|
577
|
+
"Closed connection using get_event_loop().create_task()"
|
578
|
+
)
|
572
579
|
except RuntimeError:
|
573
580
|
if self.connection:
|
574
581
|
try:
|
@@ -653,7 +660,7 @@ class CookieJar:
|
|
653
660
|
break
|
654
661
|
else:
|
655
662
|
connection = self._browser.connection
|
656
|
-
cookies = await connection.send(cdp.
|
663
|
+
cookies = await connection.send(cdp.network.get_cookies())
|
657
664
|
if requests_cookie_format:
|
658
665
|
import requests.cookies
|
659
666
|
|
@@ -683,8 +690,7 @@ class CookieJar:
|
|
683
690
|
break
|
684
691
|
else:
|
685
692
|
connection = self._browser.connection
|
686
|
-
|
687
|
-
await connection.send(cdp.storage.set_cookies(cookies))
|
693
|
+
await connection.send(cdp.network.set_cookies(cookies))
|
688
694
|
|
689
695
|
async def save(self, file: PathLike = ".session.dat", pattern: str = ".*"):
|
690
696
|
"""
|
@@ -711,7 +717,7 @@ class CookieJar:
|
|
711
717
|
break
|
712
718
|
else:
|
713
719
|
connection = self._browser.connection
|
714
|
-
cookies = await connection.send(cdp.
|
720
|
+
cookies = await connection.send(cdp.network.get_cookies())
|
715
721
|
# if not connection:
|
716
722
|
# return
|
717
723
|
# if not connection.websocket:
|
@@ -769,7 +775,7 @@ class CookieJar:
|
|
769
775
|
cookie.value,
|
770
776
|
)
|
771
777
|
break
|
772
|
-
await connection.send(cdp.
|
778
|
+
await connection.send(cdp.network.set_cookies(included_cookies))
|
773
779
|
|
774
780
|
async def clear(self):
|
775
781
|
"""
|
@@ -784,7 +790,7 @@ class CookieJar:
|
|
784
790
|
break
|
785
791
|
else:
|
786
792
|
connection = self._browser.connection
|
787
|
-
cookies = await connection.send(cdp.
|
793
|
+
cookies = await connection.send(cdp.network.get_cookies())
|
788
794
|
if cookies:
|
789
795
|
await connection.send(cdp.storage.clear_cookies())
|
790
796
|
|
@@ -4,12 +4,15 @@ import asyncio
|
|
4
4
|
import fasteners
|
5
5
|
import logging
|
6
6
|
import os
|
7
|
+
import sys
|
7
8
|
import time
|
8
9
|
import types
|
9
10
|
import typing
|
10
11
|
from contextlib import suppress
|
11
12
|
from seleniumbase import config as sb_config
|
12
13
|
from seleniumbase.config import settings
|
14
|
+
from seleniumbase.core import detect_b_ver
|
15
|
+
from seleniumbase.core import proxy_helper
|
13
16
|
from seleniumbase.fixtures import constants
|
14
17
|
from seleniumbase.fixtures import shared_utils
|
15
18
|
from typing import Optional, List, Union, Callable
|
@@ -22,6 +25,7 @@ import mycdp as cdp
|
|
22
25
|
|
23
26
|
logger = logging.getLogger(__name__)
|
24
27
|
IS_LINUX = shared_utils.is_linux()
|
28
|
+
PROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK
|
25
29
|
T = typing.TypeVar("T")
|
26
30
|
|
27
31
|
|
@@ -138,6 +142,85 @@ def __activate_virtual_display_as_needed(
|
|
138
142
|
__activate_standard_virtual_display()
|
139
143
|
|
140
144
|
|
145
|
+
def __set_proxy_filenames():
|
146
|
+
DOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER
|
147
|
+
for num in range(1000):
|
148
|
+
PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, "proxy_ext_dir_%s" % num)
|
149
|
+
if os.path.exists(PROXY_DIR_PATH):
|
150
|
+
continue
|
151
|
+
proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH
|
152
|
+
return
|
153
|
+
# Exceeded upper bound. Use Defaults:
|
154
|
+
PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, "proxy_ext_dir")
|
155
|
+
proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH
|
156
|
+
|
157
|
+
|
158
|
+
def __add_chrome_ext_dir(extension_dir, dir_path):
|
159
|
+
# Add dir_path to the existing extension_dir
|
160
|
+
option_exists = False
|
161
|
+
if extension_dir:
|
162
|
+
option_exists = True
|
163
|
+
extension_dir = "%s,%s" % (
|
164
|
+
extension_dir, os.path.realpath(dir_path)
|
165
|
+
)
|
166
|
+
if not option_exists:
|
167
|
+
extension_dir = os.path.realpath(dir_path)
|
168
|
+
return extension_dir
|
169
|
+
|
170
|
+
|
171
|
+
def __add_chrome_proxy_extension(
|
172
|
+
extension_dir,
|
173
|
+
proxy_string,
|
174
|
+
proxy_user,
|
175
|
+
proxy_pass,
|
176
|
+
proxy_bypass_list=None,
|
177
|
+
multi_proxy=False,
|
178
|
+
):
|
179
|
+
"""Implementation of https://stackoverflow.com/a/35293284/7058266
|
180
|
+
for https://stackoverflow.com/q/12848327/7058266
|
181
|
+
(Run Selenium on a proxy server that requires authentication.)"""
|
182
|
+
args = " ".join(sys.argv)
|
183
|
+
bypass_list = proxy_bypass_list
|
184
|
+
if (
|
185
|
+
not ("-n" in sys.argv or " -n=" in args or args == "-c")
|
186
|
+
and not multi_proxy
|
187
|
+
):
|
188
|
+
# Single-threaded
|
189
|
+
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
|
190
|
+
with proxy_dir_lock:
|
191
|
+
proxy_helper.create_proxy_ext(
|
192
|
+
proxy_string,
|
193
|
+
proxy_user,
|
194
|
+
proxy_pass,
|
195
|
+
bypass_list,
|
196
|
+
zip_it=False,
|
197
|
+
)
|
198
|
+
proxy_dir_path = proxy_helper.PROXY_DIR_PATH
|
199
|
+
extension_dir = __add_chrome_ext_dir(
|
200
|
+
extension_dir, proxy_dir_path
|
201
|
+
)
|
202
|
+
else:
|
203
|
+
# Multi-threaded
|
204
|
+
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
|
205
|
+
with proxy_dir_lock:
|
206
|
+
with suppress(Exception):
|
207
|
+
shared_utils.make_writable(PROXY_DIR_LOCK)
|
208
|
+
if multi_proxy:
|
209
|
+
__set_proxy_filenames()
|
210
|
+
if not os.path.exists(proxy_helper.PROXY_DIR_PATH):
|
211
|
+
proxy_helper.create_proxy_ext(
|
212
|
+
proxy_string,
|
213
|
+
proxy_user,
|
214
|
+
proxy_pass,
|
215
|
+
bypass_list,
|
216
|
+
zip_it=False,
|
217
|
+
)
|
218
|
+
extension_dir = __add_chrome_ext_dir(
|
219
|
+
extension_dir, proxy_helper.PROXY_DIR_PATH
|
220
|
+
)
|
221
|
+
return extension_dir
|
222
|
+
|
223
|
+
|
141
224
|
async def start(
|
142
225
|
config: Optional[Config] = None,
|
143
226
|
*,
|
@@ -149,12 +232,14 @@ async def start(
|
|
149
232
|
browser_args: Optional[List[str]] = None,
|
150
233
|
xvfb_metrics: Optional[List[str]] = None, # "Width,Height" for Linux
|
151
234
|
sandbox: Optional[bool] = True,
|
152
|
-
lang: Optional[str] = None,
|
153
|
-
host: Optional[str] = None,
|
154
|
-
port: Optional[int] = None,
|
235
|
+
lang: Optional[str] = None, # Set the Language Locale Code
|
236
|
+
host: Optional[str] = None, # Chrome remote-debugging-host
|
237
|
+
port: Optional[int] = None, # Chrome remote-debugging-port
|
155
238
|
xvfb: Optional[int] = None, # Use a special virtual display on Linux
|
156
239
|
headed: Optional[bool] = None, # Override default Xvfb mode on Linux
|
157
240
|
expert: Optional[bool] = None, # Open up closed Shadow-root elements
|
241
|
+
proxy: Optional[str] = None, # "host:port" or "user:pass@host:port"
|
242
|
+
extension_dir: Optional[str] = None, # Chrome extension directory
|
158
243
|
**kwargs: Optional[dict],
|
159
244
|
) -> Browser:
|
160
245
|
"""
|
@@ -199,6 +284,18 @@ async def start(
|
|
199
284
|
if IS_LINUX and not headless and not headed and not xvfb:
|
200
285
|
xvfb = True # The default setting on Linux
|
201
286
|
__activate_virtual_display_as_needed(headless, headed, xvfb, xvfb_metrics)
|
287
|
+
if proxy and "@" in str(proxy):
|
288
|
+
user_with_pass = proxy.split("@")[0]
|
289
|
+
if ":" in user_with_pass:
|
290
|
+
proxy_user = user_with_pass.split(":")[0]
|
291
|
+
proxy_pass = user_with_pass.split(":")[1]
|
292
|
+
proxy_string = proxy.split("@")[1]
|
293
|
+
extension_dir = __add_chrome_proxy_extension(
|
294
|
+
extension_dir,
|
295
|
+
proxy_string,
|
296
|
+
proxy_user,
|
297
|
+
proxy_pass,
|
298
|
+
)
|
202
299
|
if not config:
|
203
300
|
config = Config(
|
204
301
|
user_data_dir,
|
@@ -212,13 +309,27 @@ async def start(
|
|
212
309
|
host=host,
|
213
310
|
port=port,
|
214
311
|
expert=expert,
|
312
|
+
proxy=proxy,
|
313
|
+
extension_dir=extension_dir,
|
215
314
|
**kwargs,
|
216
315
|
)
|
316
|
+
driver = None
|
217
317
|
try:
|
218
|
-
|
318
|
+
driver = await Browser.create(config)
|
219
319
|
except Exception:
|
220
320
|
time.sleep(0.15)
|
221
|
-
|
321
|
+
driver = await Browser.create(config)
|
322
|
+
if proxy and "@" in str(proxy):
|
323
|
+
time.sleep(0.15)
|
324
|
+
if lang:
|
325
|
+
sb_config._cdp_locale = lang
|
326
|
+
elif "locale" in kwargs:
|
327
|
+
sb_config._cdp_locale = kwargs["locale"]
|
328
|
+
elif "locale_code" in kwargs:
|
329
|
+
sb_config._cdp_locale = kwargs["locale_code"]
|
330
|
+
else:
|
331
|
+
sb_config._cdp_locale = None
|
332
|
+
return driver
|
222
333
|
|
223
334
|
|
224
335
|
async def start_async(*args, **kwargs) -> Browser:
|
@@ -226,7 +337,15 @@ async def start_async(*args, **kwargs) -> Browser:
|
|
226
337
|
binary_location = None
|
227
338
|
if "browser_executable_path" in kwargs:
|
228
339
|
binary_location = kwargs["browser_executable_path"]
|
229
|
-
|
340
|
+
else:
|
341
|
+
binary_location = detect_b_ver.get_binary_location("google-chrome")
|
342
|
+
if binary_location and not os.path.exists(binary_location):
|
343
|
+
binary_location = None
|
344
|
+
if (
|
345
|
+
shared_utils.is_chrome_130_or_newer(binary_location)
|
346
|
+
and "user_data_dir" in kwargs
|
347
|
+
and kwargs["user_data_dir"]
|
348
|
+
):
|
230
349
|
if "headless" in kwargs:
|
231
350
|
headless = kwargs["headless"]
|
232
351
|
decoy_args = kwargs
|
@@ -241,12 +360,28 @@ async def start_async(*args, **kwargs) -> Browser:
|
|
241
360
|
|
242
361
|
|
243
362
|
def start_sync(*args, **kwargs) -> Browser:
|
244
|
-
loop =
|
363
|
+
loop = None
|
364
|
+
if (
|
365
|
+
"loop" in kwargs
|
366
|
+
and kwargs["loop"]
|
367
|
+
and hasattr(kwargs["loop"], "create_task")
|
368
|
+
):
|
369
|
+
loop = kwargs["loop"]
|
370
|
+
else:
|
371
|
+
loop = asyncio.new_event_loop()
|
245
372
|
headless = False
|
246
373
|
binary_location = None
|
247
374
|
if "browser_executable_path" in kwargs:
|
248
375
|
binary_location = kwargs["browser_executable_path"]
|
249
|
-
|
376
|
+
else:
|
377
|
+
binary_location = detect_b_ver.get_binary_location("google-chrome")
|
378
|
+
if binary_location and not os.path.exists(binary_location):
|
379
|
+
binary_location = None
|
380
|
+
if (
|
381
|
+
shared_utils.is_chrome_130_or_newer(binary_location)
|
382
|
+
and "user_data_dir" in kwargs
|
383
|
+
and kwargs["user_data_dir"]
|
384
|
+
):
|
250
385
|
if "headless" in kwargs:
|
251
386
|
headless = kwargs["headless"]
|
252
387
|
decoy_args = kwargs
|
@@ -40,6 +40,8 @@ class Config:
|
|
40
40
|
host: str = AUTO,
|
41
41
|
port: int = AUTO,
|
42
42
|
expert: bool = AUTO,
|
43
|
+
proxy: Optional[str] = None,
|
44
|
+
extension_dir: Optional[str] = None,
|
43
45
|
**kwargs: dict,
|
44
46
|
):
|
45
47
|
"""
|
@@ -91,6 +93,8 @@ class Config:
|
|
91
93
|
self.host = host
|
92
94
|
self.port = port
|
93
95
|
self.expert = expert
|
96
|
+
self.proxy = proxy
|
97
|
+
self.extension_dir = extension_dir
|
94
98
|
self._extensions = []
|
95
99
|
# When using posix-ish operating system and running as root,
|
96
100
|
# you must use no_sandbox=True
|
@@ -195,6 +199,12 @@ class Config:
|
|
195
199
|
"--disable-web-security",
|
196
200
|
"--disable-site-isolation-trials",
|
197
201
|
]
|
202
|
+
if self.proxy:
|
203
|
+
args.append("--proxy-server=%s" % self.proxy.split("@")[-1])
|
204
|
+
args.append("--ignore-certificate-errors")
|
205
|
+
args.append("--ignore-ssl-errors=yes")
|
206
|
+
if self.extension_dir:
|
207
|
+
args.append("--load-extension=%s" % self.extension_dir)
|
198
208
|
if self._browser_args:
|
199
209
|
args.extend([arg for arg in self._browser_args if arg not in args])
|
200
210
|
if self.headless:
|
@@ -1,7 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import asyncio
|
3
|
+
import base64
|
4
|
+
import datetime
|
3
5
|
import logging
|
4
6
|
import pathlib
|
7
|
+
import urllib.parse
|
5
8
|
import warnings
|
6
9
|
from typing import Dict, List, Union, Optional, Tuple
|
7
10
|
from . import browser as cdp_browser
|
@@ -852,6 +855,7 @@ class Tab(Connection):
|
|
852
855
|
await self.send(
|
853
856
|
cdp.target.close_target(target_id=self.target.target_id)
|
854
857
|
)
|
858
|
+
await asyncio.sleep(0.1)
|
855
859
|
|
856
860
|
async def get_window(self) -> Tuple[
|
857
861
|
cdp.browser.WindowID, cdp.browser.Bounds
|
@@ -896,6 +900,10 @@ class Tab(Connection):
|
|
896
900
|
"""
|
897
901
|
return await self.set_window_state(left, top, width, height)
|
898
902
|
|
903
|
+
async def set_window_rect(self, left=0, top=0, width=1280, height=1024):
|
904
|
+
"""Same as set_window_size(). Uses a different naming convention."""
|
905
|
+
return await self.set_window_state(left, top, width, height)
|
906
|
+
|
899
907
|
async def activate(self):
|
900
908
|
"""Active this target (Eg: tab, window, page)"""
|
901
909
|
await self.send(cdp.target.activate_target(self.target.target_id))
|
@@ -1128,9 +1136,6 @@ class Tab(Connection):
|
|
1128
1136
|
:return: The path/filename of the saved screenshot.
|
1129
1137
|
:rtype: str
|
1130
1138
|
"""
|
1131
|
-
import urllib.parse
|
1132
|
-
import datetime
|
1133
|
-
|
1134
1139
|
await self.sleep() # Update the target's URL
|
1135
1140
|
path = None
|
1136
1141
|
if format.lower() in ["jpg", "jpeg"]:
|
@@ -1161,8 +1166,40 @@ class Tab(Connection):
|
|
1161
1166
|
"Most possible cause is the page "
|
1162
1167
|
"has not finished loading yet."
|
1163
1168
|
)
|
1164
|
-
|
1169
|
+
data_bytes = base64.b64decode(data)
|
1170
|
+
if not path:
|
1171
|
+
raise RuntimeError("Invalid filename or path: '%s'" % filename)
|
1172
|
+
path.write_bytes(data_bytes)
|
1173
|
+
return str(path)
|
1165
1174
|
|
1175
|
+
async def print_to_pdf(
|
1176
|
+
self,
|
1177
|
+
filename: Optional[PathLike] = "auto",
|
1178
|
+
) -> str:
|
1179
|
+
"""
|
1180
|
+
Saves a webpage as a PDF.
|
1181
|
+
:param filename: uses this as the save path
|
1182
|
+
:type filename: PathLike
|
1183
|
+
:return: The path/filename of the saved screenshot.
|
1184
|
+
:rtype: str
|
1185
|
+
"""
|
1186
|
+
await self.sleep() # Update the target's URL
|
1187
|
+
path = None
|
1188
|
+
ext = ".pdf"
|
1189
|
+
if not filename or filename == "auto":
|
1190
|
+
parsed = urllib.parse.urlparse(self.target.url)
|
1191
|
+
parts = parsed.path.split("/")
|
1192
|
+
last_part = parts[-1]
|
1193
|
+
last_part = last_part.rsplit("?", 1)[0]
|
1194
|
+
dt_str = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
1195
|
+
candidate = f"{parsed.hostname}__{last_part}_{dt_str}"
|
1196
|
+
path = pathlib.Path(candidate + ext) # noqa
|
1197
|
+
else:
|
1198
|
+
path = pathlib.Path(filename)
|
1199
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
1200
|
+
data, _ = await self.send(cdp.page.print_to_pdf())
|
1201
|
+
if not data:
|
1202
|
+
raise ProtocolException("Could not save PDF.")
|
1166
1203
|
data_bytes = base64.b64decode(data)
|
1167
1204
|
if not path:
|
1168
1205
|
raise RuntimeError("Invalid filename or path: '%s'" % filename)
|
@@ -26,10 +26,11 @@ class WebElement(selenium.webdriver.remote.webelement.WebElement):
|
|
26
26
|
driver.js_click(selector, by=by, timeout=1)
|
27
27
|
else:
|
28
28
|
super().click()
|
29
|
+
driver = self._parent
|
29
30
|
if not reconnect_time:
|
30
|
-
|
31
|
+
driver.reconnect(0.5)
|
31
32
|
else:
|
32
|
-
|
33
|
+
driver.reconnect(reconnect_time)
|
33
34
|
|
34
35
|
def uc_reconnect(self, reconnect_time=None):
|
35
36
|
if not reconnect_time:
|