diff --git a/README.md b/README.md index 9b441ca6..e7850ff6 100755 --- a/README.md +++ b/README.md @@ -662,6 +662,7 @@ pytest test_coffee_cart.py --trace --firefox-pref=SET # (Set a Firefox preference:value set, comma-separated.) --extension-zip=ZIP # (Load a Chrome Extension .zip|.crx, comma-separated.) --extension-dir=DIR # (Load a Chrome Extension directory, comma-separated.) +--disable-features="F1,F2" # (Disable features, comma-separated, no spaces.) --binary-location=PATH # (Set path of the Chromium browser binary to use.) --driver-version=VER # (Set the chromedriver or uc_driver version to use.) --sjw # (Skip JS Waits for readyState to be "complete" or Angular to load.) diff --git a/examples/raw_parameter_script.py b/examples/raw_parameter_script.py index d1436fa5..89a5fcd3 100644 --- a/examples/raw_parameter_script.py +++ b/examples/raw_parameter_script.py @@ -81,6 +81,7 @@ if pure_python: sb.window_size = None sb.maximize_option = False sb.visual_baseline = False + sb.disable_features = None sb._disable_beforeunload = False sb.save_screenshot_after_test = False sb.no_screenshot_after_test = False diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index 2a8ed614..6f4af19c 100644 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -138,6 +138,7 @@ pytest my_first_test.py --settings-file=custom_settings.py --firefox-pref=SET # (Set a Firefox preference:value set, comma-separated.) --extension-zip=ZIP # (Load a Chrome Extension .zip|.crx, comma-separated.) --extension-dir=DIR # (Load a Chrome Extension directory, comma-separated.) +--disable-features="F1,F2" # (Disable features, comma-separated, no spaces.) --binary-location=PATH # (Set path of the Chromium browser binary to use.) --driver-version=VER # (Set the chromedriver or uc_driver version to use.) --sjw # (Skip JS Waits for readyState to be "complete" or Angular to load.) diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index 9dc2a474..c3208df0 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -226,6 +226,7 @@ def get_configured_sb(context): sb.chromium_arg = None sb.firefox_arg = None sb.firefox_pref = None + sb.disable_features = None sb.proxy_string = None sb.proxy_bypass_list = None sb.proxy_pac_url = None @@ -748,6 +749,13 @@ def get_configured_sb(context): firefox_pref = sb.firefox_pref # revert to default sb.firefox_pref = firefox_pref continue + # Handle: -D disable-features="F1,F2" / disable_features="F1,F2" + if low_key in ["disable-features", "disable_features"]: + disable_features = userdata[key] + if disable_features == "true": + disable_features = sb.disable_features # revert to default + sb.disable_features = disable_features + continue # Handle: -D proxy=SERVER:PORT / proxy=USERNAME:PASSWORD@SERVER:PORT if low_key in ["proxy", "proxy-server", "proxy-string"]: proxy_string = userdata[key] diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 5ae567b3..01ef9cb3 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -763,6 +763,7 @@ def _set_chrome_options( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -1044,6 +1045,7 @@ def _set_chrome_options( binary_loc = detect_b_ver.get_binary_location(br_app, True) if os.path.exists(binary_loc): binary_location = binary_loc + extra_disabled_features = [] if chromium_arg: # Can be a comma-separated list of Chromium args chromium_arg_list = chromium_arg.split(",") @@ -1069,8 +1071,13 @@ def _set_chrome_options( ) if os.path.exists(binary_loc): binary_location = binary_loc + elif "disable-features=" in chromium_arg_item: + d_f = chromium_arg_item.split("disable-features=")[-1] + extra_disabled_features.append(d_f) elif len(chromium_arg_item) >= 3: chrome_options.add_argument(chromium_arg_item) + if disable_features: + extra_disabled_features.extend(disable_features.split(",")) if devtools and not headless: chrome_options.add_argument("--auto-open-devtools-for-tabs") if user_agent: @@ -1089,19 +1096,36 @@ def _set_chrome_options( if headless or headless2 or is_using_uc(undetectable, browser_name): chrome_options.add_argument("--disable-renderer-backgrounding") chrome_options.add_argument("--disable-backgrounding-occluded-windows") + chrome_options.add_argument("--disable-client-side-phishing-detection") + chrome_options.add_argument("--disable-oopr-debug-crash-dump") + chrome_options.add_argument("--disable-top-sites") chrome_options.add_argument("--ash-no-nudges") + chrome_options.add_argument("--no-crash-upload") chrome_options.add_argument("--deny-permission-prompts") + included_disabled_features = [] if user_data_dir: - chrome_options.add_argument( - "--disable-features=OptimizationHintsFetching,Translate," - "OptimizationTargetPrediction,PrivacySandboxSettings4," - "DownloadBubble,DownloadBubbleV2" - ) + included_disabled_features.append("OptimizationHintsFetching") + included_disabled_features.append("Translate") + included_disabled_features.append("OptimizationTargetPrediction") + included_disabled_features.append("PrivacySandboxSettings4") + included_disabled_features.append("DownloadBubble") + included_disabled_features.append("DownloadBubbleV2") + for item in extra_disabled_features: + if item not in included_disabled_features: + included_disabled_features.append(item) + d_f_string = ",".join(included_disabled_features) + chrome_options.add_argument("--disable-features=%s" % d_f_string) else: - chrome_options.add_argument( - "--disable-features=OptimizationHintsFetching,Translate," - "OptimizationTargetPrediction,DownloadBubble,DownloadBubbleV2" - ) + included_disabled_features.append("OptimizationHintsFetching") + included_disabled_features.append("Translate") + included_disabled_features.append("OptimizationTargetPrediction") + included_disabled_features.append("DownloadBubble") + included_disabled_features.append("DownloadBubbleV2") + for item in extra_disabled_features: + if item not in included_disabled_features: + included_disabled_features.append(item) + d_f_string = ",".join(included_disabled_features) + chrome_options.add_argument("--disable-features=%s" % d_f_string) if ( is_using_uc(undetectable, browser_name) and ( @@ -1338,6 +1362,7 @@ def get_driver( user_data_dir=None, extension_zip=None, extension_dir=None, + disable_features=None, binary_location=None, driver_version=None, page_load_strategy=None, @@ -1550,6 +1575,7 @@ def get_driver( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -1605,6 +1631,7 @@ def get_driver( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -1664,6 +1691,7 @@ def get_remote_driver( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -1784,6 +1812,7 @@ def get_remote_driver( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -1955,6 +1984,7 @@ def get_remote_driver( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -2075,6 +2105,7 @@ def get_local_driver( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -2570,18 +2601,6 @@ def get_local_driver( edge_options.add_argument( "--disable-autofill-keyboard-accessory-view[8]" ) - edge_options.add_argument("--ash-no-nudges") - edge_options.add_argument("--deny-permission-prompts") - if user_data_dir: - edge_options.add_argument( - "--disable-features=OptimizationHintsFetching,Translate," - "OptimizationTargetPrediction,PrivacySandboxSettings4" - ) - else: - edge_options.add_argument( - "--disable-features=OptimizationHintsFetching,Translate," - "OptimizationTargetPrediction" - ) edge_options.add_argument("--disable-browser-side-navigation") edge_options.add_argument("--disable-translate") if not enable_ws: @@ -2596,6 +2615,12 @@ def get_local_driver( if headless or headless2 or is_using_uc(undetectable, browser_name): edge_options.add_argument("--disable-renderer-backgrounding") edge_options.add_argument("--disable-backgrounding-occluded-windows") + edge_options.add_argument("--disable-client-side-phishing-detection") + edge_options.add_argument("--disable-oopr-debug-crash-dump") + edge_options.add_argument("--disable-top-sites") + edge_options.add_argument("--ash-no-nudges") + edge_options.add_argument("--no-crash-upload") + edge_options.add_argument("--deny-permission-prompts") if ( page_load_strategy and page_load_strategy.lower() in ["eager", "none"] @@ -2677,6 +2702,7 @@ def get_local_driver( edge_options.add_argument("--disable-gpu") if IS_LINUX: edge_options.add_argument("--disable-dev-shm-usage") + extra_disabled_features = [] set_binary = False if chromium_arg: # Can be a comma-separated list of Chromium args @@ -2690,8 +2716,33 @@ def get_local_driver( chromium_arg_item = "--" + chromium_arg_item if "set-binary" in chromium_arg_item: set_binary = True + elif "disable-features=" in chromium_arg_item: + d_f = chromium_arg_item.split("disable-features=")[-1] + extra_disabled_features.append(d_f) elif len(chromium_arg_item) >= 3: edge_options.add_argument(chromium_arg_item) + if disable_features: + extra_disabled_features.extend(disable_features.split(",")) + included_disabled_features = [] + if user_data_dir: + included_disabled_features.append("OptimizationHintsFetching") + included_disabled_features.append("Translate") + included_disabled_features.append("OptimizationTargetPrediction") + included_disabled_features.append("PrivacySandboxSettings4") + for item in extra_disabled_features: + if item not in included_disabled_features: + included_disabled_features.append(item) + d_f_string = ",".join(included_disabled_features) + edge_options.add_argument("--disable-features=%s" % d_f_string) + else: + included_disabled_features.append("OptimizationHintsFetching") + included_disabled_features.append("Translate") + included_disabled_features.append("OptimizationTargetPrediction") + for item in extra_disabled_features: + if item not in included_disabled_features: + included_disabled_features.append(item) + d_f_string = ",".join(included_disabled_features) + edge_options.add_argument("--disable-features=%s" % d_f_string) if (set_binary or IS_LINUX) and not binary_location: br_app = "edge" binary_loc = detect_b_ver.get_binary_location(br_app) @@ -2831,6 +2882,7 @@ def get_local_driver( user_data_dir, extension_zip, extension_dir, + disable_features, binary_location, driver_version, page_load_strategy, @@ -3348,6 +3400,7 @@ def get_local_driver( None, # user_data_dir None, # extension_zip None, # extension_dir + None, # disable_features binary_location, driver_version, page_load_strategy, @@ -3565,6 +3618,7 @@ def get_local_driver( None, # user_data_dir None, # extension_zip None, # extension_dir + None, # disable_features binary_location, driver_version, page_load_strategy, diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 408625d5..9c478696 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -3777,6 +3777,7 @@ class BaseCase(unittest.TestCase): user_data_dir=None, extension_zip=None, extension_dir=None, + disable_features=None, binary_location=None, driver_version=None, page_load_strategy=None, @@ -3835,6 +3836,7 @@ class BaseCase(unittest.TestCase): user_data_dir - Chrome's User Data Directory to use (Chrome-only) extension_zip - A Chrome Extension ZIP file to use (Chrome-only) extension_dir - A Chrome Extension folder to use (Chrome-only) + disable_features - the option to disable features on Chrome/Edge binary_location - the path of the browser binary to use (Chromium) driver_version - the chromedriver or uc_driver version to force page_load_strategy - the option to change pageLoadStrategy (Chrome) @@ -3960,6 +3962,8 @@ class BaseCase(unittest.TestCase): extension_zip = self.extension_zip if extension_dir is None: extension_dir = self.extension_dir + if disable_features is None: + disable_features = self.disable_features if binary_location is None: binary_location = self.binary_location if driver_version is None: @@ -4040,6 +4044,7 @@ class BaseCase(unittest.TestCase): user_data_dir=user_data_dir, extension_zip=extension_zip, extension_dir=extension_dir, + disable_features=disable_features, binary_location=binary_location, driver_version=driver_version, page_load_strategy=page_load_strategy, @@ -14331,6 +14336,7 @@ class BaseCase(unittest.TestCase): self.user_data_dir = sb_config.user_data_dir self.extension_zip = sb_config.extension_zip self.extension_dir = sb_config.extension_dir + self.disable_features = sb_config.disable_features self.binary_location = sb_config.binary_location self.driver_version = sb_config.driver_version self.page_load_strategy = sb_config.page_load_strategy @@ -14652,6 +14658,7 @@ class BaseCase(unittest.TestCase): user_data_dir=self.user_data_dir, extension_zip=self.extension_zip, extension_dir=self.extension_dir, + disable_features=self.disable_features, binary_location=self.binary_location, driver_version=self.driver_version, page_load_strategy=self.page_load_strategy, diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py index 122e463d..0ee03d02 100644 --- a/seleniumbase/plugins/driver_manager.py +++ b/seleniumbase/plugins/driver_manager.py @@ -108,6 +108,7 @@ def Driver( user_data_dir=None, # Set the Chrome user data directory to use. extension_zip=None, # Load a Chrome Extension .zip|.crx, comma-separated. extension_dir=None, # Load a Chrome Extension directory, comma-separated. + disable_features=None, # "F1,F2" (Disable Chrome features, ","-separated.) binary_location=None, # Set path of the Chromium browser binary to use. driver_version=None, # Set the chromedriver or uc_driver version to use. page_load_strategy=None, # Set Chrome PLS to "normal", "eager", or "none". @@ -264,6 +265,30 @@ def Driver( proxy_string = proxy_string[1:-1] elif proxy_string.startswith("'") and proxy_string.endswith("'"): proxy_string = proxy_string[1:-1] + c_a = chromium_arg + if c_a is None and "--chromium-arg" in arg_join: + if "--chromium-arg=" in arg_join: + c_a = arg_join.split("--chromium-arg=")[1].split(" ")[0] + elif "--chromium-arg " in arg_join: + c_a = arg_join.split("--chromium-arg ")[1].split(" ")[0] + if c_a: + if c_a.startswith('"') and c_a.endswith('"'): + c_a = c_a[1:-1] + elif c_a.startswith("'") and c_a.endswith("'"): + c_a = c_a[1:-1] + chromium_arg = c_a + d_f = disable_features + if d_f is None and "--disable-features" in arg_join: + if "--disable-features=" in arg_join: + d_f = arg_join.split("--disable-features=")[1].split(" ")[0] + elif "--disable-features " in arg_join: + d_f = arg_join.split("--disable-features ")[1].split(" ")[0] + if d_f: + if d_f.startswith('"') and d_f.endswith('"'): + d_f = d_f[1:-1] + elif c_a.startswith("'") and d_f.endswith("'"): + d_f = d_f[1:-1] + disable_features = d_f user_agent = agent recorder_mode = False if recorder_ext: @@ -505,6 +530,7 @@ def Driver( user_data_dir=user_data_dir, extension_zip=extension_zip, extension_dir=extension_dir, + disable_features=disable_features, binary_location=binary_location, driver_version=driver_version, page_load_strategy=page_load_strategy, diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index ca0686e8..e85b910d 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -55,6 +55,7 @@ def pytest_addoption(parser): --firefox-pref=SET (Set a Firefox preference:value set, comma-separated.) --extension-zip=ZIP (Load a Chrome Extension .zip|.crx, comma-separated.) --extension-dir=DIR (Load a Chrome Extension directory, comma-separated.) + --disable-features="F1,F2" (Disable features, comma-separated, no spaces.) --binary-location=PATH (Set path of the Chromium browser binary to use.) --driver-version=VER (Set the chromedriver or uc_driver version to use.) --sjw (Skip JS Waits for readyState to be "complete" or Angular to load.) @@ -624,6 +625,16 @@ def pytest_addoption(parser): (Can also be a comma-separated list of directories.) Default: None.""", ) + parser.addoption( + "--disable_features", + "--disable-features", + action="store", + dest="disable_features", + default=None, + help="""Disable Chromium features from Chrome/Edge browsers. + Format: A comma-separated list of Chromium features. + Default: None.""", + ) parser.addoption( "--binary_location", "--binary-location", @@ -1477,6 +1488,7 @@ def pytest_configure(config): sb_config.firefox_pref = config.getoption("firefox_pref") sb_config.extension_zip = config.getoption("extension_zip") sb_config.extension_dir = config.getoption("extension_dir") + sb_config.disable_features = config.getoption("disable_features") sb_config.binary_location = config.getoption("binary_location") sb_config.driver_version = config.getoption("driver_version") sb_config.page_load_strategy = config.getoption("page_load_strategy") diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index e0b13368..2fdcbd00 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -72,6 +72,7 @@ def SB( user_data_dir=None, # Set the Chrome user data directory to use. extension_zip=None, # Load a Chrome Extension .zip|.crx, comma-separated. extension_dir=None, # Load a Chrome Extension directory, comma-separated. + disable_features=None, # "F1,F2" (Disable Chrome features, ","-separated.) binary_location=None, # Set path of the Chromium browser binary to use. driver_version=None, # Set the chromedriver or uc_driver version to use. skip_js_waits=None, # Skip JS Waits (readyState=="complete" and Angular). @@ -325,6 +326,30 @@ def SB( proxy_string = proxy_string[1:-1] elif proxy_string.startswith("'") and proxy_string.endswith("'"): proxy_string = proxy_string[1:-1] + c_a = chromium_arg + if c_a is None and "--chromium-arg" in arg_join: + if "--chromium-arg=" in arg_join: + c_a = arg_join.split("--chromium-arg=")[1].split(" ")[0] + elif "--chromium-arg " in arg_join: + c_a = arg_join.split("--chromium-arg ")[1].split(" ")[0] + if c_a: + if c_a.startswith('"') and c_a.endswith('"'): + c_a = c_a[1:-1] + elif c_a.startswith("'") and c_a.endswith("'"): + c_a = c_a[1:-1] + chromium_arg = c_a + d_f = disable_features + if d_f is None and "--disable-features" in arg_join: + if "--disable-features=" in arg_join: + d_f = arg_join.split("--disable-features=")[1].split(" ")[0] + elif "--disable-features " in arg_join: + d_f = arg_join.split("--disable-features ")[1].split(" ")[0] + if d_f: + if d_f.startswith('"') and d_f.endswith('"'): + d_f = d_f[1:-1] + elif c_a.startswith("'") and d_f.endswith("'"): + d_f = d_f[1:-1] + disable_features = d_f user_agent = agent recorder_mode = False if recorder_ext: @@ -731,6 +756,7 @@ def SB( sb_config.chromium_arg = chromium_arg sb_config.firefox_arg = firefox_arg sb_config.firefox_pref = firefox_pref + sb_config.disable_features = disable_features sb_config.proxy_string = proxy_string sb_config.proxy_bypass_list = proxy_bypass_list sb_config.proxy_pac_url = proxy_pac_url @@ -833,6 +859,7 @@ def SB( sb.chromium_arg = sb_config.chromium_arg sb.firefox_arg = sb_config.firefox_arg sb.firefox_pref = sb_config.firefox_pref + sb.disable_features = sb_config.disable_features sb.proxy_string = sb_config.proxy_string sb.proxy_bypass_list = sb_config.proxy_bypass_list sb.proxy_pac_url = sb_config.proxy_pac_url diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index 3e9af8bc..4adc70f2 100644 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -36,6 +36,7 @@ class SeleniumBrowser(Plugin): --firefox-pref=SET (Set a Firefox preference:value set, comma-separated.) --extension-zip=ZIP (Load a Chrome Extension .zip|.crx, comma-separated.) --extension-dir=DIR (Load a Chrome Extension directory, comma-separated.) + --disable-features="F1,F2" (Disable features, comma-separated, no spaces.) --binary-location=PATH (Set path of the Chromium browser binary to use.) --driver-version=VER (Set the chromedriver or uc_driver version to use.) --sjw (Skip JS Waits for readyState to be "complete" or Angular to load.) @@ -365,6 +366,16 @@ class SeleniumBrowser(Plugin): (Can also be a comma-separated list of directories.) Default: None.""", ) + parser.addoption( + "--disable_features", + "--disable-features", + action="store", + dest="disable_features", + default=None, + help="""Disable Chromium features from Chrome/Edge browsers. + Format: A comma-separated list of Chromium features. + Default: None.""", + ) parser.addoption( "--binary_location", "--binary-location", @@ -1089,6 +1100,7 @@ class SeleniumBrowser(Plugin): test.test.user_data_dir = self.options.user_data_dir test.test.extension_zip = self.options.extension_zip test.test.extension_dir = self.options.extension_dir + test.test.disable_features = self.options.disable_features test.test.binary_location = self.options.binary_location test.test.driver_version = self.options.driver_version test.test.page_load_strategy = self.options.page_load_strategy