Skip to content

Commit 076b226

Browse files
authored
Merge branch 'main' into andrew/remove_unused_code
2 parents c1e8663 + daa6092 commit 076b226

File tree

4 files changed

+230
-1
lines changed

4 files changed

+230
-1
lines changed

CHANGELOG.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
- Remove unused system inspection code
2+
- Add a set of helper functions to await for tab loading and send javascript
23
v1.2.1
34
- Use custom threadpool for functions that could be running during shutdown:
45
Python's stdlib threadpool isn't available during interpreter shutdown, nor

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ asyncio_default_fixture_loop_scope = "function"
109109
log_cli = false
110110
addopts = "--import-mode=append"
111111

112+
# tell poe to use the env we give it, otherwise it detects uv and overrides flags
112113
[tool.poe]
113-
executor.type = "virtualenv"
114+
executor.type = "simple"
114115

115116
[tool.poe.tasks]
116117
test_proc = "pytest --log-level=1 -W error -n auto -v -rfE --capture=fd tests/test_process.py"
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Async helper functions for common Chrome DevTools Protocol patterns."""
2+
3+
from __future__ import annotations
4+
5+
import asyncio
6+
from typing import TYPE_CHECKING
7+
8+
if TYPE_CHECKING:
9+
from choreographer import Browser, Tab
10+
11+
from . import BrowserResponse
12+
13+
14+
async def create_and_wait(
15+
browser: Browser,
16+
url: str = "",
17+
*,
18+
timeout: float = 30.0,
19+
) -> Tab:
20+
"""
21+
Create a new tab and wait for it to load.
22+
23+
Args:
24+
browser: Browser instance
25+
url: URL to navigate to (default: blank page)
26+
timeout: Seconds to wait for page load (default: 30.0)
27+
28+
Returns:
29+
The created Tab
30+
31+
"""
32+
tab = await browser.create_tab(url)
33+
temp_session = await tab.create_session()
34+
35+
try:
36+
load_future = temp_session.subscribe_once("Page.loadEventFired")
37+
await temp_session.send_command("Page.enable")
38+
await temp_session.send_command("Runtime.enable")
39+
40+
if url:
41+
try:
42+
await asyncio.wait_for(load_future, timeout=timeout)
43+
except (asyncio.TimeoutError, asyncio.CancelledError, TimeoutError):
44+
# Stop the page load when timeout occurs
45+
await temp_session.send_command("Page.stopLoading")
46+
raise
47+
finally:
48+
await tab.close_session(temp_session.session_id)
49+
50+
return tab
51+
52+
53+
async def navigate_and_wait(
54+
tab: Tab,
55+
url: str,
56+
*,
57+
timeout: float = 30.0,
58+
) -> Tab:
59+
"""
60+
Navigate an existing tab to a URL and wait for it to load.
61+
62+
Args:
63+
tab: Tab to navigate
64+
url: URL to navigate to
65+
timeout: Seconds to wait for page load (default: 30.0)
66+
67+
Returns:
68+
The Tab after navigation completes
69+
70+
"""
71+
temp_session = await tab.create_session()
72+
73+
try:
74+
await temp_session.send_command("Page.enable")
75+
await temp_session.send_command("Runtime.enable")
76+
load_future = temp_session.subscribe_once("Page.loadEventFired")
77+
try:
78+
79+
async def _freezers():
80+
# If no resolve, will freeze
81+
await temp_session.send_command("Page.navigate", params={"url": url})
82+
# Can freeze if resolve bad
83+
await load_future
84+
85+
await asyncio.wait_for(_freezers(), timeout=timeout)
86+
except (asyncio.TimeoutError, asyncio.CancelledError, TimeoutError):
87+
# Stop the navigation when timeout occurs
88+
await temp_session.send_command("Page.stopLoading")
89+
raise
90+
finally:
91+
await tab.close_session(temp_session.session_id)
92+
93+
return tab
94+
95+
96+
async def execute_js_and_wait(
97+
tab: Tab,
98+
expression: str,
99+
*,
100+
timeout: float = 30.0,
101+
) -> BrowserResponse:
102+
"""
103+
Execute JavaScript in a tab and return the result.
104+
105+
Args:
106+
tab: Tab to execute JavaScript in
107+
expression: JavaScript expression to evaluate
108+
timeout: Seconds to wait for execution (default: 30.0)
109+
110+
Returns:
111+
Response dict from Runtime.evaluate with 'result' and optional
112+
'exceptionDetails'
113+
114+
"""
115+
temp_session = await tab.create_session()
116+
117+
try:
118+
await temp_session.send_command("Page.enable")
119+
await temp_session.send_command("Runtime.enable")
120+
121+
response = await asyncio.wait_for(
122+
temp_session.send_command(
123+
"Runtime.evaluate",
124+
params={
125+
"expression": expression,
126+
"awaitPromise": True,
127+
"returnByValue": True,
128+
},
129+
),
130+
timeout=timeout,
131+
)
132+
133+
return response
134+
finally:
135+
await tab.close_session(temp_session.session_id)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import asyncio
2+
3+
import logistro
4+
import pytest
5+
6+
from choreographer.protocol.devtools_async_helpers import (
7+
create_and_wait,
8+
execute_js_and_wait,
9+
navigate_and_wait,
10+
)
11+
12+
pytestmark = pytest.mark.asyncio(loop_scope="function")
13+
14+
_logger = logistro.getLogger(__name__)
15+
16+
17+
# Errata: don't use data urls, whether or not they load is variable
18+
# depends on how long chrome has been open for, how they were entered,
19+
# etc
20+
21+
22+
@pytest.mark.asyncio
23+
async def test_create_and_wait(browser):
24+
"""Test create_and_wait with both valid data URL and blank URL."""
25+
_logger.info("testing create_and_wait...")
26+
27+
# Count tabs before
28+
initial_tab_count = len(browser.tabs)
29+
30+
# Create a simple HTML page as a data URL
31+
data_url = "chrome://version"
32+
33+
# Test 1: Create tab with data URL - should succeed
34+
tab1 = await create_and_wait(browser, url=data_url, timeout=5.0)
35+
assert tab1 is not None
36+
37+
# Verify the page loaded correctly using execute_js_and_wait
38+
result = await execute_js_and_wait(tab1, "window.location.href", timeout=5.0)
39+
location = result["result"]["result"]["value"]
40+
assert location.startswith(data_url)
41+
42+
# Test 2: Create tab without URL - should succeed (blank page)
43+
tab2 = await create_and_wait(browser, url="", timeout=5.0)
44+
assert tab2 is not None
45+
46+
# Verify we have 2 more tabs
47+
final_tab_count = len(browser.tabs)
48+
assert final_tab_count == initial_tab_count + 2
49+
50+
# Test 3: Create tab with bad URL that won't load - should timeout
51+
with pytest.raises(asyncio.TimeoutError):
52+
await create_and_wait(browser, url="http://192.0.2.1:9999", timeout=0.5)
53+
54+
55+
@pytest.mark.asyncio
56+
async def test_navigate_and_wait(browser):
57+
"""Test navigate_and_wait with both valid data URL and bad URL."""
58+
_logger.info("testing navigate_and_wait...")
59+
# Create two blank tabs first
60+
tab = await browser.create_tab("")
61+
62+
# navigating to dataurls seems to be fine right now,
63+
# but if one day you have an error here,
64+
# change to the strategy above
65+
66+
# Create a data URL with identifiable content
67+
html_content1 = "<html><body><h1>Navigation Test 1</h1></body></html>"
68+
data_url1 = f"data:text/html,{html_content1}"
69+
70+
html_content2 = "<html><body><h1>Navigation Test 2</h1></body></html>"
71+
data_url2 = f"data:text/html,{html_content2}"
72+
73+
# Test 1: Navigate first tab to valid data URL - should succeed
74+
result_tab1 = await navigate_and_wait(tab, url=data_url1, timeout=5.0)
75+
assert result_tab1 is tab
76+
77+
# Verify the navigation succeeded using execute_js_and_wait
78+
result = await execute_js_and_wait(tab, "window.location.href", timeout=5.0)
79+
location = result["result"]["result"]["value"]
80+
assert location.startswith("data:text/html")
81+
82+
# Test 2: Navigate second tab to another valid data URL - should succeed
83+
result_tab2 = await navigate_and_wait(tab, url=data_url2, timeout=5.0)
84+
assert result_tab2 is tab
85+
86+
# Verify the navigation succeeded
87+
result = await execute_js_and_wait(tab, "window.location.href", timeout=5.0)
88+
location = result["result"]["result"]["value"]
89+
assert location.startswith("data:text/html")
90+
# Test 3: Navigate to bad URL that won't load - should timeout
91+
with pytest.raises(asyncio.TimeoutError):
92+
await navigate_and_wait(tab, url="http://192.0.2.1:9999", timeout=0.5)

0 commit comments

Comments
 (0)