Skip to content

Commit e12db77

Browse files
authored
Merge pull request #4378 from seleniumbase/cdp-mode-patch-113
CDP Mode: Patch 113
2 parents cb1ad4f + 838147d commit e12db77

15 files changed

Lines changed: 274 additions & 48 deletions

File tree

examples/cdp_mode/ReadMe.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ sb.cdp.tile_windows(windows=None, max_columns=0)
408408
sb.cdp.grant_permissions(permissions, origin=None)
409409
sb.cdp.grant_all_permissions()
410410
sb.cdp.reset_permissions()
411+
sb.cdp.get_all_urls(absolute=True)
411412
sb.cdp.get_all_cookies(*args, **kwargs)
412413
sb.cdp.set_all_cookies(*args, **kwargs)
413414
sb.cdp.save_cookies(*args, **kwargs)
@@ -490,6 +491,11 @@ sb.cdp.enter_mfa_code(selector, totp_key=None, timeout=None)
490491
sb.cdp.activate_messenger()
491492
sb.cdp.set_messenger_theme(theme="default", location="default")
492493
sb.cdp.post_message(message, duration=None, pause=True, style="info")
494+
sb.cdp.download_file(file_url)
495+
sb.cdp.save_file_as(file_url, new_file_name)
496+
sb.cdp.assert_downloaded_file(file, timeout=None)
497+
sb.cdp.get_path_of_downloaded_file(file)
498+
sb.cdp.set_download_path(path)
493499
sb.cdp.set_locale(locale)
494500
sb.cdp.set_local_storage_item(key, value)
495501
sb.cdp.set_session_storage_item(key, value)
@@ -547,8 +553,8 @@ sb.cdp.assert_url_contains(substring)
547553
sb.cdp.assert_text(text, selector="html", timeout=None)
548554
sb.cdp.assert_exact_text(text, selector="html", timeout=None)
549555
sb.cdp.assert_text_not_visible(text, selector="body", timeout=None)
550-
sb.cdp.assert_true()
551-
sb.cdp.assert_false()
556+
sb.cdp.assert_true(expression, msg=None)
557+
sb.cdp.assert_false(expression, msg=None)
552558
sb.cdp.assert_equal(first, second)
553559
sb.cdp.assert_not_equal(first, second)
554560
sb.cdp.assert_in(first, second)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from playwright.sync_api import sync_playwright
2+
from seleniumbase import sb_cdp
3+
4+
sb = sb_cdp.Chrome()
5+
endpoint_url = sb.get_endpoint_url()
6+
7+
with sync_playwright() as p:
8+
browser = p.chromium.connect_over_cdp(endpoint_url)
9+
page = browser.contexts[0].pages[0]
10+
page.goto("https://google.com/ncr")
11+
sb.click_if_visible('button:contains("Accept all")')
12+
page.type('[name="q"]', "SeleniumBase GitHub page")
13+
sb.click('[value="Google Search"]')
14+
sb.sleep(4) # The "AI Overview" sometimes loads
15+
print(page.title())
16+
sb.save_as_pdf("google_page.pdf", folder="./downloaded_files/")
17+
print("PDF saved to ./downloaded_files/google_page.pdf")
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import math
2+
from seleniumbase import sb_cdp
3+
4+
sb = sb_cdp.Chrome()
5+
6+
""" Part 1: Using sb.download_file(file_url) """
7+
8+
sb.goto("about:blank")
9+
words_file = "wordle_words.txt"
10+
words_link = (
11+
"https://seleniumbase.github.io/cdn/txt/%s" % words_file
12+
)
13+
sb.download_file(words_link)
14+
sb.assert_downloaded_file(words_file)
15+
words_path = sb.get_path_of_downloaded_file(words_file)
16+
with open(words_path, "r") as f:
17+
words_data = f.read()
18+
print("%s | Download = %s bytes." % (words_file, len(words_data)))
19+
sb.assert_true(len(words_data) > 100) # Verify file not empty
20+
text = '"oasis","carom","cubit"'
21+
sb.assert_in(text, words_data) # Verify file has expected data
22+
23+
""" Part 2: Using click-initiated downloads """
24+
25+
sb.goto("https://pypi.org/project/sbvirtualdisplay/#files")
26+
sb.assert_element("span#pip-command")
27+
sb.assert_text("Download files", "div#files h2.page-title")
28+
sb.assert_text("Download files", "a#files-tab")
29+
pkg_header = sb.get_text("h1.package-header__name").strip()
30+
pkg_name = pkg_header.replace(" ", "-")
31+
whl_file = pkg_name + "-py3-none-any.whl"
32+
tar_gz_file = pkg_name + ".tar.gz"
33+
34+
# Click the links to download the files into: "./downloaded_files/"
35+
whl_selector = 'div#files a[href$="%s"]' % whl_file
36+
tar_selector = 'div#files a[href$="%s"]' % tar_gz_file
37+
sb.click(whl_selector) # Download the "whl" file
38+
sb.sleep(0.1)
39+
sb.click(tar_selector) # Download the "tar" file
40+
41+
# Verify that the downloaded files appear in the [Downloads Folder]
42+
# (This only guarantees that the exact file name is in the folder.)
43+
# (This does not guarantee that the downloaded files are complete.)
44+
# (Later, we'll check that the files were downloaded successfully.)
45+
sb.assert_downloaded_file(whl_file)
46+
sb.assert_downloaded_file(tar_gz_file)
47+
48+
sb.sleep(1) # Add more time to make sure downloads have completed
49+
50+
# Get the actual size of the downloaded files (in bytes)
51+
whl_path = sb.get_path_of_downloaded_file(whl_file)
52+
with open(whl_path, "rb") as f:
53+
whl_file_bytes = len(f.read())
54+
print("%s | Download = %s bytes." % (whl_file, whl_file_bytes))
55+
tar_gz_path = sb.get_path_of_downloaded_file(tar_gz_file)
56+
with open(tar_gz_path, "rb") as f:
57+
tar_gz_file_bytes = len(f.read())
58+
print("%s | Download = %s bytes." % (tar_gz_file, tar_gz_file_bytes))
59+
60+
# Check to make sure the downloaded files are not empty or too small
61+
sb.assert_true(whl_file_bytes > 5000)
62+
sb.assert_true(tar_gz_file_bytes > 5000)
63+
64+
# Get file sizes in kB to compare actual values with displayed values
65+
whl_file_kb = whl_file_bytes / 1000.0
66+
whl_line_fi = sb.get_text('a[href$=".whl"]').strip()
67+
whl_line = sb.get_text('div.file:contains("%s")' % whl_line_fi)
68+
whl_display_kb = float(whl_line.split("(")[1].split(" ")[0])
69+
tar_gz_file_kb = tar_gz_file_bytes / 1000.0
70+
tar_gz_line_fi = sb.get_text('a[href$=".tar.gz"]').strip()
71+
tar_gz_line = sb.get_text('div.file:contains("%s")' % tar_gz_line_fi)
72+
tar_gz_display_kb = float(tar_gz_line.split("(")[1].split(" ")[0])
73+
74+
# Verify downloaded files are the correct size (account for rounding)
75+
sb.assert_true(
76+
abs(math.floor(whl_file_kb) - math.floor(whl_display_kb)) < 2
77+
)
78+
sb.assert_true(
79+
abs(math.floor(tar_gz_file_kb) - math.floor(tar_gz_display_kb)) < 2
80+
)
81+
82+
# Finally quit the browser
83+
sb.quit()

examples/test_download_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_download_chromedriver_notes(self):
2222
notes_data = f.read()
2323
self.assert_true(len(notes_data) > 100) # Verify file not empty
2424
text = "Switching to nested frame fails with chrome/chromedriver 100"
25-
self.assert_true(text in notes_data) # Verify file has expected data
25+
self.assert_in(text, notes_data) # Verify file has expected data
2626

2727
def test_download_files_from_pypi(self):
2828
self.goto("https://pypi.org/project/sbvirtualdisplay/#files")

help_docs/cdp_mode_methods.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ sb.cdp.tile_windows(windows=None, max_columns=0)
4545
sb.cdp.grant_permissions(permissions, origin=None)
4646
sb.cdp.grant_all_permissions()
4747
sb.cdp.reset_permissions()
48+
sb.cdp.get_all_urls(absolute=True)
4849
sb.cdp.get_all_cookies(*args, **kwargs)
4950
sb.cdp.set_all_cookies(*args, **kwargs)
5051
sb.cdp.save_cookies(*args, **kwargs)
@@ -127,6 +128,11 @@ sb.cdp.enter_mfa_code(selector, totp_key=None, timeout=None)
127128
sb.cdp.activate_messenger()
128129
sb.cdp.set_messenger_theme(theme="default", location="default")
129130
sb.cdp.post_message(message, duration=None, pause=True, style="info")
131+
sb.cdp.download_file(file_url)
132+
sb.cdp.save_file_as(file_url, new_file_name)
133+
sb.cdp.assert_downloaded_file(file, timeout=None)
134+
sb.cdp.get_path_of_downloaded_file(file)
135+
sb.cdp.set_download_path(path)
130136
sb.cdp.set_locale(locale)
131137
sb.cdp.set_local_storage_item(key, value)
132138
sb.cdp.set_session_storage_item(key, value)
@@ -184,8 +190,8 @@ sb.cdp.assert_url_contains(substring)
184190
sb.cdp.assert_text(text, selector="html", timeout=None)
185191
sb.cdp.assert_exact_text(text, selector="html", timeout=None)
186192
sb.cdp.assert_text_not_visible(text, selector="body", timeout=None)
187-
sb.cdp.assert_true()
188-
sb.cdp.assert_false()
193+
sb.cdp.assert_true(expression, msg=None)
194+
sb.cdp.assert_false(expression, msg=None)
189195
sb.cdp.assert_equal(first, second)
190196
sb.cdp.assert_not_equal(first, second)
191197
sb.cdp.assert_in(first, second)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ exceptiongroup>=1.3.1
1010
websockets~=15.0.1;python_version<"3.10"
1111
websockets>=16.0;python_version>="3.10"
1212
filelock~=3.19.1;python_version<"3.10"
13-
filelock>=3.29.1;python_version>="3.10"
13+
filelock>=3.29.3;python_version>="3.10"
1414
fasteners>=0.20
1515
mycdp>=1.3.7
1616
pynose>=1.5.5

seleniumbase/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# seleniumbase package
2-
__version__ = "4.49.8"
2+
__version__ = "4.49.9"

seleniumbase/core/browser_launcher.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
793793
cdp.grant_permissions = CDPM.grant_permissions
794794
cdp.grant_all_permissions = CDPM.grant_all_permissions
795795
cdp.reset_permissions = CDPM.reset_permissions
796+
cdp.get_all_urls = CDPM.get_all_urls
796797
cdp.get_all_cookies = CDPM.get_all_cookies
797798
cdp.set_all_cookies = CDPM.set_all_cookies
798799
cdp.save_cookies = CDPM.save_cookies
@@ -831,6 +832,11 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
831832
cdp.activate_messenger = CDPM.activate_messenger
832833
cdp.set_messenger_theme = CDPM.set_messenger_theme
833834
cdp.post_message = CDPM.post_message
835+
cdp.download_file = CDPM.download_file
836+
cdp.save_file_as = CDPM.save_file_as
837+
cdp.assert_downloaded_file = CDPM.assert_downloaded_file
838+
cdp.get_path_of_downloaded_file = CDPM.get_path_of_downloaded_file
839+
cdp.set_download_path = CDPM.set_download_path
834840
cdp.set_locale = CDPM.set_locale
835841
cdp.set_local_storage_item = CDPM.set_local_storage_item
836842
cdp.set_session_storage_item = CDPM.set_session_storage_item

seleniumbase/core/sb_cdp.py

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,18 @@ def reset_permissions(self):
752752
driver = driver.cdp_base
753753
return self.loop.run_until_complete(driver.reset_permissions())
754754

755+
def get_all_urls(self, absolute=True):
756+
"""
757+
Convenience function that returns all links (a,link,img,script,meta).
758+
:param absolute:
759+
Try to build all the links in absolute form
760+
instead of "as is", often relative.
761+
:return: List of URLs.
762+
"""
763+
return self.loop.run_until_complete(
764+
self.page.get_all_urls(absolute=absolute)
765+
)
766+
755767
def get_all_cookies(self, *args, **kwargs):
756768
driver = self.driver
757769
if hasattr(driver, "cdp_base"):
@@ -1725,6 +1737,80 @@ def post_message(self, message, duration=None, pause=True, style="info"):
17251737
duration = float(duration) + 0.15
17261738
time.sleep(float(duration))
17271739

1740+
def download_file(self, file_url):
1741+
"""Download a file from a URL.
1742+
The default download location is: "./downloaded_files/"."""
1743+
self.loop.run_until_complete(
1744+
self.page.download_file(file_url)
1745+
)
1746+
1747+
def save_file_as(self, file_url, new_file_name):
1748+
"""Download a file from a URL and rename it.
1749+
The default download location is: "./downloaded_files/"."""
1750+
self.loop.run_until_complete(
1751+
self.page.download_file(file_url, new_file_name)
1752+
)
1753+
1754+
def assert_downloaded_file(self, file, timeout=None):
1755+
"""Asserts that the file exists in SeleniumBase's [Downloads Folder].
1756+
For browser click-initiated downloads, SeleniumBase will override
1757+
the system [Downloads Folder] to be "./downloaded_files/".
1758+
@Params
1759+
file - The filename of the downloaded file.
1760+
timeout - The time (seconds) to wait for the download to complete.
1761+
browser - If True, uses the path set by click-initiated downloads."""
1762+
downloads_folder = constants.Files.DOWNLOADS_FOLDER
1763+
abs_path = os.path.abspath(".")
1764+
downloads_path = os.path.join(abs_path, downloads_folder)
1765+
if not timeout:
1766+
timeout = settings.LARGE_TIMEOUT
1767+
start_ms = time.time() * 1000.0
1768+
stop_ms = start_ms + (timeout * 1000.0)
1769+
downloaded_file_path = os.path.join(downloads_path, file)
1770+
found = False
1771+
for x in range(int(timeout)):
1772+
shared_utils.check_if_time_limit_exceeded()
1773+
try:
1774+
self.assert_true(
1775+
os.path.exists(downloaded_file_path),
1776+
"File [%s] was not found in the downloads folder [%s]!"
1777+
% (file, downloads_path),
1778+
)
1779+
found = True
1780+
break
1781+
except Exception:
1782+
now_ms = time.time() * 1000.0
1783+
if now_ms >= stop_ms:
1784+
break
1785+
time.sleep(1)
1786+
if not found and not os.path.exists(downloaded_file_path):
1787+
plural = "s"
1788+
if timeout == 1:
1789+
plural = ""
1790+
message = (
1791+
"File {%s} was not found in the downloads folder {%s} "
1792+
"after %s second%s! (Or the download didn't complete!)"
1793+
% (file, downloads_path, timeout, plural)
1794+
)
1795+
from seleniumbase.common.exceptions import NoSuchFileException
1796+
raise NoSuchFileException(message)
1797+
1798+
def get_path_of_downloaded_file(self, file):
1799+
"""This assumes the default location of SeleniumBase downloads,
1800+
which is the "./downloaded_files/" folder where scripts run."""
1801+
downloads_folder = constants.Files.DOWNLOADS_FOLDER
1802+
abs_path = os.path.abspath(".")
1803+
downloads_path = os.path.join(abs_path, downloads_folder)
1804+
return os.path.join(downloads_path, file)
1805+
1806+
def set_download_path(self, path):
1807+
"""Set a new download path for click-initiated downloads.
1808+
(For Pure CDP Mode sync format only! -> sb_cdp.)
1809+
The default download location is: "./downloaded_files/".
1810+
Convenience methods such as assert_downloaded_file(file)
1811+
will still expect the default location."""
1812+
self.loop.run_until_complete(self.page.set_download_path(path))
1813+
17281814
def set_locale(self, locale):
17291815
"""(Settings will take effect on the next page load)"""
17301816
self.loop.run_until_complete(self.page.set_locale(locale))
@@ -3233,13 +3319,23 @@ def assert_text_not_visible(self, text, selector="body", timeout=None):
32333319
)
32343320
return True
32353321

3236-
def assert_true(self, expression):
3322+
def assert_true(self, expression, msg=None):
32373323
if not expression:
3238-
raise AssertionError("%s is not true" % expression)
3324+
if not msg:
3325+
raise AssertionError("%s is not true" % expression)
3326+
else:
3327+
raise AssertionError(
3328+
"%s is not true. (%s)" % (expression, msg)
3329+
)
32393330

3240-
def assert_false(self, expression):
3331+
def assert_false(self, expression, msg=None):
32413332
if expression:
3242-
raise AssertionError("%s is not false" % expression)
3333+
if not msg:
3334+
raise AssertionError("%s is not false" % expression)
3335+
else:
3336+
raise AssertionError(
3337+
"%s is not false. (%s)" % (expression, msg)
3338+
)
32433339

32443340
def assert_equal(self, first, second):
32453341
if first != second:

seleniumbase/fixtures/base_case.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5103,6 +5103,8 @@ def activate_cdp_mode(self, url=None, **kwargs):
51035103
self.find_element_by_text = self.cdp.find_element_by_text
51045104
if hasattr(self.cdp, "get_active_tab"):
51055105
self.get_active_tab = self.cdp.get_active_tab
5106+
if hasattr(self.cdp, "get_all_urls"):
5107+
self.get_all_urls = self.cdp.get_all_urls
51065108
if hasattr(self.cdp, "get_endpoint_url"):
51075109
self.get_endpoint_url = self.cdp.get_endpoint_url
51085110
if hasattr(self.cdp, "get_event_loop"):

0 commit comments

Comments
 (0)