From 8e97a8b277b245d738f6e2f90d74d8a8ff7302fb Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 21:33:10 -0400 Subject: [PATCH 1/9] Add option to set proxy settings via PAC URL --- examples/raw_parameter_script.py | 1 + seleniumbase/behave/behave_sb.py | 12 +++- seleniumbase/core/browser_launcher.py | 64 +++++++++++++++-- seleniumbase/core/proxy_helper.py | 92 ++++++++++++++++--------- seleniumbase/fixtures/base_case.py | 7 ++ seleniumbase/plugins/pytest_plugin.py | 22 +++++- seleniumbase/plugins/selenium_plugin.py | 18 +++++ 7 files changed, 176 insertions(+), 40 deletions(-) diff --git a/examples/raw_parameter_script.py b/examples/raw_parameter_script.py index 2d056e3d6ab..769e3614ca5 100755 --- a/examples/raw_parameter_script.py +++ b/examples/raw_parameter_script.py @@ -102,6 +102,7 @@ sb.firefox_pref = None sb.proxy_string = None sb.proxy_bypass_list = None + sb.proxy_pac_url = None sb.swiftshader = False sb.ad_block_on = False sb.highlights = None diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index 21f4096007a..a2e5ee2de60 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -31,6 +31,8 @@ -D proxy=SERVER:PORT (Connect to a proxy server:port for tests.) -D proxy=USERNAME:PASSWORD@SERVER:PORT (Use authenticated proxy server.) -D proxy-bypass-list=STRING (";"-separated hosts to bypass, Eg "*.foo.com") +-D proxy-pac-url=URL (Connect to a proxy server using a PAC_URL.pac file.) +-D proxy-pac-url=USERNAME:PASSWORD@URL (Authenticated proxy with PAC URL.) -D agent=STRING (Modify the web browser's User-Agent string.) -D mobile (Use the mobile device emulator while running tests.) -D metrics=STRING (Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio".) @@ -195,6 +197,7 @@ def get_configured_sb(context): sb.firefox_pref = None sb.proxy_string = None sb.proxy_bypass_list = None + sb.proxy_pac_url = None sb.swiftshader = False sb.ad_block_on = False sb.highlights = None @@ -619,7 +622,7 @@ def get_configured_sb(context): sb.firefox_pref = firefox_pref continue # Handle: -D proxy=SERVER:PORT / proxy=USERNAME:PASSWORD@SERVER:PORT - if low_key == "proxy": + if low_key in ["proxy", "proxy-server", "proxy-string"]: proxy_string = userdata[key] if proxy_string == "true": proxy_string = sb.proxy_string # revert to default @@ -632,6 +635,13 @@ def get_configured_sb(context): proxy_bypass_list = sb.proxy_bypass_list # revert to default sb.proxy_bypass_list = proxy_bypass_list continue + # Handle: -D proxy-pac-url=URL / proxy-pac-url=USERNAME:PASSWORD@URL + if low_key in ["proxy-pac-url", "proxy_pac_url", "pac-url", "pac_url"]: + proxy_pac_url = userdata[key] + if proxy_pac_url == "true": + proxy_pac_url = sb.proxy_pac_url # revert to default + sb.proxy_pac_url = proxy_pac_url + continue # Handle: -D swiftshader if low_key == "swiftshader": sb.swiftshader = True diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 8a80b2a1b11..8c8fa73f65a 100755 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -261,6 +261,7 @@ def _set_chrome_options( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, @@ -458,6 +459,12 @@ def _set_chrome_options( chrome_options.add_argument( "--proxy-bypass-list=%s" % proxy_bypass_list ) + elif proxy_pac_url: + if proxy_auth: + chrome_options = _add_chrome_proxy_extension( + chrome_options, None, proxy_user, proxy_pass + ) + chrome_options.add_argument("--proxy-pac-url=%s" % proxy_pac_url) if headless: if not proxy_auth and not browser_name == constants.Browser.OPERA: # Headless Chrome doesn't support extensions, which are @@ -509,6 +516,7 @@ def _set_firefox_options( locale_code, proxy_string, proxy_bypass_list, + proxy_pac_url, user_agent, disable_csp, firefox_arg, @@ -565,6 +573,9 @@ def _set_firefox_options( options.set_preference("network.proxy.ssl_port", int(proxy_port)) if proxy_bypass_list: options.set_preference("no_proxies_on", proxy_bypass_list) + elif proxy_pac_url: + options.set_preference("network.proxy.type", 2) + options.set_preference("network.proxy.autoconfig_url", proxy_pac_url) if user_agent: options.set_preference("general.useragent.override", user_agent) options.set_preference( @@ -716,6 +727,7 @@ def get_driver( port=4444, proxy_string=None, proxy_bypass_list=None, + proxy_pac_url=None, user_agent=None, cap_file=None, cap_string=None, @@ -775,6 +787,33 @@ def get_driver( proxy_string = validate_proxy_string(proxy_string) if proxy_string and proxy_user and proxy_pass: proxy_auth = True + elif proxy_pac_url: + username_and_password = None + if "@" in proxy_pac_url: + # Format => username:password@PAC_URL.pac + try: + username_and_password = proxy_pac_url.split("@")[0] + proxy_pac_url = proxy_pac_url.split("@")[1] + proxy_user = username_and_password.split(":")[0] + proxy_pass = username_and_password.split(":")[1] + except Exception: + raise Exception( + "The format for using a PAC URL with authentication " + 'is: "username:password@PAC_URL.pac". If using a PAC ' + 'URL without auth, the format is: "PAC_URL.pac".' + ) + if browser_name != constants.Browser.GOOGLE_CHROME and ( + browser_name != constants.Browser.EDGE + ): + raise Exception( + "Chrome or Edge is required when using a PAC URL " + "that has authentication! (If using a PAC URL " + "without auth, Chrome, Edge, or Firefox may be used.)" + ) + if not proxy_pac_url.lower().endswith(".pac"): + raise Exception('The proxy PAC URL must end with ".pac"!') + if proxy_pac_url and proxy_user and proxy_pass: + proxy_auth = True if browser_name == "chrome" and user_data_dir and len(user_data_dir) < 3: raise Exception( "Name length of Chrome's User Data Directory must be >= 3." @@ -792,6 +831,7 @@ def get_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, cap_file, cap_string, @@ -833,6 +873,7 @@ def get_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, @@ -874,6 +915,7 @@ def get_remote_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, cap_file, cap_string, @@ -969,6 +1011,7 @@ def get_remote_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, @@ -1050,6 +1093,7 @@ def get_remote_driver( locale_code, proxy_string, proxy_bypass_list, + proxy_pac_url, user_agent, disable_csp, firefox_arg, @@ -1175,6 +1219,7 @@ def get_remote_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, @@ -1356,6 +1401,7 @@ def get_local_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, @@ -1396,6 +1442,7 @@ def get_local_driver( locale_code, proxy_string, proxy_bypass_list, + proxy_pac_url, user_agent, disable_csp, firefox_arg, @@ -1704,10 +1751,16 @@ def get_local_driver( edge_options, proxy_string, proxy_user, proxy_pass ) edge_options.add_argument("--proxy-server=%s" % proxy_string) - if proxy_bypass_list: - edge_options.add_argument( - "--proxy-bypass-list=%s" % proxy_bypass_list - ) + if proxy_bypass_list: + edge_options.add_argument( + "--proxy-bypass-list=%s" % proxy_bypass_list + ) + elif proxy_pac_url: + if proxy_auth: + edge_options = _add_chrome_proxy_extension( + edge_options, None, proxy_user, proxy_pass + ) + edge_options.add_argument("--proxy-pac-url=%s" % proxy_pac_url) edge_options.add_argument("--test-type") edge_options.add_argument("--log-level=3") edge_options.add_argument("--no-first-run") @@ -1862,6 +1915,7 @@ def get_local_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, @@ -1917,6 +1971,7 @@ def get_local_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, @@ -2036,6 +2091,7 @@ def get_local_driver( proxy_user, proxy_pass, proxy_bypass_list, + proxy_pac_url, user_agent, recorder_ext, disable_csp, diff --git a/seleniumbase/core/proxy_helper.py b/seleniumbase/core/proxy_helper.py index 869f5c99778..38bd359f7dd 100755 --- a/seleniumbase/core/proxy_helper.py +++ b/seleniumbase/core/proxy_helper.py @@ -11,39 +11,65 @@ def create_proxy_zip(proxy_string, proxy_user, proxy_pass): """Implementation of https://stackoverflow.com/a/35293284 for https://stackoverflow.com/questions/12848327/ (Run Selenium on a proxy server that requires authentication.) - Solution involves creating & adding a Chrome extension on the fly. - * CHROME-ONLY for now! * + Solution involves creating & adding a Chromium extension on the fly. + CHROMIUM-ONLY! *** Only Chrome and Edge browsers are supported. *** """ - proxy_host = proxy_string.split(":")[0] - proxy_port = proxy_string.split(":")[1] - background_js = ( - """var config = {\n""" - """ mode: "fixed_servers",\n""" - """ rules: {\n""" - """ singleProxy: {\n""" - """ scheme: "http",\n""" - """ host: "%s",\n""" - """ port: parseInt("%s")\n""" - """ },\n""" - """ }\n""" - """ };\n""" - """chrome.proxy.settings.set(""" - """{value: config, scope: "regular"}, function() {""" - """});\n""" - """function callbackFn(details) {\n""" - """ return {\n""" - """ authCredentials: {\n""" - """ username: "%s",\n""" - """ password: "%s"\n""" - """ }\n""" - """ };\n""" - """}\n""" - """chrome.webRequest.onAuthRequired.addListener(\n""" - """ callbackFn,\n""" - """ {urls: [""]},\n""" - """ ['blocking']\n""" - """);""" % (proxy_host, proxy_port, proxy_user, proxy_pass) - ) + background_js = None + if proxy_string: + proxy_host = proxy_string.split(":")[0] + proxy_port = proxy_string.split(":")[1] + background_js = ( + """var config = {\n""" + """ mode: "fixed_servers",\n""" + """ rules: {\n""" + """ singleProxy: {\n""" + """ scheme: "http",\n""" + """ host: "%s",\n""" + """ port: parseInt("%s")\n""" + """ },\n""" + """ }\n""" + """ };\n""" + """chrome.proxy.settings.set(""" + """{value: config, scope: "regular"}, function() {""" + """});\n""" + """function callbackFn(details) {\n""" + """ return {\n""" + """ authCredentials: {\n""" + """ username: "%s",\n""" + """ password: "%s"\n""" + """ }\n""" + """ };\n""" + """}\n""" + """chrome.webRequest.onAuthRequired.addListener(\n""" + """ callbackFn,\n""" + """ {urls: [""]},\n""" + """ ['blocking']\n""" + """);""" % (proxy_host, proxy_port, proxy_user, proxy_pass) + ) + else: + background_js = ( + """var config = {\n""" + """ mode: "fixed_servers",\n""" + """ rules: {\n""" + """ }\n""" + """ };\n""" + """chrome.proxy.settings.set(""" + """{value: config, scope: "regular"}, function() {""" + """});\n""" + """function callbackFn(details) {\n""" + """ return {\n""" + """ authCredentials: {\n""" + """ username: "%s",\n""" + """ password: "%s"\n""" + """ }\n""" + """ };\n""" + """}\n""" + """chrome.webRequest.onAuthRequired.addListener(\n""" + """ callbackFn,\n""" + """ {urls: [""]},\n""" + """ ['blocking']\n""" + """);""" % (proxy_user, proxy_pass) + ) manifest_json = ( """{\n""" """"version": "1.0.0",\n""" @@ -79,7 +105,7 @@ def create_proxy_zip(proxy_string, proxy_user, proxy_pass): def remove_proxy_zip_if_present(): - """Remove Chrome extension zip file used for proxy server authentication. + """Remove Chromium extension zip file used for proxy server authentication. Used in the implementation of https://stackoverflow.com/a/35293284 for https://stackoverflow.com/questions/12848327/ """ diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 02406c1d91c..8f3e9c9ac9a 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -2937,6 +2937,7 @@ def get_new_driver( port=None, proxy=None, proxy_bypass_list=None, + proxy_pac_url=None, agent=None, switch_to=True, cap_file=None, @@ -2980,6 +2981,7 @@ def get_new_driver( port - if using a Selenium Grid, set the host port here proxy - if using a proxy server, specify the "host:port" combo here proxy_bypass_list - ";"-separated hosts to bypass (Eg. "*.foo.com") + proxy_pac_url - designates the proxy PAC URL to use (Chromium-only) switch_to - the option to switch to the new driver (default = True) cap_file - the file containing desired capabilities for the browser cap_string - the string with desired capabilities for the browser @@ -3060,6 +3062,8 @@ def get_new_driver( proxy_string = self.proxy_string if proxy_bypass_list is None: proxy_bypass_list = self.proxy_bypass_list + if proxy_pac_url is None: + proxy_pac_url = self.proxy_pac_url user_agent = agent if user_agent is None: user_agent = self.user_agent @@ -3137,6 +3141,7 @@ def get_new_driver( port=port, proxy_string=proxy_string, proxy_bypass_list=proxy_bypass_list, + proxy_pac_url=proxy_pac_url, user_agent=user_agent, cap_file=cap_file, cap_string=cap_string, @@ -12060,6 +12065,7 @@ def setUp(self, masterqa_mode=False): self.port = sb_config.port self.proxy_string = sb_config.proxy_string self.proxy_bypass_list = sb_config.proxy_bypass_list + self.proxy_pac_url = sb_config.proxy_pac_url self.user_agent = sb_config.user_agent self.mobile_emulator = sb_config.mobile_emulator self.device_metrics = sb_config.device_metrics @@ -12372,6 +12378,7 @@ def setUp(self, masterqa_mode=False): port=self.port, proxy=self.proxy_string, proxy_bypass_list=self.proxy_bypass_list, + proxy_pac_url=self.proxy_pac_url, agent=self.user_agent, switch_to=True, cap_file=self.cap_file, diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index d149803a318..35a04823526 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -43,6 +43,8 @@ def pytest_addoption(parser): --proxy=SERVER:PORT (Connect to a proxy server:port for tests.) --proxy=USERNAME:PASSWORD@SERVER:PORT (Use authenticated proxy server.) --proxy-bypass-list=STRING (";"-separated hosts to bypass, Eg "*.foo.com") + --proxy-pac-url=URL (Connect to a proxy server using a PAC_URL.pac file.) + --proxy-pac-url=USERNAME:PASSWORD@URL (Authenticated proxy with PAC URL.) --agent=STRING (Modify the web browser's User-Agent string.) --mobile (Use the mobile device emulator while running tests.) --metrics=STRING (Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio".) @@ -410,13 +412,15 @@ def pytest_addoption(parser): ) parser.addoption( "--proxy", + "--proxy-server", + "--proxy-string", action="store", dest="proxy_string", default=None, help="""Designates the proxy server:port to use. Format: servername:port. OR - username:password@servername:port OR - A dict key from proxy_list.PROXY_LIST + username:password@servername:port OR + A dict key from proxy_list.PROXY_LIST Default: None.""", ) parser.addoption( @@ -437,6 +441,19 @@ def pytest_addoption(parser): --proxy-bypass-list="127.0.0.1:8080" Default: None.""", ) + parser.addoption( + "--proxy-pac-url", + "--proxy_pac_url", + "--pac-url", + "--pac_url", + action="store", + dest="proxy_pac_url", + default=None, + help="""Designates the proxy PAC URL to use. + Format: A URL string OR + A username:password@URL string + Default: None.""", + ) parser.addoption( "--agent", "--user-agent", @@ -1176,6 +1193,7 @@ def pytest_configure(config): sb_config.protocol = "https" sb_config.proxy_string = config.getoption("proxy_string") sb_config.proxy_bypass_list = config.getoption("proxy_bypass_list") + sb_config.proxy_pac_url = config.getoption("proxy_pac_url") sb_config.cap_file = config.getoption("cap_file") sb_config.cap_string = config.getoption("cap_string") sb_config.settings_file = config.getoption("settings_file") diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index 12809ea9608..624e2be2d11 100755 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -26,6 +26,8 @@ class SeleniumBrowser(Plugin): --proxy=SERVER:PORT (Connect to a proxy server:port for tests.) --proxy=USERNAME:PASSWORD@SERVER:PORT (Use authenticated proxy server.) --proxy-bypass-list=STRING (";"-separated hosts to bypass, Eg "*.foo.com") + --proxy-pac-url=URL (Connect to a proxy server using a PAC_URL.pac file.) + --proxy-pac-url=USERNAME:PASSWORD@URL (Authenticated proxy with PAC URL.) --agent=STRING (Modify the web browser's User-Agent string.) --mobile (Use the mobile device emulator while running tests.) --metrics=STRING (Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio".) @@ -160,6 +162,8 @@ def options(self, parser, env): ) parser.add_option( "--proxy", + "--proxy-server", + "--proxy-string", action="store", dest="proxy_string", default=None, @@ -187,6 +191,19 @@ def options(self, parser, env): --proxy-bypass-list="127.0.0.1:8080" Default: None.""", ) + parser.add_option( + "--proxy-pac-url", + "--proxy_pac_url", + "--pac-url", + "--pac_url", + action="store", + dest="proxy_pac_url", + default=None, + help="""Designates the proxy PAC URL to use. + Format: A URL string OR + A username:password@URL string + Default: None.""", + ) parser.add_option( "--agent", "--user-agent", @@ -743,6 +760,7 @@ def beforeTest(self, test): test.test.firefox_pref = self.options.firefox_pref test.test.proxy_string = self.options.proxy_string test.test.proxy_bypass_list = self.options.proxy_bypass_list + test.test.proxy_pac_url = self.options.proxy_pac_url test.test.user_agent = self.options.user_agent test.test.mobile_emulator = self.options.mobile_emulator test.test.device_metrics = self.options.device_metrics From ddb2f4cf56f24fa756568e2bfa790b386085ae65 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 21:37:37 -0400 Subject: [PATCH 2/9] Fix issue where pytest.ini was ignored for some options --- seleniumbase/plugins/pytest_plugin.py | 38 +++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 35a04823526..da59b4cb731 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1281,15 +1281,49 @@ def pytest_configure(config): sb_config._html_report_name = None # The name of the pytest html report arg_join = " ".join(sys.argv) - if ("-n" in sys.argv) or (" -n=" in arg_join) or ("-c" in sys.argv): + if ( + "-n" in sys.argv + or " -n=" in arg_join + or "-c" in sys.argv + or ( + "addopts" in config.inicfg.keys() + and ( + "-n=" in config.inicfg["addopts"] + or "-n " in config.inicfg["addopts"] + ) + ) + ): sb_config._multithreaded = True - if "--html" in sys.argv or " --html=" in arg_join: + if ( + "--html" in sys.argv + or " --html=" in arg_join + or ( + "addopts" in config.inicfg.keys() + and ( + "--html=" in config.inicfg["addopts"] + or "--html " in config.inicfg["addopts"] + ) + ) + ): sb_config._using_html_report = True sb_config._html_report_name = config.getoption("htmlpath") if sb_config.dashboard: if sb_config._html_report_name == "dashboard.html": sb_config._dash_is_html_report = True + # Recorder Mode does not support multi-threaded / multi-process runs. + if sb_config.recorder_mode and sb_config._multithreaded: + # At this point, the user likely put a "-n NUM" in the pytest.ini file. + # Since raising an exception in pytest_configure raises INTERNALERROR, + # print a message here instead and cancel Recorder Mode. + print( + "\n Recorder Mode does NOT support multi-process mode (-n)!" + '\n (DO NOT combine "--recorder" with "-n NUM_PROCESSES"!)' + '\n (The Recorder WILL BE DISABLED during this run!)\n' + ) + sb_config.recorder_mode = False + sb_config.recorder_ext = False + if sb_config.xvfb and "linux" not in sys.platform: # The Xvfb virtual display server is for Linux OS Only! sb_config.xvfb = False From bbe7f3c1fc3d82496eaf09f2805aebcc1a09c9c5 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 21:41:06 -0400 Subject: [PATCH 3/9] Fix issue with Recorder Mode and quotes in URLs --- seleniumbase/behave/behave_helper.py | 18 ++++++++++++++++-- seleniumbase/fixtures/base_case.py | 19 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/seleniumbase/behave/behave_helper.py b/seleniumbase/behave/behave_helper.py index de05af16484..7f4a0d4e4c6 100644 --- a/seleniumbase/behave/behave_helper.py +++ b/seleniumbase/behave/behave_helper.py @@ -17,7 +17,14 @@ def generate_gherkin(srt_actions): action[2] = unquote(action[2], errors="strict") except Exception: pass - sb_actions.append('Open "%s"' % action[2]) + if '"' not in action[2]: + sb_actions.append('Open "%s"' % action[2]) + elif "'" not in action[2]: + sb_actions.append("Open '%s'" % action[2]) + else: + sb_actions.append( + 'Open "%s"' % action[2].replace('"', '\\"') + ) elif action[0] == "f_url": if "%" in action[2] and python3: try: @@ -26,7 +33,14 @@ def generate_gherkin(srt_actions): action[2] = unquote(action[2], errors="strict") except Exception: pass - sb_actions.append('Open if not "%s"' % action[2]) + if '"' not in action[2]: + sb_actions.append('Open if not "%s"' % action[2]) + elif "'" not in action[2]: + sb_actions.append("Open if not '%s'" % action[2]) + else: + sb_actions.append( + 'Open if not "%s"' % action[2].replace('"', '\\"') + ) elif action[0] == "click": if '"' not in action[1]: sb_actions.append('Click "%s"' % action[1]) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 8f3e9c9ac9a..8ab86d3daa9 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -3898,7 +3898,14 @@ def __process_recorded_actions(self): action[2] = unquote(action[2], errors="strict") except Exception: pass - sb_actions.append('self.open("%s")' % action[2]) + if '"' not in action[2]: + sb_actions.append('self.open("%s")' % action[2]) + elif "'" not in action[2]: + sb_actions.append("self.open('%s')" % action[2]) + else: + sb_actions.append( + 'self.open("%s")' % action[2].replace('"', '\\"') + ) elif action[0] == "f_url": if "%" in action[2] and python3: try: @@ -3907,7 +3914,15 @@ def __process_recorded_actions(self): action[2] = unquote(action[2], errors="strict") except Exception: pass - sb_actions.append('self.open_if_not_url("%s")' % action[2]) + if '"' not in action[2]: + sb_actions.append('self.open_if_not_url("%s")' % action[2]) + elif "'" not in action[2]: + sb_actions.append("self.open_if_not_url('%s')" % action[2]) + else: + sb_actions.append( + 'self.open_if_not_url("%s")' + % action[2].replace('"', '\\"') + ) elif action[0] == "click": method = "click" if '"' not in action[1]: From af76e03f77ad829f5008d2b754f9f7a3fe69c748 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 21:42:36 -0400 Subject: [PATCH 4/9] Refresh Python dependencies --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1c03e8f8daa..08898eba990 100755 --- a/requirements.txt +++ b/requirements.txt @@ -101,7 +101,7 @@ pygments==2.11.2;python_version>="3.5" and python_version<"3.6" pygments==2.12.0;python_version>="3.6" prompt-toolkit==1.0.18;python_version<"3.5" prompt-toolkit==2.0.10;python_version>="3.5" and python_version<"3.6" -prompt-toolkit==3.0.29;python_version>="3.6" +prompt-toolkit==3.0.30;python_version>="3.6" decorator==4.4.2;python_version<"3.5" decorator==5.1.1;python_version>="3.5" ipython==5.10.0;python_version<"3.5" diff --git a/setup.py b/setup.py index 2164ea03288..a3e5b752cfc 100755 --- a/setup.py +++ b/setup.py @@ -228,7 +228,7 @@ 'pygments==2.12.0;python_version>="3.6"', 'prompt-toolkit==1.0.18;python_version<"3.5"', 'prompt-toolkit==2.0.10;python_version>="3.5" and python_version<"3.6"', # noqa: E501 - 'prompt-toolkit==3.0.29;python_version>="3.6"', + 'prompt-toolkit==3.0.30;python_version>="3.6"', 'decorator==4.4.2;python_version<"3.5"', 'decorator==5.1.1;python_version>="3.5"', 'ipython==5.10.0;python_version<"3.5"', From 5ee5d69ef340ce63f788bdc0039443b6f9ab3da7 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 21:43:53 -0400 Subject: [PATCH 5/9] Update the documentation --- README.md | 9 ++++++--- help_docs/customizing_test_runs.md | 2 ++ help_docs/features_list.md | 10 ++++++---- mkdocs_build/requirements.txt | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 26c678fbb50..e30af9ddc1b 100755 --- a/README.md +++ b/README.md @@ -440,6 +440,8 @@ The code above will leave your browser window open in case there's a failure. (i --proxy=SERVER:PORT # (Connect to a proxy server:port for tests.) --proxy=USERNAME:PASSWORD@SERVER:PORT # (Use authenticated proxy server.) --proxy-bypass-list=STRING # (";"-separated hosts to bypass, Eg "*.foo.com") +--proxy-pac-url=URL # (Connect to a proxy server using a PAC_URL.pac file.) +--proxy-pac-url=USERNAME:PASSWORD@URL # (Authenticated proxy with PAC URL.) --agent=STRING # (Modify the web browser's User-Agent string.) --mobile # (Use the mobile device emulator while running tests.) --metrics=STRING # (Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio".) @@ -963,13 +965,13 @@ def get_mirror_universe_captain_picard_superbowl_ad(superbowl_year): self.switch_to_window(1) # This switches to the new tab (0 is the first one) ``` -🔵 ProTip™: iFrames follow the same principle as new windows - you need to specify the iFrame if you want to take action on something in there +🔵 ProTip™: iframes follow the same principle as new windows - you need to specify the iframe if you want to take action on something in there ```python self.switch_to_frame('ContentManagerTextBody_ifr') -# Now you can act inside the iFrame +# Now you can act inside the iframe # .... Do something cool (here) -self.switch_to_default_content() # Exit the iFrame when you're done +self.switch_to_default_content() # Exit the iframe when you're done ``` 🔵 Executing Custom jQuery Scripts: @@ -1109,3 +1111,4 @@ pytest --reruns=1 --reruns-delay=1

SeleniumBase

PyPI version

+

diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index 9412f601641..2a43eea355c 100755 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -122,6 +122,8 @@ pytest my_first_test.py --settings-file=custom_settings.py --proxy=SERVER:PORT # (Connect to a proxy server:port for tests.) --proxy=USERNAME:PASSWORD@SERVER:PORT # (Use authenticated proxy server.) --proxy-bypass-list=STRING # (";"-separated hosts to bypass, Eg "*.foo.com") +--proxy-pac-url=URL # (Connect to a proxy server using a PAC_URL.pac file.) +--proxy-pac-url=USERNAME:PASSWORD@URL # (Authenticated proxy with PAC URL.) --agent=STRING # (Modify the web browser's User-Agent string.) --mobile # (Use the mobile device emulator while running tests.) --metrics=STRING # (Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio".) diff --git a/help_docs/features_list.md b/help_docs/features_list.md index b53f178a485..dc3b64c68c1 100755 --- a/help_docs/features_list.md +++ b/help_docs/features_list.md @@ -9,18 +9,20 @@ * Supports multiple browsers, tabs, iframes, and proxies in the same test. * Automatic smart-waiting improves reliability and prevents flaky tests. * Supports [pytest](https://docs.pytest.org/en/latest/), [unittest](https://docs.python.org/3/library/unittest.html), [nose](http://nose.readthedocs.io/en/latest/), and [behave](https://behave.readthedocs.io/en/stable/index.html) for finding/running tests. -* All the code is open source. Dive in to see details about any feature. +* All the code is open source. Look inside to learn about any feature. * Powerful logging tools for [dashboards, reports, and screenshots](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md). * Can run tests in Headless Mode to hide the browser. (``--headless``) * Can run tests multithreaded from parallel browsers. (``-n NUM_THREADS``) * Can run tests from a shared browser session. (``--reuse-session``/``--rs``) * Can run tests using Chromium's mobile device emulator. (``--mobile``) * Can run tests through a proxy server. (``--proxy=IP_ADDRESS:PORT``) +* Can run tests with proxy settings via PAC URL. (``--proxy-pac-url=URL.pac``) * Can run tests through an authenticated proxy server. (``--proxy=USER:PASS@HOST:PORT``) +* Can run tests with proxy+auth via PAC URL. (``--proxy-pac-url=USER:PASS@URL.pac``) * Can run tests with a customized browser user agent. (``--agent=USER_AGENT_STRING``) -* Can set a Chromium User Data Directory/Profile to load. (``--user_data_dir=DIR``) -* Can load Chrome Extension ZIP files. (``--extension_zip=ZIP``) -* Can load Chrome Extension folders. (``--extension_dir=DIR``) +* Can set a Chromium User Data Directory/Profile to load. (``--user-data-dir=DIR``) +* Can load Chrome Extension ZIP files. (``--extension-zip=ZIP``) +* Can load Chrome Extension folders. (``--extension-dir=DIR``) * Powerful [console scripts](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/ReadMe.md). (Type **``seleniumbase``** or **``sbase``** to use.) * Has the ability to translate tests into [multiple spoken languages](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/translations). * Has a flexible [command-line interface](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md) for customizing test runs. diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 9355e38ce1b..5d756d713cd 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -19,7 +19,7 @@ ghp-import==2.1.0 readme-renderer==35.0 pymdown-extensions==9.5 importlib-metadata==4.12.0 -bleach==5.0.0 +bleach==5.0.1 jsmin==3.0.1 lunr==0.6.2 nltk==3.7 From f039e98c695bfab339fb272816b90855b7e13b14 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 21:44:29 -0400 Subject: [PATCH 6/9] Version 3.3.3 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index df89256f372..9897ab2ac5a 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "3.3.2" +__version__ = "3.3.3" From 3d70c7e2945cb20997152881270a84025a0828b2 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 22:50:08 -0400 Subject: [PATCH 7/9] Update the pytest plugin --- seleniumbase/plugins/pytest_plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index da59b4cb731..6036026626c 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1286,7 +1286,8 @@ def pytest_configure(config): or " -n=" in arg_join or "-c" in sys.argv or ( - "addopts" in config.inicfg.keys() + sys.version_info[0] >= 3 + and "addopts" in config.inicfg.keys() and ( "-n=" in config.inicfg["addopts"] or "-n " in config.inicfg["addopts"] @@ -1298,7 +1299,8 @@ def pytest_configure(config): "--html" in sys.argv or " --html=" in arg_join or ( - "addopts" in config.inicfg.keys() + sys.version_info[0] >= 3 + and "addopts" in config.inicfg.keys() and ( "--html=" in config.inicfg["addopts"] or "--html " in config.inicfg["addopts"] From bd92c52d695210de5e87824d317393cbac679c6c Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 22:51:11 -0400 Subject: [PATCH 8/9] Improve method reliability --- seleniumbase/fixtures/base_case.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 8ab86d3daa9..cafba86591d 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -2255,7 +2255,7 @@ def drag_and_drop( drop_selector, drop_by = self.__recalculate_selector( drop_selector, drop_by ) - drag_element = self.wait_for_element_visible( + drag_element = self.wait_for_element_clickable( drag_selector, by=drag_by, timeout=timeout ) self.__demo_mode_highlight_if_active(drag_selector, drag_by) From 8506e12b62c3d46d190142f9613c3ff6ed6b2f49 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 28 Jun 2022 22:52:32 -0400 Subject: [PATCH 9/9] Update a Python dependency --- requirements.txt | 3 +-- setup.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 08898eba990..210480c40a2 100755 --- a/requirements.txt +++ b/requirements.txt @@ -14,8 +14,7 @@ attrs>=21.4.0 PyYAML>=6.0;python_version>="3.6" traitlets>=4.3.3;python_version<"3.7" traitlets>=5.3.0;python_version>="3.7" -certifi>=2021.10.8;python_version<"3.5" -certifi>=2022.5.18;python_version>="3.5" and python_version<"3.6" +certifi>=2021.10.8;python_version<"3.6" certifi>=2022.6.15;python_version>="3.6" filelock>=3.2.1;python_version<"3.6" filelock>=3.4.1;python_version>="3.6" and python_version<"3.7" diff --git a/setup.py b/setup.py index a3e5b752cfc..120f1556760 100755 --- a/setup.py +++ b/setup.py @@ -141,8 +141,7 @@ 'PyYAML>=6.0;python_version>="3.6"', 'traitlets>=4.3.3;python_version<"3.7"', 'traitlets>=5.3.0;python_version>="3.7"', - 'certifi>=2021.10.8;python_version<"3.5"', - 'certifi>=2022.5.18;python_version>="3.5" and python_version<"3.6"', + 'certifi>=2021.10.8;python_version<"3.6"', 'certifi>=2022.6.15;python_version>="3.6"', 'filelock>=3.2.1;python_version<"3.6"', 'filelock>=3.4.1;python_version>="3.6" and python_version<"3.7"',