Skip to content

Commit 2168c87

Browse files
CopilotP4X-ng
andauthored
Address code review: fix CDPConnectionError import, JS escaping, execCommand replacement, test clarity
Agent-Logs-Url: https://github.com/HyperionGray/python-chrome-devtools-protocol/sessions/f5aedfd0-e970-4dc1-bc96-e614a946247f Co-authored-by: P4X-ng <223870169+P4X-ng@users.noreply.github.com>
1 parent 7a37092 commit 2168c87

File tree

3 files changed

+37
-14
lines changed

3 files changed

+37
-14
lines changed

cdp/browser_control.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ async def main():
3737

3838
import asyncio
3939
import base64
40+
import json as _json
4041
import typing
4142

4243
from cdp import dom, input_, page, runtime
43-
from cdp.connection import CDPConnection
44+
from cdp.connection import CDPConnection, CDPConnectionError
4445

4546
__all__ = [
4647
# Navigation
@@ -330,10 +331,28 @@ async def clear_and_type(
330331
else selector_or_node
331332
)
332333
await focus(conn, node)
333-
# Select all – platform-agnostic via JavaScript.
334-
await conn.execute(
335-
runtime.evaluate(expression="document.execCommand('selectAll', false, null)")
336-
)
334+
# Select all existing content using the modern Selection/input API.
335+
# For input/textarea: element.select(); for contenteditable: window.getSelection().
336+
obj = await conn.execute(dom.resolve_node(node_id=node))
337+
if obj and obj.object_id:
338+
await conn.execute(
339+
runtime.call_function_on(
340+
function_declaration=(
341+
"function() {"
342+
" if (typeof this.select === 'function') {"
343+
" this.select();"
344+
" } else {"
345+
" var range = document.createRange();"
346+
" range.selectNodeContents(this);"
347+
" var sel = window.getSelection();"
348+
" sel.removeAllRanges();"
349+
" sel.addRange(range);"
350+
" }"
351+
"}"
352+
),
353+
object_id=obj.object_id,
354+
)
355+
)
337356
await type_text(conn, node, text, delay=delay)
338357

339358

@@ -396,8 +415,9 @@ async def select_option(
396415
obj = await conn.execute(dom.resolve_node(node_id=node))
397416
if obj is None:
398417
raise ValueError("Could not resolve node to a remote object")
399-
escaped = value.replace("'", "\\'")
400-
expr = f"function() {{ this.value = '{escaped}'; this.dispatchEvent(new Event('change', {{bubbles: true}})); }}"
418+
# Use json.dumps to safely embed the value as a JS string literal.
419+
js_value = _json.dumps(value)
420+
expr = f"function() {{ this.value = {js_value}; this.dispatchEvent(new Event('change', {{bubbles: true}})); }}"
401421
await conn.execute(
402422
runtime.call_function_on(
403423
function_declaration=expr,
@@ -711,10 +731,9 @@ async def _wait() -> _T_Event:
711731
async for event in conn.listen():
712732
if isinstance(event, event_type):
713733
return event # type: ignore[return-value]
714-
raise CDPConnectionError("Connection closed before event arrived") # noqa: F821
734+
raise CDPConnectionError("Connection closed before event arrived")
715735

716736
try:
717-
from cdp.connection import CDPConnectionError # local import to avoid circular
718737
return await asyncio.wait_for(_wait(), timeout=timeout)
719738
except asyncio.TimeoutError:
720739
raise asyncio.TimeoutError(

examples/browser_control_example.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
Start Chrome with remote debugging enabled:
1111
chrome --remote-debugging-port=9222 --headless
1212
13-
Then run:
13+
Get the WebSocket URL for a tab:
14+
curl http://localhost:9222/json
15+
16+
Then update CDP_URL below with the "webSocketDebuggerUrl" value and run:
1417
python examples/browser_control_example.py
1518
"""
1619

@@ -20,8 +23,8 @@
2023
from cdp import page
2124

2225

23-
# Replace with a real CDP WebSocket URL from chrome://inspect or the
24-
# JSON endpoint at http://localhost:9222/json
26+
# Replace with the "webSocketDebuggerUrl" from http://localhost:9222/json
27+
# Example: "ws://localhost:9222/devtools/page/ABC123"
2528
CDP_URL = "ws://localhost:9222/devtools/page/YOUR_PAGE_ID"
2629

2730

test/test_browser_control.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,10 @@ async def test_wait_for_selector_timeout():
407407
local_name="",
408408
node_value="",
409409
)
410-
# Always return NodeId(0) = not found
410+
# Always return NodeId(0) = not found; repeat enough times for the timeout
411+
_ENOUGH_ATTEMPTS = 20 # timeout=0.1s / poll_interval=0.05s → at most ~4 polls
411412
conn = MagicMock()
412-
conn.execute = AsyncMock(side_effect=[doc_node, dom.NodeId(0)] * 20)
413+
conn.execute = AsyncMock(side_effect=[doc_node, dom.NodeId(0)] * _ENOUGH_ATTEMPTS)
413414
with pytest.raises(asyncio.TimeoutError):
414415
await wait_for_selector(conn, ".ghost", timeout=0.1, poll_interval=0.05)
415416

0 commit comments

Comments
 (0)