Skip to content

Commit

Permalink
refactor(browser): Enhance Browser Module with Type Safety and Setup …
Browse files Browse the repository at this point in the history
…Documentation
  • Loading branch information
nopedawn committed Feb 13, 2025
1 parent 1ff107b commit 2de02f5
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 99 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
install:
pip install -r requirements/doc.txt

.PHONY: install
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ Getting Started

* `Installation <https://splinter.readthedocs.io/en/latest/install/install.html>`_

or

::

git clone https://github.com/cobrateam/splinter
make install

* `Tutorial <https://splinter.readthedocs.io/en/latest/tutorial.html>`_


Expand Down
241 changes: 142 additions & 99 deletions splinter/browser.py
Original file line number Diff line number Diff line change
@@ -1,130 +1,173 @@
# Copyright 2012 splinter authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

import logging
from http.client import HTTPException
from typing import Dict
from typing import Tuple
from typing import Type
from typing import Union

from typing import Dict, Tuple, Type, Union, Optional, Any
from urllib3.exceptions import MaxRetryError

from splinter.driver import DriverAPI
from splinter.exceptions import DriverNotFoundError

logger = logging.getLogger(__name__)

# Define base exceptions tuple
driver_exceptions: Tuple[Type[Exception], ...] = (IOError, HTTPException, MaxRetryError)

# Safely add WebDriverException if available
try:
from selenium.common.exceptions import WebDriverException

driver_exceptions += (WebDriverException,)
except ImportError as e:
logger.debug(f"Import Warning: {e}")


_DRIVERS: Dict[str, Union[None, Type[DriverAPI]]] = {
"chrome": None,
"edge": None,
"firefox": None,
"remote": None,
"django": None,
"flask": None,
"zope.testbrowser": None,
}

try:
from splinter.driver.webdriver.chrome import WebDriver as ChromeWebDriver
from splinter.driver.webdriver.firefox import WebDriver as FirefoxWebDriver
from splinter.driver.webdriver.remote import WebDriver as RemoteWebDriver

_DRIVERS["chrome"] = ChromeWebDriver
_DRIVERS["firefox"] = FirefoxWebDriver
_DRIVERS["remote"] = RemoteWebDriver
except ImportError as e:
logger.debug(f"Import Warning: {e}")

try:
from splinter.driver.webdriver.edge import WebDriver as EdgeWebDriver

_DRIVERS["edge"] = EdgeWebDriver
except ImportError as e:
logger.debug(f"Import Warning: {e}")


try:
from splinter.driver.zopetestbrowser import ZopeTestBrowser

_DRIVERS["zope.testbrowser"] = ZopeTestBrowser
except ImportError as e:
logger.debug(f"Import Warning: {e}")

try:
import django # noqa
from splinter.driver.djangoclient import DjangoClient

_DRIVERS["django"] = DjangoClient
driver_exceptions = driver_exceptions + (WebDriverException,)
except ImportError as e:
logger.debug(f"Import Warning: {e}")

try:
import flask # noqa
from splinter.driver.flaskclient import FlaskClient

_DRIVERS["flask"] = FlaskClient
except ImportError as e:
logger.debug(f"Import Warning: {e}")
logger.debug("Selenium WebDriverException not available: %s", str(e))

# Type alias for driver types
DriverType = Union[None, Type[DriverAPI]]

class DriverRegistry:
"""Registry for managing browser drivers."""

_drivers: Dict[str, DriverType] = {
"chrome": None,
"edge": None,
"firefox": None,
"remote": None,
"django": None,
"flask": None,
"zope.testbrowser": None,
}

@classmethod
def register_driver(cls, name: str, driver: DriverType) -> None:
"""Register a new driver."""
cls._drivers[name] = driver

@classmethod
def get_driver(cls, name: str) -> DriverType:
"""Get a registered driver."""
return cls._drivers.get(name)

# Register WebDriver implementations
def _register_webdrivers() -> None:
try:
from splinter.driver.webdriver.chrome import WebDriver as ChromeWebDriver
from splinter.driver.webdriver.firefox import WebDriver as FirefoxWebDriver
from splinter.driver.webdriver.remote import WebDriver as RemoteWebDriver

DriverRegistry.register_driver("chrome", ChromeWebDriver)
DriverRegistry.register_driver("firefox", FirefoxWebDriver)
DriverRegistry.register_driver("remote", RemoteWebDriver)
except ImportError as e:
logger.debug("WebDriver import failed: %s", str(e))

try:
from splinter.driver.webdriver.edge import WebDriver as EdgeWebDriver
DriverRegistry.register_driver("edge", EdgeWebDriver)
except ImportError as e:
logger.debug("Edge WebDriver import failed: %s", str(e))

def get_driver(driver, retry_count: int = 3, config=None, *args, **kwargs):
"""Try to instantiate the driver.
# Register other drivers
def _register_other_drivers() -> None:
try:
from splinter.driver.zopetestbrowser import ZopeTestBrowser
DriverRegistry.register_driver("zope.testbrowser", ZopeTestBrowser)
except ImportError as e:
logger.debug("Zope TestBrowser import failed: %s", str(e))

Common selenium errors are caught and a retry attempt occurs.
This can mitigate issues running on Remote WebDriver.
try:
import django # noqa
from splinter.driver.djangoclient import DjangoClient
DriverRegistry.register_driver("django", DjangoClient)
except ImportError as e:
logger.debug("Django client import failed: %s", str(e))

try:
import flask # noqa
from splinter.driver.flaskclient import FlaskClient
DriverRegistry.register_driver("flask", FlaskClient)
except ImportError as e:
logger.debug("Flask client import failed: %s", str(e))

# Initialize drivers
_register_webdrivers()
_register_other_drivers()

def get_driver(
driver: Type[DriverAPI],
retry_count: int,
config: Optional[Dict[str, Any]] = None,
*args: Any,
**kwargs: Any
) -> DriverAPI:
"""
err = None

for _ in range(retry_count):
Try to instantiate the driver with retry mechanism.
Args:
driver: Driver class to instantiate
retry_count: Number of retry attempts
config: Optional configuration dictionary
*args: Positional arguments for driver initialization
**kwargs: Keyword arguments for driver initialization
Returns:
Instantiated driver
Raises:
Exception: Last caught exception if all retries fail
"""
if retry_count < 1:
raise ValueError("retry_count must be positive")

last_error = None
for attempt in range(retry_count):
try:
return driver(config=config, *args, **kwargs)
except driver_exceptions as e:
err = e

raise err


def Browser( # NOQA: N802
last_error = e
logger.warning(
"Driver instantiation failed (attempt %d/%d): %s",
attempt + 1,
retry_count,
str(e)
)

if last_error:
raise last_error
raise RuntimeError("Unknown error during driver instantiation")

def Browser(
driver_name: str = "firefox",
retry_count: int = 3,
config=None,
*args,
**kwargs,
):
"""Get a new driver instance.
Extra arguments will be sent to the driver instance.
If there is no driver registered with the provided ``driver_name``, this
function will raise a :class:`splinter.exceptions.DriverNotFoundError`
exception.
Arguments:
driver_name (str): Name of the driver to use.
retry_count (int): Number of times to try and instantiate the driver.
config: Optional[Dict[str, Any]] = None,
*args: Any,
**kwargs: Any
) -> DriverAPI:
"""
Get a new driver instance.
Args:
driver_name: Name of the driver to use
retry_count: Number of times to try instantiating the driver
config: Optional configuration dictionary
*args: Additional positional arguments for the driver
**kwargs: Additional keyword arguments for the driver
Returns:
Driver instance
Raises:
DriverNotFoundError: If the requested driver is not available
ValueError: If retry_count is less than 1
"""
if retry_count < 1:
raise ValueError("retry_count must be positive")

try:
driver = _DRIVERS[driver_name]
except KeyError as err:
raise DriverNotFoundError(f"{driver_name} is not a recognized driver.") from err

driver = DriverRegistry.get_driver(driver_name.lower())

if driver is None:
raise DriverNotFoundError(f"Driver for {driver_name} was not found.")

return get_driver(driver, retry_count=retry_count, config=config, *args, **kwargs)
raise DriverNotFoundError(
f"Driver '{driver_name}' is not available. "
"Please ensure the required dependencies are installed."
)

return get_driver(driver, retry_count, config, *args, **kwargs)

0 comments on commit 2de02f5

Please sign in to comment.