Skip to content

Core Architecture

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

Core Architecture & Design Philosophy

The architecture of osn-selenium is built on the principle of strict decoupling. By separating data models, abstract interfaces, and execution logic, the framework achieves high maintainability and functional parity between its synchronous and asynchronous versions.


Base Models (osn_selenium/models.py)

At the lowest level, the framework uses a standardized set of models to represent data, geometry, and configuration. Most models inherit from Pydantic-based base classes found in _base_models.py to ensure strict validation.

Data Model Bases

  • DictModel: A strict Pydantic model that forbids extra fields, enforces type validation, and strips whitespace from strings. It serves as the base for most configuration objects.
  • ExtraDictModel: A flexible version of DictModel that allows additional fields, used primarily for browser flags and experimental options where future-proofing is necessary.

Geometry & Positioning

The framework provides several lightweight models to standardize browser coordinates and dimensions:

  • Point: Represents a 2D point with integer $(x, y)$ coordinates.
  • Position: A DictModel representation of a coordinate.
  • Size: Represents dimensions (width, height).
  • Rectangle: Combines Position and Size to define element bounds.
  • WindowRect: A specialized model for browser window geometry with sensible defaults (e.g., $1024 \times 768$).

The Abstraction Layer (osn_selenium/abstract/)

To solve the "God Object" problem found in standard Selenium, osn-selenium defines every component through Abstract Base Classes (ABCs).

The abstract/ directory contains the "contracts" that all implementations must follow. This ensures that whether you are using a sync Chrome driver or a trio_threads Edge driver, the method names, arguments, and expected return types are identical.

Abstract Category Purpose
executors/ Defines how CDP and JavaScript commands should be routed.
instances/ Defines the interface for objects like WebElement, Alert, and ShadowRoot.
webdriver/ Defines the core lifecycle and navigation methods for the driver itself.

TrioThreadMixin: The Async Engine

The TrioThreadMixin (found in osn_selenium/trio_threads_mixin.py) is the core utility that enables the asynchronous trio_threads architecture.

Since the underlying Selenium/WebDriver protocol is inherently blocking (synchronous), this mixin provides a bridge to the Trio event loop.

Key Capabilities:

  • sync_to_trio: A decorator/wrapper that takes a blocking synchronous function and executes it in an external worker thread using trio.to_thread.run_sync. It also handles ContextVariable exchange, ensuring that variables like CURRENT_BROWSING_CONTEXT are preserved across worker threads.
# A standard blocking function
def sync_foo(name: str, value: int):
    # Imagine a slow or blocking operation here
    return f"Hello {name}, your ID is {value}"

# Inside a class inheriting from TrioThreadMixin:
async def trio_threads_demo(self):
    # Wrap the function to make it async
    async_foo = self.sync_to_trio(sync_foo, lock=True)
    
    # Now you can await it without blocking the event loop
    result = await async_foo("User", value=42)
    print(result) # Output: Hello User, your ID is 42
  • Context Management: sync_to_trio_context allows synchronous context managers to be used as asynchronous ones.
from contextlib import contextmanager

# A standard synchronous context manager factory
@contextmanager
def sync_bar_resource(resource_id: str):
    print(f"Connecting to {resource_id}...")
    yield f"Resource-Object-{resource_id}"
    print(f"Disconnecting from {resource_id}...")

# Inside a class inheriting from TrioThreadMixin:
async def trio_threads_context_demo(self):
    # Wrap the factory function
    async_bar_factory = self.sync_to_trio_context(sync_bar_resource, lock=True)
    
    # Now use it with 'async with'
    # Both entering and exiting will happen in worker threads
    async with async_bar_factory("API_01") as resource:
        print(f"Using: {resource}")
  • Concurrency Control: It manages a trio.CapacityLimiter to limit the number of concurrent threads and a trio.Lock to ensure thread-safe access to the driver instance.

Implementation Architectures (Sync vs. Trio)

The framework is split into two primary architectures that determine how you interact with the library.

sync (Synchronous)

  • Execution: Methods are called directly and block the execution of the script until the browser responds.
  • Logic: Directly implements the abstract interfaces using standard blocking calls.

trio_threads (Asynchronous - Threaded)

  • Execution: Every method is a coroutine and must be called using await.
  • Logic: Uses the TrioThreadMixin to offload blocking driver calls to threads. This allows a single Python script to manage dozens of browser instances simultaneously without freezing the event loop.

trio_threads (Asynchronous)

BiDi Mapping

  • Execution: Fully asynchronous, coroutine-based.
  • Logic: Utilizes the BiDiBridge to translate W3C commands into direct WebSocket messages. Moves away from thread pools for command execution, offering higher performance and context isolation.

Functional Identity

Despite the different execution models, the implementations are functionally identical.

# Synchronous Interaction
driver.get("https://example.com")
el = driver.find_element(By.ID, "login")

# Asynchronous Interaction (TrioThreads & BiDi)
await driver.get("https://example.com")
el = await driver.find_element(By.ID, "login")

This consistency is achieved by having both implementations wrap the same unified logic, ensuring that transitioning from sync to async requires only changing the imports and adding await keywords.

Clone this wiki locally