Skip to content

Commit eaa4237

Browse files
committed
Add integration tests for frontend_path redirects
Test on_load and on_mount redirects with and without a frontend_path prefix in both dev mode (AppHarness) and prod mode (AppHarnessProd). See #5674
1 parent 7f5883c commit eaa4237

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
import os
2+
from collections.abc import Generator
3+
4+
import pytest
5+
from playwright.sync_api import Page, expect
6+
7+
from reflex.testing import AppHarness, AppHarnessProd
8+
9+
10+
def OnLoadRedirectApp():
11+
"""App to demonstrate an on_load redirection issue.
12+
13+
See https://github.com/reflex-dev/reflex/issues/5674 for details.
14+
"""
15+
import reflex as rx
16+
17+
@rx.page("/")
18+
def index():
19+
return rx.container(
20+
rx.input(
21+
value=rx.State.router.session.client_token,
22+
read_only=True,
23+
id="token",
24+
),
25+
rx.vstack(
26+
rx.heading("This is the index page!"),
27+
rx.button("Go to Subpage!", on_click=rx.redirect("/subpage")),
28+
),
29+
)
30+
31+
@rx.page("/subpage")
32+
def subpage():
33+
return rx.container(
34+
rx.vstack(
35+
rx.heading("This is the sub page!"),
36+
rx.button("Go to index!", on_click=rx.redirect("/")),
37+
rx.button("Bounce to index!", on_click=rx.redirect("/bouncer")),
38+
)
39+
)
40+
41+
@rx.page("/bouncer", on_load=rx.redirect("/"))
42+
def bouncer():
43+
return rx.container(
44+
rx.vstack(
45+
rx.heading("This is the bouncer page!"),
46+
rx.text("You should not be here!"),
47+
rx.button("Go to index!", on_click=rx.redirect("/")),
48+
),
49+
)
50+
51+
app = rx.App() # noqa: F841
52+
53+
54+
@pytest.fixture
55+
def onload_redirect_app(tmp_path) -> Generator[AppHarness, None, None]:
56+
"""Start the OnLoadRedirectApp in prod mode without a frontend_path.
57+
58+
Baseline to show on_load redirects work without a frontend_path.
59+
60+
Args:
61+
tmp_path: pytest tmp_path fixture
62+
63+
Yields:
64+
running AppHarness instance
65+
"""
66+
with AppHarnessProd.create(
67+
root=tmp_path / "onload_redirect_app",
68+
app_source=OnLoadRedirectApp,
69+
) as harness:
70+
assert harness.app_instance is not None, "app is not running"
71+
yield harness
72+
73+
74+
@pytest.fixture
75+
def onload_redirect_with_prefix_app_dev(tmp_path) -> Generator[AppHarness, None, None]:
76+
"""Start the OnLoadRedirectApp in dev mode with frontend_path set to /prefix.
77+
78+
Args:
79+
tmp_path: pytest tmp_path fixture
80+
81+
Yields:
82+
running AppHarness instance
83+
"""
84+
os.environ["REFLEX_FRONTEND_PATH"] = "/prefix"
85+
try:
86+
with AppHarness.create(
87+
root=tmp_path / "onload_redirect_with_prefix_app_dev",
88+
app_source=OnLoadRedirectApp,
89+
) as harness:
90+
assert harness.app_instance is not None, "app is not running"
91+
yield harness
92+
finally:
93+
os.environ.pop("REFLEX_FRONTEND_PATH", None)
94+
95+
96+
@pytest.fixture
97+
def onload_redirect_with_prefix_app_prod(
98+
tmp_path,
99+
) -> Generator[AppHarness, None, None]:
100+
"""Start the OnLoadRedirectApp in prod mode with frontend_path set to /prefix.
101+
102+
Args:
103+
tmp_path: pytest tmp_path fixture
104+
105+
Yields:
106+
running AppHarness instance
107+
"""
108+
os.environ["REFLEX_FRONTEND_PATH"] = "/prefix"
109+
try:
110+
with AppHarnessProd.create(
111+
root=tmp_path / "onload_redirect_with_prefix_app_prod",
112+
app_source=OnLoadRedirectApp,
113+
) as harness:
114+
assert harness.app_instance is not None, "app is not running"
115+
yield harness
116+
finally:
117+
os.environ.pop("REFLEX_FRONTEND_PATH", None)
118+
119+
120+
def OnMountRedirectApp():
121+
"""App to demonstrate on_mount redirection behaviour."""
122+
import reflex as rx
123+
124+
@rx.page("/")
125+
def index():
126+
return rx.container(
127+
rx.input(
128+
value=rx.State.router.session.client_token,
129+
read_only=True,
130+
id="token",
131+
),
132+
rx.vstack(
133+
rx.heading("This is the index page!"),
134+
rx.button("Go to Subpage!", on_click=rx.redirect("/subpage")),
135+
),
136+
)
137+
138+
@rx.page("/subpage")
139+
def subpage():
140+
return rx.container(
141+
rx.vstack(
142+
rx.heading("This is the sub page!"),
143+
rx.button("Go to index!", on_click=rx.redirect("/")),
144+
rx.button("Bounce to index!", on_click=rx.redirect("/bouncer")),
145+
)
146+
)
147+
148+
@rx.page("/bouncer")
149+
def bouncer():
150+
return rx.container(
151+
rx.vstack(
152+
rx.heading("This is the bouncer page!"),
153+
rx.text("You should not be here!"),
154+
rx.spinner("Go to index!", on_mount=rx.redirect("/")),
155+
),
156+
)
157+
158+
app = rx.App() # noqa: F841
159+
160+
161+
@pytest.fixture
162+
def onmount_redirect_app(tmp_path) -> Generator[AppHarness, None, None]:
163+
"""Start the OnMountRedirectApp in prod mode without a frontend_path.
164+
165+
Baseline to show on_mount redirects work without a frontend_path.
166+
167+
Args:
168+
tmp_path: pytest tmp_path fixture
169+
170+
Yields:
171+
running AppHarness instance
172+
"""
173+
with AppHarnessProd.create(
174+
root=tmp_path / "onmount_redirect_app",
175+
app_source=OnMountRedirectApp,
176+
) as harness:
177+
assert harness.app_instance is not None, "app is not running"
178+
yield harness
179+
180+
181+
@pytest.fixture
182+
def onmount_redirect_with_prefix_app_dev(
183+
tmp_path,
184+
) -> Generator[AppHarness, None, None]:
185+
"""Start the OnMountRedirectApp in dev mode with frontend_path set to /prefix.
186+
187+
Args:
188+
tmp_path: pytest tmp_path fixture
189+
190+
Yields:
191+
running AppHarness instance
192+
"""
193+
os.environ["REFLEX_FRONTEND_PATH"] = "/prefix"
194+
try:
195+
with AppHarness.create(
196+
root=tmp_path / "onmount_redirect_with_prefix_app_dev",
197+
app_source=OnMountRedirectApp,
198+
) as harness:
199+
assert harness.app_instance is not None, "app is not running"
200+
yield harness
201+
finally:
202+
os.environ.pop("REFLEX_FRONTEND_PATH", None)
203+
204+
205+
@pytest.fixture
206+
def onmount_redirect_with_prefix_app_prod(
207+
tmp_path,
208+
) -> Generator[AppHarness, None, None]:
209+
"""Start the OnMountRedirectApp in prod mode with frontend_path set to /prefix.
210+
211+
Args:
212+
tmp_path: pytest tmp_path fixture
213+
214+
Yields:
215+
running AppHarness instance
216+
"""
217+
os.environ["REFLEX_FRONTEND_PATH"] = "/prefix"
218+
try:
219+
with AppHarnessProd.create(
220+
root=tmp_path / "onmount_redirect_with_prefix_app_prod",
221+
app_source=OnMountRedirectApp,
222+
) as harness:
223+
assert harness.app_instance is not None, "app is not running"
224+
yield harness
225+
finally:
226+
os.environ.pop("REFLEX_FRONTEND_PATH", None)
227+
228+
229+
@pytest.mark.ignore_console_error
230+
@pytest.mark.parametrize(
231+
("app_fixture_name", "frontend_path"),
232+
[
233+
("onload_redirect_app", ""),
234+
("onload_redirect_with_prefix_app_dev", "/prefix"),
235+
("onload_redirect_with_prefix_app_prod", "/prefix"),
236+
("onmount_redirect_app", ""),
237+
("onmount_redirect_with_prefix_app_dev", "/prefix"),
238+
("onmount_redirect_with_prefix_app_prod", "/prefix"),
239+
],
240+
)
241+
def test_redirection_triggers(
242+
app_fixture_name: str, frontend_path: str, page: Page, request
243+
):
244+
"""Ensure that on_load and on_mount redirects work with/without a frontend_path.
245+
246+
Tests both dev mode (AppHarness) and prod mode (AppHarnessProd) to cover
247+
the bug reported in https://github.com/reflex-dev/reflex/issues/5674,
248+
where redirects from on_load and on_mount handlers were lost when serving
249+
from a non-root frontend path.
250+
251+
Args:
252+
app_fixture_name: Name of the app fixture to use for the test.
253+
frontend_path: The frontend_path used by the app fixture.
254+
page: Playwright Page object to interact with the app.
255+
request: Pytest request object to access fixtures.
256+
"""
257+
app_fixture = request.getfixturevalue(app_fixture_name)
258+
assert app_fixture.frontend_url is not None
259+
260+
base_url = app_fixture.frontend_url.rstrip("/")
261+
base_url += frontend_path
262+
page.goto(f"{base_url}/")
263+
expect(page.get_by_text("This is the index page!")).to_be_visible()
264+
265+
# Go to /subpage
266+
page.get_by_role("button", name="Go to Subpage!").click()
267+
expect(page.get_by_text("This is the sub page!")).to_be_visible()
268+
expect(page).to_have_url(f"{base_url}/subpage")
269+
270+
# Click "Bounce to index!" (should redirect to index via on_load/on_mount)
271+
page.get_by_role("button", name="Bounce to index!").click()
272+
expect(page.get_by_text("This is the index page!")).to_be_visible()
273+
expect(page).to_have_url(f"{base_url}/")
274+
275+
# Directly navigate to /bouncer/ and verify redirect
276+
page.goto(f"{base_url}/bouncer/")
277+
expect(page.get_by_text("This is the index page!")).to_be_visible()
278+
279+
# Check that 404's work and do not change the url
280+
page.goto(f"{base_url}/not-a-page")
281+
expect(page.get_by_text("404: Page not found")).to_be_visible()
282+
expect(page).to_have_url(f"{base_url}/not-a-page")

0 commit comments

Comments
 (0)