From 9f00241fc6d637ee8bd36bc11cb62ba49d14d768 Mon Sep 17 00:00:00 2001 From: Fernando Celmer Date: Wed, 18 Mar 2026 01:11:50 -0300 Subject: [PATCH] fix: replace FirefoxBinary with Selenium 4 equivalents FirefoxBinary (selenium.webdriver.firefox.firefox_binary) was removed in Selenium 4, making tbselenium completely unusable despite its selenium>=4 requirement. This fixes the ModuleNotFoundError raised on any import. Changes: - tbselenium/tbbinary.py: remove FirefoxBinary subclass; replace with a standalone TBBinary class that retains the kill() process-management method without depending on the removed Selenium 3 API. - tbselenium/tbdriver.py: replace options.binary (Selenium 3) with options.binary_location (Selenium 4 API); replace deprecated Service(executable_path=..., log_path=...) kwargs with Service(executable=..., log_output=...) as required by Selenium 4.10+. - tbselenium/test/test_browser.py: update binary assertions to use options.binary_location directly (plain string path) instead of the removed FirefoxBinary.which() helper method. Fixes #222 Co-Authored-By: Claude Sonnet 4.6 --- tbselenium/tbbinary.py | 20 +++++++++++++------- tbselenium/tbdriver.py | 18 ++++++++---------- tbselenium/test/test_browser.py | 19 +++++-------------- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/tbselenium/tbbinary.py b/tbselenium/tbbinary.py index 8f98822..e701621 100644 --- a/tbselenium/tbbinary.py +++ b/tbselenium/tbbinary.py @@ -1,16 +1,22 @@ -from selenium.webdriver.firefox.firefox_binary import FirefoxBinary +import subprocess -class TBBinary(FirefoxBinary): +class TBBinary: ''' - Extend FirefoxBinary to better handle terminated browser processes. + Manage the Tor Browser (Firefox) binary process. + + FirefoxBinary was removed in Selenium 4. This class replaces the previous + TBBinary(FirefoxBinary) subclass and provides only the process-management + functionality that tbselenium relies on internally. ''' - def kill(self): - """Kill the browser. + def __init__(self, firefox_path=None, log_file=None): + self.firefox_path = firefox_path + self.log_file = log_file + self.process = None - This is useful when the browser is stuck. - """ + def kill(self): + """Kill the browser process if it is still running.""" if self.process and self.process.poll() is None: self.process.kill() self.process.wait() diff --git a/tbselenium/tbdriver.py b/tbselenium/tbdriver.py index 0b33979..fbdc4ce 100644 --- a/tbselenium/tbdriver.py +++ b/tbselenium/tbdriver.py @@ -79,22 +79,20 @@ def __init__(self, if use_custom_profile: print(f'Using custom profile: {self.tbb_profile_path}') tbb_service = Service( - executable_path=executable_path, - log_path=tbb_logfile_path, # TODO: deprecated, use log_output + executable=executable_path, + log_output=tbb_logfile_path, service_args=["--marionette-port", "2828"], port=geckodriver_port ) else: tbb_service = Service( - executable_path=executable_path, - log_path=tbb_logfile_path, + executable=executable_path, + log_output=tbb_logfile_path, port=geckodriver_port ) - # options.binary is path to the Firefox binary and it can be a string - # or a FirefoxBinary object. If it's a string, it will be converted to - # a FirefoxBinary object. - # https://github.com/SeleniumHQ/selenium/blob/7cfd137085fcde932cd71af78642a15fd56fe1f1/py/selenium/webdriver/firefox/options.py#L54 - self.options.binary = self.tbb_fx_binary_path + # Use binary_location (Selenium 4 API) to point to the TBB Firefox + # binary. FirefoxBinary and options.binary were removed in Selenium 4. + self.options.binary_location = self.tbb_fx_binary_path self.options.add_argument('--class') self.options.add_argument('"Tor Browser"') if headless: @@ -309,7 +307,7 @@ def export_env_vars(self): prepend_to_env_var("PATH", self.tbb_browser_dir) def get_tb_binary(self, logfile=None): - """Return FirefoxBinary pointing to the TBB's firefox binary.""" + """Return a TBBinary instance pointing to the TBB's firefox binary.""" tbb_logfile = open(logfile, 'a+') if logfile else None return TBBinary(firefox_path=self.tbb_fx_binary_path, log_file=tbb_logfile) diff --git a/tbselenium/test/test_browser.py b/tbselenium/test/test_browser.py index 84d1687..e4b62e4 100644 --- a/tbselenium/test/test_browser.py +++ b/tbselenium/test/test_browser.py @@ -28,13 +28,9 @@ def tearDownClass(cls): def test_correct_firefox_binary(self): """Make sure we use the Firefox binary from the TBB directory.""" - try: - # driver.binary was removed in selenium-4.10.0 - # https://github.com/SeleniumHQ/selenium/pull/12030/files#diff-89ba579445647535b74423c7bf4b8be79ef1ce33847a2768e623c3083a33545dL127 - tbbinary = self.driver.options.binary - except AttributeError: - tbbinary = self.driver.binary - self.assertTrue(tbbinary.which('firefox').startswith(TBB_PATH)) + # binary_location is the Selenium 4 API (a plain string path) + tbbinary_path = self.driver.options.binary_location + self.assertTrue(tbbinary_path.startswith(TBB_PATH)) # TODO: log output is always empty @pytest.mark.xfail @@ -54,13 +50,8 @@ def test_should_load_tbb_firefox_libs(self): driver = self.driver geckodriver_pid = driver.service.process.pid process = psutil.Process(geckodriver_pid) - try: - # driver.binary was removed in selenium-4.10.0 - # https://github.com/SeleniumHQ/selenium/pull/12030/files#diff-89ba579445647535b74423c7bf4b8be79ef1ce33847a2768e623c3083a33545dL127 - tbbinary = self.driver.options.binary - except AttributeError: - tbbinary = self.driver.binary - tbbinary_path = tbbinary.which('firefox') + FF_BINARY_SUFFIX + # binary_location is the Selenium 4 API (a plain string path) + tbbinary_path = self.driver.options.binary_location + FF_BINARY_SUFFIX for child in process.children(): if tbbinary_path == child.exe(): tb_pid = child.pid