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

+
Coffee Cart (Test Page)
Demo Page (Test Page)
+
Simple App (Test Page)
MFA Login (Test Page)
TinyMCE (Test Page)
Error Page (Test Page)
-
Drag-&-Drop (Test Page)
+
Drag & Drop (Test Page)
Device Farm (Virtual)
HTML Playground Page
SeleniumBase in iframe
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',