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
22 changes: 22 additions & 0 deletions examples/cdp_mode/raw_cdp_mobile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import mycdp
from seleniumbase import sb_cdp

sb = sb_cdp.Chrome()
tab = sb.get_active_tab()
loop = sb.get_event_loop()
loop.run_until_complete(
tab.send(
mycdp.emulation.set_device_metrics_override(
width=411, height=731, device_scale_factor=3, mobile=True
)
)
)
url = "https://gitlab.com/users/sign_in"
sb.open(url)
sb.sleep(2)
sb.solve_captcha()
# (The rest is for testing and demo purposes)
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")')
34 changes: 34 additions & 0 deletions examples/cdp_mode/raw_mobile_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import asyncio
import mycdp
import time
from seleniumbase import cdp_driver
from seleniumbase import decorators


async def main():
url = "https://gitlab.com/users/sign_in"
driver = await cdp_driver.start_async()
await driver.main_tab.send(
mycdp.emulation.set_device_metrics_override(
width=411, height=731, device_scale_factor=3, mobile=True
)
)
page = await driver.get(url, lang="en")
time.sleep(3)
try:
element = await page.select('[style*="grid"] div div', timeout=1)
await element.mouse_click_with_offset_async(32, 28)
except Exception:
pass # Maybe CAPTCHA was already bypassed
element = await page.select('label[for="user_login"]')
await element.flash_async(duration=1.5, color="44EE44")
time.sleep(1)
element = await page.select('[data-testid="sign-in-button"]')
await element.flash_async(duration=2, color="44EE44")
time.sleep(2)

if __name__ == "__main__":
# Call an async function with awaited methods
loop = asyncio.new_event_loop()
with decorators.print_runtime("raw_mobile_async.py"):
loop.run_until_complete(main())
24 changes: 24 additions & 0 deletions examples/cdp_mode/raw_mobile_gitlab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import mycdp
from seleniumbase import SB

with SB(uc=True, test=True) as sb:
url = "https://gitlab.com/users/sign_in"
sb.activate_cdp_mode()
tab = sb.cdp.get_active_tab()
loop = sb.cdp.get_event_loop()
loop.run_until_complete(
tab.send(
mycdp.emulation.set_device_metrics_override(
width=411, height=731, device_scale_factor=3, mobile=True
)
)
)
sb.open(url)
sb.sleep(2)
sb.solve_captcha()
# (The rest is for testing and demo purposes)
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")')
sb.post_message("SeleniumBase wasn't detected", duration=4)
24 changes: 24 additions & 0 deletions examples/cdp_mode/raw_mobile_roblox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import mycdp
from seleniumbase import SB

with SB(uc=True, test=True) as sb:
url = "https://www.roblox.com/"
agent = (
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 "
"(KHTML, like Gecko) Mobile Safari/537.36"
)
sb.activate_cdp_mode(agent=agent)
tab = sb.cdp.get_active_tab()
loop = sb.cdp.get_event_loop()
loop.run_until_complete(
tab.send(
mycdp.emulation.set_device_metrics_override(
width=411, height=731, device_scale_factor=3, mobile=True
)
)
)
sb.open(url)
sb.assert_element("#download-the-app-container")
sb.assert_text("Roblox for Android")
sb.highlight('span:contains("Roblox for Android")', loops=8)
sb.highlight('span:contains("Continue in App")', loops=8)
35 changes: 35 additions & 0 deletions examples/cdp_mode/raw_stopandshop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Test Stop & Shop search. Non-US IPs might be blocked."""
from seleniumbase import SB

with SB(uc=True, test=True, guest=True, ad_block=True) as sb:
url = "https://stopandshop.com/"
sb.activate_cdp_mode(url)
sb.sleep(2.6)
if not sb.is_element_present("#brand-logo_link"):
sb.refresh()
sb.sleep(2.6)
sb.wait_for_element("#brand-logo_link", timeout=3)
query = "Fresh Turkey"
required_text = "Turkey"
search_box = 'input[type="search"]'
sb.wait_for_element(search_box)
sb.sleep(1.2)
sb.press_keys(search_box, query)
sb.sleep(1.2)
sb.click("button.search-btn")
sb.sleep(3.2)
print('*** Stop & Shop Search for "%s":' % query)
print(' (Results must contain "%s".)' % required_text)
print(' (Results cannot contain "Out of Stock")')
unique_item_text = []
item_selector = "div.product-tile_content"
items = sb.find_elements(item_selector)
for item in items:
sb.sleep(0.06)
if "Out of Stock" not in item.text:
if required_text in item.text:
item.flash(color="44CC88")
sb.sleep(0.025)
if item.text not in unique_item_text:
unique_item_text.append(item.text)
print("* " + item.text)
2 changes: 1 addition & 1 deletion mkdocs_build/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Minimum Python version: 3.10 (for generating docs only)

regex>=2025.11.3
pymdown-extensions>=10.17.1
pymdown-extensions>=10.17.2
pipdeptree>=2.30.0
python-dateutil>=2.8.2
Markdown==3.10
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.44.18"
__version__ = "4.44.19"
13 changes: 13 additions & 0 deletions seleniumbase/core/browser_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,10 @@ def _uc_gui_click_captcha(
'[data-callback="onCaptchaSuccess"]'
):
frame = '[data-callback="onCaptchaSuccess"]'
elif driver.is_element_present(
"div:not([class]) > div:not([class])"
):
frame = "div:not([class]) > div:not([class])"
else:
return
if (
Expand Down Expand Up @@ -1829,6 +1833,10 @@ def _uc_gui_handle_captcha_(driver, frame="iframe", ctype=None):
frame = ".cf-turnstile-wrapper"
elif driver.is_element_present('[class="cf-turnstile"]'):
frame = '[class="cf-turnstile"]'
elif driver.is_element_present(
"div:not([class]) > div:not([class])"
):
frame = "div:not([class]) > div:not([class])"
else:
return
else:
Expand Down Expand Up @@ -3048,6 +3056,11 @@ def get_driver(
if _special_binary_exists(binary_location, "atlas"):
driver_dir = DRIVER_DIR_ATLAS
sb_config._cdp_browser = "atlas"
if undetectable and mobile_emulator:
# For stealthy mobile mode, see the CDP Mode examples
# to learn how to properly configure it.
user_agent = None # Undo the override
mobile_emulator = False # Instead, set from CDP Mode
if (
hasattr(sb_config, "settings")
and getattr(sb_config.settings, "NEW_DRIVER_DIR", None)
Expand Down
4 changes: 4 additions & 0 deletions seleniumbase/core/sb_cdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2030,6 +2030,10 @@ def __click_captcha(self, use_cdp=False):
'[data-callback="onCaptchaSuccess"]'
):
selector = '[data-callback="onCaptchaSuccess"]'
elif self.is_element_present(
"div:not([class]) > div:not([class])"
):
selector = "div:not([class]) > div:not([class])"
else:
return
if not selector:
Expand Down
29 changes: 29 additions & 0 deletions seleniumbase/fixtures/base_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -10136,6 +10136,21 @@ def assert_element(self, selector, by="css selector", timeout=None):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if self.__is_cdp_swap_needed():
if self.demo_mode:
selector, by = self.__recalculate_selector(
selector, by, xp_ok=False
)
a_t = "ASSERT"
if self._language != "English":
from seleniumbase.fixtures.words import SD

a_t = SD.translate_assert(self._language)
messenger_post = "<b>%s %s</b>: %s" % (
a_t, by.upper(), selector
)
self.__highlight_with_assert_success(
messenger_post, selector, by
)
self.cdp.assert_element(selector, timeout=timeout)
return True
if isinstance(selector, list):
Expand Down Expand Up @@ -10430,6 +10445,20 @@ def assert_text(
messenger_post, selector, by
)
elif self.__is_cdp_swap_needed():
if self.demo_mode:
a_t = "ASSERT TEXT"
i_n = "in"
if self._language != "English":
from seleniumbase.fixtures.words import SD

a_t = SD.translate_assert_text(self._language)
i_n = SD.translate_in(self._language)
messenger_post = "<b>%s</b>: {%s} %s %s: %s" % (
a_t, text, i_n, by.upper(), selector
)
self.__highlight_with_assert_success(
messenger_post, selector, by
)
self.cdp.assert_text(text, selector, timeout=timeout)
return True
elif self.__is_shadow_selector(selector):
Expand Down
34 changes: 29 additions & 5 deletions seleniumbase/fixtures/js_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,18 @@ def safe_execute_script(driver, script):
This method will load jQuery if it wasn't already loaded."""
try:
execute_script(driver, script)
except TypeError as e:
if (
(
shared_utils.is_cdp_swap_needed(driver)
or hasattr(driver, "_swap_driver")
)
and "cannot unpack non-iterable" in str(e)
):
pass
else:
activate_jquery(driver) # It's a good thing we can define it here
execute_script(driver, script)
except Exception:
# The likely reason this fails is because: "jQuery is not defined"
activate_jquery(driver) # It's a good thing we can define it here
Expand Down Expand Up @@ -1311,22 +1323,34 @@ def slow_scroll_to_element(driver, element, *args, **kwargs):
element_location_y = None
try:
if shared_utils.is_cdp_swap_needed(driver):
element.get_position().y
element_location_y = element.get_position().y
else:
element_location_y = element.location["y"]
except Exception:
element.location_once_scrolled_into_view
if shared_utils.is_cdp_swap_needed(driver):
element.scroll_into_view()
else:
element.location_once_scrolled_into_view
return
try:
element_location_x = element.location["x"]
if shared_utils.is_cdp_swap_needed(driver):
element_location_x = element.get_position().x
else:
element_location_x = element.location["x"]
except Exception:
element_location_x = 0
try:
element_width = element.size["width"]
if shared_utils.is_cdp_swap_needed(driver):
element_width = element.get_position().width
else:
element_width = element.size["width"]
except Exception:
element_width = 0
try:
screen_width = driver.get_window_size()["width"]
if shared_utils.is_cdp_swap_needed(driver):
screen_width = driver.cdp.get_window_size()["width"]
else:
screen_width = driver.get_window_size()["width"]
except Exception:
screen_width = execute_script(driver, "return window.innerWidth;")
element_location_y = element_location_y - constants.Scroll.Y_OFFSET
Expand Down
10 changes: 4 additions & 6 deletions seleniumbase/undetected/cdp_driver/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ def __init__(
self._default_browser_args = [
"--window-size=%s,%s" % (start_width, start_height),
"--window-position=%s,%s" % (start_x, start_y),
"--remote-allow-origins=*",
"--no-first-run",
"--no-service-autorun",
"--disable-auto-reload",
Expand All @@ -138,8 +137,10 @@ def __init__(
'--simulate-outdated-no-au="Tue, 31 Dec 2099 23:59:59 GMT"',
"--password-store=basic",
"--deny-permission-prompts",
"--disable-infobars",
"--disable-application-cache",
"--test-type",
"--disable-breakpad",
"--disable-setuid-sandbox",
"--disable-prompt-on-repost",
"--disable-password-generation",
"--disable-ipc-flooding-protection",
Expand All @@ -149,8 +150,8 @@ def __init__(
"--disable-client-side-phishing-detection",
"--disable-top-sites",
"--disable-translate",
"--dns-prefetch-disable",
"--disable-renderer-backgrounding",
"--disable-background-networking",
"--disable-dev-shm-usage",
]

Expand Down Expand Up @@ -207,9 +208,6 @@ def __call__(self):
"OptimizationHintsFetching,InterestFeedContentSuggestions,"
"DisableLoadExtensionCommandLineSwitch"
]
if self.proxy:
args += ["--test-type"]
args += ["--disable-session-crashed-bubble"]
if self.expert:
args += [
"--disable-web-security",
Expand Down