Skip to content

Commit 1482de1

Browse files
committed
misc fixes in order to get reconnect test to work
1 parent e07461c commit 1482de1

File tree

7 files changed

+107
-100
lines changed

7 files changed

+107
-100
lines changed

src/idom/server/_asgi.py

+3-11
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,7 @@ async def check_if_started():
2222
await asyncio.sleep(0.2)
2323
started.set()
2424

25-
coros = [server.serve(), check_if_started()]
26-
_, pending = await asyncio.wait(
27-
list(map(asyncio.create_task, coros)), return_when=FIRST_EXCEPTION
28-
)
29-
30-
for task in pending:
31-
task.cancel()
32-
3325
try:
34-
await asyncio.gather(*list(pending))
35-
except CancelledError:
36-
pass
26+
await asyncio.gather(server.serve(), check_if_started())
27+
finally:
28+
await asyncio.wait_for(server.shutdown(), timeout=3)

src/idom/testing.py

+26-25
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def __init__(
136136
self.server = server
137137
if driver is not None:
138138
if isinstance(driver, Page):
139-
self._page = driver
139+
self.page = driver
140140
else:
141141
self._browser = driver
142142
self._next_view_id = 0
@@ -145,29 +145,27 @@ async def show(
145145
self,
146146
component: RootComponentConstructor,
147147
query: dict[str, Any] | None = None,
148-
) -> Page:
148+
) -> None:
149149
self._next_view_id += 1
150150
view_id = f"display-{self._next_view_id}"
151151
self.server.mount(lambda: html.div({"id": view_id}, component()))
152152

153-
await self._page.goto(self.server.url(query=query))
154-
await self._page.wait_for_selector(f"#{view_id}", state="attached")
155-
156-
return self._page
153+
await self.page.goto(self.server.url(query=query))
154+
await self.page.wait_for_selector(f"#{view_id}", state="attached")
157155

158-
async def __aenter__(self: _Self) -> _Self:
156+
async def __aenter__(self) -> DisplayFixture:
159157
es = self._exit_stack = AsyncExitStack()
160158

161-
if not hasattr(self, "_page"):
159+
if not hasattr(self, "page"):
162160
if not hasattr(self, "_browser"):
163161
pw = await es.enter_async_context(async_playwright())
164162
browser = await pw.chromium.launch()
165163
else:
166164
browser = self._browser
167-
self._page = await browser.new_page()
165+
self.page = await browser.new_page()
168166

169167
if not hasattr(self, "server"):
170-
self.server = ServerFixture(**self._server_options)
168+
self.server = ServerFixture()
171169
await es.enter_async_context(self.server)
172170

173171
return self
@@ -196,7 +194,7 @@ class ServerFixture:
196194

197195
_records: list[logging.LogRecord]
198196
_server_future: asyncio.Task[Any]
199-
_exit_stack = ExitStack()
197+
_exit_stack = AsyncExitStack()
200198

201199
def __init__(
202200
self,
@@ -205,7 +203,6 @@ def __init__(
205203
app: Any | None = None,
206204
implementation: ServerImplementation[Any] = default_server,
207205
) -> None:
208-
self.server_implementation = implementation
209206
self.host = host
210207
self.port = port or find_available_port(host, allow_reuse_waiting_ports=False)
211208
self.mount, self._root_component = hotswap()
@@ -216,7 +213,9 @@ def __init__(
216213
"If an application instance its corresponding "
217214
"server implementation must be provided too."
218215
)
216+
219217
self._app = app
218+
self._server_implementation = implementation
220219

221220
@property
222221
def log_records(self) -> List[logging.LogRecord]:
@@ -270,21 +269,28 @@ def list_logged_exceptions(
270269
found.append(error)
271270
return found
272271

273-
async def __aenter__(self: _Self) -> _Self:
274-
self._exit_stack = ExitStack()
272+
async def __aenter__(self) -> ServerFixture:
273+
self._exit_stack = AsyncExitStack()
275274
self._records = self._exit_stack.enter_context(capture_idom_logs())
276275

277-
app = self._app or self.server_implementation.create_development_app()
278-
self.server_implementation.configure(app, self._root_component)
276+
app = self._app or self._server_implementation.create_development_app()
277+
self._server_implementation.configure(app, self._root_component)
279278

280279
started = asyncio.Event()
281-
self._server_future = asyncio.ensure_future(
282-
self.server_implementation.serve_development_app(
280+
server_future = asyncio.create_task(
281+
self._server_implementation.serve_development_app(
283282
app, self.host, self.port, started
284283
)
285284
)
286285

287-
self._exit_stack.callback(self._server_future.cancel)
286+
async def stop_server():
287+
server_future.cancel()
288+
try:
289+
await asyncio.wait_for(server_future, timeout=3)
290+
except asyncio.CancelledError:
291+
pass
292+
293+
self._exit_stack.push_async_callback(stop_server)
288294

289295
try:
290296
await asyncio.wait_for(started.wait(), timeout=3)
@@ -301,15 +307,10 @@ async def __aexit__(
301307
exc_value: Optional[BaseException],
302308
traceback: Optional[TracebackType],
303309
) -> None:
304-
self._exit_stack.close()
310+
await self._exit_stack.aclose()
305311

306312
self.mount(None) # reset the view
307313

308-
try:
309-
await asyncio.wait_for(self._server_future, timeout=3)
310-
except asyncio.CancelledError:
311-
pass
312-
313314
logged_errors = self.list_logged_exceptions(del_log_records=False)
314315
if logged_errors: # pragma: no cover
315316
raise LogAssertionError("Unexpected logged exception") from logged_errors[0]

tests/test_client.py

+27-14
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
import asyncio
22
import time
3+
from contextlib import AsyncExitStack
34
from pathlib import Path
45

6+
from playwright.async_api import Browser
7+
58
import idom
6-
from idom.testing import ServerFixture
9+
from idom.testing import DisplayFixture, ServerFixture
710

811

912
JS_DIR = Path(__file__).parent / "js"
1013

1114

12-
async def test_automatic_reconnect(create_driver):
13-
# we need to wait longer here because the automatic reconnect is not instance
14-
driver = create_driver(implicit_wait_timeout=10, page_load_timeout=10)
15+
async def test_automatic_reconnect(browser: Browser):
16+
page = await browser.new_page()
17+
18+
# we need to wait longer here because the automatic reconnect is not instant
19+
page.set_default_timeout(10000)
1520

1621
@idom.component
1722
def OldComponent():
1823
return idom.html.p({"id": "old-component"}, "old")
1924

20-
mount_point = ServerFixture()
25+
async with AsyncExitStack() as exit_stack:
26+
server = await exit_stack.enter_async_context(ServerFixture(port=8000))
27+
display = await exit_stack.enter_async_context(
28+
DisplayFixture(server, driver=page)
29+
)
30+
31+
await display.show(OldComponent)
2132

22-
async with mount_point:
23-
mount_point.mount(OldComponent)
24-
driver.get(mount_point.url())
2533
# ensure the element is displayed before stopping the server
26-
driver.find_element("id", "old-component")
34+
await page.wait_for_selector("#old-component")
2735

2836
# the server is disconnected but the last view state is still shown
29-
driver.find_element("id", "old-component")
37+
await page.wait_for_selector("#old-component")
3038

3139
set_state = idom.Ref(None)
3240

@@ -35,16 +43,21 @@ def NewComponent():
3543
state, set_state.current = idom.hooks.use_state(0)
3644
return idom.html.p({"id": f"new-component-{state}"}, f"new-{state}")
3745

38-
with mount_point:
39-
mount_point.mount(NewComponent)
46+
async with AsyncExitStack() as exit_stack:
47+
server = await exit_stack.enter_async_context(ServerFixture(port=8000))
48+
display = await exit_stack.enter_async_context(
49+
DisplayFixture(server, driver=page)
50+
)
51+
52+
await display.show(NewComponent)
4053

4154
# Note the lack of a page refresh before looking up this new component. The
4255
# client should attempt to reconnect and display the new view automatically.
43-
driver.find_element("id", "new-component-0")
56+
await page.wait_for_selector("#new-component-0")
4457

4558
# check that we can resume normal operation
4659
set_state.current(1)
47-
driver.find_element("id", "new-component-1")
60+
await page.wait_for_selector("#new-component-1")
4861

4962

5063
def test_style_can_be_changed(display, driver, driver_wait):

tests/test_html.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ def HasScript():
3939
}"""
4040
)
4141

42-
page = await display.show(Root)
42+
await display.show(Root)
4343

44-
mount_state = await page.wait_for_selector("#mount-state", state="attached")
44+
mount_state = await display.page.wait_for_selector("#mount-state", state="attached")
4545
poll_mount_state = poll(mount_state.get_attribute, "data-value")
4646

4747
await poll_mount_state.until_equals("true")
@@ -74,12 +74,14 @@ def HasScript():
7474
),
7575
)
7676

77-
page = await display.show(HasScript)
77+
await display.show(HasScript)
7878

79-
mount_count = await page.wait_for_selector("#mount-count", state="attached")
79+
mount_count = await display.page.wait_for_selector("#mount-count", state="attached")
8080
poll_mount_count = poll(mount_count.get_attribute, "data-value")
8181

82-
unmount_count = await page.wait_for_selector("#unmount-count", state="attached")
82+
unmount_count = await display.page.wait_for_selector(
83+
"#unmount-count", state="attached"
84+
)
8385
poll_unmount_count = poll(unmount_count.get_attribute, "data-value")
8486

8587
await poll_mount_count.until_equals("1")
@@ -114,7 +116,7 @@ def HasScript():
114116
),
115117
)
116118

117-
page = await display.show(HasScript)
119+
await display.show(HasScript)
118120

119121
for i in range(1, 4):
120122
script_file = config.IDOM_WEB_MODULES_DIR.current / file_name_template.format(
@@ -129,7 +131,7 @@ def HasScript():
129131

130132
incr_src_id.current()
131133

132-
run_count = await page.wait_for_selector("#run-count", state="attached")
134+
run_count = await display.page.wait_for_selector("#run-count", state="attached")
133135
poll_run_count = poll(run_count.get_attribute, "data-value")
134136
await poll_run_count.until_equals("1")
135137

tests/test_sample.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44

55
async def test_sample_app(display: DisplayFixture):
6-
page = await display.show(App)
7-
h1 = await page.wait_for_selector("h1")
6+
await display.show(App)
7+
8+
h1 = await display.page.wait_for_selector("h1")
89
assert (await h1.text_content()) == "Sample Application"

tests/test_web/test_module.py

+18-16
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,19 @@ def ShowCurrentComponent():
4040
)
4141
return current_component
4242

43-
page = await display.show(ShowCurrentComponent)
43+
await display.show(ShowCurrentComponent)
4444

45-
await page.wait_for_selector("#some-component", state="attached")
45+
await display.page.wait_for_selector("#some-component", state="attached")
4646

4747
set_current_component.current(
4848
idom.html.h1({"id": "some-other-component"}, "some other component")
4949
)
5050

5151
# the new component has been displayed
52-
await page.wait_for_selector("#some-other-component", state="attached")
52+
await display.page.wait_for_selector("#some-other-component", state="attached")
5353

5454
# the unmount callback for the old component was called
55-
await page.wait_for_selector("#unmount-flag", state="attached")
55+
await display.page.wait_for_selector("#unmount-flag", state="attached")
5656

5757

5858
async def test_module_from_url(browser):
@@ -76,8 +76,9 @@ def ShowSimpleButton():
7676

7777
async with ServerFixture(app=app, implementation=sanic_implementation) as server:
7878
async with DisplayFixture(server, browser) as display:
79-
page = await display.show(ShowSimpleButton)
80-
await page.wait_for_selector("#my-button")
79+
await display.show(ShowSimpleButton)
80+
81+
await display.page.wait_for_selector("#my-button")
8182

8283

8384
def test_module_from_template_where_template_does_not_exist():
@@ -88,8 +89,9 @@ def test_module_from_template_where_template_does_not_exist():
8889
async def test_module_from_template(display: DisplayFixture):
8990
victory = idom.web.module_from_template("react", "[email protected]")
9091
VictoryBar = idom.web.export(victory, "VictoryBar")
91-
page = await display.show(VictoryBar)
92-
await page.wait_for_selector(".VictoryContainer")
92+
await display.show(VictoryBar)
93+
94+
await display.page.wait_for_selector(".VictoryContainer")
9395

9496

9597
async def test_module_from_file(display: DisplayFixture):
@@ -108,9 +110,9 @@ def ShowSimpleButton():
108110
{"id": "my-button", "onClick": lambda event: is_clicked.set_current(True)}
109111
)
110112

111-
page = await display.show(ShowSimpleButton)
113+
await display.show(ShowSimpleButton)
112114

113-
button = await page.wait_for_selector("#my-button")
115+
button = await display.page.wait_for_selector("#my-button")
114116
await button.click()
115117
poll(lambda: is_clicked.current).until_is(True)
116118

@@ -198,13 +200,13 @@ async def test_module_exports_multiple_components(display: DisplayFixture):
198200
["Header1", "Header2"],
199201
)
200202

201-
page = await display.show(lambda: Header1({"id": "my-h1"}, "My Header 1"))
203+
await display.show(lambda: Header1({"id": "my-h1"}, "My Header 1"))
202204

203-
await page.wait_for_selector("#my-h1", state="attached")
205+
await display.page.wait_for_selector("#my-h1", state="attached")
204206

205-
page = await display.show(lambda: Header2({"id": "my-h2"}, "My Header 2"))
207+
await display.show(lambda: Header2({"id": "my-h2"}, "My Header 2"))
206208

207-
await page.wait_for_selector("#my-h2", state="attached")
209+
await display.page.wait_for_selector("#my-h2", state="attached")
208210

209211

210212
async def test_imported_components_can_render_children(display: DisplayFixture):
@@ -213,15 +215,15 @@ async def test_imported_components_can_render_children(display: DisplayFixture):
213215
)
214216
Parent, Child = idom.web.export(module, ["Parent", "Child"])
215217

216-
page = await display.show(
218+
await display.show(
217219
lambda: Parent(
218220
Child({"index": 1}),
219221
Child({"index": 2}),
220222
Child({"index": 3}),
221223
)
222224
)
223225

224-
parent = await page.wait_for_selector("#the-parent", state="attached")
226+
parent = await display.page.wait_for_selector("#the-parent", state="attached")
225227
children = await parent.query_selector_all("li")
226228

227229
assert len(children) == 3

0 commit comments

Comments
 (0)