Repository navigation
WebDrivers
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.
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
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.
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)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)The trio_threads implementation achieves pseudo-asynchronicity by using the TrioThreadMixin utilities. The workflow involves:
-
Offloading: Sending the blocking
_implfunction call to an external worker thread pool managed by Trio (self.sync_to_trio). -
Awaiting: Suspending the Trio task until the background thread completes (
await). -
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. |
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,
)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. |
A driver implementation, such as the asynchronous Yandex WebDriver (webdrivers/trio_threads/yandex/__init__.py), is created by stacking these packages together:
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.
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).
-
Readability and Debugging: A bug in window management is contained within
CoreWindowMixin, not mixed with network handling logic. -
Modularity and Reusability: All standard navigation methods live in
CoreNavigationMixinand are reused identically by Chrome, Edge, and Yandex. -
Cross-Architecture Consistency: Each mixin exists in both the
syncandtrio_threadsimplementations, guaranteeing feature parity regardless of the chosen runtime environment. - Extensibility: Adding a new browser feature only requires creating a small, dedicated mixin.
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.
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):
# --- osn_selenium/webdrivers/core/__init__.py ---
class CoreWebDriver(
CoreActionsMixin,
CoreAuthMixin,
CoreBaseMixin,
CoreCaptureMixin,
CoreComponentsMixin,
CoreDevToolsMixin,
CoreElementMixin,
CoreFileMixin,
CoreLifecycleMixin,
CoreNavigationMixin,
CoreScriptMixin,
CoreSettingsMixin,
CoreStorageMixin,
CoreTimeoutsMixin,
CoreWindowMixin,
AbstractCoreWebDriver,
):
...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):
# --- osn_selenium/webdrivers/trio_threads/__init__.py ---
class BlinkWebDriver(
BlinkBaseMixin,
BlinkCastingMixin,
BlinkFeaturesMixin,
BlinkLifecycleMixin,
BlinkLoggingMixin,
BlinkNetworkMixin,
BlinkSettingsMixin,
CoreWebDriver,
AbstractBlinkWebDriver,
):
...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):
# --- 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.