Error Handling¶
Exception hierarchy¶
All cdpwave exceptions inherit from CDPError:
CDPError
├── ConnectionClosedError # WebSocket closed
├── CommandError # CDP error response (has .code, .message)
├── CommandTimeoutError # Command didn't respond in time
├── SessionClosedError # Session closed by browser
├── BrowserNotFoundError # No browser found on system
├── DiscoveryError # HTTP discovery failed
├── LaunchTimeoutError # Browser didn't start in time
└── LaunchError # Browser crashed during startup
Catch specific exceptions¶
from cdpwave import (
CDPError,
CommandError,
CommandTimeoutError,
BrowserNotFoundError,
)
try:
async with await CDPClient.launch(headless=True) as client:
session = await client.new_page()
result = await session.runtime.evaluate("document.title", return_by_value=True)
except BrowserNotFoundError:
print("Install Chrome or set CDPWAVE_BROWSER_PATH")
except CommandTimeoutError:
print("Command timed out — browser may be overloaded")
except CommandError as e:
print(f"CDP error {e.code}: {e.message}")
except CDPError as e:
print(f"cdpwave error: {e}")
CommandError details¶
CommandError includes the CDP error code and message:
try:
await session.send("NonExistent.method")
except CommandError as e:
print(f"Code: {e.code}") # -32601
print(f"Message: {e.message}") # "'NonExistent.method' wasn't found"
print(f"Data: {e.data}") # None or dict
Session closed¶
If the browser closes a tab (e.g., window.close() in JS), the session becomes closed. Further send() calls raise SessionClosedError:
from cdpwave import SessionClosedError
try:
await session.runtime.evaluate("window.close()")
await asyncio.sleep(1)
await session.runtime.evaluate("1 + 1")
except SessionClosedError:
print("Session was closed by the browser")
Launch failures¶
from cdpwave import LaunchTimeoutError, LaunchError
try:
client = await CDPClient.launch(timeout=5.0)
except LaunchTimeoutError:
print("Browser didn't start within 5 seconds")
except LaunchError as e:
print(f"Browser crashed: {e}")
Cleanup is guaranteed¶
async with ensures cleanup even when exceptions occur:
async with await CDPClient.launch(headless=True) as client:
raise ValueError("something went wrong")
# client.close() still runs — browser terminated, WebSocket closed
close() is idempotent and never raises. Calling it multiple times is safe.
Best practices¶
- Always use
async withforCDPClientandCDPSession - Catch
BrowserNotFoundErrorto give helpful install instructions - Catch
CommandTimeoutErrorfor slow pages or overloaded browsers - Let
CDPErrorbe the catch-all for unexpected cdpwave issues - Don't catch
SessionClosedErrorunless you have recovery logic
Handling experimental domains¶
Some CDP domains are experimental and may not be available in all Chrome
versions. Use CommandError with code -32601 (method not found) to
detect and gracefully skip unavailable domains:
from cdpwave import CDPClient, CommandError
async with await CDPClient.launch(headless=True) as client:
session = await client.new_page()
try:
await session.bluetooth_emulation.enable()
await session.bluetooth_emulation.disable()
except CommandError as exc:
if exc.code == -32601:
print("BluetoothEmulation not available in this Chrome version")
else:
raise
Helper pattern for multiple experimental domains¶
def is_method_not_found(exc: Exception) -> bool:
return isinstance(exc, CommandError) and exc.code == -32601
experimental_domains = [
"bluetooth_emulation",
"autofill",
"fed_cm",
"web_mcp",
]
for domain_name in experimental_domains:
domain = getattr(session, domain_name)
try:
await domain.enable()
await domain.disable()
except CommandError as exc:
if not is_method_not_found(exc):
raise
print(f"{domain_name} not available, skipping")