From 8446e8b432dc4e2d2aff84cfc56fd6d2fed92a32 Mon Sep 17 00:00:00 2001 From: Robin Whittleton Date: Thu, 16 Jul 2026 09:05:27 +0200 Subject: [PATCH 1/2] feat: initial support for Safari rendering of images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There isn’t any safaridriver support for headless mode yet unfortunately. --- README.md | 6 ++++++ se/browser.py | 21 +++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 56562c91..00ba6636 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,12 @@ ln -s $(pipx environment --value PIPX_LOCAL_VENVS)/standardebooks/lib/python3.*/ brew install standardebooks ``` +When building a project with images, a browser instance is used to render SVGs to PNGs. If you don’t have Chrome or Firefox installed then you need to manually enable Safari to be used to do this: + +```shell +safaridriver --enable +``` + ### Alternative: pipx If you want a more granular install you can install the dependencies manually and the toolset with `pipx`: diff --git a/se/browser.py b/se/browser.py index efc873dc..951ef1b2 100644 --- a/se/browser.py +++ b/se/browser.py @@ -17,6 +17,7 @@ from selenium.webdriver.firefox.options import Options as FirefoxOptions from selenium.webdriver.firefox.firefox_profile import FirefoxProfile from selenium.webdriver.firefox.service import Service as FirefoxService +from selenium.webdriver.safari.options import Options as SafariOptions from selenium.webdriver.remote.webdriver import WebDriver import se @@ -32,6 +33,7 @@ class BrowserType(Enum): FIREFOX = 1 CHROME = 2 + SAFARI = 3 @staticmethod def from_string(value: str|None) -> "BrowserType | None": @@ -50,6 +52,9 @@ def from_string(value: str|None) -> "BrowserType | None": if "chrome" in value or "chromium" in value: return BrowserType.CHROME + if "safari" in value: + return BrowserType.SAFARI + return None @staticmethod @@ -120,7 +125,7 @@ def __init__(self): except Exception as ex: raise se.MissingDependencyException(f"Couldn’t find web browser. Message: {ex}") - raise se.MissingDependencyException("Couldn’t find [command]chrome[/], [command]chromium[/], or [command]firefox[/].") + raise se.MissingDependencyException("Couldn’t find [command]chrome[/], [command]chromium[/], [command]firefox[/], or [command]safari[/].") @property def driver(self) -> WebDriver: @@ -136,8 +141,11 @@ def driver(self) -> WebDriver: elif self.type == BrowserType.FIREFOX: self._driver = self._initialize_selenium_firefox_webdriver() + elif self.type == BrowserType.SAFARI: + self._driver = self._initialize_selenium_safari_webdriver() + else: - raise se.MissingDependencyException("Couldn’t find [command]chrome[/], [command]chromium[/], or [command]firefox[/].") + raise se.MissingDependencyException("Couldn’t find [command]chrome[/], [command]chromium[/], [command]firefox[/], or [command]safari[/].") except Exception as ex: raise se.MissingDependencyException(f"Couldn’t start web browser. Message: {ex}") @@ -242,3 +250,12 @@ def _initialize_selenium_firefox_webdriver(self) -> WebDriver: return webdriver.Firefox(options=options, service=service) # type: ignore This is an error in Selenium's type stub. return webdriver.Firefox(options=options) # type: ignore This is an error in Selenium's type stub. + + def _initialize_selenium_safari_webdriver(self) -> WebDriver: + """ + Initialize a Selenium Safari-compatible driver and return it for use in other applications. + """ + + options = SafariOptions() + + return webdriver.Safari(options=options) # type: ignore This is an error in Selenium's type stub. From d72062ec1973fbad364f0da0ff4c2c3f96534d87 Mon Sep 17 00:00:00 2001 From: Robin Whittleton Date: Thu, 16 Jul 2026 23:00:01 +0200 Subject: [PATCH 2/2] feat: use Safari as a last-resort browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …due to lack of options. --- se/browser.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/se/browser.py b/se/browser.py index 951ef1b2..ab90d088 100644 --- a/se/browser.py +++ b/se/browser.py @@ -92,8 +92,15 @@ def __init__(self): self.executable_path: Path self._driver: WebDriver | None = None + browsers = list(installed_browsers.browsers()) + + # Safari is a backup choice so make sure it’s last + safari_entry = next((browser for browser in browsers if browser['name'] == 'safari'), None) + if safari_entry: + browsers.append(browsers.pop(browsers.index(safari_entry))) + try: - for installed_browser in installed_browsers.browsers(): + for installed_browser in browsers: browser_type = BrowserType.from_string(installed_browser["name"]) if browser_type: installed_browser_location = str(installed_browser["location"])