SeleniumHQ/selenium · critical · RuntimeError

CDP devtools module not loaded. Call import_devtools() first

Error message

CDP devtools module not loaded. Call import_devtools() first.

What it means

Raised as a RuntimeError by CdpConnection._handle_event() when the global `devtools` module is None at the time an event message is received from the WebSocket. The devtools module (generated CDP type definitions) must be loaded via import_devtools() before any event can be parsed. The _handle_event method calls devtools.util.parse_json_event(data), so it requires devtools to be initialized. This fires during background event dispatch, not at call time.

Source

Thrown at py/private/cdp.py:314

            # Otherwise, continue the generator to parse the JSON result
            # into a CDP object.
            try:
                _ = cmd.send(data["result"])
                raise InternalError("The command's generator function did not exit when expected!")
            except StopIteration as exit:
                return_ = exit.value
            self.inflight_result[cmd_id] = return_
        event.set()

    def _handle_event(self, data: dict):
        """Handle an event.

        Args:
            data: event as a JSON dictionary
        """
        global devtools
        if devtools is None:
            raise RuntimeError("CDP devtools module not loaded. Call import_devtools() first.")
        event = devtools.util.parse_json_event(data)
        logger.debug("Received event: %s", event)
        to_remove = set()
        for sender in self.channels[type(event)]:
            try:
                sender.send_nowait(event)
            except trio.WouldBlock:
                logger.error('Unable to send event "%r" due to full channel %s', event, sender)
            except trio.BrokenResourceError:
                to_remove.add(sender)
        if to_remove:
            self.channels[type(event)] -= to_remove


class CdpSession(CdpBase):
    """Contains the state for a CDP session.

    Generally you should not instantiate this object yourself; you should call

View on GitHub (pinned to aa36b38e69)

Solutions

  1. Call import_devtools() early in your setup, before creating or using any CDP connection. It is normally called automatically by Connection.create().
  2. Ensure you are using the standard connection creation path (Connection.create) which calls import_devtools() rather than constructing CdpConnection directly.
  3. If customizing initialization, call import_devtools() as the first step before any WebSocket communication begins.

Example fix

# before
# Connection created without import_devtools()
conn = CdpConnection(ws)
# ... events arrive, _handle_event raises RuntimeError

# after
from selenium.webdriver.common.bidi.cdp import import_devtools
import_devtools()
conn = await Connection.create(url)  # standard path
# events now parse correctly
Defensive patterns

Strategy: validation

Validate before calling

from selenium.webdriver.common.bidi.cdp import devtools, import_devtools
if devtools is None:
    import_devtools()

Try / catch

try:
    await conn.do_work()
except RuntimeError as e:
    if 'devtools module not loaded' in str(e):
        import_devtools()
        await conn.do_work()
    else:
        raise

Prevention

When it happens

Trigger: A CDP event arrives on the WebSocket (dispatched by the reader task to _handle_event) before import_devtools() has been called. The global `devtools` variable is None, so event parsing cannot proceed. This is a race or initialization-order error.

Common situations: Forgetting to call import_devtools() (typically auto-called during connection setup but can be missed in custom setups); a timing issue where events arrive before the devtools import completes; manually constructing a CdpConnection without the standard initialization path.

Related errors


AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14). Data as JSON: /api/errors/11f6ce8b0b8ad55c. Report an issue: GitHub.