Repository navigation
BiDi Mapping
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.
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.
Instead of HTTP requests, the framework performs real-time translation:
-
Interception: The user calls
await driver.find_element(By.ID, "btn"). -
Mapping: The framework looks up
Command.FIND_ELEMENTin its registry. -
Translation: It converts arguments into a BiDi
browsingContext.locateNodespayload. -
Transmission: The message is sent over the WebSocket via
trio. -
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.
The system is composed of three critical components found in osn_selenium.trio_bidi:
The engine of the new architecture. It manages:
- WebSocket Connection: Maintains the persistent link to the browser.
-
Task Channels: Uses
trio.MemorySendChannelto queue outgoing commands. -
Event Loop: Runs background listeners (
_WebsocketResponseHandler) to dispatch incoming messages and resolve pending tasks.
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. |
The mixin that powers the driver class. It exposes concurrency primitives and manages the BrowsingContext state.
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")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.
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.