From 7c8744798ed97616fe246effe982b93b0cc1d15e Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:08:41 -0400 Subject: [PATCH 1/9] Fix command injection in console scripts --- seleniumbase/behave/behave_sb.py | 17 +++-- seleniumbase/console_scripts/run.py | 4 +- seleniumbase/console_scripts/sb_mkchart.py | 11 ++-- seleniumbase/console_scripts/sb_mkdir.py | 17 +++-- seleniumbase/console_scripts/sb_mkfile.py | 11 ++-- seleniumbase/console_scripts/sb_mkpres.py | 11 ++-- seleniumbase/console_scripts/sb_mkrec.py | 75 ++++++++++------------ seleniumbase/fixtures/base_case.py | 8 +-- 8 files changed, 72 insertions(+), 82 deletions(-) diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index c58f654ac33..62e39900b20 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -1134,14 +1134,19 @@ def _get_test_ids_(): def dashboard_pre_processing(): import subprocess - command_args = sys.argv[1:] - command_string = " ".join(command_args) - command_string = command_string.replace("--quiet", "") - command_string = command_string.replace("-q", "") + # Safely filter out --quiet and -q without shell string joining + command_args = [ + arg for arg in sys.argv[1:] if arg not in ("--quiet", "-q") + ] + + # Construct a explicit argument list without shell=True + cmd = ["behave", "-d"] + command_args + ["--show-source"] + proc = subprocess.Popen( - "behave -d %s --show-source" % command_string, + cmd, stdout=subprocess.PIPE, - shell=True, + stderr=subprocess.PIPE, + shell=False, ) (output, error) = proc.communicate() filename_count = 0 diff --git a/seleniumbase/console_scripts/run.py b/seleniumbase/console_scripts/run.py index 4eb97262a61..444833b709c 100644 --- a/seleniumbase/console_scripts/run.py +++ b/seleniumbase/console_scripts/run.py @@ -1205,7 +1205,7 @@ def main(): show_behave_options() elif command == "proxy" or command == "--proxy": import fasteners - import os + import subprocess import warnings with warnings.catch_warnings(): warnings.simplefilter("ignore", category=UserWarning) @@ -1219,7 +1219,7 @@ def main(): shared_utils.pip_install( "proxy.py", version=constants.ProxyPy.VER ) - os.system("proxy %s" % " ".join(sys.argv[2:])) + subprocess.run(["proxy"] + sys.argv[2:]) elif command == "help" or command == "--help" or command == "-h": if len(command_args) >= 1: if command_args[0] == "get": diff --git a/seleniumbase/console_scripts/sb_mkchart.py b/seleniumbase/console_scripts/sb_mkchart.py index 54d8fa0ccd4..8d81adf38ee 100644 --- a/seleniumbase/console_scripts/sb_mkchart.py +++ b/seleniumbase/console_scripts/sb_mkchart.py @@ -24,6 +24,7 @@ """ import colorama import os +import subprocess import sys @@ -256,12 +257,10 @@ def main(): file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() - if " " not in file_name: - os.system("sbase print %s -n" % file_name) - elif '"' not in file_name: - os.system('sbase print "%s" -n' % file_name) - else: - os.system("sbase print '%s' -n" % file_name) + + print_cmd = ["sbase", "print", file_name, "-n"] + subprocess.run(print_cmd) + success = ( "\n" + c1 + '* Chart Presentation: "' + file_name + '" was created! *' "" + cr + "\n" diff --git a/seleniumbase/console_scripts/sb_mkdir.py b/seleniumbase/console_scripts/sb_mkdir.py index 67fa4e49e43..7c819470a0f 100644 --- a/seleniumbase/console_scripts/sb_mkdir.py +++ b/seleniumbase/console_scripts/sb_mkdir.py @@ -21,6 +21,7 @@ """ import colorama import os +import subprocess import sys @@ -393,12 +394,14 @@ def main(): data.append(" ├── pytest.ini") data.append(" ├── requirements.txt") data.append(" └── setup.cfg") - file_path = "%s/%s" % (dir_name, "outline.rst") + file_path = os.path.join(dir_name, "outline.rst") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() - os.system("sbase print %s -n" % file_path) + + subprocess.run(["sbase", "print", file_path, "-n"]) os.remove(file_path) + success = ( "\n" + c1 + '* Directory "' + dir_name + '" was created ' "with config files! *" + cr + "\n" @@ -834,16 +837,12 @@ def main(): data.append(" ├── google_test.py") data.append(" ├── sb_swag_test.py") data.append(" └── swag_labs_test.py") - file_path = "%s/%s" % (dir_name, "outline.rst") + file_path = os.path.join(dir_name, "outline.rst") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() - if " " not in file_path: - os.system("sbase print %s -n" % file_path) - elif '"' not in file_path: - os.system('sbase print "%s" -n' % file_path) - else: - os.system("sbase print '%s' -n" % file_path) + + subprocess.run(["sbase", "print", file_path, "-n"]) os.remove(file_path) success = ( diff --git a/seleniumbase/console_scripts/sb_mkfile.py b/seleniumbase/console_scripts/sb_mkfile.py index 8e5bb873604..ffb37c5c29b 100644 --- a/seleniumbase/console_scripts/sb_mkfile.py +++ b/seleniumbase/console_scripts/sb_mkfile.py @@ -42,6 +42,7 @@ """ import colorama import os +import subprocess import sys @@ -415,12 +416,10 @@ def main(): file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() - if " " not in file_name: - os.system("sbase print %s -n" % file_name) - elif '"' not in file_name: - os.system('sbase print "%s" -n' % file_name) - else: - os.system("sbase print '%s' -n" % file_name) + + print_cmd = ["sbase", "print", file_name, "-n"] + subprocess.run(print_cmd) + success = ( "\n" + c1 + '* Test file: "' + file_name + '" was created! *' "" + cr + "\n" diff --git a/seleniumbase/console_scripts/sb_mkpres.py b/seleniumbase/console_scripts/sb_mkpres.py index f29978d7f93..d800f7bab43 100644 --- a/seleniumbase/console_scripts/sb_mkpres.py +++ b/seleniumbase/console_scripts/sb_mkpres.py @@ -24,6 +24,7 @@ """ import colorama import os +import subprocess import sys @@ -275,12 +276,10 @@ def main(): file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() - if " " not in file_name: - os.system("sbase print %s -n" % file_name) - elif '"' not in file_name: - os.system('sbase print "%s" -n' % file_name) - else: - os.system("sbase print '%s' -n" % file_name) + + print_cmd = ["sbase", "print", file_name, "-n"] + subprocess.run(print_cmd) + success = ( "\n" + c1 + '* Presentation: "' + file_name + '" was created! *' "" + cr + "\n" diff --git a/seleniumbase/console_scripts/sb_mkrec.py b/seleniumbase/console_scripts/sb_mkrec.py index 1b265ee00a6..fb240f53283 100644 --- a/seleniumbase/console_scripts/sb_mkrec.py +++ b/seleniumbase/console_scripts/sb_mkrec.py @@ -32,6 +32,7 @@ import colorama import shutil import os +import subprocess import sys @@ -265,10 +266,11 @@ def main(): "" + c1 + file_name + "" + cr + "\n" ) print(success) - run_cmd = None + + run_cmd = [sys_executable, "-m", "pytest", file_name, "--rec", "-q", "-s"] if ( - not start_page - or ( + start_page + and not ( use_uc and ( start_page.startswith("http:") @@ -278,70 +280,61 @@ def main(): and not esc_end ) ): - run_cmd = "%s -m pytest %s --rec -q -s" % (sys_executable, file_name) - else: - run_cmd = "%s -m pytest %s --rec -q -s --url=%s" % ( - sys_executable, file_name, start_page - ) - if '"' not in start_page: - run_cmd = '%s -m pytest %s --rec -q -s --url="%s"' % ( - sys_executable, file_name, start_page - ) - elif "'" not in start_page: - run_cmd = "%s -m pytest %s --rec -q -s --url='%s'" % ( - sys_executable, file_name, start_page - ) + run_cmd.append("--url=%s" % start_page) + if use_edge: - run_cmd += " --edge" + run_cmd.append("--edge") elif use_opera: - run_cmd += " --opera" + run_cmd.append("--opera") elif use_brave: - run_cmd += " --brave" + run_cmd.append("--brave") elif use_comet: - run_cmd += " --comet" + run_cmd.append("--comet") elif use_chromium: - run_cmd += " --use-chromium" + run_cmd.append("--use-chromium") if force_gui: - run_cmd += " --gui" + run_cmd.append("--gui") if use_uc: - run_cmd += " --uc" + run_cmd.append("--uc") if rec_behave: - run_cmd += " --rec-behave" + run_cmd.append("--rec-behave") if rec_sb_mgr: - run_cmd += " --rec-sb-mgr" + run_cmd.append("--rec-sb-mgr") if rec_sb_cdp: - run_cmd += " --rec-sb-cdp" - print(run_cmd) - os.system(run_cmd) + run_cmd.append("--rec-sb-cdp") + + print(" ".join(run_cmd)) + subprocess.run(run_cmd) + if os.path.exists(file_path): os.remove(file_path) recorded_filename = file_name[:-3] + "_rec.py" recordings_dir = os.path.join(dir_name, "recordings") recorded_file = os.path.join(recordings_dir, recorded_filename) - prefix = "%s -m " % sys_executable - if " " not in recorded_file: - os.system("%sseleniumbase print %s -n" % (prefix, recorded_file)) - elif '"' not in recorded_file: - os.system('%sseleniumbase print "%s" -n' % (prefix, recorded_file)) - else: - os.system("%sseleniumbase print '%s' -n" % (prefix, recorded_file)) + + print_cmd = [ + sys_executable, "-m", "seleniumbase", "print", recorded_file, "-n" + ] + subprocess.run(print_cmd) + shutil.copy(recorded_file, file_path) success = ( "\n" + c2 + "***" + cr + " RECORDING COPIED to: " "" + c1 + file_name + cr + "\n" ) print(success) + if rec_behave: recorded_filename = file_name[:-3] + "_rec.feature" recordings_dir = os.path.join(dir_name, "recordings") features_dir = os.path.join(recordings_dir, "features") recorded_file = os.path.join(features_dir, recorded_filename) - if " " not in recorded_file: - os.system("%sseleniumbase print %s -n" % (prefix, recorded_file)) - elif '"' not in recorded_file: - os.system('%sseleniumbase print "%s" -n' % (prefix, recorded_file)) - else: - os.system("%sseleniumbase print '%s' -n" % (prefix, recorded_file)) + + print_behave_cmd = [ + sys_executable, "-m", "seleniumbase", "print", recorded_file, "-n" + ] + subprocess.run(print_behave_cmd) + success = ( "\n" + c2 + "***" + cr + " BEHAVE RECORDING at: " "" + c1 + os.path.relpath(recorded_file) + cr + "\n" diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 84bbf203a1b..591a891b424 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -6304,6 +6304,7 @@ def __process_recorded_actions(self): self.__process_recorded_behave_actions(srt_actions, colorama) def __process_recorded_behave_actions(self, srt_actions, colorama): + import subprocess from seleniumbase.behave import behave_helper behave_actions = behave_helper.generate_gherkin(srt_actions) @@ -6384,12 +6385,7 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): if getattr(self, "rec_print", None): spc = "" print() - if " " not in file_path: - os.system("sbase print %s -n" % file_path) - elif '"' not in file_path: - os.system('sbase print "%s" -n' % file_path) - else: - os.system("sbase print '%s' -n" % file_path) + subprocess.run(["sbase", "print", file_path, "-n"]) stars = "*" * star_len c1 = "" c2 = "" From 7c9871c812bf410164b3a6ed14aec9b9cfe2abd3 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:10:56 -0400 Subject: [PATCH 2/9] Fix issue with clearing a field that has autocomplete --- seleniumbase/fixtures/base_case.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 591a891b424..cf0e9aa5c74 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -937,7 +937,8 @@ def update_text( if self.__needs_minimum_wait(): time.sleep(0.04) try: - element.clear() # May need https://stackoverflow.com/a/50691625 + with suppress(Exception): + element.clear() # View https://stackoverflow.com/a/50691625 backspaces = Keys.BACK_SPACE * 42 # Is the answer to everything element.send_keys(backspaces) # In case autocomplete keeps text except (Stale_Exception, ENI_Exception): From 969a36c19fb3a05eec50821ad6be63805a68e21a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:12:07 -0400 Subject: [PATCH 3/9] Fix typos in behave_sb.py --- seleniumbase/behave/behave_sb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index 62e39900b20..b6de353eb88 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -334,7 +334,7 @@ def get_configured_sb(context): continue # Handle: -D locale-code=CODE / locale_code=CODE / locale=CODE if low_key in ["locale-code", "locale_code", "locale"]: - sb.start_page = userdata[key] + sb.locale_code = userdata[key] continue # Handle: -D pdb / ipdb if low_key in ["pdb", "ipdb"]: @@ -881,7 +881,7 @@ def get_configured_sb(context): sb.cap_file = cap_file continue # Handle: -D cap-string=STRING / cap_string=STRING - if low_key == "cap_string": + if low_key in ["cap-string", "cap_string"]: cap_string = userdata[key] if cap_string == "true": cap_string = sb.cap_string # revert to default From 47034f13729e9f24564c18ba1952b1307798f080 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:12:43 -0400 Subject: [PATCH 4/9] Refactor console scripts --- seleniumbase/console_scripts/sb_mkchart.py | 6 ++- seleniumbase/console_scripts/sb_mkdir.py | 61 ++++++++++++---------- seleniumbase/console_scripts/sb_mkfile.py | 6 ++- seleniumbase/console_scripts/sb_mkpres.py | 6 ++- seleniumbase/console_scripts/sb_mkrec.py | 2 +- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/seleniumbase/console_scripts/sb_mkchart.py b/seleniumbase/console_scripts/sb_mkchart.py index 8d81adf38ee..f623f40e44c 100644 --- a/seleniumbase/console_scripts/sb_mkchart.py +++ b/seleniumbase/console_scripts/sb_mkchart.py @@ -74,15 +74,17 @@ def main(): command_args = sys.argv[2:] file_name = command_args[0] + if file_name: + file_name = str(file_name) if file_name == "-h" or file_name == "--help": invalid_run_command("help") elif not file_name.endswith(".py"): error_msg = 'File name must end with ".py"!' - elif "*" in file_name or len(str(file_name)) < 4: + elif "*" in file_name or len(file_name) < 4: error_msg = "Invalid file name!" elif file_name.startswith("-"): error_msg = 'File name cannot start with "-"!' - elif "/" in str(file_name) or "\\" in str(file_name): + elif any(sep in file_name for sep in ("/", "\\")): error_msg = "File must be created in the current directory!" elif os.path.exists(os.getcwd() + "/" + file_name): error_msg = 'File "%s" already exists in this directory!' % file_name diff --git a/seleniumbase/console_scripts/sb_mkdir.py b/seleniumbase/console_scripts/sb_mkdir.py index 7c819470a0f..925835fa376 100644 --- a/seleniumbase/console_scripts/sb_mkdir.py +++ b/seleniumbase/console_scripts/sb_mkdir.py @@ -69,15 +69,17 @@ def main(): command_args = sys.argv[2:] dir_name = command_args[0] + if dir_name: + dir_name = str(dir_name) if dir_name == "-h" or dir_name == "--help": invalid_run_command("help") - elif len(str(dir_name)) < 2: + elif len(dir_name) < 2: error_msg = "Directory name length must be at least 2 characters long!" - elif "/" in str(dir_name) or "\\" in str(dir_name): + elif any(sep in dir_name for sep in ("/", "\\")): error_msg = 'Directory name must not include slashes ("/", "\\")!' elif dir_name.startswith("-"): error_msg = 'Directory name cannot start with "-"!' - elif os.path.exists(os.getcwd() + "/" + dir_name): + elif os.path.exists(os.path.join(os.getcwd(), dir_name)): error_msg = ( 'Directory "%s" already exists in this directory!' % dir_name ) @@ -118,7 +120,7 @@ def main(): pass data.append(seleniumbase_req) data.append("") - file_path = "%s/%s" % (dir_name, "requirements.txt") + file_path = os.path.join(dir_name, "requirements.txt") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -156,7 +158,7 @@ def main(): data.append(" staging: custom marker") data.append(" production: custom marker") data.append("") - file_path = "%s/%s" % (dir_name, "pytest.ini") + file_path = os.path.join(dir_name, "pytest.ini") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -173,14 +175,14 @@ def main(): data.append("[behave]") data.append("show_skipped=false") data.append("show_timings=false") - file_path = "%s/%s" % (dir_name, "setup.cfg") + file_path = os.path.join(dir_name, "setup.cfg") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() data = [] data.append("") - file_path = "%s/%s" % (dir_name, "__init__.py") + file_path = os.path.join(dir_name, "__init__.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -322,15 +324,15 @@ def main(): data.append("temp") data.append("temp_*/") data.append("node_modules") - file_path = "%s/%s" % (dir_name, ".gitignore") + file_path = os.path.join(dir_name, ".gitignore") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() if gha: - dir_name_b = dir_name + "/" + ".github" + dir_name_b = os.path.join(dir_name, ".github") os.mkdir(dir_name_b) - dir_name_c = dir_name_b + "/" + "workflows" + dir_name_c = os.path.join(dir_name_b, "workflows") os.mkdir(dir_name_c) data = [] @@ -378,7 +380,7 @@ def main(): data.append(" path: ./latest_logs/") data.append(" if-no-files-found: ignore") data.append("") - file_path = "%s/%s" % (dir_name_c, "python-package.yml") + file_path = os.path.join(dir_name_c, "python-package.yml") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -440,7 +442,7 @@ def main(): data.append(' self.js_click("a#logout_sidebar_link")') data.append(' self.assert_element("div#login_button_container")') data.append("") - file_path = "%s/%s" % (dir_name, "my_first_test.py") + file_path = os.path.join(dir_name, "my_first_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -533,7 +535,7 @@ def main(): data.append(' self.type("input", "Have a Nice Day!")') data.append(' self.assert_text("SeleniumBase", "h2")') data.append("") - file_path = "%s/%s" % (dir_name, "test_demo_site.py") + file_path = os.path.join(dir_name, "test_demo_site.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -573,17 +575,17 @@ def main(): data.append(' self.assert_title_contains(title_text)') data.append(' self.save_screenshot_to_logs()') data.append("") - file_path = "%s/%s" % (dir_name, "parameterized_test.py") + file_path = os.path.join(dir_name, "parameterized_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() - dir_name_2 = dir_name + "/" + "boilerplates" + dir_name_2 = os.path.join(dir_name, "boilerplates") os.mkdir(dir_name_2) data = [] data.append("") - file_path = "%s/%s" % (dir_name_2, "__init__.py") + file_path = os.path.join(dir_name_2, "__init__.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -618,7 +620,7 @@ def main(): data.append(" # <<< Placeholder. Add your code here. >>>") data.append(" pass") data.append("") - file_path = "%s/%s" % (dir_name_2, "base_test_case.py") + file_path = os.path.join(dir_name_2, "base_test_case.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -627,7 +629,7 @@ def main(): data.append("class Page(object):") data.append(' html = "html"') data.append("") - file_path = "%s/%s" % (dir_name_2, "page_objects.py") + file_path = os.path.join(dir_name_2, "page_objects.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -643,7 +645,7 @@ def main(): data.append(" self.example_method()") data.append(" self.assert_element(Page.html)") data.append("") - file_path = "%s/%s" % (dir_name_2, "boilerplate_test.py") + file_path = os.path.join(dir_name_2, "boilerplate_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -667,7 +669,7 @@ def main(): data.append(' self.assert_text("Hello!", "p")') data.append(' DataPage().add_input_text(self, "Goodbye!")') data.append("") - file_path = "%s/%s" % (dir_name_2, "classic_obj_test.py") + file_path = os.path.join(dir_name_2, "classic_obj_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -687,17 +689,17 @@ def main(): data.append(' sb.assert_text("Hello!", "p")') data.append(' DataPage().add_input_text(sb, "Goodbye!")') data.append("") - file_path = "%s/%s" % (dir_name_2, "sb_fixture_test.py") + file_path = os.path.join(dir_name_2, "sb_fixture_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() - dir_name_3 = dir_name_2 + "/" + "samples" + dir_name_3 = os.path.join(dir_name_2, "samples") os.mkdir(dir_name_3) data = [] data.append("") - file_path = "%s/%s" % (dir_name_3, "__init__.py") + file_path = os.path.join(dir_name_3, "__init__.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -716,9 +718,10 @@ def main(): data.append(" self.get_new_driver(undetectable=True)") data.append(' self.goto("https://google.com/ncr")') data.append( - " sb.click_if_visible('button:contains(\"Accept all\")')" + " self.click_if_visible('button:contains(\"Accept all\")')" ) data.append(' self.assert_title_contains("Google")') + data.append(" self.sleep(0.05)") data.append(" self.save_screenshot_to_logs()") data.append(' self.type(HomePage.search_box, "GitHub")') data.append(" self.assert_element(HomePage.search_button)") @@ -729,7 +732,7 @@ def main(): ' self.assert_text("github.com", ResultsPage.search_results)' ) data.append("") - file_path = "%s/%s" % (dir_name_3, "google_test.py") + file_path = os.path.join(dir_name_3, "google_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -737,7 +740,7 @@ def main(): data = [] data.append("class HomePage(object):") data.append(" dialog_box = '[role=\"dialog\"] div'") - data.append(" search_box = '[title=\"Search\"]'") + data.append(" search_box = '[name=\"q\"]'") data.append(" search_button = 'input[value=\"Google Search\"]'") data.append( ' feeling_lucky_button = """input[value="I\'m Feeling Lucky"]"""' @@ -747,7 +750,7 @@ def main(): data.append("class ResultsPage(object):") data.append(' search_results = "div#center_col"') data.append("") - file_path = "%s/%s" % (dir_name_3, "google_objects.py") + file_path = os.path.join(dir_name_3, "google_objects.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -779,7 +782,7 @@ def main(): data.append(' self.js_click("a#logout_sidebar_link")') data.append(' self.assert_element("div#login_button_container")') data.append("") - file_path = "%s/%s" % (dir_name_3, "swag_labs_test.py") + file_path = os.path.join(dir_name_3, "swag_labs_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() @@ -806,7 +809,7 @@ def main(): data.append(' sb.js_click("a#logout_sidebar_link")') data.append(' sb.assert_element("div#login_button_container")') data.append("") - file_path = "%s/%s" % (dir_name_3, "sb_swag_test.py") + file_path = os.path.join(dir_name_3, "sb_swag_test.py") file = open(file_path, mode="w+", encoding="utf-8") file.writelines("\r\n".join(data)) file.close() diff --git a/seleniumbase/console_scripts/sb_mkfile.py b/seleniumbase/console_scripts/sb_mkfile.py index ffb37c5c29b..100a6130341 100644 --- a/seleniumbase/console_scripts/sb_mkfile.py +++ b/seleniumbase/console_scripts/sb_mkfile.py @@ -113,15 +113,17 @@ def main(): command_args = sys.argv[2:] file_name = command_args[0] + if file_name: + file_name = str(file_name) if file_name == "-h" or file_name == "--help": invalid_run_command("help") elif not file_name.endswith(".py"): error_msg = 'File name must end with ".py"!' - elif "*" in file_name or len(str(file_name)) < 4: + elif "*" in file_name or len(file_name) < 4: error_msg = "Invalid file name!" elif file_name.startswith("-"): error_msg = 'File name cannot start with "-"!' - elif "/" in str(file_name) or "\\" in str(file_name): + elif any(sep in file_name for sep in ("/", "\\")): error_msg = "File must be created in the current directory!" elif os.path.exists(os.getcwd() + "/" + file_name): error_msg = 'File "%s" already exists in this directory!' % file_name diff --git a/seleniumbase/console_scripts/sb_mkpres.py b/seleniumbase/console_scripts/sb_mkpres.py index d800f7bab43..0f982ce4adc 100644 --- a/seleniumbase/console_scripts/sb_mkpres.py +++ b/seleniumbase/console_scripts/sb_mkpres.py @@ -74,15 +74,17 @@ def main(): command_args = sys.argv[2:] file_name = command_args[0] + if file_name: + file_name = str(file_name) if file_name == "-h" or file_name == "--help": invalid_run_command("help") elif not file_name.endswith(".py"): error_msg = 'File name must end with ".py"!' - elif "*" in file_name or len(str(file_name)) < 4: + elif "*" in file_name or len(file_name) < 4: error_msg = "Invalid file name!" elif file_name.startswith("-"): error_msg = 'File name cannot start with "-"!' - elif "/" in str(file_name) or "\\" in str(file_name): + elif any(sep in file_name for sep in ("/", "\\")): error_msg = "File must be created in the current directory!" elif os.path.exists(os.getcwd() + "/" + file_name): error_msg = 'File "%s" already exists in this directory!' % file_name diff --git a/seleniumbase/console_scripts/sb_mkrec.py b/seleniumbase/console_scripts/sb_mkrec.py index fb240f53283..4564e2fd3c8 100644 --- a/seleniumbase/console_scripts/sb_mkrec.py +++ b/seleniumbase/console_scripts/sb_mkrec.py @@ -117,7 +117,7 @@ def main(): error_msg = "Invalid file name!" elif file_name.startswith("-"): error_msg = 'File name cannot start with "-"!' - elif "/" in str(file_name) or "\\" in str(file_name): + elif any(sep in str(file_name) for sep in ("/", "\\")): error_msg = "File must be created in the current directory!" elif file_name == "abc.py": error_msg = '"abc.py" is a reserved Python module! Use another name!' From f7da99c23471a0f0a1101b0afd9a0a82d38a99ba Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:13:38 -0400 Subject: [PATCH 5/9] Update the MCP server --- mcp_servers/server.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/mcp_servers/server.py b/mcp_servers/server.py index 44c142b46cf..fb61f2f323e 100644 --- a/mcp_servers/server.py +++ b/mcp_servers/server.py @@ -646,15 +646,15 @@ def get_attributes( attribute: str | None = None, timeout: float = 5, ) -> str | dict[str, Any] | None: - """Get a specific HTML attribute (or all attributes) from the - first-matching element. Examples of possible attributes include - href, src, value, class, id, name, type, aria-label, etc. + """Get a specific HTML attribute (or all attributes) from the selected + element. Examples of possible attributes include href, src, value, class, + id, name, type, aria-label, etc. Args: selector: CSS selector or SeleniumBase-supported XPath selector. - attribute: Specific HTML attribute to retrieve. When omitted, return - all HTML attributes of the first matching element as a dictionary. + attribute: Specific HTML attribute to retrieve. When omitted, returns + all HTML attributes of the matching element as a dictionary. timeout: Maximum seconds to wait for the target element. Default: 5. @@ -1807,17 +1807,17 @@ def manage_tabs( ) @handle_sb_errors def solve_captcha() -> str: - """Attempt a SeleniumBase CDP-based CAPTCHA interaction, such as clicking + """Perform a SeleniumBase CDP-based CAPTCHA interaction, such as clicking a CAPTCHA checkbox, or performing a drag/drop action on a slider CAPTCHA. Supported CAPTCHAs include: Cloudflare Turnstile, reCAPTCHA, hCaptcha, DataDome Slider, and FriendlyCaptcha. - This tool automatically detects the coordinates of CAPTCHA checkboxes - for determining the correct location to perform the click. If no CAPTCHA - is detected on the current page, then no click action is attempted. + This tool automatically detects the location of CAPTCHA checkboxes + for the click action. If none of the supported CAPTCHAs are detected + on the current page, then no click action is attempted. - The tool does not guarantee that the CAPTCHA was solved. Some CAPTCHA + The tool does not guarantee that the CAPTCHA gets solved. Some CAPTCHA controls are embedded inside shadow DOM or otherwise do not expose an easy success signal. A successful attempt may result in changes to page state or browser cookies. @@ -1826,17 +1826,19 @@ def solve_captcha() -> str: 1. Inspect the webpage with get_content when you need to determine whether CAPTCHA-related controls are present. 2. Call 'solve_captcha' to attempt the CAPTCHA interaction. - 3. Use 'get_page_info', 'get_content', 'check_if_condition', - or 'manage_cookies' to inspect resulting page/session state. + 3. Use 'get_page_info', 'get_content', 'check_if_condition', or + 'manage_cookies' to inspect the resulting page/session state + to determine whether the interaction appears to have succeeded. Returns: - A confirmation message of the CAPTCHA interaction. - The message is the same for both successful and failed attempts. + A message indicating that the CAPTCHA interaction was attempted. - Notes: - Clicking with the Chrome DevTools Protocol (CDP) is generally - stealthier than clicking with JavaScript because CDP actions - can avoid triggering `isTrusted: false`. + Tool selection: + - Need to interact with one of the supported CAPTCHAs -> + use solve_captcha. + - Need to hover an element in order to click another -> + use hover_action with action="hover_and_click". + - Need to click a specific visible element -> use click_element. """ sb = _get_sb() sb.solve_captcha() From 4bc64fb41dcd6ea6030887456fe97de72a3443a7 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:14:31 -0400 Subject: [PATCH 6/9] Refresh Python dependencies --- requirements.txt | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 685c8f3020a..0feb78f0fd6 100755 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ filelock>=3.32.7 fasteners>=0.20 mycdp>=1.4.0 pynose>=1.5.5 -platformdirs>=4.11.8 +platformdirs>=4.11.9 typing-extensions>=4.16.0 sbvirtualdisplay>=1.4.0 MarkupSafe>=3.0.3 @@ -25,7 +25,7 @@ pyyaml>=6.0.3 pygments>=2.20.1 pyreadline3>=3.5.4;platform_system=="Windows" tabcompleter>=1.4.1 -pdbp>=1.8.2 +pdbp>=1.8.3 idna>=3.19 charset-normalizer>=3.5.1,<4 urllib3>=2.8.0,<3 diff --git a/setup.py b/setup.py index 600d3a9339f..4fcb5e4eb97 100755 --- a/setup.py +++ b/setup.py @@ -177,7 +177,7 @@ 'fasteners>=0.20', 'mycdp>=1.4.0', 'pynose>=1.5.5', - 'platformdirs>=4.11.8', + 'platformdirs>=4.11.9', 'typing-extensions>=4.16.0', 'sbvirtualdisplay>=1.4.0', 'MarkupSafe>=3.0.3', @@ -191,7 +191,7 @@ 'pygments>=2.20.1', 'pyreadline3>=3.5.4;platform_system=="Windows"', 'tabcompleter>=1.4.1', - 'pdbp>=1.8.2', + 'pdbp>=1.8.3', 'idna>=3.19', 'charset-normalizer>=3.5.1,<4', 'urllib3>=2.8.0,<3', From a64c668254f20c3384c9f0ec1635de7e9eba45ce Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:15:00 -0400 Subject: [PATCH 7/9] Update MCP versioning --- mcp_servers/pyproject.toml | 2 +- server.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mcp_servers/pyproject.toml b/mcp_servers/pyproject.toml index 559be6156c9..8b994dda3a2 100644 --- a/mcp_servers/pyproject.toml +++ b/mcp_servers/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "seleniumbase-mcp" -version = "1.3.7dev0" +version = "1.3.8dev0" description = "MCP server exposing SeleniumBase CDP Mode as tools for MCP clients." readme = "README.md" requires-python = ">=3.10" diff --git a/server.json b/server.json index 8f57bcc3173..d48c3574ff4 100644 --- a/server.json +++ b/server.json @@ -3,7 +3,7 @@ "name": "io.github.seleniumbase/seleniumbase", "title": "SeleniumBase MCP", "description": "Stealthy browser automation, testing, and web-scraping via CDP Mode.", - "version": "4.54.7", + "version": "4.54.8", "repository": { "url": "https://github.com/seleniumbase/SeleniumBase", "source": "github" @@ -13,7 +13,7 @@ { "registryType": "pypi", "identifier": "seleniumbase", - "version": "4.54.7", + "version": "4.54.8", "transport": { "type": "stdio" }, From c1b492a650c8077ae6ef43915b3a455fab8f4e8d Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:25:57 -0400 Subject: [PATCH 8/9] Update examples --- examples/boilerplates/samples/google_objects.py | 2 +- examples/test_hack_search.py | 4 ++-- examples/tour_examples/ReadMe.md | 10 +++++----- examples/tour_examples/bootstrap_google_tour.py | 10 +++++----- examples/tour_examples/google_tour.py | 10 +++++----- examples/tour_examples/hopscotch_google_tour.py | 10 +++++----- examples/tour_examples/introjs_google_tour.py | 10 +++++----- examples/tour_examples/shepherd_google_tour.py | 10 +++++----- examples/wordle_test.py | 14 ++++++++++++-- 9 files changed, 45 insertions(+), 35 deletions(-) diff --git a/examples/boilerplates/samples/google_objects.py b/examples/boilerplates/samples/google_objects.py index 792d77a495f..385732ca183 100644 --- a/examples/boilerplates/samples/google_objects.py +++ b/examples/boilerplates/samples/google_objects.py @@ -3,7 +3,7 @@ class HomePage(object): dialog_box = '[role="dialog"] div' - search_box = '[title="Search"]' + search_box = '[name="q"]' search_button = 'input[value="Google Search"]' diff --git a/examples/test_hack_search.py b/examples/test_hack_search.py index 0c8b69036e0..13688d7fdaa 100644 --- a/examples/test_hack_search.py +++ b/examples/test_hack_search.py @@ -16,11 +16,11 @@ def test_hack_search(self): self.goto("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') self.hide_elements("iframe") - self.assert_element('[title="Search"]') + self.assert_element('[name="q"]') self.sleep(0.5) self.set_attribute('[action="/search"]', "action", "//bing.com/search") self.set_attributes('[value="Google Search"]', "value", "Bing Search") - self.type('[title="Search"]', "SeleniumBase GitHub Page URL") + self.type('[name="q"]', "SeleniumBase GitHub Page URL") self.sleep(0.5) self.js_click('[value="Bing Search"]') self.highlight("h1.b_logo", loops=8) diff --git a/examples/tour_examples/ReadMe.md b/examples/tour_examples/ReadMe.md index 472e614d2cb..050e8d0bf7d 100644 --- a/examples/tour_examples/ReadMe.md +++ b/examples/tour_examples/ReadMe.md @@ -108,27 +108,27 @@ class MyTourClass(BaseCase): self.get_new_driver(undetectable=True) self.goto("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') - self.wait_for_element('[title="Search"]') + self.wait_for_element('[name="q"]') self.hide_elements("iframe") # Create a website tour using the ShepherdJS library with "dark" theme # Same as: self.create_shepherd_tour(theme="dark") self.create_tour(theme="dark") self.add_tour_step("Welcome to Google!", title="SeleniumBase Tours") - self.add_tour_step("Type in your query here.", '[title="Search"]') + self.add_tour_step("Type in your query here.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "Google") + self.highlight_type('[name="q"]', "Google") self.wait_for_element('[role="listbox"]') # Wait for autocomplete # Create a website tour using the ShepherdJS library with "light" theme # Same as: self.create_shepherd_tour(theme="light") self.create_tour(theme="light") self.add_tour_step("Then click to search.", '[value="Google Search"]') - self.add_tour_step("Or press [ENTER] after entry.", '[title="Search"]') + self.add_tour_step("Or press [ENTER] after entry.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "GitHub\n") + self.highlight_type('[name="q"]', "GitHub\n") self.ad_block() self.wait_for_element("#search") diff --git a/examples/tour_examples/bootstrap_google_tour.py b/examples/tour_examples/bootstrap_google_tour.py index f2c4a78dd03..17c5d748762 100644 --- a/examples/tour_examples/bootstrap_google_tour.py +++ b/examples/tour_examples/bootstrap_google_tour.py @@ -8,23 +8,23 @@ def test_google_tour(self): self.get_new_driver(undetectable=True) self.goto("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') - self.wait_for_element('[title="Search"]') + self.wait_for_element('[name="q"]') self.hide_elements("iframe") self.create_bootstrap_tour() # OR self.create_tour(theme="bootstrap") self.add_tour_step("Welcome to Google!", title="SeleniumBase Tours") - self.add_tour_step("Type in your query here.", '[title="Search"]') + self.add_tour_step("Type in your query here.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "Google") + self.highlight_type('[name="q"]', "Google") self.wait_for_element('[role="listbox"]') # Wait for autocomplete self.create_bootstrap_tour() self.add_tour_step("Then click to search.", '[value="Google Search"]') - self.add_tour_step("Or press [ENTER] after entry.", '[title="Search"]') + self.add_tour_step("Or press [ENTER] after entry.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "GitHub\n") + self.highlight_type('[name="q"]', "GitHub\n") self.ad_block() self.wait_for_element("#search") diff --git a/examples/tour_examples/google_tour.py b/examples/tour_examples/google_tour.py index 314642bded4..e5086fa7f28 100644 --- a/examples/tour_examples/google_tour.py +++ b/examples/tour_examples/google_tour.py @@ -8,27 +8,27 @@ def test_google_tour(self): self.get_new_driver(undetectable=True) self.goto("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') - self.wait_for_element('[title="Search"]') + self.wait_for_element('[name="q"]') self.hide_elements("iframe") # Create a website tour using the ShepherdJS library with "dark" theme # Same as: self.create_shepherd_tour(theme="dark") self.create_tour(theme="dark") self.add_tour_step("Welcome to Google!", title="SeleniumBase Tours") - self.add_tour_step("Type in your query here.", '[title="Search"]') + self.add_tour_step("Type in your query here.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "Google") + self.highlight_type('[name="q"]', "Google") self.wait_for_element('[role="listbox"]') # Wait for autocomplete # Create a website tour using the ShepherdJS library with "light" theme # Same as: self.create_shepherd_tour(theme="light") self.create_tour(theme="light") self.add_tour_step("Then click to search.", '[value="Google Search"]') - self.add_tour_step("Or press [ENTER] after entry.", '[title="Search"]') + self.add_tour_step("Or press [ENTER] after entry.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "GitHub\n") + self.highlight_type('[name="q"]', "GitHub") self.ad_block() self.wait_for_element("#search") diff --git a/examples/tour_examples/hopscotch_google_tour.py b/examples/tour_examples/hopscotch_google_tour.py index 871b8f8ee21..58f4dd5d4cc 100644 --- a/examples/tour_examples/hopscotch_google_tour.py +++ b/examples/tour_examples/hopscotch_google_tour.py @@ -8,23 +8,23 @@ def test_google_tour(self): self.get_new_driver(undetectable=True) self.goto("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') - self.wait_for_element('[title="Search"]') + self.wait_for_element('[name="q"]') self.hide_elements("iframe") self.create_hopscotch_tour() # OR self.create_tour(theme="hopscotch") self.add_tour_step("Welcome to Google!", title="SeleniumBase Tours") - self.add_tour_step("Type in your query here.", '[title="Search"]') + self.add_tour_step("Type in your query here.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "Google") + self.highlight_type('[name="q"]', "Google") self.wait_for_element('[role="listbox"]') # Wait for autocomplete self.create_hopscotch_tour() self.add_tour_step("Then click to search.", '[value="Google Search"]') - self.add_tour_step("Or press [ENTER] after entry.", '[title="Search"]') + self.add_tour_step("Or press [ENTER] after entry.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "GitHub\n") + self.highlight_type('[name="q"]', "GitHub\n") self.ad_block() self.wait_for_element("#search") diff --git a/examples/tour_examples/introjs_google_tour.py b/examples/tour_examples/introjs_google_tour.py index e6f7f1c2cb0..108f8924958 100644 --- a/examples/tour_examples/introjs_google_tour.py +++ b/examples/tour_examples/introjs_google_tour.py @@ -8,23 +8,23 @@ def test_google_tour(self): self.get_new_driver(undetectable=True) self.goto("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') - self.wait_for_element('[title="Search"]') + self.wait_for_element('[name="q"]') self.hide_elements("iframe") self.create_introjs_tour() # OR self.create_tour(theme="introjs") self.add_tour_step("Welcome to Google!", title="SeleniumBase Tours") - self.add_tour_step("Type in your query here.", '[title="Search"]') + self.add_tour_step("Type in your query here.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "Google") + self.highlight_type('[name="q"]', "Google") self.wait_for_element('[role="listbox"]') # Wait for autocomplete self.create_introjs_tour() self.add_tour_step("Then click to search.", '[value="Google Search"]') - self.add_tour_step("Or press [ENTER] after entry.", '[title="Search"]') + self.add_tour_step("Or press [ENTER] after entry.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "GitHub\n") + self.highlight_type('[name="q"]', "GitHub\n") self.ad_block() self.wait_for_element("#search") diff --git a/examples/tour_examples/shepherd_google_tour.py b/examples/tour_examples/shepherd_google_tour.py index 38f2c7a1338..a428225da2e 100644 --- a/examples/tour_examples/shepherd_google_tour.py +++ b/examples/tour_examples/shepherd_google_tour.py @@ -8,23 +8,23 @@ def test_google_tour(self): self.get_new_driver(undetectable=True) self.goto("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') - self.wait_for_element('[title="Search"]') + self.wait_for_element('[name="q"]') self.hide_elements("iframe") self.create_shepherd_tour(theme="dark") self.add_tour_step("Welcome to Google!", title="SeleniumBase Tours") - self.add_tour_step("Type in your query here.", '[title="Search"]') + self.add_tour_step("Type in your query here.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "Google") + self.highlight_type('[name="q"]', "Google") self.wait_for_element('[role="listbox"]') # Wait for autocomplete self.create_shepherd_tour(theme="light") self.add_tour_step("Then click to search.", '[value="Google Search"]') - self.add_tour_step("Or press [ENTER] after entry.", '[title="Search"]') + self.add_tour_step("Or press [ENTER] after entry.", '[name="q"]') self.play_tour() - self.highlight_type('[title="Search"]', "GitHub\n") + self.highlight_type('[name="q"]', "GitHub\n") self.ad_block() self.wait_for_element("#search") diff --git a/examples/wordle_test.py b/examples/wordle_test.py index 72158d29daa..80534ce2c5f 100644 --- a/examples/wordle_test.py +++ b/examples/wordle_test.py @@ -54,16 +54,23 @@ def test_wordle(self): self.goto_if_not_url("about:blank") self.skip("Skip this test in headless mode!") self.goto("https://www.nytimes.com/games/wordle/index.html") - self.click_if_visible("button.purr-blocker-card__button", timeout=2) + self.sleep(0.5) + self.click_if_visible("button.purr-blocker-card__button", timeout=1) + self.sleep(0.2) self.click_if_visible('button:contains("Play")', timeout=2) + self.sleep(0.2) self.click_if_visible('button[class*="Skip-module"]', timeout=4) + self.sleep(0.2) self.click_if_visible('svg[data-testid="icon-close"]', timeout=2) + self.sleep(0.2) self.remove_elements('div[class*="Ad-module]') self.initialize_word_list() random.seed() word = random.choice(self.word_list) num_attempts = 0 found_word = False + self.wait_for_element('button[data-key]') + self.sleep(0.7) for attempt in range(6): num_attempts += 1 if len(self.word_list) == 0: @@ -74,8 +81,10 @@ def test_wordle(self): letters.append(letter) button = 'button[data-key="%s"]' % letter self.click(button) + self.sleep(0.2) button = 'button[class*="oneAndAHalf"]' self.click(button) + self.sleep(0.2) row = ( 'div[class*="Board"] div[class*="Row-module"]:nth-of-type(%s) ' % num_attempts @@ -92,6 +101,7 @@ def test_wordle(self): break self.word_list.remove(word) self.modify_word_list(word, letter_status) + self.sleep(0.2) self.save_screenshot_to_logs() if found_word: @@ -104,4 +114,4 @@ def test_wordle(self): if __name__ == "__main__": from pytest import main - main([__file__, "-s"]) + main([__file__, "-s", "--ad-block"]) From 1b18c10456cc064ce38e7024913b285b3053197a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 16 Sep 2026 20:26:19 -0400 Subject: [PATCH 9/9] Version 4.54.8 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 64d1fbb3eed..3bc0a2024eb 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.54.7" +__version__ = "4.54.8"