Skip to content

Commit 4530dfa

Browse files
CopilotP4X-ng
andcommitted
Merge PR #23: Add event handling documentation and network events example
Co-authored-by: P4X-ng <223870169+P4X-ng@users.noreply.github.com>
1 parent 7d2facb commit 4530dfa

3 files changed

Lines changed: 172 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ async with open_cdp(cdp_url) as conn:
4141
```
4242

4343
This example code is explained [in the documentation](https://trio-cdp.readthedocs.io)
44-
and more example code can be found in the `examples/` directory of this repository.
44+
and more example code can be found in the `examples/` directory of this repository,
45+
including examples for taking screenshots and monitoring network events.

docs/getting_started.rst

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,63 @@ we get the outer HTML of the node. This snippet shows some new APIs, but the
120120
mechanics of sending commands and getting responses are the same as the previous
121121
snippets.
122122

123-
A more complete version of this example can be found in ``examples/get_title.py`` in
124-
the repository. There is also a screenshot example in ``examples/screenshot.py``. The
125-
unit tests in ``tests/`` also provide some helpful sample code.
123+
Listening to Events
124+
-------------------
125+
126+
Trio CDP provides two patterns for handling browser events:
127+
128+
Using ``wait_for()``
129+
~~~~~~~~~~~~~~~~~~~~
130+
131+
The ``wait_for()`` method is useful when you need to wait for a single event before
132+
continuing execution. We've already seen this in the navigation example above, where
133+
we wait for ``page.LoadEventFired``. Here's the pattern:
134+
135+
.. code::
136+
137+
async with session.wait_for(page.LoadEventFired) as event_proxy:
138+
# Trigger an action that will cause the event
139+
await page.navigate(url='https://example.com')
140+
# After the context exits, event_proxy.value contains the event
141+
print(f"Page loaded at timestamp: {event_proxy.value.timestamp}")
142+
143+
Using ``listen()``
144+
~~~~~~~~~~~~~~~~~~
145+
146+
The ``listen()`` method returns an async iterator that continuously yields events as
147+
they occur. This is useful for monitoring ongoing activity, such as network requests:
148+
149+
.. code::
150+
151+
# Enable network events
152+
await network.enable()
153+
154+
# Listen for network events
155+
async for event in session.listen(
156+
network.RequestWillBeSent,
157+
network.ResponseReceived
158+
):
159+
if isinstance(event, network.RequestWillBeSent):
160+
print(f"Request: {event.request.url}")
161+
elif isinstance(event, network.ResponseReceived):
162+
print(f"Response: {event.response.url} (status: {event.response.status})")
163+
164+
You can listen to multiple event types at once by passing them all to ``listen()``.
165+
The iterator will yield events of any of the specified types as they occur.
166+
167+
**Important:** Don't forget to enable events for the domain you're interested in!
168+
For example, call ``await network.enable()`` before listening to network events,
169+
or ``await page.enable()`` before listening to page events. You can also use the
170+
context managers ``session.page_enable()`` or ``session.dom_enable()`` for automatic
171+
cleanup.
172+
173+
Examples
174+
--------
175+
176+
A more complete version of the basic example can be found in ``examples/get_title.py`` in
177+
the repository. There are additional examples showing:
178+
179+
- ``examples/screenshot.py`` - Taking screenshots of web pages
180+
- ``examples/network_events.py`` - Monitoring network events using both ``wait_for()`` and ``listen()``
181+
182+
The unit tests in ``tests/`` also provide helpful sample code.

examples/network_events.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
'''
2+
Monitor network events from a web page.
3+
4+
This example demonstrates how to listen to network events such as
5+
RequestWillBeSent, ResponseReceived, etc. It shows both patterns for
6+
handling events:
7+
8+
1. wait_for() - wait for a single event
9+
2. listen() - continuously listen for multiple events
10+
11+
To use this example, start Chrome (or any other browser that supports CDP) with
12+
the option `--remote-debugging-port=9000`. The URL that Chrome is listening on
13+
is displayed in the terminal after Chrome starts up.
14+
15+
Then run this script with the Chrome URL as the first argument and the target
16+
website URL as the second argument:
17+
18+
$ python examples/network_events.py \
19+
ws://localhost:9000/devtools/browser/facfb2295-... \
20+
https://www.hyperiongray.com
21+
'''
22+
import logging
23+
import os
24+
import sys
25+
26+
import trio
27+
from trio_cdp import open_cdp, network, page, target
28+
29+
30+
log_level = os.environ.get('LOG_LEVEL', 'info').upper()
31+
logging.basicConfig(level=getattr(logging, log_level))
32+
logger = logging.getLogger('network_events')
33+
logging.getLogger('trio-websocket').setLevel(logging.WARNING)
34+
35+
36+
async def main():
37+
logger.info('Connecting to browser: %s', sys.argv[1])
38+
async with open_cdp(sys.argv[1]) as conn:
39+
logger.info('Listing targets')
40+
targets = await target.get_targets()
41+
42+
for t in targets:
43+
if (t.type == 'page' and
44+
not t.url.startswith('devtools://') and
45+
not t.attached):
46+
target_id = t.target_id
47+
break
48+
49+
logger.info('Attaching to target id=%s', target_id)
50+
async with conn.open_session(target_id) as session:
51+
52+
# Enable network events
53+
logger.info('Enabling network events')
54+
await network.enable()
55+
56+
# Enable page events for navigation
57+
logger.info('Enabling page events')
58+
await page.enable()
59+
60+
# Pattern 1: Using wait_for() to wait for a specific event
61+
# This is useful when you need to wait for a single event before
62+
# continuing execution.
63+
logger.info('Navigating to %s (using wait_for pattern)', sys.argv[2])
64+
async with session.wait_for(page.LoadEventFired):
65+
await page.navigate(url=sys.argv[2])
66+
logger.info('Page loaded')
67+
68+
# Pattern 2: Using listen() to continuously monitor events
69+
# This creates an async iterator that yields events as they occur.
70+
# You can listen to multiple event types simultaneously.
71+
logger.info('Monitoring network events (press Ctrl+C to stop)...')
72+
73+
# Create an async iterator for network events
74+
async for event in session.listen(
75+
network.RequestWillBeSent,
76+
network.ResponseReceived,
77+
network.LoadingFinished,
78+
network.LoadingFailed
79+
):
80+
# Handle different event types
81+
if isinstance(event, network.RequestWillBeSent):
82+
logger.info(
83+
'Request: %s %s',
84+
event.request.method,
85+
event.request.url
86+
)
87+
elif isinstance(event, network.ResponseReceived):
88+
logger.info(
89+
'Response: %s (status: %d)',
90+
event.response.url,
91+
event.response.status
92+
)
93+
elif isinstance(event, network.LoadingFinished):
94+
logger.info('Loading finished: %s', event.request_id)
95+
elif isinstance(event, network.LoadingFailed):
96+
logger.info(
97+
'Loading failed: %s (error: %s)',
98+
event.request_id,
99+
event.error_text
100+
)
101+
102+
103+
if __name__ == '__main__':
104+
if len(sys.argv) != 3:
105+
sys.stderr.write('Usage: network_events.py <browser url> <target url>\n')
106+
sys.exit(1)
107+
try:
108+
trio.run(main, restrict_keyboard_interrupt_to_checkpoints=True)
109+
except KeyboardInterrupt:
110+
logger.info('Interrupted by user')

0 commit comments

Comments
 (0)