Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions cdp_use/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def __init__(
self.max_ws_frame_size = max_ws_frame_size
self.ws: Optional[websockets.ClientConnection] = None
self.msg_id: int = 0
self.pending_requests: Dict[int, asyncio.Future] = {}
self.pending_requests: Dict[int, tuple[asyncio.Future, str]] = {}
self._message_handler_task = None

# Initialize the type-safe CDP library
Expand Down Expand Up @@ -311,14 +311,47 @@ async def _handle_messages(self):

# Handle response messages (with id)
if "id" in data and data["id"] in self.pending_requests:
future = self.pending_requests.pop(data["id"])
future, request_method = self.pending_requests.pop(data["id"])
# Check if future is already done to avoid InvalidStateError
if not future.done():
if "error" in data:
logger.debug(
f"CDP Error for request {data['id']}: {data['error']}"
)
future.set_exception(RuntimeError(data["error"]))
error = data["error"]
# Suppress CDP error -32000 "Browser window not found"
# only for Browser.getWindowForTarget.
# This error occurs during race conditions when
# getWindowForTarget is called for a target that doesn't
# yet have an associated window (e.g., during file uploads,
# page transitions, or new tab creation). The target exists
# in CDP but has no window binding yet, so
# Browser.getWindowForTarget returns -32000. Returning a
# default result prevents the event handler from crashing and
# corrupting the session.
if (
isinstance(error, dict)
and error.get("code") == -32000
and request_method == "Browser.getWindowForTarget"
):
logger.info(
f"CDP error {error.get('code')} suppressed for "
f"Browser.getWindowForTarget request {data['id']}: "
f"{error.get('message', 'unknown')} - "
f"returning default window result"
)
future.set_result({
"windowId": 0,
"bounds": {
"left": 0,
"top": 0,
"width": 1920,
"height": 1080,
"windowState": "normal",
},
})
else:
logger.debug(
f"CDP Error for request {data['id']}: {error}"
)
future.set_exception(RuntimeError(error))
else:
future.set_result(data["result"])
else:
Expand Down Expand Up @@ -346,14 +379,14 @@ async def _handle_messages(self):
except websockets.exceptions.ConnectionClosed as e:
logger.debug(f"WebSocket connection closed: {e}")
# Connection closed, resolve all pending futures with an exception
for future in self.pending_requests.values():
for future, _ in self.pending_requests.values():
if not future.done():
future.set_exception(ConnectionError("WebSocket connection closed"))
self.pending_requests.clear()
except Exception as e:
logger.error(f"Error in message handler: {e}")
# Handle other exceptions
for future in self.pending_requests.values():
for future, _ in self.pending_requests.values():
if not future.done():
future.set_exception(e)
self.pending_requests.clear()
Expand All @@ -379,9 +412,9 @@ async def send_raw(
if session_id:
msg["sessionId"] = session_id

# Create a future for this request
# Create a future for this request, storing method for error handling
future = asyncio.Future()
self.pending_requests[self.msg_id] = future
self.pending_requests[self.msg_id] = (future, method)

await self.ws.send(json.dumps(msg))

Expand Down