diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md
index 6b079c80a40..041446d8335 100644
--- a/examples/cdp_mode/ReadMe.md
+++ b/examples/cdp_mode/ReadMe.md
@@ -203,7 +203,7 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb:
card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]'
hotels = sb.cdp.select_all(card_info)
print("Hyatt Hotels in %s:" % location)
- print("(" + sb.cdp.get_text("ul.b-color_text-white") + ")")
+ print("(" + sb.cdp.get_text('span[class*="summary_destination"]') + ")")
if len(hotels) == 0:
print("No availability over the selected dates!")
for hotel in hotels:
@@ -331,12 +331,13 @@ with SB(uc=True, test=True, locale="en", pls="none") as sb:
url = "https://www.nike.com/"
sb.activate_cdp_mode(url)
sb.sleep(2.5)
- sb.cdp.click('div[data-testid="user-tools-container"]')
+ sb.click('[data-testid="user-tools-container"] search')
sb.sleep(1.5)
search = "Nike Air Force 1"
- sb.cdp.press_keys('input[type="search"]', search)
+ sb.press_keys('input[type="search"]', search)
sb.sleep(4)
- elements = sb.cdp.select_all('ul[data-testid*="products"] figure .details')
+ details = 'ul[data-testid*="products"] figure .details'
+ elements = sb.select_all(details)
if elements:
print('**** Found results for "%s": ****' % search)
for element in elements:
@@ -528,6 +529,7 @@ sb.cdp.assert_in(first, second)
sb.cdp.assert_not_in(first, second)
sb.cdp.scroll_into_view(selector)
sb.cdp.scroll_to_y(y)
+sb.cdp.scroll_by_y(y)
sb.cdp.scroll_to_top()
sb.cdp.scroll_to_bottom()
sb.cdp.scroll_up(amount=25)
diff --git a/examples/cdp_mode/raw_cdp_copilot.py b/examples/cdp_mode/raw_cdp_copilot.py
index 2c59657ec33..555f5458faf 100644
--- a/examples/cdp_mode/raw_cdp_copilot.py
+++ b/examples/cdp_mode/raw_cdp_copilot.py
@@ -25,7 +25,8 @@
sb.wait_for_element_absent(stop_button, timeout=50)
sb.wait_for_element(thumbs_up, timeout=20)
sb.sleep(0.6)
-sb.click('button[data-testid*="scroll-to-bottom"]')
+scroll = 'button[data-testid*="scroll-to-bottom"]'
+sb.click_if_visible(scroll)
sb.sleep(2.2)
folder = "downloaded_files"
file_name = "copilot_results.html"
diff --git a/examples/cdp_mode/raw_cdp_kohls.py b/examples/cdp_mode/raw_cdp_kohls.py
index 3ffbe78d88b..290eed5054b 100644
--- a/examples/cdp_mode/raw_cdp_kohls.py
+++ b/examples/cdp_mode/raw_cdp_kohls.py
@@ -1,25 +1,37 @@
from seleniumbase import sb_cdp
url = "https://www.kohls.com/"
-sb = sb_cdp.Chrome(url, locale="en", guest=True)
+sb = sb_cdp.Chrome(url, locale="en", ad_block=True)
sb.sleep(2.8)
-search = "Mickey Mouse 100 friends teal pillow"
-required_text = "Mickey"
+search = "Mickey Mouse Blanket"
+req_1 = "Mickey"
+req_2 = "Blanket"
sb.press_keys('input[name="search"]', search + "\n")
sb.sleep(5)
-for item in sb.find_elements("div.products-container-right"):
+item_selector = 'div[data-testid*="wallet-wrapper"]'
+if not sb.is_element_present(item_selector):
+ item_selector = "li.products_grid"
+for item in sb.find_elements(item_selector):
if "Sponsored" in item.text:
item.remove_from_dom()
sb.remove_elements("#tce-sticky-wrapper")
sb.remove_elements("li.sponsored-product")
sb.remove_elements("#tce-dec-ces-3-banner")
print('*** Kohls Search for "%s":' % search)
-for item in sb.find_elements("ul.products a img"):
+print(' (Results must contain "%s" and "%s".)' % (req_1, req_2))
+title_selector = "div.prod_nameBlock p"
+if not sb.is_element_present(title_selector):
+ title_selector = 'a[class*="sm:text"][href*="/product/"]'
+for item in sb.find_elements(title_selector):
if item:
item.flash(color="44CC88")
- title = item.get_attribute("title")
- if title and required_text in title:
- print("* " + title)
- sb.sleep(0.1)
+ title = item.text
+ if title:
+ if (
+ req_1.lower() in title.lower()
+ and req_2.lower() in title.lower()
+ ):
+ print("* " + title)
+ sb.sleep(0.1)
sb.sleep(1)
sb.driver.stop()
diff --git a/examples/cdp_mode/raw_cdp_nike.py b/examples/cdp_mode/raw_cdp_nike.py
index 2f19a88f2f8..2bdb3ec9739 100644
--- a/examples/cdp_mode/raw_cdp_nike.py
+++ b/examples/cdp_mode/raw_cdp_nike.py
@@ -2,12 +2,14 @@
url = "https://www.nike.com/"
sb = sb_cdp.Chrome(url)
-sb.click('div[data-testid="user-tools-container"]')
+sb.sleep(1.2)
+sb.click('[data-testid="user-tools-container"] search')
sb.sleep(1)
search = "Pegasus"
sb.press_keys('input[type="search"]', search)
sb.sleep(4)
-elements = sb.select_all('ul[data-testid*="products"] figure .details')
+details = 'ul[data-testid*="products"] figure .details'
+elements = sb.select_all(details)
if elements:
print('**** Found results for "%s": ****' % search)
for element in elements:
diff --git a/examples/cdp_mode/raw_copilot.py b/examples/cdp_mode/raw_copilot.py
index 535cc07d118..ebcaffe19d5 100644
--- a/examples/cdp_mode/raw_copilot.py
+++ b/examples/cdp_mode/raw_copilot.py
@@ -26,7 +26,8 @@
sb.wait_for_element_absent(stop_button, timeout=50)
sb.wait_for_element(thumbs_up, timeout=20)
sb.sleep(0.6)
- sb.click('button[data-testid*="scroll-to-bottom"]')
+ scroll = 'button[data-testid*="scroll-to-bottom"]'
+ sb.click_if_visible(scroll)
sb.sleep(2.2)
folder = "downloaded_files"
file_name = "copilot_results.html"
diff --git a/examples/cdp_mode/raw_form_turnstile.py b/examples/cdp_mode/raw_form_turnstile.py
new file mode 100644
index 00000000000..8ac6120b4fe
--- /dev/null
+++ b/examples/cdp_mode/raw_form_turnstile.py
@@ -0,0 +1,20 @@
+from seleniumbase import SB
+
+with SB(uc=True, test=True) as sb:
+ url = "seleniumbase.io/apps/form_turnstile"
+ sb.activate_cdp_mode(url)
+ sb.press_keys("#name", "SeleniumBase")
+ sb.press_keys("#email", "test@test.test")
+ sb.press_keys("#phone", "1-555-555-5555")
+ sb.click('[for="date"]')
+ sb.click("td.is-today button")
+ sb.click('div[class="select-wrapper"] input')
+ sb.click('span:contains("9:00 PM")')
+ sb.highlight_click('input[value="AR"] + span')
+ sb.click('input[value="cc"] + span')
+ sb.scroll_down(40)
+ sb.uc_gui_click_captcha()
+ sb.highlight("img#captcha-success", timeout=3)
+ sb.highlight_click('button:contains("Request & Pay")')
+ sb.highlight("img#submit-success")
+ sb.highlight('button:contains("Success!")')
diff --git a/examples/cdp_mode/raw_hyatt.py b/examples/cdp_mode/raw_hyatt.py
index fff0b4e8570..ce5e7e99a5e 100644
--- a/examples/cdp_mode/raw_hyatt.py
+++ b/examples/cdp_mode/raw_hyatt.py
@@ -17,7 +17,7 @@
card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]'
hotels = sb.cdp.select_all(card_info)
print("Hyatt Hotels in %s:" % location)
- print("(" + sb.cdp.get_text("ul.b-color_text-white") + ")")
+ print("(" + sb.cdp.get_text('span[class*="summary_destination"]') + ")")
if len(hotels) == 0:
print("No availability over the selected dates!")
for hotel in hotels:
diff --git a/examples/cdp_mode/raw_kohls.py b/examples/cdp_mode/raw_kohls.py
index 1b57bf427b8..d5d4b11a7c0 100644
--- a/examples/cdp_mode/raw_kohls.py
+++ b/examples/cdp_mode/raw_kohls.py
@@ -4,22 +4,34 @@
url = "https://www.kohls.com/"
sb.activate_cdp_mode(url)
sb.sleep(2.6)
- search = "Mickey Mouse 100 friends teal pillow"
- required_text = "Mickey"
+ search = "Mickey Mouse Blanket"
+ req_1 = "Mickey"
+ req_2 = "Blanket"
sb.cdp.press_keys('input[name="search"]', search + "\n")
sb.sleep(5)
- for item in sb.cdp.find_elements("div.products-container-right"):
+ item_selector = 'div[data-testid*="wallet-wrapper"]'
+ if not sb.is_element_present(item_selector):
+ item_selector = "li.products_grid"
+ for item in sb.cdp.find_elements(item_selector):
if "Sponsored" in item.text:
item.remove_from_dom()
sb.cdp.remove_elements("#tce-sticky-wrapper")
sb.cdp.remove_elements("li.sponsored-product")
sb.cdp.remove_elements("#tce-dec-ces-3-banner")
print('*** Kohls Search for "%s":' % search)
- for item in sb.cdp.find_elements("ul.products a img"):
+ print(' (Results must contain "%s" and "%s".)' % (req_1, req_2))
+ title_selector = "div.prod_nameBlock p"
+ if not sb.is_element_present(title_selector):
+ title_selector = 'a[class*="sm:text"][href*="/product/"]'
+ for item in sb.cdp.find_elements(title_selector):
if item:
item.flash(color="44CC88")
- title = item.get_attribute("title")
- if title and required_text in title:
- print("* " + title)
- sb.sleep(0.1)
+ title = item.text
+ if title:
+ if (
+ req_1.lower() in title.lower()
+ and req_2.lower() in title.lower()
+ ):
+ print("* " + title)
+ sb.sleep(0.1)
sb.sleep(1)
diff --git a/examples/cdp_mode/raw_nike.py b/examples/cdp_mode/raw_nike.py
index 7e28e2fcede..770a13424c2 100644
--- a/examples/cdp_mode/raw_nike.py
+++ b/examples/cdp_mode/raw_nike.py
@@ -4,12 +4,13 @@
url = "https://www.nike.com/"
sb.activate_cdp_mode(url)
sb.sleep(2.5)
- sb.cdp.click('div[data-testid="user-tools-container"]')
+ sb.click('[data-testid="user-tools-container"] search')
sb.sleep(1.5)
search = "Nike Air Force 1"
- sb.cdp.press_keys('input[type="search"]', search)
+ sb.press_keys('input[type="search"]', search)
sb.sleep(4)
- elements = sb.cdp.select_all('ul[data-testid*="products"] figure .details')
+ details = 'ul[data-testid*="products"] figure .details'
+ elements = sb.select_all(details)
if elements:
print('**** Found results for "%s": ****' % search)
for element in elements:
diff --git a/examples/cdp_mode/raw_res_nike.py b/examples/cdp_mode/raw_res_nike.py
index f4000c0e917..06dcbdaca6a 100644
--- a/examples/cdp_mode/raw_res_nike.py
+++ b/examples/cdp_mode/raw_res_nike.py
@@ -31,7 +31,7 @@ async def receive_handler(event: mycdp.network.ResponseReceived):
sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler)
sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler)
sb.sleep(2.5)
- sb.cdp.click('div[data-testid="user-tools-container"]')
+ sb.cdp.click('[data-testid="user-tools-container"] search')
sb.sleep(1.5)
search = "Nike Air Force 1"
sb.cdp.press_keys('input[type="search"]', search)
diff --git a/examples/locale_code_test.py b/examples/locale_code_test.py
index 64d6fc6f615..aea662d8713 100644
--- a/examples/locale_code_test.py
+++ b/examples/locale_code_test.py
@@ -12,8 +12,9 @@ def test_locale_code(self):
language_info = self.get_text(
"settings-ui::shadow "
"settings-main::shadow "
- "settings-basic-page::shadow "
+ "settings-languages-page-index::shadow "
"settings-languages-page::shadow "
+ "settings-section "
"#languagesSection div.start div"
)
print("Language info (chrome://settings/languages):")
diff --git a/examples/presenter/uc_presentation_4.py b/examples/presenter/uc_presentation_4.py
index e16ed4e050e..9adc9bbcf48 100644
--- a/examples/presenter/uc_presentation_4.py
+++ b/examples/presenter/uc_presentation_4.py
@@ -691,8 +691,9 @@ def test_presentation_4(self):
'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]'
)
hotels = sb.cdp.select_all(card_info)
+ destination_selector = 'span[class*="summary_destination"]'
print("Hyatt Hotels in %s:" % location)
- print("(" + sb.cdp.get_text("ul.b-color_text-white") + ")")
+ print("(" + sb.cdp.get_text(destination_selector) + ")")
if len(hotels) == 0:
print("No availability over the selected dates!")
for hotel in hotels:
@@ -833,16 +834,15 @@ def test_presentation_4(self):
url = "https://www.nike.com/"
sb.activate_cdp_mode(url)
sb.sleep(2.5)
- sb.cdp.click('div[data-testid="user-tools-container"]')
+ sb.click('[data-testid="user-tools-container"] search')
sb.sleep(1.5)
search = "Nike Air Force 1"
- sb.cdp.press_keys('input[type="search"]', search)
+ sb.press_keys('input[type="search"]', search)
sb.sleep(4)
- elements = sb.cdp.select_all(
- 'ul[data-testid*="products"] figure .details'
- )
+ details = 'ul[data-testid*="products"] figure .details'
+ elements = sb.select_all(details)
if elements:
- print('\n\n**** Found results for "%s": ****' % search)
+ print('**** Found results for "%s": ****' % search)
for element in elements:
print("* " + element.text)
sb.sleep(2)
diff --git a/examples/raw_form_turnstile.py b/examples/raw_form_turnstile.py
index 0ff1be0fa52..7af920545bd 100644
--- a/examples/raw_form_turnstile.py
+++ b/examples/raw_form_turnstile.py
@@ -2,7 +2,7 @@
with SB(uc=True, test=True) as sb:
url = "seleniumbase.io/apps/form_turnstile"
- sb.uc_open_with_reconnect(url, 2)
+ sb.uc_open_with_reconnect(url, 1.1)
sb.press_keys("#name", "SeleniumBase")
sb.press_keys("#email", "test@test.test")
sb.press_keys("#phone", "1-555-555-5555")
diff --git a/examples/raw_gitlab.py b/examples/raw_gitlab.py
new file mode 100644
index 00000000000..731bf79d962
--- /dev/null
+++ b/examples/raw_gitlab.py
@@ -0,0 +1,11 @@
+from seleniumbase import SB
+
+with SB(uc=True, test=True) as sb:
+ url = "https://gitlab.com/users/sign_in"
+ sb.uc_open_with_reconnect(url)
+ sb.uc_gui_click_captcha() # Only if needed
+ sb.assert_element('label[for="user_login"]')
+ sb.assert_element('input[data-testid*="username"]')
+ sb.assert_element('input[data-testid*="password"]')
+ sb.set_messenger_theme(location="bottom_center")
+ sb.post_message("SeleniumBase wasn't detected!")
diff --git a/examples/raw_gui_click.py b/examples/raw_gui_click.py
index 614aada2345..a56a091c0bb 100644
--- a/examples/raw_gui_click.py
+++ b/examples/raw_gui_click.py
@@ -1,17 +1,20 @@
-import sys
from seleniumbase import SB
-# An bad UserAgent forces CAPTCHA-solving on macOS
-agent = "cool"
-if "linux" in sys.platform or "win32" in sys.platform:
- agent = None # Use the default UserAgent
-
-with SB(uc=True, test=True, rtf=True, agent=agent) as sb:
- url = "https://gitlab.com/users/sign_in"
- sb.uc_open_with_reconnect(url)
- sb.uc_gui_click_captcha() # Only if needed
- sb.assert_element('label[for="user_login"]')
- sb.assert_element('input[data-testid*="username"]')
- sb.assert_element('input[data-testid*="password"]')
- sb.set_messenger_theme(location="bottom_center")
- sb.post_message("SeleniumBase wasn't detected!")
+with SB(uc=True, test=True) as sb:
+ url = "seleniumbase.io/apps/form_turnstile"
+ sb.uc_open_with_reconnect(url, 1.1)
+ sb.press_keys("#name", "SeleniumBase")
+ sb.press_keys("#email", "test@test.test")
+ sb.press_keys("#phone", "1-555-555-5555")
+ sb.click('[for="date"]')
+ sb.click("td.is-today button")
+ sb.click('div[class="select-wrapper"] input')
+ sb.click('span:contains("9:00 PM")')
+ sb.highlight_click('input[value="AR"] + span')
+ sb.click('input[value="cc"] + span')
+ sb.scroll_to('div[class*="cf-turnstile"]')
+ sb.uc_gui_click_captcha()
+ sb.highlight("img#captcha-success", timeout=3)
+ sb.highlight_click('button:contains("Request & Pay")')
+ sb.highlight("img#submit-success")
+ sb.highlight('button:contains("Success!")')
diff --git a/examples/raw_pyautogui.py b/examples/raw_pyautogui.py
index 5e3bca507ae..d77aa257e7c 100644
--- a/examples/raw_pyautogui.py
+++ b/examples/raw_pyautogui.py
@@ -1,17 +1,9 @@
-import sys
from seleniumbase import SB
-# An bad UserAgent forces CAPTCHA-solving on macOS
-agent = "cool"
-if "linux" in sys.platform or "win32" in sys.platform:
- agent = None # Use the default UserAgent
-
-with SB(uc=True, test=True, rtf=True, agent=agent) as sb:
- url = "https://gitlab.com/users/sign_in"
+with SB(uc=True, test=True) as sb:
+ url = "https://seleniumbase.io/apps/turnstile"
sb.uc_open_with_reconnect(url)
sb.uc_gui_handle_captcha() # Only if needed
- sb.assert_element('label[for="user_login"]')
- sb.assert_element('input[data-testid*="username"]')
- sb.assert_element('input[data-testid*="password"]')
- sb.set_messenger_theme(location="bottom_center")
- sb.post_message("SeleniumBase wasn't detected!")
+ sb.assert_element("img#captcha-success", timeout=3)
+ sb.set_messenger_theme(location="top_left")
+ sb.post_message("SeleniumBase wasn't detected", duration=3)
diff --git a/examples/test_hack_search.py b/examples/test_hack_search.py
index 9f27e4c480d..5c8c5b990d8 100644
--- a/examples/test_hack_search.py
+++ b/examples/test_hack_search.py
@@ -8,10 +8,9 @@
class HackingTests(BaseCase):
def test_hack_search(self):
- if self.headless:
+ if self.headless or self.browser != "chrome":
self.open_if_not_url("about:blank")
- print("\n Skipping test in headless mode.")
- self.skip('Skipping test in headless mode.')
+ self.skip('Skip test if headless or not chrome.')
if not self.undetectable:
self.get_new_driver(undetectable=True)
self.open("https://google.com/ncr")
diff --git a/examples/test_todomvc.py b/examples/test_todomvc.py
index 1ed61ca16c3..9acb6c8f77f 100644
--- a/examples/test_todomvc.py
+++ b/examples/test_todomvc.py
@@ -4,6 +4,15 @@
class TodoMVC(BaseCase):
+ def setUp(self):
+ super().setUp()
+ self.open_new_window()
+
+ def tearDown(self):
+ self.save_teardown_screenshot()
+ self.driver.close()
+ super().tearDown()
+
@parameterized.expand([["jquery"], ["react"], ["vue"]])
def test_todomvc(self, framework):
self.open("https://todomvc.com/")
diff --git a/examples/test_window_switching.py b/examples/test_window_switching.py
index 9b698641dad..19fcc556bc4 100644
--- a/examples/test_window_switching.py
+++ b/examples/test_window_switching.py
@@ -9,6 +9,8 @@
class TabSwitchingTests(BaseCase):
def test_switch_to_tabs(self):
+ self.open("about:blank")
+ self.get_new_driver()
self.open("data:text/html,
Page A
")
self.assert_text("Page A")
self.open_new_window()
@@ -17,6 +19,6 @@ def test_switch_to_tabs(self):
self.switch_to_window(0)
self.assert_text("Page A")
self.assert_text_not_visible("Page B")
- self.switch_to_window(1)
+ self.switch_to_window(-1)
self.assert_text("Page B")
self.assert_text_not_visible("Page A")
diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md
index 8777c451d22..477c8e938b2 100644
--- a/help_docs/method_summary.md
+++ b/help_docs/method_summary.md
@@ -37,14 +37,12 @@ self.submit(selector, by="css selector")
self.clear(selector, by="css selector", timeout=None)
self.focus(selector, by="css selector", timeout=None)
self.refresh()
-# Duplicates:
-# self.refresh_page(), self.reload_page(), self.reload()
+# Duplicates: self.refresh_page(), self.reload_page(), self.reload()
self.get_current_url()
self.get_origin()
self.get_page_source()
self.get_title()
-# Duplicates:
-# self.get_page_title()
+# Duplicates: self.get_page_title()
self.get_user_agent()
self.get_locale_code()
self.go_back()
@@ -159,7 +157,9 @@ self.set_window_rect(x, y, width, height)
self.set_window_size(width, height)
self.set_window_position(x, y)
self.maximize_window()
+# Duplicates: self.maximize()
self.minimize_window()
+# Duplicates: self.minimize()
self.reset_window_size()
self.switch_to_frame(frame="iframe", timeout=None, invisible=False)
self.switch_to_default_content()
@@ -168,23 +168,17 @@ with self.frame_switch(frame, timeout=None):
# Indented Code Block for Context Manager (Must use "with")
self.set_content_to_frame(frame, timeout=None)
self.set_content_to_default(nested=False)
-# Duplicates:
-# self.set_content_to_default_content(nested=False)
+# Duplicates: self.set_content_to_default_content(nested=False)
self.set_content_to_parent()
-# Duplicates:
-# self.set_content_to_parent_frame()
+# Duplicates: self.set_content_to_parent_frame()
self.open_new_window(switch_to=True)
-# Duplicates:
-# self.open_new_tab(switch_to=True)
+# Duplicates: self.open_new_tab(switch_to=True)
self.switch_to_window(window, timeout=None)
-# Duplicates:
-# self.switch_to_tab(tab, timeout=None)
+# Duplicates: self.switch_to_tab(tab, timeout=None)
self.switch_to_default_window()
-# Duplicates:
-# self.switch_to_default_tab()
+# Duplicates: self.switch_to_default_tab()
self.switch_to_newest_window()
-# Duplicates:
-# self.switch_to_newest_tab()
+# Duplicates: self.switch_to_newest_tab()
self.get_new_driver(
browser=None,
headless=None,
@@ -252,13 +246,11 @@ self.save_data_to_logs(data, file_name=None)
self.append_data_to_logs(data, file_name=None)
self.save_page_source_to_logs(name=None)
self.save_page_source(name, folder=None)
-# Duplicates:
-# self.save_as_html(name, folder=None)
+# Duplicates: self.save_as_html(name, folder=None)
self.save_cookies(name="cookies.txt")
self.load_cookies(name="cookies.txt", expiry=False)
self.delete_all_cookies()
-# Duplicates:
-# self.clear_all_cookies()
+# Duplicates: self.clear_all_cookies()
self.delete_saved_cookies(name="cookies.txt")
self.get_saved_cookies(name="cookies.txt")
self.get_cookie(name)
@@ -269,8 +261,7 @@ self.add_cookies(cookies, expiry=False)
self.wait_for_ready_state_complete(timeout=None)
self.wait_for_angularjs(timeout=None)
self.sleep(seconds)
-# Duplicates:
-# self.wait(seconds)
+# Duplicates: self.wait(seconds)
self.install_addon(xpi_file)
self.activate_jquery()
self.activate_demo_mode()
@@ -302,6 +293,10 @@ self.slow_scroll_to(selector, by="css selector", timeout=None)
self.scroll_into_view(selector, by="css selector", timeout=None)
self.scroll_to_top()
self.scroll_to_bottom()
+self.scroll_to_y(y)
+self.scroll_by_y(y)
+self.scroll_up(amount=25)
+self.scroll_down(amount=25)
self.click_xpath(xpath)
self.js_click(selector, by="css selector", all_matches=False, timeout=None, scroll=True)
self.js_click_if_present(selector, by="css selector", timeout=0)
@@ -316,8 +311,7 @@ self.show_elements(selector, by="css selector")
self.remove_element(selector, by="css selector")
self.remove_elements(selector, by="css selector")
self.ad_block()
-# Duplicates:
-# self.block_ads()
+# Duplicates: self.block_ads()
self.show_file_choosers()
self.disable_beforeunload()
self.get_domain_url(url)
@@ -345,8 +339,7 @@ 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)
+# 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)
@@ -355,8 +348,7 @@ self.get_data_from_downloaded_file(file, timeout=None, browser=False)
self.is_downloaded_file_present(file, browser=False)
self.is_downloaded_file_regex_present(regex, browser=False)
self.delete_downloaded_file_if_present(file, browser=False)
-# Duplicates:
-# self.delete_downloaded_file(file, browser=False)
+# Duplicates: self.delete_downloaded_file(file, browser=False)
self.assert_downloaded_file(file, timeout=None, browser=False)
self.assert_downloaded_file_regex(regex, timeout=None, browser=False)
self.assert_data_in_downloaded_file(data, file, timeout=None, browser=False)
@@ -423,16 +415,14 @@ self.set_local_storage_item(key, value)
self.get_local_storage_item(key)
self.remove_local_storage_item(key)
self.clear_local_storage()
-# Duplicates:
-# self.delete_local_storage()
+# Duplicates: self.delete_local_storage()
self.get_local_storage_keys()
self.get_local_storage_items()
self.set_session_storage_item(key, value)
self.get_session_storage_item(key)
self.remove_session_storage_item(key)
self.clear_session_storage()
-# Duplicates:
-# self.delete_session_storage()
+# Duplicates: self.delete_session_storage()
self.get_session_storage_keys()
self.get_session_storage_items()
@@ -496,8 +486,7 @@ self.create_introjs_tour(name=None)
self.set_introjs_colors(theme_color=None, hover_color=None)
self.add_tour_step(message, selector=None, name=None, title=None, theme=None, alignment=None)
self.play_tour(name=None, interval=0)
-# Duplicates:
-# self.start_tour(name=None, interval=0):
+# Duplicates: self.start_tour(name=None, interval=0):
self.export_tour(name=None, filename="my_tour.js", url=None)
############
@@ -584,8 +573,7 @@ self.find_link_text(link_text, timeout=None)
# self.wait_for_link_text(link_text, timeout=None)
# self.wait_for_link_text_visible(link_text, timeout=None)
self.assert_link_text(link_text, timeout=None)
-# Duplicates:
-# self.assert_link(link_text, timeout=None)
+# Duplicates: self.assert_link(link_text, timeout=None)
############
@@ -630,14 +618,11 @@ self.assert_attribute_not_present(
############
self.accept_alert(timeout=None)
-# Duplicates:
-# self.wait_for_and_accept_alert(timeout=None)
+# Duplicates: self.wait_for_and_accept_alert(timeout=None)
self.dismiss_alert(timeout=None)
-# Duplicates:
-# self.wait_for_and_dismiss_alert(timeout=None)
+# Duplicates: self.wait_for_and_dismiss_alert(timeout=None)
self.switch_to_alert(timeout=None)
-# Duplicates:
-# self.wait_for_and_switch_to_alert(timeout=None)
+# Duplicates: self.wait_for_and_switch_to_alert(timeout=None)
############
@@ -678,8 +663,7 @@ self.deferred_check_window(
# name="default", level=0, baseline=False,
# check_domain=True, full_diff=False, fs=False)
self.process_deferred_asserts(print_only=False)
-# Duplicates:
-# self.process_delayed_asserts(print_only=False)
+# Duplicates: self.process_delayed_asserts(print_only=False)
############
diff --git a/requirements.txt b/requirements.txt
index 9ea30632e58..80b4a0d2cdc 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
pip>=25.0.1;python_version<"3.9"
-pip>=25.2;python_version>="3.9"
+pip>=25.3;python_version>="3.9"
packaging>=25.0
setuptools~=70.2;python_version<"3.10"
setuptools>=80.9.0;python_version>="3.10"
@@ -52,7 +52,7 @@ websocket-client~=1.8.0;python_version<"3.9"
websocket-client~=1.9.0;python_version>="3.9"
selenium==4.27.1;python_version<"3.9"
selenium==4.32.0;python_version>="3.9" and python_version<"3.10"
-selenium==4.37.0;python_version>="3.10"
+selenium==4.38.0;python_version>="3.10"
cssselect==1.2.0;python_version<"3.9"
cssselect==1.3.0;python_version>="3.9"
sortedcontainers==2.4.0
diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py
index 276ca19001c..edaa71ccb98 100755
--- a/seleniumbase/__version__.py
+++ b/seleniumbase/__version__.py
@@ -1,2 +1,2 @@
# seleniumbase package
-__version__ = "4.43.3"
+__version__ = "4.44.0"
diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py
index c8336f73b1f..9ff1e9c5370 100644
--- a/seleniumbase/core/browser_launcher.py
+++ b/seleniumbase/core/browser_launcher.py
@@ -551,6 +551,19 @@ def uc_open_with_tab(driver, url):
def uc_open_with_reconnect(driver, url, reconnect_time=None):
"""Open a url, disconnect chromedriver, wait, and reconnect."""
+ if (
+ hasattr(sb_config, "_cdp_browser")
+ and sb_config._cdp_browser in ["comet", "opera", "atlas"]
+ ):
+ if not __is_cdp_swap_needed(driver):
+ if not driver.current_url.startswith(
+ ("about", "data", "chrome")
+ ):
+ driver.get("about:blank")
+ uc_activate_cdp_mode(driver, url)
+ else:
+ driver.cdp.open(url)
+ return
url = shared_utils.fix_url_as_needed(url)
if __is_cdp_swap_needed(driver):
driver.cdp.get(url)
@@ -885,6 +898,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
cdp.assert_not_in = CDPM.assert_not_in
cdp.scroll_into_view = CDPM.scroll_into_view
cdp.scroll_to_y = CDPM.scroll_to_y
+ cdp.scroll_by_y = CDPM.scroll_by_y
cdp.scroll_to_top = CDPM.scroll_to_top
cdp.scroll_to_bottom = CDPM.scroll_to_bottom
cdp.scroll_up = CDPM.scroll_up
@@ -2979,12 +2993,16 @@ def get_driver(
driver_dir = DRIVER_DIR_CHS
if _special_binary_exists(binary_location, "opera"):
driver_dir = DRIVER_DIR_OPERA
+ sb_config._cdp_browser = "opera"
if _special_binary_exists(binary_location, "brave"):
driver_dir = DRIVER_DIR_BRAVE
+ sb_config._cdp_browser = "brave"
if _special_binary_exists(binary_location, "comet"):
driver_dir = DRIVER_DIR_COMET
+ sb_config._cdp_browser = "comet"
if _special_binary_exists(binary_location, "atlas"):
driver_dir = DRIVER_DIR_ATLAS
+ sb_config._cdp_browser = "atlas"
if (
hasattr(sb_config, "settings")
and hasattr(sb_config.settings, "NEW_DRIVER_DIR")
@@ -2997,6 +3015,8 @@ def get_driver(
browser_name = browser
else:
browser_name = "chrome" # The default if not specified
+ if browser_name in constants.ChromiumSubs.chromium_subs:
+ browser_name = "chrome"
browser_name = browser_name.lower()
if headless2 and browser_name == constants.Browser.FIREFOX:
headless2 = False # Only for Chromium
diff --git a/seleniumbase/core/detect_b_ver.py b/seleniumbase/core/detect_b_ver.py
index 1006c810b9a..58963886e38 100644
--- a/seleniumbase/core/detect_b_ver.py
+++ b/seleniumbase/core/detect_b_ver.py
@@ -278,6 +278,7 @@ def opera_on_windows_path(browser_type=None):
),
):
for subitem in (
+ "Programs/Opera",
"Opera",
"Opera/Application",
):
@@ -336,6 +337,7 @@ def comet_on_windows_path(browser_type=None):
):
for subitem in (
"Comet/Application",
+ "Programs/Comet",
):
try:
candidates.append(os.sep.join((item, subitem, "Comet.exe")))
@@ -364,6 +366,7 @@ def atlas_on_windows_path(browser_type=None):
):
for subitem in (
"Atlas/Application",
+ "Programs/Atlas",
):
try:
candidates.append(os.sep.join((item, subitem, "Atlas.exe")))
diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py
index 9d364aab64d..e9fdfc33f96 100644
--- a/seleniumbase/core/sb_cdp.py
+++ b/seleniumbase/core/sb_cdp.py
@@ -1029,12 +1029,19 @@ def js_dumps(self, obj_name):
return self.loop.run_until_complete(self.page.js_dumps(obj_name))
def maximize(self):
- if self.get_window()[1].window_state.value == "maximized":
- return
- elif self.get_window()[1].window_state.value == "minimized":
- self.loop.run_until_complete(self.page.maximize())
- time.sleep(0.044)
- return self.loop.run_until_complete(self.page.maximize())
+ try:
+ if self.get_window()[1].window_state.value == "maximized":
+ return
+ elif self.get_window()[1].window_state.value == "minimized":
+ self.loop.run_until_complete(self.page.maximize())
+ time.sleep(0.044)
+ return self.loop.run_until_complete(self.page.maximize())
+ except Exception:
+ with suppress(Exception):
+ width = self.evaluate("screen.availWidth;")
+ height = self.evaluate("screen.availHeight;")
+ self.__set_window_rect(0, 0, width, height)
+ return
def minimize(self):
if self.get_window()[1].window_state.value != "minimized":
@@ -1725,18 +1732,18 @@ def gui_click_x_y(self, x, y, timeframe=0.25):
win_x = window_rect["x"]
win_y = window_rect["y"]
scr_width = pyautogui.size().width
- self.maximize()
- self.__add_light_pause()
- win_width = self.get_window_size()["width"]
+ win_width = self.evaluate("screen.availWidth;")
width_ratio = round(float(scr_width) / float(win_width), 2)
width_ratio += 0.01
if width_ratio < 0.45 or width_ratio > 2.55:
width_ratio = 1.01
sb_config._saved_width_ratio = width_ratio
- self.minimize()
- self.__add_light_pause()
- self.__set_window_rect(win_x, win_y, width, height)
- self.__add_light_pause()
+ with suppress(Exception):
+ self.get_window() # If this fails, skip the rest
+ self.minimize()
+ self.__add_light_pause()
+ self.__set_window_rect(win_x, win_y, width, height)
+ self.__add_light_pause()
x = x * (width_ratio + 0.03)
y = y * (width_ratio - 0.03)
self.bring_active_window_to_front()
@@ -2021,18 +2028,18 @@ def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35):
win_x = window_rect["x"]
win_y = window_rect["y"]
scr_width = pyautogui.size().width
- self.maximize()
- self.__add_light_pause()
- win_width = self.get_window_size()["width"]
+ win_width = self.evaluate("screen.availWidth;")
width_ratio = round(float(scr_width) / float(win_width), 2)
width_ratio += 0.01
if width_ratio < 0.45 or width_ratio > 2.55:
width_ratio = 1.01
sb_config._saved_width_ratio = width_ratio
- self.minimize()
- self.__add_light_pause()
- self.__set_window_rect(win_x, win_y, width, height)
- self.__add_light_pause()
+ with suppress(Exception):
+ self.get_window() # If this fails, skip the rest
+ self.minimize()
+ self.__add_light_pause()
+ self.__set_window_rect(win_x, win_y, width, height)
+ self.__add_light_pause()
x1 = x1 * width_ratio
y1 = y1 * (width_ratio - 0.02)
x2 = x2 * width_ratio
@@ -2114,15 +2121,14 @@ def gui_hover_x_y(self, x, y, timeframe=0.25):
width_ratio = sb_config._saved_width_ratio
else:
scr_width = pyautogui.size().width
- self.maximize()
- self.__add_light_pause()
- win_width = self.get_window_size()["width"]
+ win_width = self.evaluate("screen.availWidth;")
width_ratio = round(float(scr_width) / float(win_width), 2)
width_ratio += 0.01
if width_ratio < 0.45 or width_ratio > 2.55:
width_ratio = 1.01
sb_config._saved_width_ratio = width_ratio
- self.__set_window_rect(win_x, win_y, width, height)
+ with suppress(Exception):
+ self.__set_window_rect(win_x, win_y, width, height)
self.__add_light_pause()
self.bring_active_window_to_front()
elif (
@@ -2694,6 +2700,13 @@ def scroll_to_y(self, y):
self.loop.run_until_complete(self.page.evaluate(js_code))
self.loop.run_until_complete(self.page.wait())
+ def scroll_by_y(self, y):
+ y = int(y)
+ js_code = "window.scrollBy(0, %s);" % y
+ with suppress(Exception):
+ self.loop.run_until_complete(self.page.evaluate(js_code))
+ self.loop.run_until_complete(self.page.wait())
+
def scroll_to_top(self):
js_code = "window.scrollTo(0, 0);"
with suppress(Exception):
@@ -2707,11 +2720,21 @@ def scroll_to_bottom(self):
self.loop.run_until_complete(self.page.wait())
def scroll_up(self, amount=25):
- self.loop.run_until_complete(self.page.scroll_up(amount))
+ """Scrolls up as a percentage of the page."""
+ try:
+ self.loop.run_until_complete(self.page.scroll_up(amount))
+ except Exception:
+ amount = self.get_window_size()["height"] * amount / 100
+ self.execute_script("window.scrollBy(0, -%s);" % amount)
self.loop.run_until_complete(self.page.wait())
def scroll_down(self, amount=25):
- self.loop.run_until_complete(self.page.scroll_down(amount))
+ """Scrolls down as a percentage of the page."""
+ try:
+ self.loop.run_until_complete(self.page.scroll_down(amount))
+ except Exception:
+ amount = self.get_window_size()["height"] * amount / 100
+ self.execute_script("window.scrollBy(0, %s);" % amount)
self.loop.run_until_complete(self.page.wait())
def save_page_source(self, name, folder=None):
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index fac0ff6cf4d..938be66719e 100644
--- a/seleniumbase/fixtures/base_case.py
+++ b/seleniumbase/fixtures/base_case.py
@@ -3577,9 +3577,18 @@ def maximize_window(self):
self.cdp.maximize()
return
self._check_browser()
- self.driver.maximize_window()
+ try:
+ self.driver.maximize_window()
+ except Exception:
+ with suppress(Exception):
+ width = self.execute_script("return screen.availWidth;")
+ height = self.execute_script("return screen.availHeight;")
+ self.set_window_rect(0, 0, width, height)
self.__demo_mode_pause_if_active(tiny=True)
+ def maximize(self):
+ self.maximize_window()
+
def minimize_window(self):
self.__check_scope()
if self.__is_cdp_swap_needed():
@@ -3589,6 +3598,9 @@ def minimize_window(self):
self.driver.minimize_window()
self.__demo_mode_pause_if_active(tiny=True)
+ def minimize(self):
+ self.minimize_window()
+
def reset_window_size(self):
self.__check_scope()
if self.__is_cdp_swap_needed():
@@ -4344,7 +4356,7 @@ def get_new_driver(
if self.is_chromium():
try:
if self.maximize_option:
- self.driver.maximize_window()
+ self.maximize_window()
self.wait_for_ready_state_complete()
else:
pass # Now handled in browser_launcher.py
@@ -4354,7 +4366,7 @@ def get_new_driver(
elif self.browser == "firefox":
try:
if self.maximize_option:
- self.driver.maximize_window()
+ self.maximize_window()
self.wait_for_ready_state_complete()
else:
with suppress(Exception):
@@ -4364,7 +4376,7 @@ def get_new_driver(
elif self.browser == "safari":
if self.maximize_option:
try:
- self.driver.maximize_window()
+ self.maximize_window()
self.wait_for_ready_state_complete()
except Exception:
pass # Keep existing browser resolution
@@ -6511,6 +6523,34 @@ def scroll_to_y(self, y):
self.execute_script(scroll_script)
time.sleep(0.012)
+ def scroll_by_y(self, y):
+ """Scrolls page by y pixels."""
+ self.__check_scope()
+ y = int(y)
+ if self.__is_cdp_swap_needed():
+ self.cdp.scroll_by_y(y)
+ return
+ scroll_script = "window.scrollBy(0, %s);" % y
+ with suppress(Exception):
+ self.execute_script(scroll_script)
+ time.sleep(0.012)
+
+ def scroll_up(self, amount=25):
+ """Scrolls up as a percentage of the page."""
+ if self.__is_cdp_swap_needed():
+ self.cdp.scroll_up(amount)
+ return
+ amount = self.get_window_size()["height"] * amount / 100
+ self.execute_script("window.scrollBy(0, -%s);" % amount)
+
+ def scroll_down(self, amount=25):
+ """Scrolls down as a percentage of the page."""
+ if self.__is_cdp_swap_needed():
+ self.cdp.scroll_down(amount)
+ return
+ amount = self.get_window_size()["height"] * amount / 100
+ self.execute_script("window.scrollBy(0, %s);" % amount)
+
def click_xpath(self, xpath):
"""Technically, self.click() automatically detects xpath selectors,
so self.click_xpath() is just a longer name for the same action."""
@@ -11408,7 +11448,13 @@ def __get_new_timeout(self, timeout):
def __is_cdp_swap_needed(self):
"""If the driver is disconnected, use a CDP method when available."""
- return shared_utils.is_cdp_swap_needed(self.driver)
+ cdp_swap_needed = shared_utils.is_cdp_swap_needed(self.driver)
+ if cdp_swap_needed:
+ if not self.cdp:
+ self.cdp = self.driver.cdp
+ return True
+ else:
+ return False
############
@@ -14001,7 +14047,7 @@ def __click_with_offset(
)
raise Exception(message)
except InvalidArgumentException:
- if not self.browser == "chrome":
+ if not self.is_chromium():
raise
chrome_version = self.driver.capabilities["browserVersion"]
major_chrome_version = chrome_version.split(".")[0]
@@ -14616,7 +14662,7 @@ def __get_shadow_element(
try:
shadow_root = element.shadow_root
except Exception:
- if self.browser == "chrome":
+ if self.is_chromium():
chrome_dict = self.driver.capabilities["chrome"]
chrome_dr_version = chrome_dict["chromedriverVersion"]
chromedriver_version = chrome_dr_version.split(" ")[0]
diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py
index 58f754e9658..75ad0ed9eff 100644
--- a/seleniumbase/fixtures/constants.py
+++ b/seleniumbase/fixtures/constants.py
@@ -264,16 +264,13 @@ class Reveal:
class HighCharts:
+ LIB = "https://cdn.jsdelivr.net/npm/highcharts"
VER = "10.3.3"
- HC_CSS = "https://code.highcharts.com/%s/css/highcharts.css" % VER
- HC_JS = "https://code.highcharts.com/%s/highcharts.js" % VER
- EXPORTING_JS = "https://code.highcharts.com/%s/modules/exporting.js" % VER
- EXPORT_DATA_JS = (
- "https://code.highcharts.com/%s/modules/export-data.js" % VER
- )
- ACCESSIBILITY_JS = (
- "https://code.highcharts.com/%s/modules/accessibility.js" % VER
- )
+ HC_CSS = "%s@%s/css/highcharts.css" % (LIB, VER)
+ HC_JS = "%s@%s/highcharts.js" % (LIB, VER)
+ EXPORTING_JS = "%s@%s/modules/exporting.js" % (LIB, VER)
+ EXPORT_DATA_JS = "%s@%s/modules/export-data.js" % (LIB, VER)
+ ACCESSIBILITY_JS = "%s@%s/modules/accessibility.js" % (LIB, VER)
class BootstrapTour:
diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py
index 373d5d6571b..ac8a358c277 100644
--- a/seleniumbase/fixtures/page_actions.py
+++ b/seleniumbase/fixtures/page_actions.py
@@ -1530,7 +1530,7 @@ def save_page_source(driver, name, folder=None):
rendered_source = log_helper.get_html_source_with_base_href(
driver, page_source
)
- html_file = open(html_file_path, "w+", "utf-8")
+ html_file = open(html_file_path, mode="w+", encoding="utf-8")
html_file.write(rendered_source)
html_file.close()
diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py
index 6eeee58ef77..9624766c4d9 100644
--- a/seleniumbase/plugins/driver_manager.py
+++ b/seleniumbase/plugins/driver_manager.py
@@ -455,8 +455,6 @@ def Driver(
)
if sb_config._browser_shortcut:
browser = sb_config._browser_shortcut
- if browser in constants.ChromiumSubs.chromium_subs:
- browser = "chrome" # Still uses chromedriver
if headless is None:
if "--headless" in sys_argv:
headless = True
@@ -953,6 +951,10 @@ def Driver(
driver_version = None
break
count += 1
+ if browser in constants.ChromiumSubs.chromium_subs:
+ if not binary_location:
+ browser = "chrome" # Still uses chromedriver
+ sb_config._browser_shortcut = browser
browser_name = browser
# Launch a web browser
diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py
index 0a34b304a68..6ca77ccd923 100644
--- a/seleniumbase/plugins/pytest_plugin.py
+++ b/seleniumbase/plugins/pytest_plugin.py
@@ -1676,8 +1676,23 @@ def pytest_configure(config):
sb_config.browser = config.getoption("browser")
if sb_config._browser_shortcut:
sb_config.browser = sb_config._browser_shortcut
- if sb_config.browser in constants.ChromiumSubs.chromium_subs:
- sb_config.browser = "chrome" # Still uses chromedriver
+ elif sys_argv == ["-c"]: # Multithreading messes with args
+ if config.getoption("use_opera"):
+ bin_loc = detect_b_ver.get_binary_location("opera")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.browser = "opera"
+ elif config.getoption("use_brave"):
+ bin_loc = detect_b_ver.get_binary_location("brave")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.browser = "brave"
+ elif config.getoption("use_comet"):
+ bin_loc = detect_b_ver.get_binary_location("comet")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.browser = "comet"
+ elif config.getoption("use_atlas"):
+ bin_loc = detect_b_ver.get_binary_location("atlas")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.browser = "atlas"
sb_config.account = config.getoption("account")
sb_config.data = config.getoption("data")
sb_config.var1 = config.getoption("var1")
@@ -1714,6 +1729,35 @@ def pytest_configure(config):
sb_config.binary_location = config.getoption("binary_location")
if hasattr(sb_config, "_cdp_bin_loc") and sb_config._cdp_bin_loc:
sb_config.binary_location = sb_config._cdp_bin_loc
+ elif not sb_config.binary_location:
+ if (
+ config.getoption("use_opera")
+ or sb_config._browser_shortcut == "opera"
+ ):
+ bin_loc = detect_b_ver.get_binary_location("opera")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.binary_location = bin_loc
+ elif (
+ config.getoption("use_brave")
+ or sb_config._browser_shortcut == "brave"
+ ):
+ bin_loc = detect_b_ver.get_binary_location("brave")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.binary_location = bin_loc
+ elif (
+ config.getoption("use_comet")
+ or sb_config._browser_shortcut == "comet"
+ ):
+ bin_loc = detect_b_ver.get_binary_location("comet")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.binary_location = bin_loc
+ elif (
+ config.getoption("use_atlas")
+ or sb_config._browser_shortcut == "atlas"
+ ):
+ bin_loc = detect_b_ver.get_binary_location("atlas")
+ if bin_loc and os.path.exists(bin_loc):
+ sb_config.binary_location = bin_loc
if config.getoption("use_cft") and not sb_config.binary_location:
sb_config.binary_location = "cft"
elif config.getoption("use_chs") and not sb_config.binary_location:
@@ -1726,6 +1770,10 @@ def pytest_configure(config):
sb_config.headless = True
sb_config.headless1 = False
sb_config.headless2 = False
+ if sb_config.browser in constants.ChromiumSubs.chromium_subs:
+ if not sb_config.binary_location:
+ sb_config.browser = "chrome" # Still uses chromedriver
+ sb_config._browser_shortcut = sb_config.browser
sb_config.driver_version = config.getoption("driver_version")
sb_config.page_load_strategy = config.getoption("page_load_strategy")
sb_config.with_testing_base = config.getoption("with_testing_base")
diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py
index ad58dbbeb21..e276fe6a907 100644
--- a/seleniumbase/plugins/sb_manager.py
+++ b/seleniumbase/plugins/sb_manager.py
@@ -1132,8 +1132,6 @@ def SB(
sb_config.browser = browser
if sb_config._browser_shortcut:
sb_config.browser = sb_config._browser_shortcut
- if sb_config.browser in constants.ChromiumSubs.chromium_subs:
- sb_config.browser = "chrome" # Still uses chromedriver
if not hasattr(sb_config, "is_behave"):
sb_config.is_behave = False
if not hasattr(sb_config, "is_pytest"):
@@ -1242,6 +1240,10 @@ def SB(
sb_config.interval = interval
sb_config.cap_file = cap_file
sb_config.cap_string = cap_string
+ if sb_config.browser in constants.ChromiumSubs.chromium_subs:
+ if not sb_config.binary_location:
+ sb_config.browser = "chrome" # Still uses chromedriver
+ sb_config._browser_shortcut = sb_config.browser
sb = BaseCase()
sb.with_testing_base = sb_config.with_testing_base
diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py
index 5df85ab9f8d..0c6a48b4825 100644
--- a/seleniumbase/plugins/selenium_plugin.py
+++ b/seleniumbase/plugins/selenium_plugin.py
@@ -1302,8 +1302,6 @@ def beforeTest(self, test):
if sb_config._browser_shortcut:
self.options.browser = sb_config._browser_shortcut
test.test.browser = sb_config._browser_shortcut
- if test.test.browser in constants.ChromiumSubs.chromium_subs:
- test.test.browser = "chrome" # Still uses chromedriver
test.test.cap_file = self.options.cap_file
test.test.cap_string = self.options.cap_string
test.test.headless = self.options.headless
@@ -1355,6 +1353,10 @@ def beforeTest(self, test):
test.test.headless = True
test.test.headless1 = False
test.test.headless2 = False
+ if test.test.browser in constants.ChromiumSubs.chromium_subs:
+ if not sb_config.binary_location:
+ test.test.browser = "chrome" # Still uses chromedriver
+ sb_config._browser_shortcut = test.test.browser
test.test.driver_version = self.options.driver_version
test.test.page_load_strategy = self.options.page_load_strategy
test.test.chromium_arg = self.options.chromium_arg
diff --git a/setup.py b/setup.py
index 3cb0a08561d..93ef5b3c836 100755
--- a/setup.py
+++ b/setup.py
@@ -148,7 +148,7 @@
python_requires=">=3.8",
install_requires=[
'pip>=25.0.1;python_version<"3.9"',
- 'pip>=25.2;python_version>="3.9"',
+ 'pip>=25.3;python_version>="3.9"',
'packaging>=25.0',
'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues
'setuptools>=80.9.0;python_version>="3.10"',
@@ -201,7 +201,7 @@
'websocket-client~=1.9.0;python_version>="3.9"',
'selenium==4.27.1;python_version<"3.9"',
'selenium==4.32.0;python_version>="3.9" and python_version<"3.10"',
- 'selenium==4.37.0;python_version>="3.10"',
+ 'selenium==4.38.0;python_version>="3.10"',
'cssselect==1.2.0;python_version<"3.9"',
'cssselect==1.3.0;python_version>="3.9"',
"sortedcontainers==2.4.0",