Skip to content

Commit ee7598f

Browse files
committed
Refactor handling of closed WebSocket connection (fixes #5)
This is refactored to be more similar to the way trio-websocket works: the reader task should will catch ConnectionClosed and quietly exit. The public APIs (i.e. session.execute()) will raise ConnectionClosed instead. It's easier for the caller to catch an exception in a foreground task than in a background task.
1 parent c317a26 commit ee7598f

9 files changed

Lines changed: 135 additions & 50 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.coverage
22
*.egg-info
33
.mypy_cache
4+
.vscode
45
__pycache__
56
dist
67
test.png

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ publish: test
66
twine upload dist/*
77

88
test:
9-
pytest test/ --cov=trio_cdp --cov-report=term-missing
9+
pytest tests/ --cov=trio_cdp --cov-report=term-missing

README.md

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ multiplex commands, responses, and events over a single connection.
1515
The example demonstrates the salient features of the library.
1616

1717
```python
18-
async with open_cdp_connection(cdp_url) as conn:
18+
async with open_cdp(cdp_url) as conn:
1919
# Find the first available target (usually a browser tab).
2020
targets = await conn.execute(target.get_targets())
2121
target_id = targets[0].id
@@ -40,7 +40,7 @@ We'll go through this example bit by bit. First, it starts with a context
4040
manager:
4141

4242
```python
43-
async with open_cdp_connection(cdp_url) as conn:
43+
async with open_cdp(cdp_url) as conn:
4444
```
4545

4646
This context manager opens a connection to the browser when the block is entered
@@ -119,11 +119,11 @@ snippets.
119119

120120
A more complete version of this example can be found in `examples/get_title.py`.
121121
There is also a screenshot example in `examples/screenshot.py`. The unit tests
122-
in `test/` also provide more examples.
122+
in `tests/` also provide more examples.
123123

124124
To run the examples, you need a Chrome binary in your system. You can get one like this:
125125

126-
#### FOR MAC
126+
## Running Examples on MacOS
127127

128128
**Terminal 1**
129129

@@ -132,7 +132,7 @@ This sets up the chrome browser in a specific version, and runs it in debug mode
132132
```
133133
wget https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac%2F678035%2Fchrome-mac.zip?generation=1563322360871926&alt=media
134134
unzip chrome-mac.zip && rm chrome-mac.zip
135-
./chrome-mac/Chromium.app/Contents/MacOS/Chromium --remote-debugging-port=9000
135+
./chrome-mac/Chromium.app/Contents/MacOS/Chromium --remote-debugging-port=9000
136136
> DevTools listening on ws://127.0.0.1:9000/devtools/browser/<DEV_SESSION_GUID>
137137
```
138138

@@ -144,7 +144,7 @@ This runs the example browser automation script on the instantiated browser wind
144144
python examples/get_title.py ws://127.0.0.1:9000/devtools/browser/<DEV_SESSION_GUID> https://hyperiongray.com
145145
```
146146

147-
#### FOR LINUX
147+
## Running Examples on Linux
148148

149149
**Terminal 1**
150150

@@ -153,7 +153,7 @@ This sets up the chrome browser in a specific version, and runs it in debug mode
153153
```
154154
wget https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/678025/chrome-linux.zip
155155
unzip chrome-linux.zip && rm chrome-linux.zip
156-
./chrome-linux/chrome --remote-debugging-port=9000
156+
./chrome-linux/chrome --remote-debugging-port=9000
157157
> DevTools listening on ws://127.0.0.1:9000/devtools/browser/<DEV_SESSION_GUID>
158158
```
159159

@@ -165,7 +165,27 @@ This runs the example browser automation script on the instantiated browser wind
165165
python examples/get_title.py ws://127.0.0.1:9000/devtools/browser/<DEV_SESSION_GUID> https://hyperiongray.com
166166
```
167167

168-
<a href="https://www.hyperiongray.com/?pk_campaign=github&pk_kwd=trio-cdp"><img alt="define hyperion gray" width="500px" src="https://hyperiongray.s3.amazonaws.com/define-hg.svg"></a>
168+
## Changelog
169+
170+
### 0.5.0
171+
172+
* **Backwards Compability Break:** Rename `open_cdp_connection()` to `open_cdp()`.
173+
* Fix `ConnectionClosed` bug.
174+
175+
### 0.4.0
176+
177+
* Add support for passing in a nursery. (Supports usage in Jupyter notebook.)
178+
179+
### 0.3.0
169180

170-
id=E89C70427E6B7D2F56365B3E4C2268AA
171-
id=CC6E9EA42D2FFBABEDCC4E3282EF2A74
181+
* New APIs for enabling DOM events and Page events.
182+
183+
### 0.2.0
184+
185+
* Restructure event listeners.
186+
187+
### 0.1.0
188+
189+
* Initial version
190+
191+
<a href="https://www.hyperiongray.com/?pk_campaign=github&pk_kwd=trio-cdp"><img alt="define hyperion gray" width="500px" src="https://hyperiongray.s3.amazonaws.com/define-hg.svg"></a>

examples/get_title.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from cdp import dom, page, target
2020
import trio
21-
from trio_cdp import open_cdp_connection
21+
from trio_cdp import open_cdp
2222

2323

2424
log_level = os.environ.get('LOG_LEVEL', 'info').upper()
@@ -29,7 +29,7 @@
2929

3030
async def main():
3131
logger.info('Connecting to browser: %s', sys.argv[1])
32-
async with open_cdp_connection(sys.argv[1]) as conn:
32+
async with open_cdp(sys.argv[1]) as conn:
3333
logger.info('Listing targets')
3434
targets = await conn.execute(target.get_targets())
3535

examples/screenshot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from cdp import emulation, page, target
2121
import trio
22-
from trio_cdp import open_cdp_connection
22+
from trio_cdp import open_cdp
2323

2424

2525
log_level = os.environ.get('LOG_LEVEL', 'info').upper()
@@ -30,10 +30,10 @@
3030

3131
async def main():
3232
logger.info('Connecting to browser: %s', sys.argv[1])
33-
async with open_cdp_connection(sys.argv[1]) as conn:
33+
async with open_cdp(sys.argv[1]) as conn:
3434
logger.info('Listing targets')
3535
targets = await conn.execute(target.get_targets())
36-
36+
3737
for t in targets:
3838
if (t.type == 'page' and
3939
not t.url.startswith('devtools://') and

examples/take_heap_snapshot.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
the option `--remote-debugging-port=9000`. The URL that Chrome is listening on
66
is displayed in the terminal after Chrome starts up.
77
8-
Then run this script with the Chrome URL as the first argument
8+
Then run this script with the Chrome URL as the first argument
99
1010
$ python examples/take_heap_snapshot.py \
11-
ws://localhost:9000/devtools/browser/facfb2295-...
11+
ws://localhost:9000/devtools/browser/facfb2295-...
1212
'''
1313
from datetime import datetime
1414
import logging
@@ -17,7 +17,7 @@
1717

1818
from cdp import browser, dom, heap_profiler, page, target
1919
import trio
20-
from trio_cdp import open_cdp_connection
20+
from trio_cdp import open_cdp
2121

2222

2323
log_level = os.environ.get('LOG_LEVEL', 'info').upper()
@@ -52,7 +52,7 @@ async def progress_helper():
5252

5353
async def main():
5454
cdp_uri = sys.argv[1]
55-
async with open_cdp_connection(cdp_uri) as conn:
55+
async with open_cdp(cdp_uri) as conn:
5656
logger.info('Connecting')
5757
targets = await conn.execute(target.get_targets())
5858
target_id = targets[0].target_id

tests/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from functools import wraps
2+
3+
import pytest
4+
import trio
5+
6+
7+
class fail_after:
8+
''' This decorator fails if the runtime of the decorated function (as
9+
measured by the Trio clock) exceeds the specified value. '''
10+
def __init__(self, seconds):
11+
self._seconds = seconds
12+
13+
def __call__(self, fn):
14+
@wraps(fn)
15+
async def wrapper(*args, **kwargs):
16+
with trio.move_on_after(self._seconds) as cancel_scope:
17+
await fn(*args, **kwargs)
18+
if cancel_scope.cancelled_caught:
19+
pytest.fail('Test runtime exceeded the maximum {} seconds'
20+
.format(self._seconds))
21+
return wrapper
Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import trio
77
from trio_websocket import serve_websocket
88

9-
from trio_cdp import BrowserError, open_cdp_connection
9+
from . import fail_after
10+
from trio_cdp import BrowserError, open_cdp
1011

1112

1213
HOST = '127.0.0.1'
@@ -26,6 +27,7 @@ def test_browser_error():
2627
'This is extra data about the error'
2728

2829

30+
@fail_after(1)
2931
async def test_connection_execute(nursery):
3032
''' Open a connection and execute a command on it. '''
3133
async def handler(request):
@@ -53,13 +55,13 @@ async def handler(request):
5355
except Exception:
5456
logging.exception('Server exception')
5557
server = await start_server(nursery, handler)
56-
57-
async with open_cdp_connection(server) as conn:
58+
async with open_cdp(server) as conn:
5859
targets = await conn.execute(target.get_targets())
5960
assert len(targets) == 1
6061
assert isinstance(targets[0], target.TargetInfo)
6162

6263

64+
@fail_after(1)
6365
async def test_connection_invalid_json():
6466
''' If the server sends invalid JSON, that exception is raised on the reader
6567
task, which crashes the entire connection. Therefore, the entire test needs
@@ -79,11 +81,12 @@ async def handler(request):
7981
logging.exception('Server exception')
8082
server = await start_server(nursery, handler)
8183

82-
async with open_cdp_connection(server) as conn:
84+
async with open_cdp(server) as conn:
8385
targets = await conn.execute(target.get_targets())
8486
assert exc_info.value.code == -32700 # JSON parse error
8587

8688

89+
@fail_after(1)
8790
async def test_connection_browser_error(nursery):
8891
''' If the browser sends an error with a valid command ID, then that error
8992
should be raised at the point where the command was executed. Compare to
@@ -110,13 +113,14 @@ async def handler(request):
110113
logging.exception('Server exception')
111114
server = await start_server(nursery, handler)
112115

113-
async with open_cdp_connection(server) as conn:
116+
async with open_cdp(server) as conn:
114117
with pytest.raises(BrowserError) as exc_info:
115118
targets = await conn.execute(target.get_targets())
116119

117120
assert exc_info.value.code == -32000
118121

119122

123+
@fail_after(1)
120124
async def test_session_execute(nursery):
121125
''' Open a session and execute a command on it. '''
122126
async def handler(request):
@@ -159,14 +163,15 @@ async def handler(request):
159163
logging.exception('Server exception')
160164
server = await start_server(nursery, handler)
161165

162-
async with open_cdp_connection(server) as conn:
166+
async with open_cdp(server) as conn:
163167
session = await conn.open_session(target.TargetID('target1'))
164168
assert session.session_id == 'session1'
165169
node_id = await session.execute(
166170
dom.query_selector(dom.NodeId(0),'p.foo'))
167171
assert node_id == 1
168172

169173

174+
@fail_after(1)
170175
async def test_wait_for_event(nursery):
171176
''' The server sends 2 different events. The client is listening for a
172177
specific type of event and therefore only sees the 2nd one. '''
@@ -196,7 +201,7 @@ async def handler(request):
196201
logging.exception('Server exception')
197202
server = await start_server(nursery, handler)
198203

199-
async with open_cdp_connection(server) as conn:
204+
async with open_cdp(server) as conn:
200205
async with conn.wait_for(page.LoadEventFired) as event:
201206
# In real code we would do something here to trigger a load event,
202207
# e.g. clicking a link.
@@ -205,6 +210,7 @@ async def handler(request):
205210
assert event.value.timestamp == 2
206211

207212

213+
@fail_after(1)
208214
async def test_listen_for_events(nursery):
209215
''' The server sends 2 different events. The client is listening for a
210216
specific type of event and therefore only sees the 2nd one. '''
@@ -234,7 +240,7 @@ async def handler(request):
234240
logging.exception('Server exception')
235241
server = await start_server(nursery, handler)
236242

237-
async with open_cdp_connection(server) as conn:
243+
async with open_cdp(server) as conn:
238244
n = 1
239245
async for event in conn.listen(page.LoadEventFired):
240246
assert isinstance(event, page.LoadEventFired)

0 commit comments

Comments
 (0)