Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 36 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,43 +65,68 @@

--------

<p align="left">📗 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py">raw_google.py</a>, which performs a Google search:</p>
<p align="left">📗 For performing a Google Search without hitting the "unusual traffic" page, you can use SeleniumBase UC Mode. Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py">SeleniumBase/examples/raw_google.py</a>, which exports the search results into different formats (PDF, HTML, PNG):</p>

```python
from seleniumbase import SB

with SB(test=True, uc=True) as sb:
sb.open("https://google.com/ncr")
sb.type('[title="Search"]', "SeleniumBase GitHub page\n")
sb.click('[href*="github.com/seleniumbase/"]')
sb.save_screenshot_to_logs() # ./latest_logs/
sb.type('[title="Search"]', "SeleniumBase GitHub page")
sb.click("div:not([jsname]) > * > input")
print(sb.get_page_title())
sb.sleep(2) # Wait for the "AI Overview" result
if sb.is_text_visible("Generating"):
sb.wait_for_text("AI Overview")
sb.save_as_pdf_to_logs() # Saved to ./latest_logs/
sb.save_page_source_to_logs()
sb.save_screenshot_to_logs()
```

> `python raw_google.py`

<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py"><img src="https://seleniumbase.github.io/cdn/gif/google_search.gif" alt="SeleniumBase Test" title="SeleniumBase Test" width="480" /></a>
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py"><img src="https://seleniumbase.github.io/cdn/img/google_sb_result.png" alt="SeleniumBase on Google" title="SeleniumBase on Google" width="440" /></a>

--------

<p align="left">📗 Here's an example of bypassing Cloudflare's challenge page: <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_gitlab.py">SeleniumBase/examples/cdp_mode/raw_gitlab.py</a></p>
<p align="left">📗 Here's an example of bypassing Cloudflare's challenge page with UC Mode + CDP Mode: <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_gitlab.py">SeleniumBase/examples/cdp_mode/raw_gitlab.py</a></p>

```python
from seleniumbase import SB

with SB(uc=True, test=True, locale="en") as sb:
url = "https://gitlab.com/users/sign_in"
sb.activate_cdp_mode(url)
sb.sleep(1)
sb.sleep(2.2)
sb.uc_gui_click_captcha()
sb.sleep(2)
sb.assert_text("Username", '[for="user_login"]', timeout=3)
sb.assert_element('label[for="user_login"]')
sb.highlight('button:contains("Sign in")')
sb.highlight('h1:contains("GitLab.com")')
sb.post_message("SeleniumBase wasn't detected", duration=4)
```

<img src="https://seleniumbase.github.io/other/cf_sec.jpg" title="SeleniumBase" width="332"> <img src="https://seleniumbase.github.io/other/gitlab_bypass.png" title="SeleniumBase" width="288">

<p align="left">📙 You can also use SeleniumBase's pure CDP Mode, which doesn't use chromedriver, Selenium, or a Python context manager at all: <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_gitlab.py">SeleniumBase/examples/cdp_mode/raw_cdp_gitlab.py</a></p>

```python
from seleniumbase import sb_cdp

url = "https://gitlab.com/users/sign_in"
sb = sb_cdp.Chrome(url)
sb.sleep(2.5)
sb.gui_click_captcha()
sb.highlight('h1:contains("GitLab.com")')
sb.highlight('button:contains("Sign in")')
sb.driver.stop()
```

> (Due to a change in Chrome 137 where the --load-extension switch was removed, one limitation with this format is that you can't load extensions directly. The other formats weren't affected by this change.)

--------

<p align="left">📗 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_get_swag.py">test_get_swag.py</a>, which tests an e-commerce site:</p>
<p align="left">📗 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_get_swag.py">SeleniumBase/examples/test_get_swag.py</a>, which tests an e-commerce site:</p>

```python
from seleniumbase import BaseCase
Expand Down Expand Up @@ -133,7 +158,7 @@ class MyTestClass(BaseCase):

--------

<p align="left">📗 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_coffee_cart.py" target="_blank">test_coffee_cart.py</a>, which verifies an e-commerce site:</p>
<p align="left">📗 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_coffee_cart.py" target="_blank">SeleniumBase/examples/test_coffee_cart.py</a>, which verifies an e-commerce site:</p>

```zsh
pytest test_coffee_cart.py --demo
Expand All @@ -147,7 +172,7 @@ pytest test_coffee_cart.py --demo

<a id="multiple_examples"></a>

<p align="left">📗 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py" target="_blank">test_demo_site.py</a>, which covers several actions:</p>
<p align="left">📗 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py" target="_blank">SeleniumBase/examples/test_demo_site.py</a>, which covers several actions:</p>

```zsh
pytest test_demo_site.py
Expand Down
1 change: 1 addition & 0 deletions examples/cdp_mode/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ sb.cdp.scroll_up(amount=25)
sb.cdp.scroll_down(amount=25)
sb.cdp.save_screenshot(name, folder=None, selector=None)
sb.cdp.print_to_pdf(name, folder=None)
sb.cdp.save_as_pdf(name, folder=None)
```

--------
Expand Down
12 changes: 8 additions & 4 deletions examples/raw_google.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

with SB(test=True, uc=True) as sb:
sb.open("https://google.com/ncr")
sb.type('[title="Search"]', "SeleniumBase GitHub page\n")
sb.sleep(1)
sb.click('[href*="github.com/seleniumbase/"]')
sb.save_screenshot_to_logs() # ./latest_logs/
sb.type('[title="Search"]', "SeleniumBase GitHub page")
sb.click("div:not([jsname]) > * > input")
print(sb.get_page_title())
sb.sleep(2) # Wait for the "AI Overview" result
if sb.is_text_visible("Generating"):
sb.wait_for_text("AI Overview")
sb.save_as_pdf_to_logs() # Saved to ./latest_logs/
sb.save_page_source_to_logs()
sb.save_screenshot_to_logs()
3 changes: 3 additions & 0 deletions help_docs/method_summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ self.switch_to_driver(driver)
self.switch_to_default_driver()
self.save_screenshot(name, folder=None, selector=None, by="css selector")
self.save_screenshot_to_logs(name=None, selector=None, by="css selector")
self.save_as_pdf_to_logs(name=None)
self.save_data_to_logs(data, file_name=None)
self.append_data_to_logs(data, file_name=None)
self.save_page_source(name, folder=None)
Expand Down Expand Up @@ -342,6 +343,8 @@ self.save_data_as(data, file_name, destination_folder=None)
self.append_data_to_file(data, file_name, destination_folder=None)
self.get_file_data(file_name, folder=None)
self.print_to_pdf(name, folder=None)
# Duplicates:
# self.save_as_pdf(name, folder=None)
self.get_downloads_folder()
self.get_browser_downloads_folder()
self.get_downloaded_files(regex=None, browser=False)
Expand Down
2 changes: 1 addition & 1 deletion mkdocs_build/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pathspec==0.12.1
Babel==2.17.0
paginate==0.5.7
mkdocs==1.6.1
mkdocs-material==9.6.19
mkdocs-material==9.6.20
mkdocs-exclude-search==0.6.6
mkdocs-simple-hooks==0.1.5
mkdocs-material-extensions==1.3.1
9 changes: 9 additions & 0 deletions sbase/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,15 @@ def set_attributes(context, selector, attribute, value):
sb.set_attributes(selector, attribute, value)


@step("Save as PDF to logs")
@step("Save as PDF to the logs")
@step("User saves page as PDF to logs")
@step("User saves page as PDF to the logs")
def save_as_pdf_to_logs(context):
sb = context.sb
sb.save_as_pdf_to_logs()


@step("Save page source to logs")
@step("Save the page source to the logs")
@step("User saves page source to logs")
Expand Down
2 changes: 1 addition & 1 deletion seleniumbase/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "4.41.6"
__version__ = "4.41.7"
2 changes: 2 additions & 0 deletions seleniumbase/behave/behave_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,8 @@ def generate_gherkin(srt_actions):
)
elif action[0] == "ss_tl":
sb_actions.append("Save screenshot to logs")
elif action[0] == "pdftl":
sb_actions.append("Save as PDF to logs")
elif action[0] == "spstl":
sb_actions.append("Save page source to logs")
elif action[0] == "sh_fc":
Expand Down
1 change: 1 addition & 0 deletions seleniumbase/core/browser_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
cdp.scroll_down = CDPM.scroll_down
cdp.save_screenshot = CDPM.save_screenshot
cdp.print_to_pdf = CDPM.print_to_pdf
cdp.save_as_pdf = CDPM.save_as_pdf
cdp.page = page # async world
cdp.driver = driver.cdp_base # async world
cdp.tab = cdp.page # shortcut (original)
Expand Down
3 changes: 3 additions & 0 deletions seleniumbase/core/recorder_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,9 @@ def generate_sbase_code(srt_actions):
elif action[0] == "ss_tl":
method = "save_screenshot_to_logs"
sb_actions.append("self.%s()" % method)
elif action[0] == "pdftl":
method = "save_as_pdf_to_logs"
sb_actions.append("self.%s()" % method)
elif action[0] == "spstl":
method = "save_page_source_to_logs"
sb_actions.append("self.%s()" % method)
Expand Down
3 changes: 3 additions & 0 deletions seleniumbase/core/sb_cdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2571,6 +2571,9 @@ def print_to_pdf(self, name, folder=None):
filename = os.path.join(folder, name)
self.loop.run_until_complete(self.page.print_to_pdf(filename))

def save_as_pdf(self, *args, **kwargs):
self.print_to_pdf(*args, **kwargs)


class Chrome(CDPMethods):
def __init__(self, url=None, **kwargs):
Expand Down
51 changes: 50 additions & 1 deletion seleniumbase/fixtures/base_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def __initialize_variables(self):
self.__requests_timeout = None
self.__page_source_count = 0
self.__screenshot_count = 0
self.__saved_pdf_count = 0
self.__logs_data_count = 0
self.__last_data_file = None
self.__level_0_visual_f = False
Expand Down Expand Up @@ -4492,7 +4493,8 @@ def save_screenshot_to_logs(
If a provided selector is not found, then takes a full-page screenshot.
(The last_page / failure screenshot is always "screenshot.png")
The screenshot will be in PNG format."""
self.wait_for_ready_state_complete()
if not self.__is_cdp_swap_needed():
self.wait_for_ready_state_complete()
test_logpath = os.path.join(self.log_path, self.__get_test_id())
self.__create_log_path_as_needed(test_logpath)
if name:
Expand All @@ -4510,6 +4512,11 @@ def save_screenshot_to_logs(
if selector and by:
selector, by = self.__recalculate_selector(selector, by)
if page_actions.is_element_present(self.driver, selector, by):
if self.__is_cdp_swap_needed():
selector = self.convert_to_css_selector(selector, by=by)
return self.cdp.save_screenshot(
name, folder=test_logpath, selector=selector
)
return page_actions.save_screenshot(
self.driver, name, test_logpath, selector, by
)
Expand All @@ -4523,8 +4530,49 @@ def save_screenshot_to_logs(
action = ["ss_tl", "", origin, time_stamp]
self.__extra_actions.append(action)
sb_config._has_logs = True
if self.__is_cdp_swap_needed():
return self.cdp.save_screenshot(name, folder=test_logpath)
return page_actions.save_screenshot(self.driver, name, test_logpath)

def save_as_pdf(self, name, folder=None):
"""Same as self.print_to_pdf()"""
return self.print_to_pdf(name, folder=folder)

def save_as_pdf_to_logs(self, name=None):
"""Saves the page as a PDF to the "latest_logs/" folder.
Naming is automatic:
If NO NAME provided: "_1_PDF.pdf", "_2_PDF.pdf", etc.
If NAME IS provided, then: "_1_name.pdf", "_2_name.pdf", etc."""
if not self.__is_cdp_swap_needed():
self.wait_for_ready_state_complete()
test_logpath = os.path.join(self.log_path, self.__get_test_id())
self.__create_log_path_as_needed(test_logpath)
if name:
name = str(name)
self.__saved_pdf_count += 1
if not name or len(name) == 0:
name = "_%s_PDF.pdf" % self.__saved_pdf_count
else:
pre_name = "_%s_" % self.__saved_pdf_count
if len(name) >= 4 and name[-4:].lower() == ".pdf":
name = name[:-4]
if len(name) == 0:
name = "PDF"
name = "%s%s.pdf" % (pre_name, name)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
origin = self.get_origin()
action = ["pdftl", "", origin, time_stamp]
self.__extra_actions.append(action)
sb_config._has_logs = True
if self.__is_cdp_swap_needed():
return self.cdp.print_to_pdf(name, folder=test_logpath)
return self.print_to_pdf(name, test_logpath)

def save_page_source_to_logs(self, name=None):
"""Saves the page HTML to the "latest_logs/" folder.
Naming is automatic:
Expand Down Expand Up @@ -5517,6 +5565,7 @@ def __process_recorded_actions(self):
ext_actions.append("s_scr")
ext_actions.append("ss_tf")
ext_actions.append("ss_tl")
ext_actions.append("pdftl")
ext_actions.append("spstl")
ext_actions.append("da_el")
ext_actions.append("da_ep")
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@
'pdfminer.six==20250324;python_version<"3.9"',
'pdfminer.six==20250506;python_version>="3.9"',
'cryptography==39.0.2;python_version<"3.9"',
'cryptography==45.0.7;python_version>="3.9"',
'cryptography==46.0.1;python_version>="3.9"',
'cffi==1.17.1;python_version<"3.9"',
'cffi==2.0.0;python_version>="3.9"',
'pycparser==2.22;python_version<"3.9"',
Expand All @@ -296,7 +296,7 @@
],
# pip install -e .[psutil]
"psutil": [
"psutil==7.0.0",
"psutil==7.1.0",
],
# pip install -e .[pyautogui]
"pyautogui": [
Expand Down