Skip to content

WebDrivers

Eduard Dik edited this page Feb 4, 2026 · 2 revisions

WebDrivers Overview

The webdrivers package is the heart of the osn-selenium library. It is responsible for managing the browser lifecycle, executing commands, and maintaining a consistent interface across different execution architectures.

Instead of a single monolithic class, the drivers are built using a Mixin-based architecture. This allows the library to separate concerns (like navigation, window management, or CDP interaction) and reuse logic between synchronous and asynchronous implementations.


Package Structure

osn_selenium.webdrivers
├── protocols.py              # Structural interfaces (Sync/TrioThread protocols)
├── sync/                     # Synchronous execution architecture
│   ├── core/                 # Basic WebDriver functionality (Sync)
│   ├── blink/                # Chromium-specific logic (Sync)
│   ├── chrome/               # Chrome implementation (Sync)
│   ├── edge/                 # Edge implementation (Sync)
│   └── yandex/               # Yandex Browser implementation (Sync)
├── trio_bidi/             # Asynchronous execution architecture (BiDi)
│   ├── core/                 # Basic WebDriver functionality (BiDi)
│   ├── blink/                # Chromium-specific logic (BiDi)
│   ├── chrome/               # Chrome implementation (BiDi)
│   ├── edge/                 # Edge implementation (BiDi)
│   └── yandex/               # Yandex Browser implementation (BiDi)
├── trio_threads/             # Asynchronous execution architecture (Trio)
│   ├── core/                 # Basic WebDriver functionality (Trio)
│   ├── blink/                # Chromium-specific logic (Trio)
│   ├── chrome/               # Chrome implementation (Trio)
│   ├── edge/                 # Edge implementation (Trio)
│   └── yandex/               # Yandex Browser implementation (Trio)
└── unified/                  # Shared internal implementation layer
    ├── core/                 # Base mixins for all architectures
    ├── blink/                # Base Chromium mixins
    ├── chrome/               # Shared Chrome logic
    ├── edge/                 # Shared Edge logic
    └── yandex/               # Shared Yandex logic

Implementations Logic

To demonstrate how osn-selenium manages complexity and maintains two distinct execution modes (Synchronous and Asynchronous), we analyze the difference in control flow for core methods like navigation (get), element search (find_elements), context switching (switch_to), and script execution (execute_script).

Every publicly exposed method across all drivers (Chrome, Edge, etc.) ultimately relies on a core, blocking method defined in the unified layer.

1. The Unified Core (osn_selenium/webdrivers/unified/...)

The unified package holds the single source of truth for all functional logic. These methods (prefixed with _impl) execute the original, blocking Selenium commands and are shared by both sync and trio_threads layers. This centralization is key for maintainability.

Core Function File Location
_find_elements_impl unified/core/element.py
_get_impl unified/core/navigation.py
_switch_to_impl unified/core/window.py
_execute_script_impl unified/core/script.py
# --- osn_selenium/webdrivers/unified/core/element.py ---

@requires_driver
def _find_elements_impl(self, by: str = By.ID, value: Optional[str] = None) -> List[legacyWebElement]:
    """[U] Executes the core Selenium blocking method for finding elements."""
    return self._driver_impl.find_elements(by=by, value=value)

# --- osn_selenium/webdrivers/unified/core/navigation.py ---

@requires_driver
def _get_impl(self, url: str) -> None:
    """[U] Executes the core Selenium blocking method for navigation."""
    self._driver_impl.get(url=url)

2. The Synchronous Layer (osn_selenium/webdrivers/sync/...)

The sync implementation simply calls the _impl methods directly since Python's synchronous environment is compatible with the blocking nature of the original Selenium calls. Its primary task is object encapsulation: ensuring that the raw Selenium return object is wrapped in a corresponding Sync Instance Wrapper (e.g., osn_selenium.instances.sync.web_element.WebElement).

Example Method Description
find_elements Calls _find_elements_impl and converts resulting legacyWebElement objects into SyncWebElement instances.
switch_to Calls _switch_to_impl and wraps the raw Selenium SwitchTo object into our SyncSwitchTo implementation.
# --- osn_selenium/webdrivers/sync/core/element.py ---

def find_elements(self, by: str = By.ID, value: Optional[str] = None) -> List[WebElement]:
    # 1. Direct call to the blocking unified implementation
    legacy_elements = self._find_elements_impl(by=by, value=value)
    
    # 2. Wrap results into SyncWebElement instances
    return [
        get_sync_instance_wrapper(wrapper_class=WebElement, legacy_object=element) 
        for element in legacy_elements
    ]

# --- osn_selenium/webdrivers/sync/core/navigation.py ---

def get(self, url: str) -> None:
    # Direct call, no wrapping needed for None return type
    self._get_impl(url=url)

3. The Trio-Threads Layer (osn_selenium/webdrivers/trio_threads/...)

The trio_threads implementation achieves pseudo-asynchronicity by using the TrioThreadMixin utilities. The workflow involves:

  1. Offloading: Sending the blocking _impl function call to an external worker thread pool managed by Trio (self.sync_to_trio).
  2. Awaiting: Suspending the Trio task until the background thread completes (await).
  3. Encapsulation: Wrapping the raw Selenium object into a TrioThread Instance Wrapper (e.g., osn_selenium.instances.trio_threads.web_element.WebElement), injecting Trio synchronization primitives (lock, limiter).
Example Method Description
execute_script Uses await self.sync_to_trio(...) to run the script without blocking the event loop, enabling concurrency.
switch_to Runs the internal _switch_to_impl in a thread and returns an asynchronous SwitchTo object.

4. The Trio-BiDi Layer (osn_selenium/webdrivers/trio_bidi/...)

The trio_bidi layer is distinct. It utilizes BiDi Mapping for communication. It inherits from TrioBiDiMixin, which sets up a custom BiDiBridgeRemoteConnection. When you call a method here, it may still use sync_to_trio for lifecycle management, but standard commands are routed through the remote connection bridge, which converts W3C commands to BiDi WebSocket messages.

# --- osn_selenium/webdrivers/trio_threads/core/script.py ---

async def execute_script(self, script: str, *args: Any) -> Any:
    # Send the blocking call to a Trio thread, then await the result
    unwrapped = unwrap_args(args=args)
    result = await self.sync_to_trio(sync_function=self._execute_script_impl)(script, *unwrapped)
    
    # Wrap any resulting elements/objects with TrioThread wrappers
    return wrap_trio_thread_args(args=result, lock=self._lock, limiter=self._capacity_limiter)


# --- osn_selenium/webdrivers/trio_threads|trio_bidi/core/window.py ---

def switch_to(self) -> SwitchTo:
    # Returns an object that manages context switching,
    # whose own methods (like .frame()) will also be asynchronous.
    legacy = self._switch_to_impl()
    return get_trio_thread_instance_wrapper(
        wrapper_class=SwitchTo,
        legacy_object=legacy,
        lock=self._lock,
        limiter=self._capacity_limiter,
    )

WebDriver Classes

Within the sync and trio_threads packages, the driver implementations are structured hierarchically to maximize code reuse, following the underlying browser engine architecture (Chromium/Blink).

Sub-package Role Responsibility
core Foundation / Base Contains generic WebDriver functionality, shared by all browsers (e.g., navigation, timeouts, actions, file handling). This layer is independent of the browser engine.
blink Chromium-Engine Base Extends core with logic common to all Chromium-based browsers (Chrome, Edge, Yandex). This includes base classes for managing CDP connection details, Blink-specific browser flags, and process lifecycle management.
chrome Chrome Implementation Extends blink with features specific to Google Chrome. This package uses the ChromeFlagsManager and configures the driver to launch the official Google Chrome binary.
edge Edge Implementation Extends blink with features specific to Microsoft Edge. This package uses the EdgeFlagsManager and configures the driver to launch the official Microsoft Edge binary.
yandex Yandex Implementation Extends chrome (as Yandex Browser is a Chrome fork). This package uses the specialized YandexFlagsManager and configures the driver to launch the Yandex Browser binary.

How Drivers Are Composed (Example: YandexWebDriver)

A driver implementation, such as the asynchronous Yandex WebDriver (webdrivers/trio_threads/yandex/__init__.py), is created by stacking these packages together:

$$\text{YandexWebDriver} = \text{Yandex Mixins} + \text{Chrome Mixins} + \text{Blink Mixins} + \text{Core Mixins}$$

This composition ensures that Yandex gets all the core functions, plus all Chromium features (CDP, network), plus its own unique flags and paths, all encapsulated within the asynchronous (Trio) execution environment.


Mixin Philosophy

Traditional Selenium frameworks often struggle with a design flaw known as the "God Object" pattern, where the main WebDriver class contains hundreds of methods responsible for everything: navigation, element finding, window management, and CDP execution. This leads to codebases that are difficult to debug, extend, and maintain.

osn-selenium solves this by implementing a comprehensive Mixin-based architecture. This system splits the entire functionality into numerous small, specialized classes (Mixins), each responsible for only one aspect (e.g., CoreCaptureMixin, BlinkNetworkMixin, CoreTimeoutsMixin).

Advantages of the Mixin Architecture:

  1. Readability and Debugging: A bug in window management is contained within CoreWindowMixin, not mixed with network handling logic.
  2. Modularity and Reusability: All standard navigation methods live in CoreNavigationMixin and are reused identically by Chrome, Edge, and Yandex.
  3. Cross-Architecture Consistency: Each mixin exists in both the sync and trio_threads implementations, guaranteeing feature parity regardless of the chosen runtime environment.
  4. Extensibility: Adding a new browser feature only requires creating a small, dedicated mixin.

Composition and MRO

The final WebDriver instances (e.g., ChromeWebDriver or YandexWebDriver) are created by aggregating these specialized mixins. This uses Python's Method Resolution Order (MRO) to determine which method implementation (e.g., Core vs. Blink) is executed.

1. Core WebDriver Composition

The CoreWebDriver (located in webdrivers/[sync|trio_threads]/core/__init__.py) is the base, defining the standard set of features available to any browser:

Mixin Group Module Path Purpose
CoreBaseMixin .core.base Session, status, capabilities, is_active check.
CoreNavigationMixin .core.navigation get(), back(), forward(), title().
CoreElementMixin .core.element find_element(), find_elements().
CoreLifecycleMixin .core.lifecycle start_webdriver(), quit(), restart_webdriver().
CoreActionsMixin .core.actions action_chains(), web_driver_wait().

Formula for CoreWebDriver (Simplified):

$$\text{CoreWebDriver} = \text{CoreActions} + \text{CoreAuth} + \text{CoreBase} + \text{...} + \text{CoreWindow}$$

# --- osn_selenium/webdrivers/core/__init__.py ---

class CoreWebDriver(
    CoreActionsMixin,
    CoreAuthMixin,
    CoreBaseMixin,
    CoreCaptureMixin,
    CoreComponentsMixin,
    CoreDevToolsMixin,
    CoreElementMixin,
    CoreFileMixin,
    CoreLifecycleMixin,
    CoreNavigationMixin,
    CoreScriptMixin,
    CoreSettingsMixin,
    CoreStorageMixin,
    CoreTimeoutsMixin,
    CoreWindowMixin,
    AbstractCoreWebDriver,
):
    ...

2. Blink WebDriver Composition (Adding Chromium Logic)

The BlinkWebDriver builds upon the Core foundation by injecting logic specific to Chromium's DevTools capabilities and flags management.

Mixin Group Module Path Purpose
BlinkBaseMixin .blink.base Manages browser_exe path and debugging connection details.
BlinkLifecycleMixin .blink.lifecycle Adds process management logic (Popen, polling) to start_webdriver.
BlinkNetworkMixin .blink.network set_network_conditions(), delete_network_conditions().
BlinkCastingMixin .blink.casting Screen mirroring functionality.

Formula for BlinkWebDriver (Simplified):

$$\text{BlinkWebDriver} = \text{CoreWebDriver} + \text{BlinkBase} + \text{BlinkLifecycle} + \text{BlinkNetwork} + \text{...}$$

# --- osn_selenium/webdrivers/trio_threads/__init__.py ---

class BlinkWebDriver(
    BlinkBaseMixin,
    BlinkCastingMixin,
    BlinkFeaturesMixin,
    BlinkLifecycleMixin,
    BlinkLoggingMixin,
    BlinkNetworkMixin,
    BlinkSettingsMixin,
    CoreWebDriver,
    AbstractBlinkWebDriver,
):
    ...

3. Final Driver Composition (Example: ChromeWebDriver)

The final ChromeWebDriver combines all layers into a definitive, usable class:

Mixin Group Module Path Purpose
ChromeBaseMixin .chrome.base Concrete implementation of _create_driver_impl using webdriver.Chrome.
ChromeLifecycleMixin .chrome.lifecycle Overrides generic BlinkLifecycle to ensure proper usage of ChromeFlagsManager.

Final Formula for ChromeWebDriver (Sync/Trio):

$$\text{ChromeWebDriver} = \text{Chrome Mixins} + \text{Blink Mixins} + \text{Core Mixins}$$

# --- osn_selenium/webdrivers/chrome/__init__.py ---

class ChromeWebDriver(
    ChromeBaseMixin,
    ChromeLifecycleMixin,
    ChromeSettingsMixin,
    BlinkWebDriver,
    AbstractChromeWebDriver,
):
    ...

While the resulting MRO chain for the final WebDriver classes is structurally complex, the system is designed specifically to eliminate the difficulty developers face in navigating large, monolithic "God Object" codebases.

Clone this wiki locally