Skip to content

BiDi Mapping

Eduard Dik edited this page Feb 4, 2026 · 1 revision

BiDi Mapping Overview

The trio_bidi architecture represents the future of osn-selenium. Unlike trio_threads, which achieves concurrency by offloading blocking calls to worker threads, trio_bidi implements a native, event-driven communication layer. It intercepts standard W3C WebDriver commands, translates them into messages for the WebDriver BiDi protocol, and executes them over a single WebSocket connection.


The Core Concept

In standard Selenium, calling driver.find_element(...) sends an HTTP request to the chromedriver binary. This is blocking. In the trio_bidi architecture, we replace the standard RemoteConnection with a specialized BiDiBridge.

The Translation Layer

Instead of HTTP requests, the framework performs real-time translation:

  1. Interception: The user calls await driver.find_element(By.ID, "btn").
  2. Mapping: The framework looks up Command.FIND_ELEMENT in its registry.
  3. Translation: It converts arguments into a BiDi browsingContext.locateNodes payload.
  4. Transmission: The message is sent over the WebSocket via trio.
  5. Response: The BiDi JSON response is mapped back into a standard W3C format (e.g., {'value': {'element-6066...':'...'}}).

This allows you to use standard Selenium APIs semantically, while the underlying engine is fully asynchronous and non-blocking.


Architecture Overview

The system is composed of three critical components found in osn_selenium.trio_bidi:

1. BiDiConnectionPool

The engine of the new architecture. It manages:

  • WebSocket Connection: Maintains the persistent link to the browser.
  • Task Channels: Uses trio.MemorySendChannel to queue outgoing commands.
  • Event Loop: Runs background listeners (_WebsocketResponseHandler) to dispatch incoming messages and resolve pending tasks.

2. Request/Response Functions

Located in osn_selenium/trio_bidi/mapping/, these modules define the logic for translating commands.

W3C Command BiDi Equivalent Behavior
GET browsingContext.navigate Navigate to URL and wait for interactive state.
FIND_ELEMENT browsingContext.locateNodes Returns sharedId nodes converted to W3C Elements.
GET_TITLE script.evaluate Injects document.title via JS evaluation.
CLICK_ELEMENT script.callFunction Injects a specialized JS snippet to click safely.

3. TrioBiDiMixin

The mixin that powers the driver class. It exposes concurrency primitives and manages the BrowsingContext state.


Context Switching: The use_context Pattern

One of the most powerful features of trio_bidi is the ability to execute tasks in multiple browser tabs (contexts) simultaneously within the same execution loop, without global state conflicts. Standard Selenium uses driver.switch_to.window(), which changes focus globally. In trio_bidi, context is stored in contextvars, meaning each Trio task can have its own active window.

async with driver.use_context(tab_1_id):
    # Standard commands now target Tab 1
    await driver.get("https://example.com")

    # You can launch a background task bound to this tab
    driver.nursery.start_soon(my_scraper_task, driver.current_context_id)

async with driver.use_context(tab_2_id):
    # Meanwhile, navigate Tab 2 without affecting Tab 1's state
    await driver.get("https://google.com")

Internal JS Injectors

Not all W3C commands have a direct BiDi equivalent (e.g., complex element interactions). To ensure robustness, the mapping layer utilizes internal snippet classes:

  • WEB_ELEMENT: Handles clicks, clearing inputs, and attribute retrieval with explicit guards (checks that mimic Selenium's interactability checks).
  • WINDOW: Handles screen orientation and dimension retrieval.
  • CAPTURE: Handles page source extraction.

Handling Exceptions

The mapping layer includes sophisticated error redirection logic (_error_redirects.py). If a BiDi operation fails, the error is translated into the correct Selenium exception.

Example:

  • BiDi returns: no such node
  • Mapped to W3C: error: "no such element"
  • Redirection Logic: If checking for an element state, it may be remapped to StaleElementReferenceException to match standard behavior.

Clone this wiki locally