diff --git a/help_docs/ReadMe.md b/help_docs/ReadMe.md
index 300084a7ed3..885c2f9a54b 100644
--- a/help_docs/ReadMe.md
+++ b/help_docs/ReadMe.md
@@ -81,11 +81,13 @@
Demo Pages / Web Examples
+
+
-
+
diff --git a/mkdocs.yml b/mkdocs.yml
index 6a808310558..a0dc32364b7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -133,6 +133,7 @@ nav:
- Demo Pages:
- 🍵 Coffee Cart (Test App): https://seleniumbase.io/coffee/
- 📑 Demo Page (Test Page): https://seleniumbase.io/demo_page
+ - 🔑 Simple App (Test Page): https://seleniumbase.io/simple/login
- 🔑 MFA Login (Test App): https://seleniumbase.io/realworld/login
- 📝 TinyMCE (Test Page): https://seleniumbase.io/tinymce/
- 🔢 Calculator (Test App): https://seleniumbase.io/apps/calculator
diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt
index a16ded9e9e9..f39fce5179f 100644
--- a/mkdocs_build/requirements.txt
+++ b/mkdocs_build/requirements.txt
@@ -20,7 +20,7 @@ paginate==0.5.6
pyquery==2.0.0
readtime==3.0.0
mkdocs==1.5.3
-mkdocs-material==9.4.4
+mkdocs-material==9.4.5
mkdocs-exclude-search==0.6.5
mkdocs-simple-hooks==0.1.5
mkdocs-material-extensions==1.2
diff --git a/requirements.txt b/requirements.txt
index 1e70e3021f5..c6c6a9f44aa 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -25,7 +25,7 @@ trio==0.22.2
trio-websocket==0.11.1
wsproto==1.2.0
selenium==4.11.2;python_version<"3.8"
-selenium==4.13.0;python_version>="3.8"
+selenium==4.14.0;python_version>="3.8"
cssselect==1.2.0
sortedcontainers==2.4.0
fasteners==0.19
diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py
index 9c7096253f9..9d57c441ed8 100755
--- a/seleniumbase/__version__.py
+++ b/seleniumbase/__version__.py
@@ -1,2 +1,2 @@
# seleniumbase package
-__version__ = "4.19.2"
+__version__ = "4.20.0"
diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py
index 6a2d8453d02..f7dc9021928 100644
--- a/seleniumbase/core/browser_launcher.py
+++ b/seleniumbase/core/browser_launcher.py
@@ -319,7 +319,15 @@ def has_cf(text):
return False
-def uc_special_open_if_cf(driver, url, proxy_string=None):
+def uc_special_open_if_cf(
+ driver,
+ url,
+ proxy_string=None,
+ mobile_emulator=None,
+ device_width=None,
+ device_height=None,
+ device_pixel_ratio=None,
+):
if (
url.startswith("http:") or url.startswith("https:")
):
@@ -345,6 +353,36 @@ def uc_special_open_if_cf(driver, url, proxy_string=None):
driver.close()
driver.switch_to.window(driver.window_handles[-1])
time.sleep(0.02)
+ if mobile_emulator:
+ uc_metrics = {}
+ if (
+ type(device_width) is int
+ and type(device_height) is int
+ and type(device_pixel_ratio) is int
+ ):
+ uc_metrics["width"] = device_width
+ uc_metrics["height"] = device_height
+ uc_metrics["pixelRatio"] = device_pixel_ratio
+ else:
+ uc_metrics["width"] = constants.Mobile.WIDTH
+ uc_metrics["height"] = constants.Mobile.HEIGHT
+ uc_metrics["pixelRatio"] = constants.Mobile.RATIO
+ set_device_metrics_override = dict(
+ {
+ "width": uc_metrics["width"],
+ "height": uc_metrics["height"],
+ "deviceScaleFactor": uc_metrics["pixelRatio"],
+ "mobile": True
+ }
+ )
+ try:
+ driver.execute_cdp_cmd(
+ 'Emulation.setDeviceMetricsOverride',
+ set_device_metrics_override
+ )
+ except Exception:
+ pass
+ time.sleep(0.03)
else:
driver.default_get(url) # The original one
else:
@@ -739,7 +777,7 @@ def _set_chrome_options(
"excludeSwitches",
["enable-automation", "enable-logging", "enable-blink-features"],
)
- if mobile_emulator:
+ if mobile_emulator and not is_using_uc(undetectable, browser_name):
emulator_settings = {}
device_metrics = {}
if (
@@ -751,9 +789,9 @@ def _set_chrome_options(
device_metrics["height"] = device_height
device_metrics["pixelRatio"] = device_pixel_ratio
else:
- device_metrics["width"] = 360
- device_metrics["height"] = 640
- device_metrics["pixelRatio"] = 2
+ device_metrics["width"] = constants.Mobile.WIDTH
+ device_metrics["height"] = constants.Mobile.HEIGHT
+ device_metrics["pixelRatio"] = constants.Mobile.RATIO
emulator_settings["deviceMetrics"] = device_metrics
if user_agent:
emulator_settings["userAgent"] = user_agent
@@ -1279,8 +1317,8 @@ def get_driver(
if (uc_cdp_events or uc_subprocess) and not undetectable:
undetectable = True
if is_using_uc(undetectable, browser_name) and mobile_emulator:
- mobile_emulator = False
- user_agent = None
+ if not user_agent:
+ user_agent = constants.Mobile.AGENT
if page_load_strategy and page_load_strategy.lower() == "none":
settings.PAGE_LOAD_STRATEGY = "none"
proxy_auth = False
@@ -2359,7 +2397,7 @@ def get_local_driver(
elif headless:
if "--headless" not in edge_options.arguments:
edge_options.add_argument("--headless")
- if mobile_emulator:
+ if mobile_emulator and not is_using_uc(undetectable, browser_name):
emulator_settings = {}
device_metrics = {}
if (
@@ -2371,9 +2409,9 @@ def get_local_driver(
device_metrics["height"] = device_height
device_metrics["pixelRatio"] = device_pixel_ratio
else:
- device_metrics["width"] = 360
- device_metrics["height"] = 640
- device_metrics["pixelRatio"] = 2
+ device_metrics["width"] = constants.Mobile.WIDTH
+ device_metrics["height"] = constants.Mobile.HEIGHT
+ device_metrics["pixelRatio"] = constants.Mobile.RATIO
emulator_settings["deviceMetrics"] = device_metrics
if user_agent:
emulator_settings["userAgent"] = user_agent
@@ -3416,7 +3454,13 @@ def get_local_driver(
driver.default_get = driver.get # Save copy of original
if uc_activated:
driver.get = lambda url: uc_special_open_if_cf(
- driver, url, proxy_string
+ driver,
+ url,
+ proxy_string,
+ mobile_emulator,
+ device_width,
+ device_height,
+ device_pixel_ratio,
)
driver.uc_open = lambda url: uc_open(driver, url)
driver.uc_open_with_tab = (
@@ -3425,6 +3469,35 @@ def get_local_driver(
driver.uc_open_with_reconnect = (
lambda url: uc_open_with_reconnect(driver, url)
)
+ if mobile_emulator:
+ uc_metrics = {}
+ if (
+ type(device_width) is int
+ and type(device_height) is int
+ and type(device_pixel_ratio) is int
+ ):
+ uc_metrics["width"] = device_width
+ uc_metrics["height"] = device_height
+ uc_metrics["pixelRatio"] = device_pixel_ratio
+ else:
+ uc_metrics["width"] = constants.Mobile.WIDTH
+ uc_metrics["height"] = constants.Mobile.HEIGHT
+ uc_metrics["pixelRatio"] = constants.Mobile.RATIO
+ set_device_metrics_override = dict(
+ {
+ "width": uc_metrics["width"],
+ "height": uc_metrics["height"],
+ "deviceScaleFactor": uc_metrics["pixelRatio"],
+ "mobile": True
+ }
+ )
+ try:
+ driver.execute_cdp_cmd(
+ 'Emulation.setDeviceMetricsOverride',
+ set_device_metrics_override
+ )
+ except Exception:
+ pass
return extend_driver(driver)
else: # Running headless on Linux (and not using --uc)
try:
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index c73936e90f8..9fec0f2e3e9 100644
--- a/seleniumbase/fixtures/base_case.py
+++ b/seleniumbase/fixtures/base_case.py
@@ -3887,12 +3887,8 @@ def get_new_driver(
if d_p_r is None:
d_p_r = self.__device_pixel_ratio
if is_mobile and not user_agent:
- # Use the Pixel 4 user agent by default if not specified
- user_agent = (
- "Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) "
- "AppleWebKit/537.36 (KHTML, like Gecko) "
- "Chrome/89.0.4389.105 Mobile Safari/537.36"
- )
+ # Use a Pixel user agent by default if not specified
+ user_agent = constants.Mobile.AGENT
valid_browsers = constants.ValidBrowsers.valid_browsers
if browser_name not in valid_browsers:
raise Exception(
@@ -14351,15 +14347,9 @@ def setUp(self, masterqa_mode=False):
self.mobile_emulator = True
except Exception:
raise Exception(exception_string)
- if self.mobile_emulator:
- if not self.user_agent:
- # Use the Pixel 4 user agent by default if not specified
- self.user_agent = (
- "Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) "
- "AppleWebKit/537.36 (KHTML, like Gecko) "
- "Chrome/89.0.4389.105 Mobile Safari/537.36"
- )
-
+ if self.mobile_emulator and not self.user_agent:
+ # Use a Pixel user agent by default if not specified
+ self.user_agent = constants.Mobile.AGENT
if self.browser in ["firefox", "ie", "safari"]:
# The Recorder Mode browser extension is only for Chrome/Edge.
if self.recorder_mode:
@@ -15314,11 +15304,11 @@ def __activate_sb_mgr_post_mortem_debug_mode(self):
# Post Mortem Debug Mode ("python --pdb")
def __activate_debug_mode_in_teardown(self):
- """Activate Debug Mode in tearDown() when using "--final-debug"."""
+ """Activate Final Trace / Debug Mode"""
import pdb
pdb.set_trace()
- # Final Debug Mode ("--final-debug")
+ # Final Trace ("--ftrace")
def has_exception(self):
"""(This method should ONLY be used in custom tearDown() methods.)
@@ -15813,6 +15803,11 @@ def tearDown(self):
and sb_config._do_sb_post_mortem
):
self.__activate_sb_mgr_post_mortem_debug_mode()
+ elif (
+ hasattr(sb_config, "_do_sb_final_trace")
+ and sb_config._do_sb_final_trace
+ ):
+ self.__activate_debug_mode_in_teardown()
# (Pynose / Behave / Pure Python) Close all open browser windows
self.__quit_all_drivers()
# Resume tearDown() for all test runners, (Pytest / Pynose / Behave)
diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py
index 1122042277a..28effe814a4 100644
--- a/seleniumbase/fixtures/constants.py
+++ b/seleniumbase/fixtures/constants.py
@@ -346,6 +346,19 @@ class SeleniumWire:
VER = "5.1.0"
+class Mobile:
+ # Default values for mobile settings
+ WIDTH = 390
+ HEIGHT = 715
+ RATIO = 3
+ AGENT = (
+ "Mozilla/5.0 (Linux; Android 13; Pixel 7 XL "
+ "Build/SP2A.220505.006.A1; wv) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
+ "Chrome/110.0.5028.105 Mobile Safari/537.36"
+ )
+
+
class ValidBrowsers:
valid_browsers = [
"chrome",
diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py
index 3cf7fbd7e9f..30a5f38b5be 100644
--- a/seleniumbase/fixtures/js_utils.py
+++ b/seleniumbase/fixtures/js_utils.py
@@ -910,7 +910,7 @@ def post_messenger_success_message(driver, message, msg_dur=None):
theme = "future"
location = "bottom_right"
if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator:
- location = "top_center"
+ location = "top_right"
set_messenger_theme(driver, theme=theme, location=location)
post_message(driver, message, msg_dur, style="success")
time.sleep(msg_dur + 0.07)
diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py
index 367ccd468df..b529d1c0c40 100644
--- a/seleniumbase/plugins/driver_manager.py
+++ b/seleniumbase/plugins/driver_manager.py
@@ -341,9 +341,6 @@ def Driver(
uc_cdp_events = True
else:
uc_cdp_events = False
- if undetectable and is_mobile:
- is_mobile = False
- user_agent = None
if use_auto_ext is None:
if "--use-auto-ext" in sys_argv:
use_auto_ext = True
diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py
index cfb752f8a08..e56dbbebca4 100644
--- a/seleniumbase/plugins/pytest_plugin.py
+++ b/seleniumbase/plugins/pytest_plugin.py
@@ -554,7 +554,7 @@ def pytest_addoption(parser):
help="""Designates the three device metrics of the mobile
emulator: CSS Width, CSS Height, and Pixel-Ratio.
Format: A comma-separated string with the 3 values.
- Example: "375,734,3"
+ Examples: "375,734,5" or "411,731,3" or "390,715,3"
Default: None. (Will use default values if None)""",
)
parser.addoption(
@@ -1399,10 +1399,6 @@ def pytest_addoption(parser):
"\n If you need both, override get_new_driver() from BaseCase:"
"\n https://seleniumbase.io/help_docs/syntax_formats/#sb_sf_09\n"
)
- if undetectable and "--mobile" in sys_argv:
- raise Exception(
- "\n SeleniumBase doesn't support mixing --uc with --mobile\n"
- )
def pytest_configure(config):
diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py
index 979b9be9214..1ee410a3797 100644
--- a/seleniumbase/plugins/sb_manager.py
+++ b/seleniumbase/plugins/sb_manager.py
@@ -308,6 +308,8 @@ def SB(
is_mobile = True
else:
is_mobile = False
+ if is_mobile:
+ sb_config.mobile_emulator = True
proxy_string = proxy
user_agent = agent
recorder_mode = False
@@ -370,7 +372,6 @@ def SB(
sb_config.proxy_driver = True
if variables and type(variables) is str and len(variables) > 0:
import ast
-
bad_input = False
if (
not variables.startswith("{")
@@ -458,9 +459,6 @@ def SB(
uc_cdp_events = True
else:
uc_cdp_events = False
- if undetectable and is_mobile:
- is_mobile = False
- user_agent = None
if use_auto_ext is None:
if "--use-auto-ext" in sys_argv:
use_auto_ext = True
@@ -875,6 +873,13 @@ def SB(
finally:
if sb._has_failure and "--pdb" in sys_argv:
sb_config._do_sb_post_mortem = True
+ elif (
+ "--final-debug" in sys_argv
+ or "--final-trace" in sys_argv
+ or "--fdebug" in sys_argv
+ or "--ftrace" in sys_argv
+ ):
+ sb_config._do_sb_final_trace = True
try:
sb.tearDown()
except Exception as t_e:
diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py
index e1eceacd4ff..ac99818453c 100644
--- a/seleniumbase/plugins/selenium_plugin.py
+++ b/seleniumbase/plugins/selenium_plugin.py
@@ -296,7 +296,7 @@ def options(self, parser, env):
help="""Designates the three device metrics of the mobile
emulator: CSS Width, CSS Height, and Pixel-Ratio.
Format: A comma-separated string with the 3 values.
- Example: "375,734,3"
+ Examples: "375,734,5" or "411,731,3" or "390,715,3"
Default: None. (Will use default values if None)""",
)
parser.addoption(
@@ -1163,16 +1163,6 @@ def beforeTest(self, test):
)
self.options.use_wire = False
test.test.use_wire = False
- if self.options.mobile_emulator and self.options.undetectable:
- print(
- "\n"
- "SeleniumBase doesn't support mixing --uc with --mobile.\n"
- "(Only UC Mode without Mobile will be used for this run)\n"
- )
- self.options.mobile_emulator = False
- test.test.mobile_emulator = False
- self.options.user_agent = None
- test.test.user_agent = None
# Recorder Mode can still optimize scripts in --headless2 mode.
if self.options.recorder_mode and self.options.headless:
self.options.headless = False
@@ -1205,6 +1195,7 @@ def beforeTest(self, test):
sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT
sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT
sb_config._context_of_runner = False # Context Manager Compatibility
+ sb_config.mobile_emulator = self.options.mobile_emulator
sb_config.proxy_driver = self.options.proxy_driver
sb_config.multi_proxy = self.options.multi_proxy
# The driver will be received later
diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py
index 557ca738617..7ac4de3194b 100644
--- a/seleniumbase/undetected/__init__.py
+++ b/seleniumbase/undetected/__init__.py
@@ -2,6 +2,7 @@
import logging
import os
import re
+import requests
import subprocess
import sys
import time
@@ -138,8 +139,6 @@ def __init__(
options._session = self
debug_host = "127.0.0.1"
debug_port = 9222
- import requests
-
special_port_free = False # If the port isn't free, don't use 9222
try:
res = requests.get("http://127.0.0.1:9222", timeout=1)
@@ -151,7 +150,6 @@ def __init__(
sys_argv = sys.argv
arg_join = " ".join(sys_argv)
from seleniumbase import config as sb_config
-
if (
(("-n" in sys.argv) or (" -n=" in arg_join) or ("-c" in sys.argv))
or (hasattr(sb_config, "multi_proxy") and sb_config.multi_proxy)
@@ -197,7 +195,6 @@ def __init__(
keep_user_data_dir = True
else:
import tempfile
-
user_data_dir = os.path.normpath(tempfile.mkdtemp())
keep_user_data_dir = False
arg = "--user-data-dir=%s" % user_data_dir
@@ -206,7 +203,6 @@ def __init__(
if not language:
try:
import locale
-
language = locale.getlocale()[0].replace("_", "-")
except Exception:
pass
@@ -241,7 +237,6 @@ def __init__(
options.handle_prefs(user_data_dir)
try:
import json
-
with open(
os.path.join(
os.path.abspath(user_data_dir),
@@ -286,19 +281,22 @@ def __init__(
)
self.browser_pid = browser.pid
service_ = None
+ log_output = subprocess.PIPE
+ if sys.version_info < (3, 8):
+ log_output = os.devnull
if patch_driver:
service_ = selenium.webdriver.chrome.service.Service(
executable_path=self.patcher.executable_path,
service_args=["--disable-build-check"],
port=port,
- log_output=os.devnull,
+ log_output=log_output,
)
else:
service_ = selenium.webdriver.chrome.service.Service(
executable_path=driver_executable_path,
service_args=["--disable-build-check"],
port=port,
- log_output=os.devnull,
+ log_output=log_output,
)
if hasattr(service_, "creationflags"):
setattr(service_, "creationflags", creationflags)
@@ -321,7 +319,6 @@ def __getattribute__(self, item):
return super().__getattribute__(item)
else:
import inspect
-
original = super().__getattribute__(item)
if inspect.ismethod(original) and not inspect.isclass(original):
def newfunc(*args, **kwargs):
@@ -455,7 +452,6 @@ def quit(self):
and not self.keep_user_data_dir
):
import shutil
-
for _ in range(5):
try:
shutil.rmtree(self.user_data_dir, ignore_errors=False)
@@ -506,7 +502,6 @@ def __hash__(self):
def find_chrome_executable():
from seleniumbase.core import detect_b_ver
-
binary_location = detect_b_ver.get_binary_location("google-chrome", True)
if os.path.exists(binary_location) and os.access(binary_location, os.X_OK):
return os.path.normpath(binary_location)
diff --git a/setup.py b/setup.py
index 0dd5669a8bc..0a1cffcd797 100755
--- a/setup.py
+++ b/setup.py
@@ -158,7 +158,7 @@
'trio-websocket==0.11.1',
'wsproto==1.2.0',
'selenium==4.11.2;python_version<"3.8"',
- 'selenium==4.13.0;python_version>="3.8"',
+ 'selenium==4.14.0;python_version>="3.8"',
'cssselect==1.2.0',
"sortedcontainers==2.4.0",
'fasteners==0.19',